1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 1999-2004 Eidos Interactive
4 Copyright (C) 2005-2020 Warzone 2100 Project
5
6 Warzone 2100 is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Warzone 2100 is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Warzone 2100; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /** \file
21 * Render routines for 3D coloured and shaded transparency rendering.
22 */
23
24 #include <unordered_map>
25 #include <string.h>
26
27 #include "lib/framework/frame.h"
28 #include "lib/ivis_opengl/ivisdef.h"
29 #include "lib/ivis_opengl/imd.h"
30 #include "lib/ivis_opengl/piefunc.h"
31 #include "lib/ivis_opengl/tex.h"
32 #include "lib/ivis_opengl/piedef.h"
33 #include "lib/ivis_opengl/piestate.h"
34 #include "lib/ivis_opengl/piepalette.h"
35 #include "lib/ivis_opengl/pieclip.h"
36 #include "lib/ivis_opengl/pieblitfunc.h"
37 #include "piematrix.h"
38 #include "screen.h"
39
40 #include <string.h>
41 #include <vector>
42 #include <algorithm>
43
44 #include <glm/glm.hpp>
45 #include <glm/gtc/matrix_transform.hpp>
46
47 #define BUFFER_OFFSET(i) (reinterpret_cast<char *>(i))
48 #define SHADOW_END_DISTANCE (8000*8000) // Keep in sync with lighting.c:FOG_END
49
50 /*
51 * Local Variables
52 */
53
54 static size_t pieCount = 0;
55 static size_t polyCount = 0;
56 static bool shadows = false;
57 static gfx_api::gfxFloat lighting0[LIGHT_MAX][4];
58
59 /*
60 * Source
61 */
62
pie_InitLighting()63 void pie_InitLighting()
64 {
65 // set scene color, ambient, diffuse and specular light intensities of sun
66 // diffuse lighting is turned off because players dislike it
67 const gfx_api::gfxFloat defaultLight[LIGHT_MAX][4] = {
68 {0.0f, 0.0f, 0.0f, 1.0f}, // LIGHT_EMISSIVE
69 {0.5f, 0.5f, 0.5f, 1.0f}, // LIGHT_AMBIENT
70 {1.0f, 1.0f, 1.0f, 1.0f}, // LIGHT_DIFFUSE
71 {1.0f, 1.0f, 1.0f, 1.0f} // LIGHT_SPECULAR
72 };
73 memcpy(lighting0, defaultLight, sizeof(lighting0));
74 }
75
pie_Lighting0(LIGHTING_TYPE entry,const float value[4])76 void pie_Lighting0(LIGHTING_TYPE entry, const float value[4])
77 {
78 lighting0[entry][0] = value[0];
79 lighting0[entry][1] = value[1];
80 lighting0[entry][2] = value[2];
81 lighting0[entry][3] = value[3];
82 }
83
pie_setShadows(bool drawShadows)84 void pie_setShadows(bool drawShadows)
85 {
86 shadows = drawShadows;
87 }
88
89 static Vector3f currentSunPosition(0.f, 0.f, 0.f);
90
pie_BeginLighting(const Vector3f & light)91 void pie_BeginLighting(const Vector3f &light)
92 {
93 currentSunPosition = light;
94 }
95
96 /***************************************************************************
97 * pie_Draw3dShape
98 *
99 * Project and render a pumpkin image to render surface
100 * Will support zbuffering, texturing, coloured lighting and alpha effects
101 * Avoids recalculating vertex projections for every poly
102 ***************************************************************************/
103
104 struct ShadowcastingShape
105 {
106 glm::mat4 matrix;
107 iIMDShape *shape;
108 int flag;
109 int flag_data;
110 glm::vec4 light;
111 };
112
113 struct SHAPE
114 {
115 glm::mat4 matrix;
116 iIMDShape *shape;
117 int frame;
118 PIELIGHT colour;
119 PIELIGHT teamcolour;
120 int flag;
121 int flag_data;
122 float stretch;
123 };
124
125 static std::vector<ShadowcastingShape> scshapes;
126 static std::vector<SHAPE> tshapes;
127 static std::vector<SHAPE> shapes;
128 static gfx_api::buffer* pZeroedVertexBuffer = nullptr;
129
getZeroedVertexBuffer(size_t size)130 static gfx_api::buffer* getZeroedVertexBuffer(size_t size)
131 {
132 static size_t currentSize = 0;
133 if (!pZeroedVertexBuffer || (currentSize < size))
134 {
135 if (pZeroedVertexBuffer)
136 {
137 delete pZeroedVertexBuffer;
138 }
139 pZeroedVertexBuffer = gfx_api::context::get().create_buffer_object(gfx_api::buffer::usage::vertex_buffer);
140 std::vector<UBYTE> tempZeroes(size, 0);
141 pZeroedVertexBuffer->upload(size, tempZeroes.data());
142 currentSize = size;
143 }
144 return pZeroedVertexBuffer;
145 }
146
pie_Draw3DButton(iIMDShape * shape,PIELIGHT teamcolour,const glm::mat4 & matrix)147 static void pie_Draw3DButton(iIMDShape *shape, PIELIGHT teamcolour, const glm::mat4 &matrix)
148 {
149 auto* tcmask = shape->tcmaskpage != iV_TEX_INVALID ? &pie_Texture(shape->tcmaskpage) : nullptr;
150 auto* normalmap = shape->normalpage != iV_TEX_INVALID ? &pie_Texture(shape->normalpage) : nullptr;
151 auto* specularmap = shape->specularpage != iV_TEX_INVALID ? &pie_Texture(shape->specularpage) : nullptr;
152
153 gfx_api::buffer* pTangentBuffer = (shape->buffers[VBO_TANGENT] != nullptr) ? shape->buffers[VBO_TANGENT] : getZeroedVertexBuffer(shape->vertexCount * 4 * sizeof(gfx_api::gfxFloat));
154
155 const PIELIGHT colour = WZCOL_WHITE;
156 gfx_api::Draw3DButtonPSO::get().bind();
157 gfx_api::constant_buffer_type<SHADER_BUTTON> cbuf{
158 pal_PIELIGHTtoVec4(colour), pal_PIELIGHTtoVec4(teamcolour), 0.f, tcmask ? 1 : 0, 0, normalmap != nullptr, specularmap != nullptr, 0, 0, 0.f, matrix, pie_PerspectiveGet() * matrix, glm::transpose(glm::inverse(matrix)),
159 glm::vec4(0.f), glm::vec4(0.f), glm::vec4(0.f), glm::vec4(0.f), glm::vec4(0.f), glm::vec4(0.f), 0.f, 0.f, shape->buffers[VBO_TANGENT] != nullptr };
160 gfx_api::Draw3DButtonPSO::get().bind_constants(cbuf);
161
162 gfx_api::Draw3DButtonPSO::get().bind_textures(&pie_Texture(shape->texpage), tcmask, normalmap, specularmap);
163 gfx_api::Draw3DButtonPSO::get().bind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD], pTangentBuffer);
164 gfx_api::context::get().bind_index_buffer(*shape->buffers[VBO_INDEX], gfx_api::index_type::u16);
165 gfx_api::Draw3DButtonPSO::get().draw_elements(shape->polys.size() * 3, 0);
166 polyCount += shape->polys.size();
167 gfx_api::Draw3DButtonPSO::get().unbind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD], pTangentBuffer);
168 gfx_api::context::get().unbind_index_buffer(*shape->buffers[VBO_INDEX]);
169 }
170
171 struct templatedState
172 {
173 SHADER_MODE shader = SHADER_NONE;
174 const iIMDShape * shape = nullptr;
175 int pieFlag = 0;
176
templatedStatetemplatedState177 templatedState()
178 : shader(SHADER_NONE), shape(nullptr), pieFlag(0)
179 { }
180
templatedStatetemplatedState181 templatedState(SHADER_MODE shader, const iIMDShape * shape, int pieFlag)
182 : shader(shader), shape(shape), pieFlag(pieFlag)
183 { }
184
operator ==templatedState185 bool operator==(const templatedState& rhs) const
186 {
187 return (shader == rhs.shader)
188 && (shape == rhs.shape)
189 && (pieFlag == rhs.pieFlag);
190 }
operator !=templatedState191 bool operator!=(const templatedState& rhs) const
192 {
193 return !operator==(rhs);
194 }
195 };
196
197 template<SHADER_MODE shader, typename AdditivePSO, typename AlphaPSO, typename PremultipliedPSO, typename OpaquePSO>
draw3dShapeTemplated(const templatedState & lastState,const PIELIGHT & colour,const PIELIGHT & teamcolour,const float & stretch,const int & ecmState,const float & timestate,const glm::mat4 & matrix,glm::vec4 & sceneColor,glm::vec4 & ambient,glm::vec4 & diffuse,glm::vec4 & specular,const iIMDShape * shape,int pieFlag,int frame)198 static void draw3dShapeTemplated(const templatedState &lastState, const PIELIGHT &colour, const PIELIGHT &teamcolour, const float& stretch, const int& ecmState, const float& timestate, const glm::mat4 & matrix, glm::vec4 &sceneColor, glm::vec4 &ambient, glm::vec4 &diffuse, glm::vec4 &specular, const iIMDShape * shape, int pieFlag, int frame)
199 {
200 templatedState currentState = templatedState(shader, shape, pieFlag);
201
202 auto* tcmask = shape->tcmaskpage != iV_TEX_INVALID ? &pie_Texture(shape->tcmaskpage) : nullptr;
203 auto* normalmap = shape->normalpage != iV_TEX_INVALID ? &pie_Texture(shape->normalpage) : nullptr;
204 auto* specularmap = shape->specularpage != iV_TEX_INVALID ? &pie_Texture(shape->specularpage) : nullptr;
205
206 gfx_api::constant_buffer_type<shader> cbuf{
207 pal_PIELIGHTtoVec4(colour), pal_PIELIGHTtoVec4(teamcolour), stretch, tcmask ? 1 : 0, 0, normalmap != nullptr, specularmap != nullptr, ecmState, !(pieFlag & pie_PREMULTIPLIED), timestate, matrix, pie_PerspectiveGet() * matrix, glm::transpose(glm::inverse(matrix)),
208 glm::vec4(currentSunPosition, 0.f), sceneColor, ambient, diffuse, specular, glm::vec4(0.f), 0.f, 0.f, shape->buffers[VBO_TANGENT] != nullptr };
209
210 gfx_api::buffer* pTangentBuffer = (shape->buffers[VBO_TANGENT] != nullptr) ? shape->buffers[VBO_TANGENT] : getZeroedVertexBuffer(shape->vertexCount * 4 * sizeof(gfx_api::gfxFloat));
211
212 /* Set tranlucency */
213 if (pieFlag & pie_ADDITIVE)
214 {
215 AdditivePSO::get().bind();
216 AdditivePSO::get().bind_constants(cbuf);
217 if (currentState != lastState)
218 {
219 AdditivePSO::get().bind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD], pTangentBuffer);
220 AdditivePSO::get().bind_textures(&pie_Texture(shape->texpage), tcmask, normalmap, specularmap);
221 }
222 AdditivePSO::get().draw_elements(shape->polys.size() * 3, frame * shape->polys.size() * 3 * sizeof(uint16_t));
223 // AdditivePSO::get().unbind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD]);
224 }
225 else if (pieFlag & pie_TRANSLUCENT)
226 {
227 AlphaPSO::get().bind();
228 AlphaPSO::get().bind_constants(cbuf);
229 if (currentState != lastState)
230 {
231 AlphaPSO::get().bind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD], pTangentBuffer);
232 AlphaPSO::get().bind_textures(&pie_Texture(shape->texpage), tcmask, normalmap, specularmap);
233 }
234 AlphaPSO::get().draw_elements(shape->polys.size() * 3, frame * shape->polys.size() * 3 * sizeof(uint16_t));
235 // AlphaPSO::get().unbind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD]);
236 }
237 else if (pieFlag & pie_PREMULTIPLIED)
238 {
239 PremultipliedPSO::get().bind();
240 PremultipliedPSO::get().bind_constants(cbuf);
241 if (currentState != lastState)
242 {
243 PremultipliedPSO::get().bind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD], pTangentBuffer);
244 PremultipliedPSO::get().bind_textures(&pie_Texture(shape->texpage), tcmask, normalmap, specularmap);
245 }
246 PremultipliedPSO::get().draw_elements(shape->polys.size() * 3, frame * shape->polys.size() * 3 * sizeof(uint16_t));
247 // PremultipliedPSO::get().unbind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD]);
248 }
249 else
250 {
251 OpaquePSO::get().bind();
252 OpaquePSO::get().bind_constants(cbuf);
253 if (currentState != lastState)
254 {
255 OpaquePSO::get().bind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD], pTangentBuffer);
256 OpaquePSO::get().bind_textures(&pie_Texture(shape->texpage), tcmask, normalmap, specularmap);
257 }
258 OpaquePSO::get().draw_elements(shape->polys.size() * 3, frame * shape->polys.size() * 3 * sizeof(uint16_t));
259 // OpaquePSO::get().unbind_vertex_buffers(shape->buffers[VBO_VERTEX], shape->buffers[VBO_NORMAL], shape->buffers[VBO_TEXCOORD]);
260 }
261 }
262
pie_Draw3DShape2(const templatedState & lastState,const iIMDShape * shape,int frame,PIELIGHT colour,PIELIGHT teamcolour,int pieFlag,int pieFlagData,glm::mat4 const & matrix)263 static templatedState pie_Draw3DShape2(const templatedState &lastState, const iIMDShape *shape, int frame, PIELIGHT colour, PIELIGHT teamcolour, int pieFlag, int pieFlagData, glm::mat4 const &matrix)
264 {
265 bool light = true;
266
267 /* Set fog status */
268 if (!(pieFlag & pie_FORCE_FOG) && (pieFlag & pie_ADDITIVE || pieFlag & pie_TRANSLUCENT || pieFlag & pie_PREMULTIPLIED))
269 {
270 pie_SetFogStatus(false);
271 }
272 else
273 {
274 pie_SetFogStatus(true);
275 }
276
277 /* Set translucency */
278 if (pieFlag & pie_ADDITIVE)
279 {
280 colour.byte.a = (UBYTE)pieFlagData;
281 light = false;
282 }
283 else if (pieFlag & pie_TRANSLUCENT)
284 {
285 colour.byte.a = (UBYTE)pieFlagData;
286 light = false;
287 }
288 else if (pieFlag & pie_PREMULTIPLIED)
289 {
290 light = false;
291 }
292
293 if (pieFlag & pie_ECM)
294 {
295 light = true;
296 pie_SetShaderEcmEffect(true);
297 }
298
299 glm::vec4 sceneColor(lighting0[LIGHT_EMISSIVE][0], lighting0[LIGHT_EMISSIVE][1], lighting0[LIGHT_EMISSIVE][2], lighting0[LIGHT_EMISSIVE][3]);
300 glm::vec4 ambient(lighting0[LIGHT_AMBIENT][0], lighting0[LIGHT_AMBIENT][1], lighting0[LIGHT_AMBIENT][2], lighting0[LIGHT_AMBIENT][3]);
301 glm::vec4 diffuse(lighting0[LIGHT_DIFFUSE][0], lighting0[LIGHT_DIFFUSE][1], lighting0[LIGHT_DIFFUSE][2], lighting0[LIGHT_DIFFUSE][3]);
302 glm::vec4 specular(lighting0[LIGHT_SPECULAR][0], lighting0[LIGHT_SPECULAR][1], lighting0[LIGHT_SPECULAR][2], lighting0[LIGHT_SPECULAR][3]);
303
304 frame %= std::max<int>(1, shape->numFrames);
305
306 templatedState currentState = templatedState((light) ? SHADER_COMPONENT : SHADER_NOLIGHT, shape, pieFlag);
307 if (currentState != lastState)
308 {
309 gfx_api::context::get().bind_index_buffer(*shape->buffers[VBO_INDEX], gfx_api::index_type::u16);
310 }
311
312 if (light)
313 {
314 draw3dShapeTemplated<SHADER_COMPONENT, gfx_api::Draw3DShapeAdditive, gfx_api::Draw3DShapeAlpha, gfx_api::Draw3DShapePremul, gfx_api::Draw3DShapeOpaque>(lastState, colour, teamcolour, pie_GetShaderStretchDepth(), pie_GetShaderEcmEffect(), pie_GetShaderTime(), matrix, sceneColor, ambient, diffuse, specular, shape, pieFlag, frame);
315 }
316 else
317 {
318 draw3dShapeTemplated<SHADER_NOLIGHT, gfx_api::Draw3DShapeNoLightAdditive, gfx_api::Draw3DShapeNoLightAlpha, gfx_api::Draw3DShapeNoLightPremul, gfx_api::Draw3DShapeNoLightOpaque>(lastState, colour, teamcolour, pie_GetShaderStretchDepth(), pie_GetShaderEcmEffect(), pie_GetShaderTime(), matrix, sceneColor, ambient, diffuse, specular, shape, pieFlag, frame);
319 }
320
321 polyCount += shape->polys.size();
322
323 pie_SetShaderEcmEffect(false);
324
325 return currentState;
326 }
327
edgeLessThan(EDGE const & e1,EDGE const & e2)328 static inline bool edgeLessThan(EDGE const &e1, EDGE const &e2)
329 {
330 if (e1.from != e2.from)
331 {
332 return e1.from < e2.from;
333 }
334 return e1.to < e2.to;
335 }
336
flipEdge(EDGE & e)337 static inline void flipEdge(EDGE &e)
338 {
339 std::swap(e.from, e.to);
340 }
341
342 /// scale the height according to the flags
scale_y(float y,int flag,int flag_data)343 static inline float scale_y(float y, int flag, int flag_data)
344 {
345 float tempY = y;
346 if (flag & pie_RAISE)
347 {
348 tempY = y - flag_data;
349 if (y - flag_data < 0)
350 {
351 tempY = 0;
352 }
353 }
354 else if (flag & pie_HEIGHT_SCALED)
355 {
356 if (y > 0)
357 {
358 tempY = (y * flag_data) / pie_RAISE_SCALE;
359 }
360 }
361 return tempY;
362 }
363
hash_combine(std::size_t & seed)364 inline void hash_combine(std::size_t& seed) { }
365
366 template <typename T, typename... Rest>
hash_combine(std::size_t & seed,const T & v,Rest...rest)367 inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
368 std::hash<T> hasher;
369 #if SIZE_MAX >= UINT64_MAX
370 seed ^= hasher(v) + 0x9e3779b97f4a7c15L + (seed<<6) + (seed>>2);
371 #else
372 seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
373 #endif
374 hash_combine(seed, rest...);
375 }
376
hash_vec4(const glm::vec4 & vec)377 std::size_t hash_vec4(const glm::vec4& vec)
378 {
379 std::size_t h=0;
380 hash_combine(h, vec[0], vec[1], vec[2], vec[3]);
381 return h;
382 }
383
384 struct ShadowDrawParameters {
385 int flag;
386 int flag_data;
387 // glm::mat4 modelViewMatrix; // the modelViewMatrix doesn't change any of the vertex / buffer calculations
388 glm::vec4 light;
389
ShadowDrawParametersShadowDrawParameters390 ShadowDrawParameters(int flag, int flag_data, const glm::vec4 &light)
391 : flag(flag)
392 , flag_data(flag_data)
393 , light(light)
394 { }
395
operator ==ShadowDrawParameters396 bool operator ==(const ShadowDrawParameters &b) const
397 {
398 return (flag == b.flag) && (flag_data == b.flag_data) && (light == b.light);
399 }
400 };
401
402 namespace std {
403 template <>
404 struct hash<ShadowDrawParameters>
405 {
operator ()std::hash406 std::size_t operator()(const ShadowDrawParameters& k) const
407 {
408 std::size_t h = 0;
409 hash_combine(h, k.flag, k.flag_data, hash_vec4(k.light));
410 return h;
411 }
412 };
413 }
414
415 struct ShadowCache {
416
417 struct CachedShadowData {
418 uint64_t lastQueriedFrameCount = 0;
419 std::vector<Vector3f> vertexes;
420
CachedShadowDataShadowCache::CachedShadowData421 CachedShadowData() { }
422 };
423
424 typedef std::unordered_map<ShadowDrawParameters, CachedShadowData> ShadowDrawParametersToCachedDataMap;
425 typedef std::unordered_map<iIMDShape *, ShadowDrawParametersToCachedDataMap, std::hash<iIMDShape *>> ShapeMap;
426
findCacheForShadowDrawShadowCache427 const CachedShadowData* findCacheForShadowDraw(iIMDShape *shape, int flag, int flag_data, const glm::vec4 &light)
428 {
429 auto it = shapeMap.find(shape);
430 if (it == shapeMap.end()) {
431 return nullptr;
432 }
433 auto it_cachedData = it->second.find(ShadowDrawParameters(flag, flag_data, light));
434 if (it_cachedData == it->second.end()) {
435 return nullptr;
436 }
437 // update the frame in which we requested this shadow cache
438 it_cachedData->second.lastQueriedFrameCount = _currentFrame;
439 return &(it_cachedData->second);
440 }
441
createCacheForShadowDrawShadowCache442 CachedShadowData& createCacheForShadowDraw(iIMDShape *shape, int flag, int flag_data, const glm::vec4 &light)
443 {
444 auto result = shapeMap[shape].emplace(ShadowDrawParameters(flag, flag_data, light), CachedShadowData());
445 result.first->second.lastQueriedFrameCount = _currentFrame;
446 return result.first->second;
447 }
448
addPremultipliedVertexesShadowCache449 void addPremultipliedVertexes(const CachedShadowData& cachedData, const glm::mat4 &modelViewMatrix)
450 {
451 float mat_a = modelViewMatrix[0].x;
452 float mat_b = modelViewMatrix[1].x;
453 float mat_c = modelViewMatrix[2].x;
454 float mat_d = modelViewMatrix[3].x;
455 float mat_e = modelViewMatrix[0].y;
456 float mat_f = modelViewMatrix[1].y;
457 float mat_g = modelViewMatrix[2].y;
458 float mat_h = modelViewMatrix[3].y;
459 float mat_i = modelViewMatrix[0].z;
460 float mat_j = modelViewMatrix[1].z;
461 float mat_k = modelViewMatrix[2].z;
462 float mat_l = modelViewMatrix[3].z;
463 float premult_x;
464 float premult_y;
465 float premult_z;
466 for (auto &vertex : cachedData.vertexes)
467 {
468 premult_x = vertex.x*mat_a + vertex.y*mat_b + vertex.z*mat_c + mat_d;
469 premult_y = vertex.x*mat_e + vertex.y*mat_f + vertex.z*mat_g + mat_h;
470 premult_z = vertex.x*mat_i + vertex.y*mat_j + vertex.z*mat_k + mat_l;
471 vertexes.push_back(Vector3f(premult_x, premult_y, premult_z));
472 }
473 }
474
getPremultipliedVertexesShadowCache475 const std::vector<Vector3f>& getPremultipliedVertexes()
476 {
477 return vertexes;
478 }
479
clearPremultipliedVertexesShadowCache480 void clearPremultipliedVertexes()
481 {
482 vertexes.clear();
483 }
484
setCurrentFrameShadowCache485 void setCurrentFrame(uint64_t currentFrame)
486 {
487 _currentFrame = currentFrame;
488 }
489
removeUnusedShadowCache490 size_t removeUnused()
491 {
492 std::vector<ShapeMap::iterator> unusedShapes;
493 size_t oldItemsRemoved = 0;
494 for (auto it_shape = shapeMap.begin(); it_shape != shapeMap.end(); ++it_shape)
495 {
496 std::vector<ShadowDrawParametersToCachedDataMap::iterator> unusedBuffersForShape;
497 for (auto it_shadowDrawParams = it_shape->second.begin(); it_shadowDrawParams != it_shape->second.end(); ++it_shadowDrawParams)
498 {
499 if (it_shadowDrawParams->second.lastQueriedFrameCount != _currentFrame)
500 {
501 unusedBuffersForShape.push_back(it_shadowDrawParams);
502 }
503 }
504 for (auto &item : unusedBuffersForShape)
505 {
506 it_shape->second.erase(item);
507 ++oldItemsRemoved;
508 }
509 if (it_shape->second.empty())
510 {
511 // remove from the root shapeMap
512 unusedShapes.push_back(it_shape);
513 }
514 }
515 for (auto &item : unusedShapes)
516 {
517 shapeMap.erase(item);
518 }
519 return oldItemsRemoved;
520 }
521 private:
522 uint64_t _currentFrame = 0;
523 ShapeMap shapeMap;
524 std::vector<Vector3f> vertexes;
525 };
526
527 enum DrawShadowResult {
528 DRAW_SUCCESS_CACHED,
529 DRAW_SUCCESS_UNCACHED
530 };
531
532 /// Draw the shadow for a shape
533 /// Prequisite:
534 /// Caller must call the following before all calls to pie_DrawShadow():
535 /// const auto &program = pie_ActivateShader(SHADER_GENERIC_COLOR, pie_PerspectiveGet(), glm::vec4());
536 /// glEnableVertexAttribArray(program.locVertex);
537 /// and must call the following after all calls to pie_DrawShadow():
538 /// glDisableVertexAttribArray(program.locVertex);
539 /// pie_DeactivateShader();
540 /// The only place this is currently called is pie_ShadowDrawLoop(), which handles this properly.
pie_DrawShadow(ShadowCache & shadowCache,iIMDShape * shape,int flag,int flag_data,const glm::vec4 & light,const glm::mat4 & modelViewMatrix)541 static inline DrawShadowResult pie_DrawShadow(ShadowCache &shadowCache, iIMDShape *shape, int flag, int flag_data, const glm::vec4 &light, const glm::mat4 &modelViewMatrix)
542 {
543 static std::vector<EDGE> edgelist; // Static, to save allocations.
544 static std::vector<EDGE> edgelistFlipped; // Static, to save allocations.
545 static std::vector<EDGE> edgelistFiltered; // Static, to save allocations.
546 EDGE *drawlist = nullptr;
547
548 size_t edge_count;
549 DrawShadowResult result;
550
551 // Find cached data (if available)
552 // Note: The modelViewMatrix is not used for calculating the sorted / filtered vertices, so it's not included
553 const ShadowCache::CachedShadowData *pCached = shadowCache.findCacheForShadowDraw(shape, flag, flag_data, light);
554 if (pCached == nullptr)
555 {
556 const Vector3f *pVertices = shape->pShadowPoints->data();
557 if (flag & pie_STATIC_SHADOW && shape->shadowEdgeList)
558 {
559 drawlist = shape->shadowEdgeList;
560 edge_count = shape->nShadowEdges;
561 }
562 else
563 {
564 edgelist.clear();
565 glm::vec3 p[3];
566 for (const iIMDPoly &poly : *(shape->pShadowPolys))
567 {
568 for (int j = 0; j < 3; ++j)
569 {
570 uint32_t current = poly.pindex[j];
571 p[j] = glm::vec3(pVertices[current].x, scale_y(pVertices[current].y, flag, flag_data), pVertices[current].z);
572 }
573 if (glm::dot(glm::cross(p[2] - p[0], p[1] - p[0]), glm::vec3(light)) > 0.0f)
574 {
575 for (int n = 0; n < 3; ++n)
576 {
577 // Add the edges
578 edgelist.push_back({poly.pindex[n], poly.pindex[(n + 1)%3]});
579 }
580 }
581 }
582
583 // Remove duplicate pairs from the edge list. For example, in the list ((1 2), (2 6), (6 2), (3, 4)), remove (2 6) and (6 2).
584 edgelistFlipped = edgelist;
585 std::for_each(edgelistFlipped.begin(), edgelistFlipped.end(), flipEdge);
586 std::sort(edgelist.begin(), edgelist.end(), edgeLessThan);
587 std::sort(edgelistFlipped.begin(), edgelistFlipped.end(), edgeLessThan);
588 edgelistFiltered.resize(edgelist.size());
589 edgelistFiltered.erase(std::set_difference(edgelist.begin(), edgelist.end(), edgelistFlipped.begin(), edgelistFlipped.end(), edgelistFiltered.begin(), edgeLessThan), edgelistFiltered.end());
590
591 drawlist = &edgelistFiltered[0];
592 edge_count = edgelistFiltered.size();
593 //debug(LOG_WARNING, "we have %i edges", edge_count);
594
595 if (flag & pie_STATIC_SHADOW)
596 {
597 // then store it in the imd
598 shape->nShadowEdges = edge_count;
599 shape->shadowEdgeList = (EDGE *)realloc(shape->shadowEdgeList, sizeof(EDGE) * shape->nShadowEdges);
600 std::copy(drawlist, drawlist + edge_count, shape->shadowEdgeList);
601 }
602 }
603
604 std::vector<Vector3f> vertexes;
605 vertexes.reserve(edge_count * 6);
606 for (size_t i = 0; i < edge_count; i++)
607 {
608 int a = drawlist[i].from, b = drawlist[i].to;
609
610 glm::vec3 v1(pVertices[b].x, scale_y(pVertices[b].y, flag, flag_data), pVertices[b].z);
611 glm::vec3 v3(pVertices[a].x + light[0], scale_y(pVertices[a].y, flag, flag_data) + light[1], pVertices[a].z + light[2]);
612
613 vertexes.push_back(v1);
614 vertexes.push_back(glm::vec3(pVertices[b].x + light[0], scale_y(pVertices[b].y, flag, flag_data) + light[1], pVertices[b].z + light[2])); //v2
615 vertexes.push_back(v3);
616
617 vertexes.push_back(v3);
618 vertexes.push_back(glm::vec3(pVertices[a].x, scale_y(pVertices[a].y, flag, flag_data), pVertices[a].z)); //v4
619 vertexes.push_back(v1);
620 }
621
622 ShadowCache::CachedShadowData& cache = shadowCache.createCacheForShadowDraw(shape, flag, flag_data, light);
623 cache.vertexes = std::move(vertexes);
624 result = DRAW_SUCCESS_UNCACHED;
625 pCached = &cache;
626 }
627 else
628 {
629 result = DRAW_SUCCESS_CACHED;
630 }
631
632 // Aggregate the vertexes (pre-computed with the modelViewMatrix)
633 shadowCache.addPremultipliedVertexes(*pCached, modelViewMatrix);
634
635 return result;
636 }
637
pie_CleanUp()638 void pie_CleanUp()
639 {
640 tshapes.clear();
641 shapes.clear();
642 scshapes.clear();
643 if (pZeroedVertexBuffer)
644 {
645 delete pZeroedVertexBuffer;
646 pZeroedVertexBuffer = nullptr;
647 }
648 }
649
pie_Draw3DShape(iIMDShape * shape,int frame,int team,PIELIGHT colour,int pieFlag,int pieFlagData,const glm::mat4 & modelView)650 bool pie_Draw3DShape(iIMDShape *shape, int frame, int team, PIELIGHT colour, int pieFlag, int pieFlagData, const glm::mat4 &modelView)
651 {
652 pieCount++;
653
654 ASSERT(frame >= 0, "Negative frame %d", frame);
655 ASSERT(team >= 0, "Negative team %d", team);
656
657 const PIELIGHT teamcolour = pal_GetTeamColour(team);
658 if (pieFlag & pie_BUTTON)
659 {
660 pie_Draw3DButton(shape, teamcolour, modelView);
661 }
662 else
663 {
664 SHAPE tshape;
665 tshape.shape = shape;
666 tshape.frame = frame;
667 tshape.colour = colour;
668 tshape.teamcolour = teamcolour;
669 tshape.flag = pieFlag;
670 tshape.flag_data = pieFlagData;
671 tshape.stretch = pie_GetShaderStretchDepth();
672 tshape.matrix = modelView;
673
674 if (pieFlag & pie_HEIGHT_SCALED) // construct
675 {
676 tshape.matrix = glm::scale(tshape.matrix, glm::vec3(1.0f, (float)pieFlagData / (float)pie_RAISE_SCALE, 1.0f));
677 }
678 if (pieFlag & pie_RAISE) // collapse
679 {
680 tshape.matrix = glm::translate(tshape.matrix, glm::vec3(1.0f, (-shape->max.y * (pie_RAISE_SCALE - pieFlagData)) * (1.0f / pie_RAISE_SCALE), 1.0f));
681 }
682
683 if (pieFlag & (pie_ADDITIVE | pie_TRANSLUCENT | pie_PREMULTIPLIED))
684 {
685 tshapes.push_back(tshape);
686 }
687 else
688 {
689 if (shadows && (pieFlag & pie_SHADOW || pieFlag & pie_STATIC_SHADOW))
690 {
691 float distance;
692
693 // draw a shadow
694 ShadowcastingShape scshape;
695 scshape.matrix = modelView;
696 distance = scshape.matrix[3][0] * scshape.matrix[3][0];
697 distance += scshape.matrix[3][1] * scshape.matrix[3][1];
698 distance += scshape.matrix[3][2] * scshape.matrix[3][2];
699
700 // if object is too far in the fog don't generate a shadow.
701 if (distance < SHADOW_END_DISTANCE)
702 {
703 // Calculate the light position relative to the object
704 glm::vec4 pos_light0 = glm::vec4(currentSunPosition, 0.f);
705 glm::mat4 invmat = glm::inverse(scshape.matrix);
706
707 scshape.light = invmat * pos_light0;
708 scshape.shape = shape;
709 scshape.flag = pieFlag;
710 scshape.flag_data = pieFlagData;
711
712 scshapes.push_back(scshape);
713 }
714 }
715 shapes.push_back(tshape);
716 }
717 }
718
719 return true;
720 }
721
pie_ShadowDrawLoop(ShadowCache & shadowCache)722 static void pie_ShadowDrawLoop(ShadowCache &shadowCache)
723 {
724 size_t cachedShadowDraws = 0;
725 size_t uncachedShadowDraws = 0;
726 for (unsigned i = 0; i < scshapes.size(); i++)
727 {
728 DrawShadowResult result = pie_DrawShadow(shadowCache, scshapes[i].shape, scshapes[i].flag, scshapes[i].flag_data, scshapes[i].light, scshapes[i].matrix);
729 if (result == DRAW_SUCCESS_CACHED)
730 {
731 ++cachedShadowDraws;
732 }
733 else
734 {
735 ++uncachedShadowDraws;
736 }
737 }
738
739 const auto &premultipliedVertexes = shadowCache.getPremultipliedVertexes();
740 if (premultipliedVertexes.size() > 0)
741 {
742 // Draw the shadow volume
743 gfx_api::DrawStencilShadow::get().bind();
744 // The vertexes returned by shadowCache.getPremultipliedVertexes() are pre-multiplied by the modelViewMatrix
745 // Thus we only need to include the perspective matrix
746 gfx_api::DrawStencilShadow::get().bind_constants({ pie_PerspectiveGet(), glm::vec2(0.f), glm::vec2(0.f), glm::vec4(0.f) });
747 gfx_api::context::get().bind_streamed_vertex_buffers(premultipliedVertexes.data(), sizeof(Vector3f) * premultipliedVertexes.size());
748
749 // Batch into glDrawArrays calls of <= SHADOW_BATCH_MAX
750 static const size_t SHADOW_BATCH_MAX = 8192 * 3; // must be divisible by 3
751 size_t vertex_count = premultipliedVertexes.size();
752 for (size_t startingIndex = 0; startingIndex < vertex_count; startingIndex += SHADOW_BATCH_MAX)
753 {
754 gfx_api::DrawStencilShadow::get().draw(std::min(vertex_count - startingIndex, SHADOW_BATCH_MAX), startingIndex);
755 }
756
757 gfx_api::context::get().disable_all_vertex_buffers();
758 }
759
760 shadowCache.clearPremultipliedVertexes();
761
762 // debug(LOG_INFO, "Cached shadow draws: %lu, uncached shadow draws: %lu", cachedShadowDraws, uncachedShadowDraws);
763 }
764
765 static ShadowCache shadowCache;
766
pie_DrawShadows(uint64_t currentGameFrame)767 static void pie_DrawShadows(uint64_t currentGameFrame)
768 {
769 const float width = pie_GetVideoBufferWidth();
770 const float height = pie_GetVideoBufferHeight();
771 shadowCache.setCurrentFrame(currentGameFrame);
772
773 pie_ShadowDrawLoop(shadowCache);
774
775 PIELIGHT grey;
776 grey.byte = { 0, 0, 0, 128 };
777 pie_BoxFill_alpha(0, 0, width, height, grey);
778
779 scshapes.resize(0);
780 shadowCache.removeUnused();
781 }
782
783 struct less_than_shape
784 {
operator ()less_than_shape785 inline bool operator() (const SHAPE& shape1, const SHAPE& shape2)
786 {
787 return (shape1.shape < shape2.shape);
788 }
789 };
790
pie_RemainingPasses(uint64_t currentGameFrame)791 void pie_RemainingPasses(uint64_t currentGameFrame)
792 {
793 // Draw models
794 // sort list to reduce state changes
795 std::sort(shapes.begin(), shapes.end(), less_than_shape());
796 gfx_api::context::get().debugStringMarker("Remaining passes - opaque models");
797 templatedState lastState;
798 for (SHAPE const &shape : shapes)
799 {
800 pie_SetShaderStretchDepth(shape.stretch);
801 lastState = pie_Draw3DShape2(lastState, shape.shape, shape.frame, shape.colour, shape.teamcolour, shape.flag, shape.flag_data, shape.matrix);
802 }
803 gfx_api::context::get().disable_all_vertex_buffers();
804 if (!shapes.empty())
805 {
806 // unbind last index buffer bound inside pie_Draw3DShape2
807 gfx_api::context::get().unbind_index_buffer(*((shapes.back().shape)->buffers[VBO_INDEX]));
808 }
809 gfx_api::context::get().debugStringMarker("Remaining passes - shadows");
810 // Draw shadows
811 if (shadows)
812 {
813 pie_DrawShadows(currentGameFrame);
814 }
815 // Draw translucent models last
816 // TODO, sort list by Z order to do translucency correctly
817 gfx_api::context::get().debugStringMarker("Remaining passes - translucent models");
818 lastState = templatedState();
819 for (SHAPE const &shape : tshapes)
820 {
821 pie_SetShaderStretchDepth(shape.stretch);
822 lastState = pie_Draw3DShape2(lastState, shape.shape, shape.frame, shape.colour, shape.teamcolour, shape.flag, shape.flag_data, shape.matrix);
823 }
824 gfx_api::context::get().disable_all_vertex_buffers();
825 if (!tshapes.empty())
826 {
827 // unbind last index buffer bound inside pie_Draw3DShape2
828 gfx_api::context::get().unbind_index_buffer(*((tshapes.back().shape)->buffers[VBO_INDEX]));
829 }
830 pie_SetShaderStretchDepth(0);
831 tshapes.clear();
832 shapes.clear();
833 gfx_api::context::get().debugStringMarker("Remaining passes - done");
834 }
835
pie_GetResetCounts(size_t * pPieCount,size_t * pPolyCount)836 void pie_GetResetCounts(size_t *pPieCount, size_t *pPolyCount)
837 {
838 *pPieCount = pieCount;
839 *pPolyCount = polyCount;
840
841 pieCount = 0;
842 polyCount = 0;
843 }
844