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