Merge 'remotes/trunk'
[0ad.git] / source / renderer / DecalRData.cpp
blobf22117937f6614c503dd847c4f218101e66d70de
1 /* Copyright (C) 2024 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "DecalRData.h"
22 #include "graphics/Decal.h"
23 #include "graphics/Model.h"
24 #include "graphics/ShaderManager.h"
25 #include "graphics/Terrain.h"
26 #include "graphics/TextureManager.h"
27 #include "lib/allocators/DynamicArena.h"
28 #include "lib/allocators/STLAllocators.h"
29 #include "ps/CLogger.h"
30 #include "ps/CStrInternStatic.h"
31 #include "ps/Game.h"
32 #include "ps/Profile.h"
33 #include "renderer/Renderer.h"
34 #include "renderer/TerrainRenderer.h"
35 #include "simulation2/components/ICmpWaterManager.h"
36 #include "simulation2/Simulation2.h"
38 #include <algorithm>
40 // TODO: Currently each decal is a separate CDecalRData. We might want to use
41 // lots of decals for special effects like shadows, footprints, etc, in which
42 // case we should probably redesign this to batch them all together for more
43 // efficient rendering.
45 namespace
48 struct SDecalBatch
50 CDecalRData* decal;
51 CStrIntern shaderEffect;
52 CShaderDefines shaderDefines;
53 CVertexBuffer::VBChunk* vertices;
54 CVertexBuffer::VBChunk* indices;
57 struct SDecalBatchComparator
59 bool operator()(const SDecalBatch& lhs, const SDecalBatch& rhs) const
61 if (lhs.shaderEffect != rhs.shaderEffect)
62 return lhs.shaderEffect < rhs.shaderEffect;
63 if (lhs.shaderDefines != rhs.shaderDefines)
64 return lhs.shaderDefines < rhs.shaderDefines;
65 const CMaterial& lhsMaterial = lhs.decal->GetDecal()->m_Decal.m_Material;
66 const CMaterial& rhsMaterial = rhs.decal->GetDecal()->m_Decal.m_Material;
67 if (lhsMaterial.GetDiffuseTexture() != rhsMaterial.GetDiffuseTexture())
68 return lhsMaterial.GetDiffuseTexture() < rhsMaterial.GetDiffuseTexture();
69 if (lhs.vertices->m_Owner != rhs.vertices->m_Owner)
70 return lhs.vertices->m_Owner < rhs.vertices->m_Owner;
71 if (lhs.indices->m_Owner != rhs.indices->m_Owner)
72 return lhs.indices->m_Owner < rhs.indices->m_Owner;
73 return lhs.decal < rhs.decal;
77 } // anonymous namespace
79 // static
80 Renderer::Backend::IVertexInputLayout* CDecalRData::GetVertexInputLayout()
82 const uint32_t stride = sizeof(SDecalVertex);
83 const std::array<Renderer::Backend::SVertexAttributeFormat, 3> attributes{{
84 {Renderer::Backend::VertexAttributeStream::POSITION,
85 Renderer::Backend::Format::R32G32B32_SFLOAT,
86 offsetof(SDecalVertex, m_Position), stride,
87 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
88 {Renderer::Backend::VertexAttributeStream::NORMAL,
89 Renderer::Backend::Format::R32G32B32_SFLOAT,
90 offsetof(SDecalVertex, m_Normal), stride,
91 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
92 {Renderer::Backend::VertexAttributeStream::UV0,
93 Renderer::Backend::Format::R32G32_SFLOAT,
94 offsetof(SDecalVertex, m_UV), stride,
95 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}
96 }};
97 return g_Renderer.GetVertexInputLayout(attributes);
100 CDecalRData::CDecalRData(CModelDecal* decal, CSimulation2* simulation)
101 : m_Decal(decal), m_Simulation(simulation)
103 BuildVertexData();
106 CDecalRData::~CDecalRData() = default;
108 void CDecalRData::Update(CSimulation2* simulation)
110 m_Simulation = simulation;
111 if (m_UpdateFlags != 0)
113 BuildVertexData();
114 m_UpdateFlags = 0;
118 void CDecalRData::RenderDecals(
119 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
120 Renderer::Backend::IVertexInputLayout* vertexInputLayout,
121 const std::vector<CDecalRData*>& decals, const CShaderDefines& context, ShadowMap* shadow)
123 PROFILE3("render terrain decals");
124 GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain decals");
126 using Arena = Allocators::DynamicArena<256 * KiB>;
128 Arena arena;
130 using Batches = std::vector<SDecalBatch, ProxyAllocator<SDecalBatch, Arena>>;
131 Batches batches((Batches::allocator_type(arena)));
132 batches.reserve(decals.size());
134 CShaderDefines contextDecal = context;
135 contextDecal.Add(str_DECAL, str_1);
137 for (CDecalRData* decal : decals)
139 CMaterial& material = decal->m_Decal->m_Decal.m_Material;
141 if (material.GetShaderEffect().empty())
143 LOGERROR("Terrain renderer failed to load shader effect.\n");
144 continue;
147 if (material.GetSamplers().empty() || !decal->m_VBDecals || !decal->m_VBDecalsIndices)
148 continue;
150 SDecalBatch batch;
151 batch.decal = decal;
152 batch.shaderEffect = material.GetShaderEffect();
153 batch.shaderDefines = material.GetShaderDefines();
154 batch.vertices = decal->m_VBDecals.Get();
155 batch.indices = decal->m_VBDecalsIndices.Get();
157 batches.emplace_back(std::move(batch));
160 if (batches.empty())
161 return;
163 std::sort(batches.begin(), batches.end(), SDecalBatchComparator());
165 CVertexBuffer* lastIB = nullptr;
166 for (auto itTechBegin = batches.begin(), itTechEnd = batches.begin(); itTechBegin != batches.end(); itTechBegin = itTechEnd)
168 while (itTechEnd != batches.end() &&
169 itTechBegin->shaderEffect == itTechEnd->shaderEffect &&
170 itTechBegin->shaderDefines == itTechEnd->shaderDefines)
172 ++itTechEnd;
175 CShaderDefines defines = contextDecal;
176 defines.SetMany(itTechBegin->shaderDefines);
177 // TODO: move enabling blend to XML.
178 CShaderTechniquePtr techBase = g_Renderer.GetShaderManager().LoadEffect(
179 itTechBegin->shaderEffect == str_terrain_base ? str_terrain_decal : itTechBegin->shaderEffect, defines);
180 if (!techBase)
182 LOGERROR("Terrain renderer failed to load shader effect (%s)\n",
183 itTechBegin->shaderEffect.c_str());
184 continue;
187 const int numPasses = techBase->GetNumPasses();
188 for (int pass = 0; pass < numPasses; ++pass)
190 deviceCommandContext->SetGraphicsPipelineState(
191 techBase->GetGraphicsPipelineState(pass));
192 deviceCommandContext->BeginPass();
194 Renderer::Backend::IShaderProgram* shader = techBase->GetShader(pass);
195 TerrainRenderer::PrepareShader(deviceCommandContext, shader, shadow);
197 CColor shadingColor(1.0f, 1.0f, 1.0f, 1.0f);
198 const int32_t shadingColorBindingSlot =
199 shader->GetBindingSlot(str_shadingColor);
200 deviceCommandContext->SetUniform(
201 shadingColorBindingSlot, shadingColor.AsFloatArray());
203 CShaderUniforms currentStaticUniforms;
205 CVertexBuffer* lastVB = nullptr;
206 for (auto itDecal = itTechBegin; itDecal != itTechEnd; ++itDecal)
208 SDecalBatch& batch = *itDecal;
209 CDecalRData* decal = batch.decal;
210 CMaterial& material = decal->m_Decal->m_Decal.m_Material;
212 const CMaterial::SamplersVector& samplers = material.GetSamplers();
213 for (const CMaterial::TextureSampler& sampler : samplers)
214 sampler.Sampler->UploadBackendTextureIfNeeded(deviceCommandContext);
215 for (const CMaterial::TextureSampler& sampler : samplers)
217 deviceCommandContext->SetTexture(
218 shader->GetBindingSlot(sampler.Name),
219 sampler.Sampler->GetBackendTexture());
222 if (currentStaticUniforms != material.GetStaticUniforms())
224 currentStaticUniforms = material.GetStaticUniforms();
225 material.GetStaticUniforms().BindUniforms(deviceCommandContext, shader);
228 // TODO: Need to handle floating decals correctly. In particular, we need
229 // to render non-floating before water and floating after water (to get
230 // the blending right), and we also need to apply the correct lighting in
231 // each case, which doesn't really seem possible with the current
232 // TerrainRenderer.
233 // Also, need to mark the decals as dirty when water height changes.
235 // m_Decal->GetBounds().Render();
237 if (shadingColor != decal->m_Decal->GetShadingColor())
239 shadingColor = decal->m_Decal->GetShadingColor();
240 deviceCommandContext->SetUniform(
241 shadingColorBindingSlot, shadingColor.AsFloatArray());
244 if (lastVB != batch.vertices->m_Owner)
246 lastVB = batch.vertices->m_Owner;
247 ENSURE(!lastVB->GetBuffer()->IsDynamic());
249 deviceCommandContext->SetVertexInputLayout(vertexInputLayout);
251 deviceCommandContext->SetVertexBuffer(
252 0, batch.vertices->m_Owner->GetBuffer(), 0);
255 if (lastIB != batch.indices->m_Owner)
257 lastIB = batch.indices->m_Owner;
258 ENSURE(!lastIB->GetBuffer()->IsDynamic());
259 deviceCommandContext->SetIndexBuffer(batch.indices->m_Owner->GetBuffer());
262 deviceCommandContext->DrawIndexed(batch.indices->m_Index, batch.indices->m_Count, 0);
264 // bump stats
265 g_Renderer.m_Stats.m_DrawCalls++;
266 g_Renderer.m_Stats.m_TerrainTris += batch.indices->m_Count / 3;
269 deviceCommandContext->EndPass();
274 void CDecalRData::BuildVertexData()
276 PROFILE("decal build");
278 const SDecal& decal = m_Decal->m_Decal;
280 // TODO: Currently this constructs an axis-aligned bounding rectangle around
281 // the decal. It would be more efficient for rendering if we excluded tiles
282 // that are outside the (non-axis-aligned) decal rectangle.
284 ssize_t i0, j0, i1, j1;
285 m_Decal->CalcVertexExtents(i0, j0, i1, j1);
286 // Currently CalcVertexExtents might return empty rectangle, that means
287 // we can't render it.
288 if (i1 <= i0 || j1 <= j0)
290 // We have nothing to render.
291 m_VBDecals.Reset();
292 m_VBDecalsIndices.Reset();
293 return;
296 CmpPtr<ICmpWaterManager> cmpWaterManager(*m_Simulation, SYSTEM_ENTITY);
298 std::vector<SDecalVertex> vertices((i1 - i0 + 1) * (j1 - j0 + 1));
300 for (ssize_t j = j0, idx = 0; j <= j1; ++j)
302 for (ssize_t i = i0; i <= i1; ++i, ++idx)
304 SDecalVertex& vertex = vertices[idx];
305 m_Decal->m_Terrain->CalcPosition(i, j, vertex.m_Position);
307 if (decal.m_Floating && cmpWaterManager)
309 vertex.m_Position.Y = std::max(
310 vertex.m_Position.Y,
311 cmpWaterManager->GetExactWaterLevel(vertex.m_Position.X, vertex.m_Position.Z));
314 m_Decal->m_Terrain->CalcNormal(i, j, vertex.m_Normal);
316 // Map from world space back into decal texture space.
317 CVector3D inv = m_Decal->GetInvTransform().Transform(vertex.m_Position);
318 vertex.m_UV.X = 0.5f + (inv.X - decal.m_OffsetX) / decal.m_SizeX;
319 // Flip V to match our texture convention.
320 vertex.m_UV.Y = 0.5f - (inv.Z - decal.m_OffsetZ) / decal.m_SizeZ;
324 if (!m_VBDecals || m_VBDecals->m_Count != vertices.size())
326 m_VBDecals = g_Renderer.GetVertexBufferManager().AllocateChunk(
327 sizeof(SDecalVertex), vertices.size(),
328 Renderer::Backend::IBuffer::Type::VERTEX,
329 Renderer::Backend::IBuffer::Usage::TRANSFER_DST);
331 m_VBDecals->m_Owner->UpdateChunkVertices(m_VBDecals.Get(), vertices.data());
333 std::vector<u16> indices((i1 - i0) * (j1 - j0) * 6);
335 const ssize_t w = i1 - i0 + 1;
336 auto itIdx = indices.begin();
337 const size_t base = m_VBDecals->m_Index;
338 for (ssize_t dj = 0; dj < j1 - j0; ++dj)
340 for (ssize_t di = 0; di < i1 - i0; ++di)
342 const bool dir = m_Decal->m_Terrain->GetTriangulationDir(i0 + di, j0 + dj);
343 if (dir)
345 *itIdx++ = u16(((dj + 0) * w + (di + 0)) + base);
346 *itIdx++ = u16(((dj + 0) * w + (di + 1)) + base);
347 *itIdx++ = u16(((dj + 1) * w + (di + 0)) + base);
349 *itIdx++ = u16(((dj + 0) * w + (di + 1)) + base);
350 *itIdx++ = u16(((dj + 1) * w + (di + 1)) + base);
351 *itIdx++ = u16(((dj + 1) * w + (di + 0)) + base);
353 else
355 *itIdx++ = u16(((dj + 0) * w + (di + 0)) + base);
356 *itIdx++ = u16(((dj + 0) * w + (di + 1)) + base);
357 *itIdx++ = u16(((dj + 1) * w + (di + 1)) + base);
359 *itIdx++ = u16(((dj + 1) * w + (di + 1)) + base);
360 *itIdx++ = u16(((dj + 1) * w + (di + 0)) + base);
361 *itIdx++ = u16(((dj + 0) * w + (di + 0)) + base);
366 // Construct vertex buffer.
367 if (!m_VBDecalsIndices || m_VBDecalsIndices->m_Count != indices.size())
369 m_VBDecalsIndices = g_Renderer.GetVertexBufferManager().AllocateChunk(
370 sizeof(u16), indices.size(),
371 Renderer::Backend::IBuffer::Type::INDEX,
372 Renderer::Backend::IBuffer::Usage::TRANSFER_DST);
374 m_VBDecalsIndices->m_Owner->UpdateChunkVertices(m_VBDecalsIndices.Get(), indices.data());