1 /** @file drawlists.cpp  Drawable primitive list collection/management.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "de_platform.h"
22 #include "render/drawlists.h"
23 
24 #include <de/Log>
25 #include <de/memoryzone.h>
26 #include <QMultiHash>
27 #include <QtAlgorithms>
28 
29 using namespace de;
30 
31 typedef QMultiHash<GLuint, DrawList *> DrawListHash;
32 
DENG2_PIMPL(DrawLists)33 DENG2_PIMPL(DrawLists)
34 {
35     QScopedPointer<DrawList> skyMaskList;
36     DrawListHash unlitHash;
37     DrawListHash litHash;
38     DrawListHash dynHash;
39     DrawListHash shinyHash;
40     DrawListHash shadowHash;
41 
42     Impl(Public *i) : Base(i)
43     {
44         DrawListSpec newSpec;
45         newSpec.group = SkyMaskGeom;
46         skyMaskList.reset(new DrawList(newSpec));
47     }
48 
49     /// Choose the correct draw list hash table.
50     DrawListHash &listHash(GeomGroup group)
51     {
52         switch(group)
53         {
54         case UnlitGeom:  return unlitHash;
55         case LitGeom:    return litHash;
56         case LightGeom:  return dynHash;
57         case ShadowGeom: return shadowHash;
58         case ShineGeom:  return shinyHash;
59 
60         case SkyMaskGeom: break; // n/a?
61         }
62         DENG2_ASSERT(false);
63         return unlitHash;
64     }
65 };
66 
DrawLists()67 DrawLists::DrawLists() : d(new Impl(this))
68 {}
69 
clearAllLists(DrawListHash & hash)70 static void clearAllLists(DrawListHash &hash)
71 {
72     foreach(DrawList *list, hash)
73     {
74         list->clear();
75     }
76     qDeleteAll(hash);
77     hash.clear();
78 }
79 
clear()80 void DrawLists::clear()
81 {
82     clearAllLists(d->unlitHash);
83     clearAllLists(d->litHash);
84     clearAllLists(d->dynHash);
85     clearAllLists(d->shadowHash);
86     clearAllLists(d->shinyHash);
87     d->skyMaskList->clear();
88 }
89 
resetList(DrawList & list)90 static void resetList(DrawList &list)
91 {
92     list.rewind();
93 
94     // Reset the list specification.
95     // The interpolation target must be explicitly set.
96     DrawListSpec &listSpec = list.spec();
97     listSpec.unit(TU_INTER).unmanaged.glName = 0;
98     listSpec.unit(TU_INTER).texture          = 0;
99     listSpec.unit(TU_INTER).opacity          = 0;
100 
101     listSpec.unit(TU_INTER_DETAIL).unmanaged.glName = 0;
102     listSpec.unit(TU_INTER_DETAIL).texture          = 0;
103     listSpec.unit(TU_INTER_DETAIL).opacity          = 0;
104 }
105 
resetAllLists(DrawListHash & hash)106 static void resetAllLists(DrawListHash &hash)
107 {
108     foreach(DrawList *list, hash)
109     {
110         resetList(*list);
111     }
112 }
113 
reset()114 void DrawLists::reset()
115 {
116     resetAllLists(d->unlitHash);
117     resetAllLists(d->litHash);
118     resetAllLists(d->dynHash);
119     resetAllLists(d->shadowHash);
120     resetAllLists(d->shinyHash);
121     resetList(*d->skyMaskList);
122 }
123 
124 /**
125  * Specialized texture unit comparision function that ignores properties which
126  * are applied per-primitive and which should not result in list separation.
127  *
128  * (These properties are written to the primitive header and applied dynamically
129  * when reading back the draw list.)
130  */
compareTexUnit(GLTextureUnit const & lhs,GLTextureUnit const & rhs)131 static bool compareTexUnit(GLTextureUnit const &lhs, GLTextureUnit const &rhs)
132 {
133     if(lhs.texture)
134     {
135         if(lhs.texture != rhs.texture) return false;
136     }
137     else
138     {
139         if(lhs.unmanaged != rhs.unmanaged) return false;
140     }
141     if(!de::fequal(lhs.opacity, rhs.opacity)) return false;
142     // Other properties are applied per-primitive and should not affect the outcome.
143     return true;
144 }
145 
find(DrawListSpec const & spec)146 DrawList &DrawLists::find(DrawListSpec const &spec)
147 {
148     // Sky masked geometry is never textured; therefore no draw list hash.
149     /// @todo Make hash management dynamic. -ds
150     if(spec.group == SkyMaskGeom)
151     {
152         return *d->skyMaskList;
153     }
154 
155     DrawList *convertable = 0;
156 
157     // Find/create a list in the hash.
158     GLuint const key  = spec.unit(TU_PRIMARY).getTextureGLName();
159     DrawListHash &hash = d->listHash(spec.group);
160     for(DrawListHash::const_iterator it = hash.find(key);
161         it != hash.end() && it.key() == key; ++it)
162     {
163         DrawList *list = it.value();
164         DrawListSpec const &listSpec = list->spec();
165 
166         if((spec.group == ShineGeom &&
167             compareTexUnit(listSpec.unit(TU_PRIMARY), spec.unit(TU_PRIMARY))) ||
168            (spec.group != ShineGeom &&
169             compareTexUnit(listSpec.unit(TU_PRIMARY), spec.unit(TU_PRIMARY)) &&
170             compareTexUnit(listSpec.unit(TU_PRIMARY_DETAIL), spec.unit(TU_PRIMARY_DETAIL))))
171         {
172             if(!listSpec.unit(TU_INTER).hasTexture() &&
173                !spec.unit(TU_INTER).hasTexture())
174             {
175                 // This will do great.
176                 return *list;
177             }
178 
179             // Is this eligible for conversion to a blended list?
180             if(list->isEmpty() && !convertable && spec.unit(TU_INTER).hasTexture())
181             {
182                 // If necessary, this empty list will be selected.
183                 convertable = list;
184             }
185 
186             // Possibly an exact match?
187             if((spec.group == ShineGeom &&
188                 compareTexUnit(listSpec.unit(TU_INTER), spec.unit(TU_INTER))) ||
189                (spec.group != ShineGeom &&
190                 compareTexUnit(listSpec.unit(TU_INTER), spec.unit(TU_INTER)) &&
191                 compareTexUnit(listSpec.unit(TU_INTER_DETAIL), spec.unit(TU_INTER_DETAIL))))
192             {
193                 return *list;
194             }
195         }
196     }
197 
198     // Did we find a convertable list?
199     if(convertable)
200     {
201         // This list is currently empty.
202         if(spec.group == ShineGeom)
203         {
204             convertable->spec().unit(TU_INTER) = spec.unit(TU_INTER);
205         }
206         else
207         {
208             convertable->spec().unit(TU_INTER) = spec.unit(TU_INTER);
209             convertable->spec().unit(TU_INTER_DETAIL) = spec.unit(TU_INTER_DETAIL);
210         }
211 
212         return *convertable;
213     }
214 
215     // Create a new list.
216     return *hash.insert(key, new DrawList(spec)).value();
217 }
218 
findAll(GeomGroup group,FoundLists & found)219 int DrawLists::findAll(GeomGroup group, FoundLists &found)
220 {
221     found.clear();
222     if(group == SkyMaskGeom)
223     {
224         if(!d->skyMaskList->isEmpty())
225         {
226             found.append(d->skyMaskList.data());
227         }
228     }
229     else
230     {
231         DrawListHash const &hash = d->listHash(group);
232         foreach(DrawList *list, hash)
233         {
234             if(!list->isEmpty())
235             {
236                 found.append(list);
237             }
238         }
239     }
240 
241     return found.count();
242 }
243