Line data Source code
1 : /* Copyright (C) 2022 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "graphics/Color.h"
21 : #include "graphics/LightEnv.h"
22 : #include "graphics/Material.h"
23 : #include "graphics/Model.h"
24 : #include "graphics/ModelDef.h"
25 : #include "graphics/ShaderManager.h"
26 : #include "graphics/TextureManager.h"
27 : #include "lib/allocators/DynamicArena.h"
28 : #include "lib/allocators/STLAllocators.h"
29 : #include "lib/hash.h"
30 : #include "lib/ogl.h"
31 : #include "maths/Vector3D.h"
32 : #include "maths/Vector4D.h"
33 : #include "ps/CLogger.h"
34 : #include "ps/CStrInternStatic.h"
35 : #include "ps/Profile.h"
36 : #include "renderer/MikktspaceWrap.h"
37 : #include "renderer/ModelRenderer.h"
38 : #include "renderer/ModelVertexRenderer.h"
39 : #include "renderer/Renderer.h"
40 : #include "renderer/RenderModifiers.h"
41 : #include "renderer/SceneRenderer.h"
42 : #include "renderer/SkyManager.h"
43 : #include "renderer/TimeManager.h"
44 : #include "renderer/WaterManager.h"
45 :
46 : ///////////////////////////////////////////////////////////////////////////////////////////////
47 : // ModelRenderer implementation
48 :
49 0 : void ModelRenderer::Init()
50 : {
51 0 : }
52 :
53 : // Helper function to copy object-space position and normal vectors into arrays.
54 0 : void ModelRenderer::CopyPositionAndNormals(
55 : const CModelDefPtr& mdef,
56 : const VertexArrayIterator<CVector3D>& Position,
57 : const VertexArrayIterator<CVector3D>& Normal)
58 : {
59 0 : size_t numVertices = mdef->GetNumVertices();
60 0 : SModelVertex* vertices = mdef->GetVertices();
61 :
62 0 : for (size_t j = 0; j < numVertices; ++j)
63 : {
64 0 : Position[j] = vertices[j].m_Coords;
65 0 : Normal[j] = vertices[j].m_Norm;
66 : }
67 0 : }
68 :
69 : // Helper function to transform position and normal vectors into world-space.
70 0 : void ModelRenderer::BuildPositionAndNormals(
71 : CModel* model,
72 : const VertexArrayIterator<CVector3D>& Position,
73 : const VertexArrayIterator<CVector3D>& Normal)
74 : {
75 0 : CModelDefPtr mdef = model->GetModelDef();
76 0 : size_t numVertices = mdef->GetNumVertices();
77 0 : SModelVertex* vertices = mdef->GetVertices();
78 :
79 0 : if (model->IsSkinned())
80 : {
81 : // boned model - calculate skinned vertex positions/normals
82 :
83 : // Avoid the noisy warnings that occur inside SkinPoint/SkinNormal in
84 : // some broken situations
85 0 : if (numVertices && vertices[0].m_Blend.m_Bone[0] == 0xff)
86 : {
87 0 : LOGERROR("Model %s is boned with unboned animation", mdef->GetName().string8());
88 0 : return;
89 : }
90 :
91 0 : CModelDef::SkinPointsAndNormals(numVertices, Position, Normal, vertices, mdef->GetBlendIndices(), model->GetAnimatedBoneMatrices());
92 : }
93 : else
94 : {
95 0 : PROFILE("software transform");
96 : // just copy regular positions, transform normals to world space
97 0 : const CMatrix3D& transform = model->GetTransform();
98 0 : const CMatrix3D& invtransform = model->GetInvTransform();
99 0 : for (size_t j = 0; j < numVertices; ++j)
100 : {
101 0 : transform.Transform(vertices[j].m_Coords, Position[j]);
102 0 : invtransform.RotateTransposed(vertices[j].m_Norm, Normal[j]);
103 : }
104 : }
105 : }
106 :
107 :
108 : // Helper function for lighting
109 0 : void ModelRenderer::BuildColor4ub(
110 : CModel* model,
111 : const VertexArrayIterator<CVector3D>& Normal,
112 : const VertexArrayIterator<SColor4ub>& Color)
113 : {
114 0 : PROFILE("lighting vertices");
115 :
116 0 : CModelDefPtr mdef = model->GetModelDef();
117 0 : size_t numVertices = mdef->GetNumVertices();
118 0 : const CLightEnv& lightEnv = g_Renderer.GetSceneRenderer().GetLightEnv();
119 0 : CColor shadingColor = model->GetShadingColor();
120 :
121 0 : for (size_t j = 0; j < numVertices; ++j)
122 : {
123 0 : RGBColor tempcolor = lightEnv.EvaluateUnitScaled(Normal[j]);
124 0 : tempcolor.X *= shadingColor.r;
125 0 : tempcolor.Y *= shadingColor.g;
126 0 : tempcolor.Z *= shadingColor.b;
127 0 : Color[j] = ConvertRGBColorTo4ub(tempcolor);
128 : }
129 0 : }
130 :
131 :
132 0 : void ModelRenderer::GenTangents(const CModelDefPtr& mdef, std::vector<float>& newVertices, bool gpuSkinning)
133 : {
134 0 : MikkTSpace ms(mdef, newVertices, gpuSkinning);
135 0 : ms.Generate();
136 0 : }
137 :
138 :
139 : // Copy UV coordinates
140 0 : void ModelRenderer::BuildUV(
141 : const CModelDefPtr& mdef,
142 : const VertexArrayIterator<float[2]>& UV,
143 : int UVset)
144 : {
145 0 : const size_t numVertices = mdef->GetNumVertices();
146 0 : const size_t numberOfUVPerVertex = mdef->GetNumUVsPerVertex();
147 :
148 0 : for (size_t j = 0; j < numVertices; ++j)
149 : {
150 0 : const CVector2D& uv = mdef->GetUVCoordinates()[j * numberOfUVPerVertex + UVset];
151 0 : UV[j][0] = uv.X;
152 0 : UV[j][1] = 1.0 - uv.Y;
153 : }
154 0 : }
155 :
156 :
157 : // Build default indices array.
158 0 : void ModelRenderer::BuildIndices(
159 : const CModelDefPtr& mdef,
160 : const VertexArrayIterator<u16>& Indices)
161 : {
162 0 : size_t idxidx = 0;
163 0 : SModelFace* faces = mdef->GetFaces();
164 :
165 0 : for (size_t j = 0; j < mdef->GetNumFaces(); ++j)
166 : {
167 0 : SModelFace& face = faces[j];
168 0 : Indices[idxidx++] = face.m_Verts[0];
169 0 : Indices[idxidx++] = face.m_Verts[1];
170 0 : Indices[idxidx++] = face.m_Verts[2];
171 : }
172 0 : }
173 :
174 :
175 :
176 : ///////////////////////////////////////////////////////////////////////////////////////////////
177 : // ShaderModelRenderer implementation
178 :
179 :
180 : /**
181 : * Internal data of the ShaderModelRenderer.
182 : *
183 : * Separated into the source file to increase implementation hiding (and to
184 : * avoid some causes of recompiles).
185 : */
186 : struct ShaderModelRenderer::ShaderModelRendererInternals
187 : {
188 0 : ShaderModelRendererInternals(ShaderModelRenderer* r) : m_Renderer(r) { }
189 :
190 : /// Back-link to "our" renderer
191 : ShaderModelRenderer* m_Renderer;
192 :
193 : /// ModelVertexRenderer used for vertex transformations
194 : ModelVertexRendererPtr vertexRenderer;
195 :
196 : /// List of submitted models for rendering in this frame
197 : std::vector<CModel*> submissions[CSceneRenderer::CULL_MAX];
198 : };
199 :
200 :
201 : // Construction/Destruction
202 0 : ShaderModelRenderer::ShaderModelRenderer(ModelVertexRendererPtr vertexrenderer)
203 : {
204 0 : m = new ShaderModelRendererInternals(this);
205 0 : m->vertexRenderer = vertexrenderer;
206 0 : }
207 :
208 0 : ShaderModelRenderer::~ShaderModelRenderer()
209 : {
210 0 : delete m;
211 0 : }
212 0 :
213 : // Submit one model.
214 : void ShaderModelRenderer::Submit(int cullGroup, CModel* model)
215 0 : {
216 0 : CModelRData* rdata = (CModelRData*)model->GetRenderData();
217 :
218 0 : // Ensure model data is valid
219 0 : const void* key = m->vertexRenderer.get();
220 : if (!rdata || rdata->GetKey() != key)
221 : {
222 0 : rdata = m->vertexRenderer->CreateModelData(key, model);
223 : model->SetRenderData(rdata);
224 0 : model->SetDirty(~0u);
225 : }
226 :
227 0 : m->submissions[cullGroup].push_back(model);
228 0 : }
229 :
230 0 :
231 0 : // Call update for all submitted models and enter the rendering phase
232 0 : void ShaderModelRenderer::PrepareModels()
233 : {
234 : for (int cullGroup = 0; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
235 0 : {
236 0 : for (size_t i = 0; i < m->submissions[cullGroup].size(); ++i)
237 : {
238 : CModel* model = m->submissions[cullGroup][i];
239 :
240 0 : model->ValidatePosition();
241 :
242 0 : CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData());
243 : ENSURE(rdata->GetKey() == m->vertexRenderer.get());
244 0 :
245 : m->vertexRenderer->UpdateModelData(model, rdata, rdata->m_UpdateFlags);
246 0 : rdata->m_UpdateFlags = 0;
247 : }
248 0 : }
249 : }
250 0 :
251 0 :
252 : // Clear the submissions list
253 0 : void ShaderModelRenderer::EndFrame()
254 0 : {
255 : for (int cullGroup = 0; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
256 : m->submissions[cullGroup].clear();
257 0 : }
258 :
259 :
260 : // Helper structs for ShaderModelRenderer::Render():
261 0 :
262 : struct SMRSortByDistItem
263 0 : {
264 0 : size_t techIdx;
265 0 : CModel* model;
266 : float dist;
267 : };
268 :
269 : struct SMRBatchModel
270 : {
271 : bool operator()(CModel* a, CModel* b)
272 : {
273 : if (a->GetModelDef() < b->GetModelDef())
274 : return true;
275 : if (b->GetModelDef() < a->GetModelDef())
276 : return false;
277 :
278 : if (a->GetMaterial().GetDiffuseTexture() < b->GetMaterial().GetDiffuseTexture())
279 0 : return true;
280 : if (b->GetMaterial().GetDiffuseTexture() < a->GetMaterial().GetDiffuseTexture())
281 0 : return false;
282 :
283 0 : return a->GetMaterial().GetStaticUniforms() < b->GetMaterial().GetStaticUniforms();
284 : }
285 : };
286 0 :
287 : struct SMRCompareSortByDistItem
288 0 : {
289 : bool operator()(const SMRSortByDistItem& a, const SMRSortByDistItem& b)
290 : {
291 0 : // Prefer items with greater distance, so we draw back-to-front
292 : return (a.dist > b.dist);
293 :
294 : // (Distances will almost always be distinct, so we don't need to bother
295 : // tie-breaking on modeldef/texture/etc)
296 : }
297 0 : };
298 :
299 : class SMRMaterialBucketKey
300 0 : {
301 : public:
302 : SMRMaterialBucketKey(CStrIntern effect, const CShaderDefines& defines)
303 : : effect(effect), defines(defines) { }
304 :
305 : SMRMaterialBucketKey(const SMRMaterialBucketKey& entity) = default;
306 :
307 : CStrIntern effect;
308 : CShaderDefines defines;
309 :
310 : bool operator==(const SMRMaterialBucketKey& b) const
311 0 : {
312 : return (effect == b.effect && defines == b.defines);
313 : }
314 :
315 : private:
316 : SMRMaterialBucketKey& operator=(const SMRMaterialBucketKey&);
317 : };
318 :
319 : struct SMRMaterialBucketKeyHash
320 0 : {
321 : size_t operator()(const SMRMaterialBucketKey& key) const
322 : {
323 : size_t hash = 0;
324 : hash_combine(hash, key.effect.GetHash());
325 : hash_combine(hash, key.defines.GetHash());
326 : return hash;
327 : }
328 : };
329 0 :
330 : struct SMRTechBucket
331 0 : {
332 0 : CShaderTechniquePtr tech;
333 0 : CModel** models;
334 0 : size_t numModels;
335 :
336 : // Model list is stored as pointers, not as a std::vector,
337 : // so that sorting lists of this struct is fast
338 0 : };
339 :
340 : struct SMRCompareTechBucket
341 : {
342 : bool operator()(const SMRTechBucket& a, const SMRTechBucket& b)
343 : {
344 : return a.tech < b.tech;
345 : }
346 : };
347 :
348 : void ShaderModelRenderer::Render(
349 : Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
350 0 : const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags)
351 : {
352 0 : if (m->submissions[cullGroup].empty())
353 : return;
354 :
355 : CMatrix3D worldToCam;
356 0 : g_Renderer.GetSceneRenderer().GetViewCamera().GetOrientation().GetInverse(worldToCam);
357 :
358 : /*
359 : * Rendering approach:
360 0 : *
361 0 : * m->submissions contains the list of CModels to render.
362 : *
363 0 : * The data we need to render a model is:
364 0 : * - CShaderTechnique
365 : * - CTexture
366 : * - CShaderUniforms
367 : * - CModelDef (mesh data)
368 : * - CModel (model instance data)
369 : *
370 : * For efficient rendering, we need to batch the draw calls to minimise state changes.
371 : * (Uniform and texture changes are assumed to be cheaper than binding new mesh data,
372 : * and shader changes are assumed to be most expensive.)
373 : * First, group all models that share a technique to render them together.
374 : * Within those groups, sub-group by CModelDef.
375 : * Within those sub-groups, sub-sub-group by CTexture.
376 : * Within those sub-sub-groups, sub-sub-sub-group by CShaderUniforms.
377 : *
378 : * Alpha-blended models have to be sorted by distance from camera,
379 : * then we can batch as long as the order is preserved.
380 : * Non-alpha-blended models can be arbitrarily reordered to maximise batching.
381 : *
382 : * For each model, the CShaderTechnique is derived from:
383 : * - The current global 'context' defines
384 : * - The CModel's material's defines
385 : * - The CModel's material's shader effect name
386 : *
387 : * There are a smallish number of materials, and a smaller number of techniques.
388 : *
389 : * To minimise technique lookups, we first group models by material,
390 : * in 'materialBuckets' (a hash table).
391 : *
392 : * For each material bucket we then look up the appropriate shader technique.
393 : * If the technique requires sort-by-distance, the model is added to the
394 : * 'sortByDistItems' list with its computed distance.
395 : * Otherwise, the bucket's list of models is sorted by modeldef+texture+uniforms,
396 : * then the technique and model list is added to 'techBuckets'.
397 : *
398 : * 'techBuckets' is then sorted by technique, to improve batching when multiple
399 : * materials map onto the same technique.
400 : *
401 : * (Note that this isn't perfect batching: we don't sort across models in
402 : * multiple buckets that share a technique. In practice that shouldn't reduce
403 : * batching much (we rarely have one mesh used with multiple materials),
404 : * and it saves on copying and lets us sort smaller lists.)
405 : *
406 : * Extra tech buckets are added for the sorted-by-distance models without reordering.
407 : * Finally we render by looping over each tech bucket, then looping over the model
408 : * list in each, rebinding the GL state whenever it changes.
409 : */
410 :
411 : using Arena = Allocators::DynamicArena<256 * KiB>;
412 :
413 : Arena arena;
414 : using ModelListAllocator = ProxyAllocator<CModel*, Arena>;
415 : using ModelList_t = std::vector<CModel*, ModelListAllocator>;
416 : using MaterialBuckets_t = std::unordered_map<
417 : SMRMaterialBucketKey,
418 : ModelList_t,
419 0 : SMRMaterialBucketKeyHash,
420 : std::equal_to<SMRMaterialBucketKey>,
421 0 : ProxyAllocator<
422 0 : std::pair<const SMRMaterialBucketKey, ModelList_t>,
423 0 : Arena> >;
424 0 :
425 : MaterialBuckets_t materialBuckets((MaterialBuckets_t::allocator_type(arena)));
426 :
427 : {
428 : PROFILE3("bucketing by material");
429 :
430 : for (size_t i = 0; i < m->submissions[cullGroup].size(); ++i)
431 : {
432 : CModel* model = m->submissions[cullGroup][i];
433 0 :
434 : uint32_t condFlags = 0;
435 0 :
436 0 : const CShaderConditionalDefines& condefs = model->GetMaterial().GetConditionalDefines();
437 : for (size_t j = 0; j < condefs.GetSize(); ++j)
438 0 : {
439 : const CShaderConditionalDefines::CondDefine& item = condefs.GetItem(j);
440 0 : int type = item.m_CondType;
441 : switch (type)
442 0 : {
443 : case DCOND_DISTANCE:
444 0 : {
445 0 : CVector3D modelpos = model->GetTransform().GetTranslation();
446 : float dist = worldToCam.Transform(modelpos).Z;
447 0 :
448 0 : float dmin = item.m_CondArgs[0];
449 0 : float dmax = item.m_CondArgs[1];
450 :
451 0 : if ((dmin < 0 || dist >= dmin) && (dmax < 0 || dist < dmax))
452 0 : condFlags |= (1 << j);
453 0 :
454 0 : break;
455 : }
456 0 : }
457 0 : }
458 :
459 0 : CShaderDefines defs = model->GetMaterial().GetShaderDefines(condFlags);
460 0 : SMRMaterialBucketKey key(model->GetMaterial().GetShaderEffect(), defs);
461 :
462 0 : MaterialBuckets_t::iterator it = materialBuckets.find(key);
463 : if (it == materialBuckets.end())
464 : {
465 : std::pair<MaterialBuckets_t::iterator, bool> inserted = materialBuckets.insert(
466 : std::make_pair(key, ModelList_t(ModelList_t::allocator_type(arena))));
467 0 : inserted.first->second.reserve(32);
468 0 : inserted.first->second.push_back(model);
469 : }
470 0 : else
471 0 : {
472 : it->second.push_back(model);
473 0 : }
474 0 : }
475 0 : }
476 0 :
477 : using SortByDistItemsAllocator = ProxyAllocator<SMRSortByDistItem, Arena>;
478 : std::vector<SMRSortByDistItem, SortByDistItemsAllocator> sortByDistItems((SortByDistItemsAllocator(arena)));
479 :
480 0 : using SortByTechItemsAllocator = ProxyAllocator<CShaderTechniquePtr, Arena>;
481 : std::vector<CShaderTechniquePtr, SortByTechItemsAllocator> sortByDistTechs((SortByTechItemsAllocator(arena)));
482 : // indexed by sortByDistItems[i].techIdx
483 : // (which stores indexes instead of CShaderTechniquePtr directly
484 : // to avoid the shared_ptr copy cost when sorting; maybe it'd be better
485 0 : // if we just stored raw CShaderTechnique* and assumed the shader manager
486 0 : // will keep it alive long enough)
487 :
488 0 : using TechBucketsAllocator = ProxyAllocator<SMRTechBucket, Arena>;
489 0 : std::vector<SMRTechBucket, TechBucketsAllocator> techBuckets((TechBucketsAllocator(arena)));
490 :
491 : {
492 : PROFILE3("processing material buckets");
493 : for (MaterialBuckets_t::iterator it = materialBuckets.begin(); it != materialBuckets.end(); ++it)
494 : {
495 : CShaderDefines defines = context;
496 0 : defines.SetMany(it->first.defines);
497 0 : CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(it->first.effect, defines);
498 :
499 0 : // Skip invalid techniques (e.g. from data file errors)
500 0 : if (!tech)
501 0 : continue;
502 :
503 0 : if (tech->GetSortByDistance())
504 0 : {
505 0 : // Add the tech into a vector so we can index it
506 : // (There might be duplicates in this list, but that doesn't really matter)
507 : if (sortByDistTechs.empty() || sortByDistTechs.back() != tech)
508 0 : sortByDistTechs.push_back(tech);
509 0 : size_t techIdx = sortByDistTechs.size() - 1;
510 :
511 0 : // Add each model into sortByDistItems
512 : for (size_t i = 0; i < it->second.size(); ++i)
513 : {
514 : SMRSortByDistItem itemWithDist;
515 0 : itemWithDist.techIdx = techIdx;
516 0 :
517 0 : CModel* model = it->second[i];
518 : itemWithDist.model = model;
519 :
520 0 : CVector3D modelpos = model->GetTransform().GetTranslation();
521 : itemWithDist.dist = worldToCam.Transform(modelpos).Z;
522 0 :
523 0 : sortByDistItems.push_back(itemWithDist);
524 : }
525 0 : }
526 0 : else
527 : {
528 0 : // Sort model list by modeldef+texture, for batching
529 0 : // TODO: This only sorts by base texture. While this is an OK approximation
530 : // for most cases (as related samplers are usually used together), it would be better
531 0 : // to take all the samplers into account when sorting here.
532 : std::sort(it->second.begin(), it->second.end(), SMRBatchModel());
533 :
534 : // Add a tech bucket pointing at this model list
535 : SMRTechBucket techBucket = { tech, &it->second[0], it->second.size() };
536 : techBuckets.push_back(techBucket);
537 : }
538 : }
539 : }
540 0 :
541 : {
542 : PROFILE3("sorting tech buckets");
543 0 : // Sort by technique, for better batching
544 0 : std::sort(techBuckets.begin(), techBuckets.end(), SMRCompareTechBucket());
545 : }
546 :
547 : // List of models corresponding to sortByDistItems[i].model
548 : // (This exists primarily because techBuckets wants a CModel**;
549 0 : // we could avoid the cost of copying into this list by adding
550 0 : // a stride length into techBuckets and not requiring contiguous CModel*s)
551 : std::vector<CModel*, ModelListAllocator> sortByDistModels((ModelListAllocator(arena)));
552 0 :
553 : if (!sortByDistItems.empty())
554 : {
555 : {
556 : PROFILE3("sorting items by dist");
557 : std::sort(sortByDistItems.begin(), sortByDistItems.end(), SMRCompareSortByDistItem());
558 : }
559 0 :
560 : {
561 0 : PROFILE3("batching dist-sorted items");
562 :
563 0 : sortByDistModels.reserve(sortByDistItems.size());
564 0 :
565 0 : // Find runs of distance-sorted models that share a technique,
566 : // and create a new tech bucket for each run
567 :
568 0 : size_t start = 0; // start of current run
569 0 : size_t currentTechIdx = sortByDistItems[start].techIdx;
570 :
571 0 : for (size_t end = 0; end < sortByDistItems.size(); ++end)
572 : {
573 : sortByDistModels.push_back(sortByDistItems[end].model);
574 :
575 : size_t techIdx = sortByDistItems[end].techIdx;
576 0 : if (techIdx != currentTechIdx)
577 0 : {
578 : // Start of a new run - push the old run into a new tech bucket
579 0 : SMRTechBucket techBucket = { sortByDistTechs[currentTechIdx], &sortByDistModels[start], end - start };
580 : techBuckets.push_back(techBucket);
581 0 : start = end;
582 : currentTechIdx = techIdx;
583 0 : }
584 0 : }
585 :
586 : // Add the tech bucket for the final run
587 0 : SMRTechBucket techBucket = { sortByDistTechs[currentTechIdx], &sortByDistModels[start], sortByDistItems.size() - start };
588 0 : techBuckets.push_back(techBucket);
589 0 : }
590 0 : }
591 :
592 : const double time = g_Renderer.GetTimeManager().GetGlobalTime();
593 :
594 : {
595 0 : PROFILE3("rendering bucketed submissions");
596 0 :
597 : size_t idxTechStart = 0;
598 :
599 : // This vector keeps track of texture changes during rendering. It is kept outside the
600 0 : // loops to avoid excessive reallocations. The token allocation of 64 elements
601 : // should be plenty, though it is reallocated below (at a cost) if necessary.
602 0 : using TextureListAllocator = ProxyAllocator<CTexture*, Arena>;
603 0 : std::vector<CTexture*, TextureListAllocator> currentTexs((TextureListAllocator(arena)));
604 : currentTexs.reserve(64);
605 0 :
606 : // texBindings holds the identifier bindings in the shader, which can no longer be defined
607 : // statically in the ShaderRenderModifier class. texBindingNames uses interned strings to
608 : // keep track of when bindings need to be reevaluated.
609 : using BindingListAllocator = ProxyAllocator<CShaderProgram::Binding, Arena>;
610 0 : std::vector<CShaderProgram::Binding, BindingListAllocator> texBindings((BindingListAllocator(arena)));
611 0 : texBindings.reserve(64);
612 0 :
613 : using BindingNamesListAllocator = ProxyAllocator<CStrIntern, Arena>;
614 : std::vector<CStrIntern, BindingNamesListAllocator> texBindingNames((BindingNamesListAllocator(arena)));
615 : texBindingNames.reserve(64);
616 :
617 0 : while (idxTechStart < techBuckets.size())
618 0 : {
619 0 : CShaderTechniquePtr currentTech = techBuckets[idxTechStart].tech;
620 :
621 0 : // Find runs [idxTechStart, idxTechEnd) in techBuckets of the same technique
622 0 : size_t idxTechEnd;
623 0 : for (idxTechEnd = idxTechStart + 1; idxTechEnd < techBuckets.size(); ++idxTechEnd)
624 : {
625 0 : if (techBuckets[idxTechEnd].tech != currentTech)
626 : break;
627 0 : }
628 :
629 : // For each of the technique's passes, render all the models in this run
630 0 : for (int pass = 0; pass < currentTech->GetNumPasses(); ++pass)
631 0 : {
632 : currentTech->BeginPass(pass);
633 0 : deviceCommandContext->SetGraphicsPipelineState(
634 : currentTech->GetGraphicsPipelineStateDesc(pass));
635 :
636 : const CShaderProgramPtr& shader = currentTech->GetShader(pass);
637 : int streamflags = shader->GetStreamFlags();
638 0 :
639 : modifier->BeginPass(shader);
640 0 :
641 0 : m->vertexRenderer->BeginPass(streamflags);
642 :
643 : // When the shader technique changes, textures need to be
644 0 : // rebound, so ensure there are no remnants from the last pass.
645 0 : // (the vector size is set to 0, but memory is not freed)
646 : currentTexs.clear();
647 0 : texBindings.clear();
648 : texBindingNames.clear();
649 0 :
650 : CModelDef* currentModeldef = NULL;
651 : CShaderUniforms currentStaticUniforms;
652 :
653 : for (size_t idx = idxTechStart; idx < idxTechEnd; ++idx)
654 0 : {
655 0 : CModel** models = techBuckets[idx].models;
656 0 : size_t numModels = techBuckets[idx].numModels;
657 : for (size_t i = 0; i < numModels; ++i)
658 0 : {
659 0 : CModel* model = models[i];
660 :
661 0 : if (flags && !(model->GetFlags() & flags))
662 : continue;
663 0 :
664 0 : const CMaterial::SamplersVector& samplers = model->GetMaterial().GetSamplers();
665 0 : size_t samplersNum = samplers.size();
666 :
667 0 : // make sure the vectors are the right virtual sizes, and also
668 : // reallocate if there are more samplers than expected.
669 0 : if (currentTexs.size() != samplersNum)
670 : {
671 : currentTexs.resize(samplersNum, NULL);
672 0 : texBindings.resize(samplersNum, CShaderProgram::Binding());
673 0 : texBindingNames.resize(samplersNum, CStrIntern());
674 :
675 : // ensure they are definitely empty
676 : std::fill(texBindings.begin(), texBindings.end(), CShaderProgram::Binding());
677 0 : std::fill(currentTexs.begin(), currentTexs.end(), (CTexture*)NULL);
678 : std::fill(texBindingNames.begin(), texBindingNames.end(), CStrIntern());
679 0 : }
680 0 :
681 0 : // bind the samplers to the shader
682 : for (size_t s = 0; s < samplersNum; ++s)
683 : {
684 0 : const CMaterial::TextureSampler& samp = samplers[s];
685 0 :
686 0 : // check that the handles are current
687 : // and reevaluate them if necessary
688 : if (texBindingNames[s] != samp.Name || !texBindings[s].Active())
689 : {
690 0 : texBindings[s] = shader->GetTextureBinding(samp.Name);
691 : texBindingNames[s] = samp.Name;
692 0 : }
693 :
694 : // same with the actual sampler bindings
695 : CTexture* newTex = samp.Sampler.get();
696 0 : if (texBindings[s].Active() && newTex != currentTexs[s])
697 : {
698 0 : newTex->UploadBackendTextureIfNeeded(deviceCommandContext);
699 0 : shader->BindTexture(texBindings[s], newTex->GetBackendTexture());
700 : currentTexs[s] = newTex;
701 : }
702 : }
703 0 :
704 0 : // Bind modeldef when it changes
705 : CModelDef* newModeldef = model->GetModelDef().get();
706 0 : if (newModeldef != currentModeldef)
707 0 : {
708 0 : currentModeldef = newModeldef;
709 : m->vertexRenderer->PrepareModelDef(deviceCommandContext, shader, streamflags, *currentModeldef);
710 : }
711 :
712 : // Bind all uniforms when any change
713 0 : CShaderUniforms newStaticUniforms = model->GetMaterial().GetStaticUniforms();
714 0 : if (newStaticUniforms != currentStaticUniforms)
715 : {
716 0 : currentStaticUniforms = newStaticUniforms;
717 0 : currentStaticUniforms.BindUniforms(shader);
718 : }
719 :
720 : const CShaderRenderQueries& renderQueries = model->GetMaterial().GetRenderQueries();
721 0 :
722 0 : for (size_t q = 0; q < renderQueries.GetSize(); ++q)
723 : {
724 0 : CShaderRenderQueries::RenderQuery rq = renderQueries.GetItem(q);
725 0 : if (rq.first == RQUERY_TIME)
726 : {
727 : shader->Uniform(rq.second, time, 0.0f, 0.0f, 0.0f);
728 : }
729 : else if (rq.first == RQUERY_WATER_TEX)
730 0 : {
731 : const double period = 1.6;
732 0 : const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager();
733 0 : if (waterManager.m_RenderWater && waterManager.WillRenderFancyWater())
734 : {
735 0 : const CTexturePtr& waterTexture = waterManager.m_NormalMap[waterManager.GetCurrentTextureIndex(period)];
736 : waterTexture->UploadBackendTextureIfNeeded(deviceCommandContext);
737 0 : shader->BindTexture(str_waterTex, waterTexture->GetBackendTexture());
738 : }
739 0 : else
740 0 : shader->BindTexture(str_waterTex, g_Renderer.GetTextureManager().GetErrorTexture()->GetBackendTexture());
741 0 : }
742 : else if (rq.first == RQUERY_SKY_CUBE)
743 0 : {
744 0 : shader->BindTexture(str_skyCube, g_Renderer.GetSceneRenderer().GetSkyManager().GetSkyCube());
745 0 : }
746 : }
747 :
748 0 : modifier->PrepareModel(shader, model);
749 :
750 0 : CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData());
751 : ENSURE(rdata->GetKey() == m->vertexRenderer.get());
752 0 :
753 : m->vertexRenderer->RenderModel(deviceCommandContext, shader, streamflags, model, rdata);
754 : }
755 : }
756 0 :
757 : m->vertexRenderer->EndPass(deviceCommandContext, streamflags);
758 0 :
759 0 : currentTech->EndPass(pass);
760 : }
761 0 :
762 : idxTechStart = idxTechEnd;
763 : }
764 : }
765 0 : }
|