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