Line data Source code
1 : /* Copyright (C) 2023 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 "OverlayRenderer.h"
21 :
22 : #include "graphics/Camera.h"
23 : #include "graphics/LOSTexture.h"
24 : #include "graphics/Overlay.h"
25 : #include "graphics/ShaderManager.h"
26 : #include "graphics/Terrain.h"
27 : #include "graphics/TextureManager.h"
28 : #include "lib/hash.h"
29 : #include "maths/MathUtil.h"
30 : #include "maths/Quaternion.h"
31 : #include "ps/CStrInternStatic.h"
32 : #include "ps/Game.h"
33 : #include "ps/Profile.h"
34 : #include "renderer/backend/PipelineState.h"
35 : #include "renderer/DebugRenderer.h"
36 : #include "renderer/Renderer.h"
37 : #include "renderer/SceneRenderer.h"
38 : #include "renderer/TexturedLineRData.h"
39 : #include "renderer/VertexArray.h"
40 : #include "renderer/VertexBuffer.h"
41 : #include "renderer/VertexBufferManager.h"
42 : #include "simulation2/components/ICmpWaterManager.h"
43 : #include "simulation2/Simulation2.h"
44 : #include "simulation2/system/SimContext.h"
45 :
46 : #include <unordered_map>
47 :
48 : namespace
49 : {
50 :
51 120 : struct Shader
52 : {
53 : CShaderTechniquePtr technique, techniqueWireframe;
54 :
55 0 : const CShaderTechniquePtr& GetTechnique() const
56 : {
57 0 : return g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME
58 0 : ? techniqueWireframe : technique;
59 : }
60 : };
61 :
62 0 : void AdjustOverlayGraphicsPipelineState(
63 : Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc,
64 : const bool depthTestEnabled)
65 : {
66 0 : pipelineStateDesc.depthStencilState.depthTestEnabled = depthTestEnabled;
67 0 : pipelineStateDesc.depthStencilState.depthWriteEnabled = false;
68 0 : pipelineStateDesc.blendState.enabled = true;
69 0 : pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor =
70 : Renderer::Backend::BlendFactor::SRC_ALPHA;
71 0 : pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor =
72 : Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
73 0 : pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp =
74 : Renderer::Backend::BlendOp::ADD;
75 0 : }
76 :
77 30 : Shader CreateShader(
78 : const CStrIntern name, const CShaderDefines& defines,
79 : const bool depthTestEnabled)
80 : {
81 30 : Shader shader;
82 :
83 60 : shader.technique = g_Renderer.GetShaderManager().LoadEffect(
84 : name, defines,
85 0 : [depthTestEnabled](Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc)
86 0 : {
87 0 : AdjustOverlayGraphicsPipelineState(pipelineStateDesc, depthTestEnabled);
88 30 : });
89 60 : shader.techniqueWireframe = g_Renderer.GetShaderManager().LoadEffect(
90 : name, defines,
91 0 : [depthTestEnabled](Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc)
92 0 : {
93 0 : AdjustOverlayGraphicsPipelineState(pipelineStateDesc, depthTestEnabled);
94 0 : pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE;
95 30 : });
96 :
97 30 : return shader;
98 : }
99 :
100 : /**
101 : * Key used to group quads into batches for more efficient rendering. Currently groups by the combination
102 : * of the main texture and the texture mask, to minimize texture swapping during rendering.
103 : */
104 0 : struct QuadBatchKey
105 : {
106 0 : QuadBatchKey (const CTexturePtr& texture, const CTexturePtr& textureMask)
107 0 : : m_Texture(texture), m_TextureMask(textureMask)
108 0 : { }
109 :
110 0 : bool operator==(const QuadBatchKey& other) const
111 : {
112 0 : return (m_Texture == other.m_Texture && m_TextureMask == other.m_TextureMask);
113 : }
114 :
115 : CTexturePtr m_Texture;
116 : CTexturePtr m_TextureMask;
117 : };
118 :
119 : struct QuadBatchHash
120 : {
121 0 : std::size_t operator()(const QuadBatchKey& d) const
122 : {
123 0 : size_t seed = 0;
124 0 : hash_combine(seed, d.m_Texture);
125 0 : hash_combine(seed, d.m_TextureMask);
126 0 : return seed;
127 : }
128 : };
129 :
130 : /**
131 : * Holds information about a single quad rendering batch.
132 : */
133 0 : class QuadBatchData : public CRenderData
134 : {
135 : public:
136 0 : QuadBatchData() : m_IndicesBase(0), m_NumRenderQuads(0) { }
137 :
138 : /// Holds the quad overlay structures requested to be rendered in this batch. Must be cleared
139 : /// after each frame.
140 : std::vector<SOverlayQuad*> m_Quads;
141 :
142 : /// Start index of this batch into the dedicated quad indices VertexArray (see OverlayInternals).
143 : size_t m_IndicesBase;
144 : /// Amount of quads to actually render in this batch. Potentially (although unlikely to be)
145 : /// different from m_Quads.size() due to restrictions on the total amount of quads that can be
146 : /// rendered. Must be reset after each frame.
147 : size_t m_NumRenderQuads;
148 : };
149 :
150 : } // anonymous namespace
151 :
152 : struct OverlayRendererInternals
153 : {
154 : using QuadBatchMap = std::unordered_map<QuadBatchKey, QuadBatchData, QuadBatchHash>;
155 :
156 : OverlayRendererInternals();
157 6 : ~OverlayRendererInternals() = default;
158 :
159 : Renderer::Backend::IDevice* device = nullptr;
160 :
161 : std::vector<SOverlayLine*> lines;
162 : std::vector<SOverlayTexturedLine*> texlines;
163 : std::vector<SOverlaySprite*> sprites;
164 : std::vector<SOverlayQuad*> quads;
165 : std::vector<SOverlaySphere*> spheres;
166 :
167 : QuadBatchMap quadBatchMap;
168 :
169 : // Dedicated vertex/index buffers for rendering all quads (to within the limits set by
170 : // MAX_QUAD_OVERLAYS).
171 : VertexArray quadVertices;
172 : VertexArray::Attribute quadAttributePos;
173 : VertexArray::Attribute quadAttributeColor;
174 : VertexArray::Attribute quadAttributeUV;
175 : VertexIndexArray quadIndices;
176 :
177 : // Maximum amount of quad overlays we support for rendering. This limit is set to be able to
178 : // render all quads from a single dedicated VB without having to reallocate it, which is much
179 : // faster in the typical case of rendering only a handful of quads. When modifying this value,
180 : // you must take care for the new amount of quads to fit in a single backend buffer (which is
181 : // not likely to be a problem).
182 : static const size_t MAX_QUAD_OVERLAYS = 1024;
183 :
184 : // Sets of commonly-(re)used shader defines.
185 : CShaderDefines defsOverlayLineNormal;
186 : CShaderDefines defsOverlayLineAlwaysVisible;
187 : CShaderDefines defsQuadOverlay;
188 :
189 : Shader shaderTexLineNormal;
190 : Shader shaderTexLineAlwaysVisible;
191 : Shader shaderQuadOverlay;
192 : Shader shaderForegroundOverlay;
193 : Shader shaderOverlaySolid;
194 :
195 : Renderer::Backend::IVertexInputLayout* quadVertexInputLayout = nullptr;
196 : Renderer::Backend::IVertexInputLayout* foregroundVertexInputLayout = nullptr;
197 : Renderer::Backend::IVertexInputLayout* sphereVertexInputLayout = nullptr;
198 :
199 : Renderer::Backend::IVertexInputLayout* texturedLineVertexInputLayout = nullptr;
200 :
201 : // Geometry for a unit sphere
202 : std::vector<float> sphereVertexes;
203 : std::vector<u16> sphereIndexes;
204 : void GenerateSphere();
205 :
206 : // Performs one-time setup. Called from CRenderer::Open, after graphics capabilities have
207 : // been detected. Note that no backend buffer must be created before this is called, since
208 : // the shader path and graphics capabilities are not guaranteed to be stable before this
209 : // point.
210 : void Initialize();
211 : };
212 :
213 : const float OverlayRenderer::OVERLAY_VOFFSET = 0.2f;
214 :
215 6 : OverlayRendererInternals::OverlayRendererInternals()
216 : : quadVertices(Renderer::Backend::IBuffer::Type::VERTEX, true),
217 6 : quadIndices(false)
218 : {
219 6 : quadAttributePos.format = Renderer::Backend::Format::R32G32B32_SFLOAT;
220 6 : quadVertices.AddAttribute(&quadAttributePos);
221 :
222 6 : quadAttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM;
223 6 : quadVertices.AddAttribute(&quadAttributeColor);
224 :
225 6 : quadAttributeUV.format = Renderer::Backend::Format::R32G32_SFLOAT;
226 6 : quadVertices.AddAttribute(&quadAttributeUV);
227 :
228 : // Note that we're reusing the textured overlay line shader for the quad overlay rendering. This
229 : // is because their code is almost identical; the only difference is that for the quad overlays
230 : // we want to use a vertex color stream as opposed to an objectColor uniform. To this end, the
231 : // shader has been set up to switch between the two behaviours based on the USE_OBJECTCOLOR define.
232 6 : defsOverlayLineNormal.Add(str_USE_OBJECTCOLOR, str_1);
233 6 : defsOverlayLineAlwaysVisible.Add(str_USE_OBJECTCOLOR, str_1);
234 6 : defsOverlayLineAlwaysVisible.Add(str_IGNORE_LOS, str_1);
235 6 : }
236 :
237 6 : void OverlayRendererInternals::Initialize()
238 : {
239 : // Perform any initialization after graphics capabilities have been detected. Notably,
240 : // only at this point can we safely allocate backend buffer (in contrast to e.g. in the constructor),
241 : // because their creation depends on the shader path, which is not reliably set before this point.
242 :
243 6 : quadVertices.SetNumberOfVertices(MAX_QUAD_OVERLAYS * 4);
244 6 : quadVertices.Layout(); // allocate backing store
245 :
246 6 : quadIndices.SetNumberOfVertices(MAX_QUAD_OVERLAYS * 6);
247 6 : quadIndices.Layout(); // allocate backing store
248 :
249 : // Since the quads in the vertex array are independent and always consist of exactly 4 vertices per quad, the
250 : // indices are always the same; we can therefore fill in all the indices once and pretty much forget about
251 : // them. We then also no longer need its backing store, since we never change any indices afterwards.
252 6 : VertexArrayIterator<u16> index = quadIndices.GetIterator();
253 6150 : for (u16 i = 0; i < static_cast<u16>(MAX_QUAD_OVERLAYS); ++i)
254 : {
255 6144 : *index++ = i * 4 + 0;
256 6144 : *index++ = i * 4 + 1;
257 6144 : *index++ = i * 4 + 2;
258 6144 : *index++ = i * 4 + 2;
259 6144 : *index++ = i * 4 + 3;
260 6144 : *index++ = i * 4 + 0;
261 : }
262 6 : quadIndices.Upload();
263 6 : quadIndices.FreeBackingStore();
264 :
265 6 : shaderTexLineNormal =
266 12 : CreateShader(str_overlay_line, defsOverlayLineNormal, true);
267 6 : shaderTexLineAlwaysVisible =
268 12 : CreateShader(str_overlay_line, defsOverlayLineAlwaysVisible, true);
269 6 : shaderQuadOverlay =
270 12 : CreateShader(str_overlay_line, defsQuadOverlay, true);
271 6 : shaderForegroundOverlay =
272 12 : CreateShader(str_foreground_overlay, {}, false);
273 6 : shaderOverlaySolid =
274 12 : CreateShader(str_overlay_solid, {}, true);
275 :
276 6 : const uint32_t quadStride = quadVertices.GetStride();
277 6 : const std::array<Renderer::Backend::SVertexAttributeFormat, 3> quadAttributes{{
278 : {Renderer::Backend::VertexAttributeStream::POSITION,
279 12 : quadAttributePos.format, quadAttributePos.offset, quadStride,
280 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
281 : {Renderer::Backend::VertexAttributeStream::COLOR,
282 12 : quadAttributeColor.format, quadAttributeColor.offset, quadStride,
283 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
284 : {Renderer::Backend::VertexAttributeStream::UV0,
285 12 : quadAttributeUV.format, quadAttributeUV.offset, quadStride,
286 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}
287 36 : }};
288 6 : quadVertexInputLayout = g_Renderer.GetVertexInputLayout(quadAttributes);
289 :
290 6 : const std::array<Renderer::Backend::SVertexAttributeFormat, 2> foregroundAttributes{{
291 : {Renderer::Backend::VertexAttributeStream::POSITION,
292 : Renderer::Backend::Format::R32G32B32_SFLOAT, 0, sizeof(float) * 3,
293 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
294 : {Renderer::Backend::VertexAttributeStream::UV0,
295 : Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
296 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1}
297 : }};
298 6 : foregroundVertexInputLayout = g_Renderer.GetVertexInputLayout(foregroundAttributes);
299 :
300 6 : const std::array<Renderer::Backend::SVertexAttributeFormat, 1> shpereAttributes{{
301 : {Renderer::Backend::VertexAttributeStream::POSITION,
302 : Renderer::Backend::Format::R32G32B32_SFLOAT, 0, sizeof(float) * 3,
303 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}
304 : }};
305 6 : sphereVertexInputLayout = g_Renderer.GetVertexInputLayout(shpereAttributes);
306 :
307 6 : texturedLineVertexInputLayout = CTexturedLineRData::GetVertexInputLayout();
308 6 : }
309 :
310 6 : OverlayRenderer::OverlayRenderer()
311 : {
312 6 : m = new OverlayRendererInternals();
313 6 : }
314 :
315 12 : OverlayRenderer::~OverlayRenderer()
316 : {
317 6 : delete m;
318 6 : }
319 :
320 6 : void OverlayRenderer::Initialize()
321 : {
322 6 : m->Initialize();
323 6 : }
324 :
325 0 : void OverlayRenderer::Submit(SOverlayLine* line)
326 : {
327 0 : m->lines.push_back(line);
328 0 : }
329 :
330 0 : void OverlayRenderer::Submit(SOverlayTexturedLine* line)
331 : {
332 : // Simplify the rest of the code by guaranteeing non-empty lines
333 0 : if (line->m_Coords.empty())
334 0 : return;
335 :
336 0 : m->texlines.push_back(line);
337 : }
338 :
339 0 : void OverlayRenderer::Submit(SOverlaySprite* overlay)
340 : {
341 0 : m->sprites.push_back(overlay);
342 0 : }
343 :
344 0 : void OverlayRenderer::Submit(SOverlayQuad* overlay)
345 : {
346 0 : m->quads.push_back(overlay);
347 0 : }
348 :
349 0 : void OverlayRenderer::Submit(SOverlaySphere* overlay)
350 : {
351 0 : m->spheres.push_back(overlay);
352 0 : }
353 :
354 0 : void OverlayRenderer::EndFrame()
355 : {
356 0 : m->lines.clear();
357 0 : m->texlines.clear();
358 0 : m->sprites.clear();
359 0 : m->quads.clear();
360 0 : m->spheres.clear();
361 :
362 : // this should leave the capacity unchanged, which is okay since it
363 : // won't be very large or very variable
364 :
365 : // Empty the batch rendering data structures, but keep their key mappings around for the next frames
366 0 : for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
367 : {
368 0 : QuadBatchData& quadBatchData = (it->second);
369 0 : quadBatchData.m_Quads.clear();
370 0 : quadBatchData.m_NumRenderQuads = 0;
371 0 : quadBatchData.m_IndicesBase = 0;
372 : }
373 0 : }
374 :
375 0 : void OverlayRenderer::PrepareForRendering()
376 : {
377 0 : PROFILE3("prepare overlays");
378 :
379 : // This is where we should do something like sort the overlays by
380 : // color/sprite/etc for more efficient rendering
381 :
382 0 : for (size_t i = 0; i < m->texlines.size(); ++i)
383 : {
384 0 : SOverlayTexturedLine* line = m->texlines[i];
385 0 : if (!line->m_RenderData)
386 : {
387 0 : line->m_RenderData = std::make_shared<CTexturedLineRData>();
388 0 : line->m_RenderData->Update(*line);
389 : // We assume the overlay line will get replaced by the caller
390 : // if terrain changes, so we don't need to detect that here and
391 : // call Update again. Also we assume the caller won't change
392 : // any of the parameters after first submitting the line.
393 : }
394 : }
395 :
396 : // Group quad overlays by their texture/mask combination for efficient rendering
397 : // TODO: consider doing this directly in Submit()
398 0 : for (size_t i = 0; i < m->quads.size(); ++i)
399 : {
400 0 : SOverlayQuad* const quad = m->quads[i];
401 :
402 0 : QuadBatchKey textures(quad->m_Texture, quad->m_TextureMask);
403 0 : QuadBatchData& batchRenderData = m->quadBatchMap[textures]; // will create entry if it doesn't already exist
404 :
405 : // add overlay to list of quads
406 0 : batchRenderData.m_Quads.push_back(quad);
407 : }
408 :
409 0 : const CVector3D vOffset(0, OverlayRenderer::OVERLAY_VOFFSET, 0);
410 :
411 : // Write quad overlay vertices/indices to VA backing store
412 0 : VertexArrayIterator<CVector3D> vertexPos = m->quadAttributePos.GetIterator<CVector3D>();
413 0 : VertexArrayIterator<SColor4ub> vertexColor = m->quadAttributeColor.GetIterator<SColor4ub>();
414 0 : VertexArrayIterator<float[2]> vertexUV = m->quadAttributeUV.GetIterator<float[2]>();
415 :
416 0 : size_t indicesIdx = 0;
417 0 : size_t totalNumQuads = 0;
418 :
419 0 : for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
420 : {
421 0 : QuadBatchData& batchRenderData = (it->second);
422 0 : batchRenderData.m_NumRenderQuads = 0;
423 :
424 0 : if (batchRenderData.m_Quads.empty())
425 0 : continue;
426 :
427 : // Remember the current index into the (entire) indices array as our base offset for this batch
428 0 : batchRenderData.m_IndicesBase = indicesIdx;
429 :
430 : // points to the index where each iteration's vertices will be appended
431 0 : for (size_t i = 0; i < batchRenderData.m_Quads.size() && totalNumQuads < OverlayRendererInternals::MAX_QUAD_OVERLAYS; i++)
432 : {
433 0 : const SOverlayQuad* quad = batchRenderData.m_Quads[i];
434 :
435 0 : const SColor4ub quadColor = quad->m_Color.AsSColor4ub();
436 :
437 0 : *vertexPos++ = quad->m_Corners[0] + vOffset;
438 0 : *vertexPos++ = quad->m_Corners[1] + vOffset;
439 0 : *vertexPos++ = quad->m_Corners[2] + vOffset;
440 0 : *vertexPos++ = quad->m_Corners[3] + vOffset;
441 :
442 0 : (*vertexUV)[0] = 0;
443 0 : (*vertexUV)[1] = 0;
444 0 : ++vertexUV;
445 0 : (*vertexUV)[0] = 0;
446 0 : (*vertexUV)[1] = 1;
447 0 : ++vertexUV;
448 0 : (*vertexUV)[0] = 1;
449 0 : (*vertexUV)[1] = 1;
450 0 : ++vertexUV;
451 0 : (*vertexUV)[0] = 1;
452 0 : (*vertexUV)[1] = 0;
453 0 : ++vertexUV;
454 :
455 0 : *vertexColor++ = quadColor;
456 0 : *vertexColor++ = quadColor;
457 0 : *vertexColor++ = quadColor;
458 0 : *vertexColor++ = quadColor;
459 :
460 0 : indicesIdx += 6;
461 :
462 0 : totalNumQuads++;
463 0 : batchRenderData.m_NumRenderQuads++;
464 : }
465 : }
466 :
467 0 : m->quadVertices.Upload();
468 : // don't free the backing store! we'll overwrite it on the next frame to save a reallocation.
469 :
470 0 : m->quadVertices.PrepareForRendering();
471 0 : }
472 :
473 0 : void OverlayRenderer::Upload(
474 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
475 : {
476 0 : m->quadVertices.UploadIfNeeded(deviceCommandContext);
477 0 : m->quadIndices.UploadIfNeeded(deviceCommandContext);
478 0 : }
479 :
480 0 : void OverlayRenderer::RenderOverlaysBeforeWater(
481 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
482 : {
483 0 : PROFILE3_GPU("overlays (before)");
484 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Render overlays before water");
485 :
486 0 : for (SOverlayLine* line : m->lines)
487 : {
488 0 : if (line->m_Coords.empty())
489 0 : continue;
490 :
491 0 : g_Renderer.GetDebugRenderer().DrawLine(line->m_Coords, line->m_Color, static_cast<float>(line->m_Thickness));
492 : }
493 0 : }
494 :
495 0 : void OverlayRenderer::RenderOverlaysAfterWater(
496 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
497 : {
498 0 : PROFILE3_GPU("overlays (after)");
499 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Render overlays after water");
500 :
501 0 : RenderTexturedOverlayLines(deviceCommandContext);
502 0 : RenderQuadOverlays(deviceCommandContext);
503 0 : RenderSphereOverlays(deviceCommandContext);
504 0 : }
505 :
506 0 : void OverlayRenderer::RenderTexturedOverlayLines(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
507 : {
508 0 : if (m->texlines.empty())
509 0 : return;
510 :
511 0 : CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
512 :
513 0 : if (m->shaderTexLineNormal.technique)
514 : {
515 0 : const CShaderTechniquePtr& shaderTechnique = m->shaderTexLineNormal.GetTechnique();
516 0 : deviceCommandContext->SetGraphicsPipelineState(
517 0 : shaderTechnique->GetGraphicsPipelineState());
518 0 : deviceCommandContext->BeginPass();
519 :
520 0 : Renderer::Backend::IShaderProgram* shaderTexLineNormal = shaderTechnique->GetShader();
521 :
522 0 : deviceCommandContext->SetTexture(
523 0 : shaderTexLineNormal->GetBindingSlot(str_losTex), los.GetTexture());
524 :
525 : const CMatrix3D transform =
526 0 : g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
527 0 : deviceCommandContext->SetUniform(
528 0 : shaderTexLineNormal->GetBindingSlot(str_transform), transform.AsFloatArray());
529 0 : deviceCommandContext->SetUniform(
530 0 : shaderTexLineNormal->GetBindingSlot(str_losTransform),
531 0 : los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]);
532 :
533 : // batch render only the non-always-visible overlay lines using the normal shader
534 0 : RenderTexturedOverlayLines(deviceCommandContext, shaderTexLineNormal, false);
535 :
536 0 : deviceCommandContext->EndPass();
537 : }
538 :
539 0 : if (m->shaderTexLineAlwaysVisible.technique)
540 : {
541 0 : const CShaderTechniquePtr& shaderTechnique = m->shaderTexLineAlwaysVisible.GetTechnique();
542 0 : deviceCommandContext->SetGraphicsPipelineState(
543 0 : shaderTechnique->GetGraphicsPipelineState());
544 0 : deviceCommandContext->BeginPass();
545 :
546 : Renderer::Backend::IShaderProgram* shaderTexLineAlwaysVisible =
547 0 : shaderTechnique->GetShader();
548 :
549 : // TODO: losTex and losTransform are unused in the always visible shader; see if these can be safely omitted
550 0 : deviceCommandContext->SetTexture(
551 0 : shaderTexLineAlwaysVisible->GetBindingSlot(str_losTex), los.GetTexture());
552 :
553 : const CMatrix3D transform =
554 0 : g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
555 0 : deviceCommandContext->SetUniform(
556 0 : shaderTexLineAlwaysVisible->GetBindingSlot(str_transform), transform.AsFloatArray());
557 0 : deviceCommandContext->SetUniform(
558 0 : shaderTexLineAlwaysVisible->GetBindingSlot(str_losTransform),
559 0 : los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]);
560 :
561 : // batch render only the always-visible overlay lines using the LoS-ignored shader
562 0 : RenderTexturedOverlayLines(deviceCommandContext, shaderTexLineAlwaysVisible, true);
563 :
564 0 : deviceCommandContext->EndPass();
565 : }
566 : }
567 :
568 0 : void OverlayRenderer::RenderTexturedOverlayLines(
569 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
570 : Renderer::Backend::IShaderProgram* shader, bool alwaysVisible)
571 : {
572 0 : for (size_t i = 0; i < m->texlines.size(); ++i)
573 : {
574 0 : SOverlayTexturedLine* line = m->texlines[i];
575 :
576 : // render only those lines matching the requested alwaysVisible status
577 0 : if (!line->m_RenderData || line->m_AlwaysVisible != alwaysVisible)
578 0 : continue;
579 :
580 0 : ENSURE(line->m_RenderData);
581 0 : line->m_RenderData->Render(
582 0 : deviceCommandContext, m->texturedLineVertexInputLayout, *line, shader);
583 : }
584 0 : }
585 :
586 0 : void OverlayRenderer::RenderQuadOverlays(
587 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
588 : {
589 0 : if (m->quadBatchMap.empty())
590 0 : return;
591 :
592 0 : if (!m->shaderQuadOverlay.technique)
593 0 : return;
594 :
595 0 : const CShaderTechniquePtr& shaderTechnique = m->shaderQuadOverlay.GetTechnique();
596 0 : deviceCommandContext->SetGraphicsPipelineState(
597 0 : shaderTechnique->GetGraphicsPipelineState());
598 0 : deviceCommandContext->BeginPass();
599 :
600 0 : Renderer::Backend::IShaderProgram* shader = shaderTechnique->GetShader();
601 :
602 0 : CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
603 :
604 0 : deviceCommandContext->SetTexture(
605 0 : shader->GetBindingSlot(str_losTex), los.GetTexture());
606 0 : deviceCommandContext->SetUniform(
607 0 : shader->GetBindingSlot(str_losTransform),
608 0 : los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]);
609 :
610 : const CMatrix3D transform =
611 0 : g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
612 0 : deviceCommandContext->SetUniform(
613 0 : shader->GetBindingSlot(str_transform), transform.AsFloatArray());
614 :
615 0 : const uint32_t vertexStride = m->quadVertices.GetStride();
616 0 : const uint32_t firstVertexOffset = m->quadVertices.GetOffset() * vertexStride;
617 :
618 0 : deviceCommandContext->SetVertexInputLayout(m->quadVertexInputLayout);
619 :
620 0 : deviceCommandContext->SetVertexBuffer(
621 0 : 0, m->quadVertices.GetBuffer(), firstVertexOffset);
622 0 : deviceCommandContext->SetIndexBuffer(m->quadIndices.GetBuffer());
623 :
624 0 : const int32_t baseTexBindingSlot = shader->GetBindingSlot(str_baseTex);
625 0 : const int32_t maskTexBindingSlot = shader->GetBindingSlot(str_maskTex);
626 :
627 0 : for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
628 : {
629 0 : QuadBatchData& batchRenderData = it->second;
630 0 : const size_t batchNumQuads = batchRenderData.m_NumRenderQuads;
631 :
632 0 : if (batchNumQuads == 0)
633 0 : continue;
634 :
635 0 : const QuadBatchKey& maskPair = it->first;
636 :
637 0 : maskPair.m_Texture->UploadBackendTextureIfNeeded(deviceCommandContext);
638 0 : maskPair.m_TextureMask->UploadBackendTextureIfNeeded(deviceCommandContext);
639 :
640 0 : deviceCommandContext->SetTexture(
641 0 : baseTexBindingSlot, maskPair.m_Texture->GetBackendTexture());
642 0 : deviceCommandContext->SetTexture(
643 0 : maskTexBindingSlot, maskPair.m_TextureMask->GetBackendTexture());
644 :
645 0 : deviceCommandContext->DrawIndexed(m->quadIndices.GetOffset() + batchRenderData.m_IndicesBase, batchNumQuads * 6, 0);
646 :
647 0 : g_Renderer.GetStats().m_DrawCalls++;
648 0 : g_Renderer.GetStats().m_OverlayTris += batchNumQuads*2;
649 : }
650 :
651 0 : deviceCommandContext->EndPass();
652 : }
653 :
654 0 : void OverlayRenderer::RenderForegroundOverlays(
655 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
656 : const CCamera& viewCamera)
657 : {
658 0 : PROFILE3_GPU("overlays (fg)");
659 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Render foreground overlays");
660 :
661 0 : const CVector3D right = -viewCamera.GetOrientation().GetLeft();
662 0 : const CVector3D up = viewCamera.GetOrientation().GetUp();
663 :
664 0 : const CShaderTechniquePtr& shaderTechnique = m->shaderForegroundOverlay.GetTechnique();
665 0 : deviceCommandContext->SetGraphicsPipelineState(
666 0 : shaderTechnique->GetGraphicsPipelineState());
667 0 : deviceCommandContext->BeginPass();
668 :
669 0 : Renderer::Backend::IShaderProgram* shader = shaderTechnique->GetShader();
670 :
671 : const CMatrix3D transform =
672 0 : g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
673 0 : deviceCommandContext->SetUniform(
674 0 : shader->GetBindingSlot(str_transform), transform.AsFloatArray());
675 :
676 : const CVector2D uvs[6] =
677 : {
678 : {0.0f, 1.0f},
679 : {1.0f, 1.0f},
680 : {1.0f, 0.0f},
681 : {0.0f, 1.0f},
682 : {1.0f, 0.0f},
683 : {0.0f, 0.0f},
684 0 : };
685 :
686 0 : deviceCommandContext->SetVertexInputLayout(m->foregroundVertexInputLayout);
687 :
688 0 : deviceCommandContext->SetVertexBufferData(
689 0 : 1, &uvs[0], std::size(uvs) * sizeof(uvs[0]));
690 :
691 0 : const int32_t baseTexBindingSlot = shader->GetBindingSlot(str_baseTex);
692 0 : const int32_t colorMulBindingSlot = shader->GetBindingSlot(str_colorMul);
693 :
694 0 : for (size_t i = 0; i < m->sprites.size(); ++i)
695 : {
696 0 : SOverlaySprite* sprite = m->sprites[i];
697 0 : if (!i || sprite->m_Texture != m->sprites[i - 1]->m_Texture)
698 : {
699 0 : sprite->m_Texture->UploadBackendTextureIfNeeded(deviceCommandContext);
700 0 : deviceCommandContext->SetTexture(
701 0 : baseTexBindingSlot, sprite->m_Texture->GetBackendTexture());
702 : }
703 :
704 0 : deviceCommandContext->SetUniform(
705 0 : colorMulBindingSlot, sprite->m_Color.AsFloatArray());
706 :
707 : const CVector3D position[6] =
708 : {
709 0 : sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y0,
710 0 : sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y0,
711 0 : sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y1,
712 0 : sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y0,
713 0 : sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y1,
714 0 : sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y1
715 0 : };
716 :
717 0 : deviceCommandContext->SetVertexBufferData(
718 0 : 0, &position[0].X, std::size(position) * sizeof(position[0]));
719 :
720 0 : deviceCommandContext->Draw(0, 6);
721 :
722 0 : g_Renderer.GetStats().m_DrawCalls++;
723 0 : g_Renderer.GetStats().m_OverlayTris += 2;
724 : }
725 :
726 0 : deviceCommandContext->EndPass();
727 0 : }
728 :
729 0 : static void TessellateSphereFace(const CVector3D& a, u16 ai,
730 : const CVector3D& b, u16 bi,
731 : const CVector3D& c, u16 ci,
732 : std::vector<float>& vertexes, std::vector<u16>& indexes, int level)
733 : {
734 0 : if (level == 0)
735 : {
736 0 : indexes.push_back(ai);
737 0 : indexes.push_back(bi);
738 0 : indexes.push_back(ci);
739 : }
740 : else
741 : {
742 0 : CVector3D d = (a + b).Normalized();
743 0 : CVector3D e = (b + c).Normalized();
744 0 : CVector3D f = (c + a).Normalized();
745 0 : int di = vertexes.size() / 3; vertexes.push_back(d.X); vertexes.push_back(d.Y); vertexes.push_back(d.Z);
746 0 : int ei = vertexes.size() / 3; vertexes.push_back(e.X); vertexes.push_back(e.Y); vertexes.push_back(e.Z);
747 0 : int fi = vertexes.size() / 3; vertexes.push_back(f.X); vertexes.push_back(f.Y); vertexes.push_back(f.Z);
748 0 : TessellateSphereFace(a,ai, d,di, f,fi, vertexes, indexes, level-1);
749 0 : TessellateSphereFace(d,di, b,bi, e,ei, vertexes, indexes, level-1);
750 0 : TessellateSphereFace(f,fi, e,ei, c,ci, vertexes, indexes, level-1);
751 0 : TessellateSphereFace(d,di, e,ei, f,fi, vertexes, indexes, level-1);
752 : }
753 0 : }
754 :
755 0 : static void TessellateSphere(std::vector<float>& vertexes, std::vector<u16>& indexes, int level)
756 : {
757 : /* Start with a tetrahedron, then tessellate */
758 0 : float s = sqrtf(0.5f);
759 : #define VERT(a,b,c) vertexes.push_back(a); vertexes.push_back(b); vertexes.push_back(c);
760 0 : VERT(-s, 0, -s);
761 0 : VERT( s, 0, -s);
762 0 : VERT( s, 0, s);
763 0 : VERT(-s, 0, s);
764 0 : VERT( 0, -1, 0);
765 0 : VERT( 0, 1, 0);
766 : #define FACE(a,b,c) \
767 : TessellateSphereFace( \
768 : CVector3D(vertexes[a*3], vertexes[a*3+1], vertexes[a*3+2]), a, \
769 : CVector3D(vertexes[b*3], vertexes[b*3+1], vertexes[b*3+2]), b, \
770 : CVector3D(vertexes[c*3], vertexes[c*3+1], vertexes[c*3+2]), c, \
771 : vertexes, indexes, level);
772 0 : FACE(0,4,1);
773 0 : FACE(1,4,2);
774 0 : FACE(2,4,3);
775 0 : FACE(3,4,0);
776 0 : FACE(1,5,0);
777 0 : FACE(2,5,1);
778 0 : FACE(3,5,2);
779 0 : FACE(0,5,3);
780 : #undef FACE
781 : #undef VERT
782 0 : }
783 :
784 0 : void OverlayRendererInternals::GenerateSphere()
785 : {
786 0 : if (sphereVertexes.empty())
787 0 : TessellateSphere(sphereVertexes, sphereIndexes, 3);
788 0 : }
789 :
790 0 : void OverlayRenderer::RenderSphereOverlays(
791 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
792 : {
793 0 : PROFILE3_GPU("overlays (spheres)");
794 :
795 0 : if (m->spheres.empty() || m->shaderOverlaySolid.technique)
796 0 : return;
797 :
798 0 : const CShaderTechniquePtr& shaderTechnique = m->shaderOverlaySolid.GetTechnique();
799 0 : deviceCommandContext->SetGraphicsPipelineState(
800 0 : shaderTechnique->GetGraphicsPipelineState());
801 0 : deviceCommandContext->BeginPass();
802 :
803 0 : Renderer::Backend::IShaderProgram* shader = shaderTechnique->GetShader();
804 :
805 : const CMatrix3D transform =
806 0 : g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
807 0 : deviceCommandContext->SetUniform(
808 0 : shader->GetBindingSlot(str_transform), transform.AsFloatArray());
809 :
810 0 : m->GenerateSphere();
811 :
812 0 : deviceCommandContext->SetVertexInputLayout(m->sphereVertexInputLayout);
813 :
814 0 : deviceCommandContext->SetVertexBufferData(
815 0 : 0, m->sphereVertexes.data(), m->sphereVertexes.size() * sizeof(m->sphereVertexes[0]));
816 0 : deviceCommandContext->SetIndexBufferData(
817 0 : m->sphereIndexes.data(), m->sphereIndexes.size() * sizeof(m->sphereIndexes[0]));
818 :
819 0 : for (const SOverlaySphere* sphere : m->spheres)
820 : {
821 : const CVector4D instancingTransform{
822 0 : sphere->m_Center.X, sphere->m_Center.Y, sphere->m_Center.Z, sphere->m_Radius};
823 :
824 0 : deviceCommandContext->SetUniform(
825 0 : shader->GetBindingSlot(str_instancingTransform),
826 0 : instancingTransform.AsFloatArray());
827 :
828 0 : deviceCommandContext->SetUniform(
829 0 : shader->GetBindingSlot(str_color), sphere->m_Color.AsFloatArray());
830 :
831 0 : deviceCommandContext->DrawIndexed(0, m->sphereIndexes.size(), 0);
832 :
833 0 : g_Renderer.GetStats().m_DrawCalls++;
834 0 : g_Renderer.GetStats().m_OverlayTris = m->sphereIndexes.size()/3;
835 : }
836 :
837 0 : deviceCommandContext->EndPass();
838 3 : }
|