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 "DecalRData.h"
21 :
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"
37 :
38 : #include <algorithm>
39 :
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.
44 :
45 : namespace
46 : {
47 :
48 0 : struct SDecalBatch
49 : {
50 : CDecalRData* decal;
51 : CStrIntern shaderEffect;
52 : CShaderDefines shaderDefines;
53 : CVertexBuffer::VBChunk* vertices;
54 : CVertexBuffer::VBChunk* indices;
55 : };
56 :
57 : struct SDecalBatchComparator
58 : {
59 0 : bool operator()(const SDecalBatch& lhs, const SDecalBatch& rhs) const
60 : {
61 0 : if (lhs.shaderEffect != rhs.shaderEffect)
62 0 : return lhs.shaderEffect < rhs.shaderEffect;
63 0 : if (lhs.shaderDefines != rhs.shaderDefines)
64 0 : return lhs.shaderDefines < rhs.shaderDefines;
65 0 : const CMaterial& lhsMaterial = lhs.decal->GetDecal()->m_Decal.m_Material;
66 0 : const CMaterial& rhsMaterial = rhs.decal->GetDecal()->m_Decal.m_Material;
67 0 : if (lhsMaterial.GetDiffuseTexture() != rhsMaterial.GetDiffuseTexture())
68 0 : return lhsMaterial.GetDiffuseTexture() < rhsMaterial.GetDiffuseTexture();
69 0 : if (lhs.vertices->m_Owner != rhs.vertices->m_Owner)
70 0 : return lhs.vertices->m_Owner < rhs.vertices->m_Owner;
71 0 : if (lhs.indices->m_Owner != rhs.indices->m_Owner)
72 0 : return lhs.indices->m_Owner < rhs.indices->m_Owner;
73 0 : return lhs.decal < rhs.decal;
74 : }
75 : };
76 :
77 : } // anonymous namespace
78 :
79 : // static
80 6 : Renderer::Backend::IVertexInputLayout* CDecalRData::GetVertexInputLayout()
81 : {
82 6 : const uint32_t stride = sizeof(SDecalVertex);
83 6 : 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 6 : return g_Renderer.GetVertexInputLayout(attributes);
98 : }
99 :
100 0 : CDecalRData::CDecalRData(CModelDecal* decal, CSimulation2* simulation)
101 0 : : m_Decal(decal), m_Simulation(simulation)
102 : {
103 0 : BuildVertexData();
104 0 : }
105 :
106 : CDecalRData::~CDecalRData() = default;
107 :
108 0 : void CDecalRData::Update(CSimulation2* simulation)
109 : {
110 0 : m_Simulation = simulation;
111 0 : if (m_UpdateFlags != 0)
112 : {
113 0 : BuildVertexData();
114 0 : m_UpdateFlags = 0;
115 : }
116 0 : }
117 :
118 0 : void CDecalRData::RenderDecals(
119 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
120 : Renderer::Backend::IVertexInputLayout* vertexInputLayout,
121 : const std::vector<CDecalRData*>& decals, const CShaderDefines& context, ShadowMap* shadow)
122 : {
123 0 : PROFILE3("render terrain decals");
124 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain decals");
125 :
126 : using Arena = Allocators::DynamicArena<256 * KiB>;
127 :
128 0 : Arena arena;
129 :
130 : using Batches = std::vector<SDecalBatch, ProxyAllocator<SDecalBatch, Arena>>;
131 0 : Batches batches((Batches::allocator_type(arena)));
132 0 : batches.reserve(decals.size());
133 :
134 0 : CShaderDefines contextDecal = context;
135 0 : contextDecal.Add(str_DECAL, str_1);
136 :
137 0 : for (CDecalRData* decal : decals)
138 : {
139 0 : CMaterial& material = decal->m_Decal->m_Decal.m_Material;
140 :
141 0 : if (material.GetShaderEffect().empty())
142 : {
143 0 : LOGERROR("Terrain renderer failed to load shader effect.\n");
144 0 : continue;
145 : }
146 :
147 0 : if (material.GetSamplers().empty() || !decal->m_VBDecals || !decal->m_VBDecalsIndices)
148 0 : continue;
149 :
150 0 : SDecalBatch batch;
151 0 : batch.decal = decal;
152 0 : batch.shaderEffect = material.GetShaderEffect();
153 0 : batch.shaderDefines = material.GetShaderDefines();
154 0 : batch.vertices = decal->m_VBDecals.Get();
155 0 : batch.indices = decal->m_VBDecalsIndices.Get();
156 :
157 0 : batches.emplace_back(std::move(batch));
158 : }
159 :
160 0 : if (batches.empty())
161 0 : return;
162 :
163 0 : std::sort(batches.begin(), batches.end(), SDecalBatchComparator());
164 :
165 0 : CVertexBuffer* lastIB = nullptr;
166 0 : for (auto itTechBegin = batches.begin(), itTechEnd = batches.begin(); itTechBegin != batches.end(); itTechBegin = itTechEnd)
167 : {
168 0 : while (itTechEnd != batches.end() &&
169 0 : itTechBegin->shaderEffect == itTechEnd->shaderEffect &&
170 0 : itTechBegin->shaderDefines == itTechEnd->shaderDefines)
171 : {
172 0 : ++itTechEnd;
173 : }
174 :
175 0 : CShaderDefines defines = contextDecal;
176 0 : defines.SetMany(itTechBegin->shaderDefines);
177 : // TODO: move enabling blend to XML.
178 0 : CShaderTechniquePtr techBase = g_Renderer.GetShaderManager().LoadEffect(
179 0 : itTechBegin->shaderEffect == str_terrain_base ? str_terrain_decal : itTechBegin->shaderEffect, defines);
180 0 : if (!techBase)
181 : {
182 0 : LOGERROR("Terrain renderer failed to load shader effect (%s)\n",
183 : itTechBegin->shaderEffect.c_str());
184 0 : continue;
185 : }
186 :
187 0 : const int numPasses = techBase->GetNumPasses();
188 0 : for (int pass = 0; pass < numPasses; ++pass)
189 : {
190 0 : deviceCommandContext->SetGraphicsPipelineState(
191 0 : techBase->GetGraphicsPipelineState(pass));
192 0 : deviceCommandContext->BeginPass();
193 :
194 0 : Renderer::Backend::IShaderProgram* shader = techBase->GetShader(pass);
195 0 : TerrainRenderer::PrepareShader(deviceCommandContext, shader, shadow);
196 :
197 0 : CColor shadingColor(1.0f, 1.0f, 1.0f, 1.0f);
198 : const int32_t shadingColorBindingSlot =
199 0 : shader->GetBindingSlot(str_shadingColor);
200 0 : deviceCommandContext->SetUniform(
201 0 : shadingColorBindingSlot, shadingColor.AsFloatArray());
202 :
203 0 : CShaderUniforms currentStaticUniforms;
204 :
205 0 : CVertexBuffer* lastVB = nullptr;
206 0 : for (auto itDecal = itTechBegin; itDecal != itTechEnd; ++itDecal)
207 : {
208 0 : SDecalBatch& batch = *itDecal;
209 0 : CDecalRData* decal = batch.decal;
210 0 : CMaterial& material = decal->m_Decal->m_Decal.m_Material;
211 :
212 0 : const CMaterial::SamplersVector& samplers = material.GetSamplers();
213 0 : for (const CMaterial::TextureSampler& sampler : samplers)
214 0 : sampler.Sampler->UploadBackendTextureIfNeeded(deviceCommandContext);
215 0 : for (const CMaterial::TextureSampler& sampler : samplers)
216 : {
217 0 : deviceCommandContext->SetTexture(
218 0 : shader->GetBindingSlot(sampler.Name),
219 0 : sampler.Sampler->GetBackendTexture());
220 : }
221 :
222 0 : if (currentStaticUniforms != material.GetStaticUniforms())
223 : {
224 0 : currentStaticUniforms = material.GetStaticUniforms();
225 0 : material.GetStaticUniforms().BindUniforms(deviceCommandContext, shader);
226 : }
227 :
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.
234 :
235 : // m_Decal->GetBounds().Render();
236 :
237 0 : if (shadingColor != decal->m_Decal->GetShadingColor())
238 : {
239 0 : shadingColor = decal->m_Decal->GetShadingColor();
240 0 : deviceCommandContext->SetUniform(
241 0 : shadingColorBindingSlot, shadingColor.AsFloatArray());
242 : }
243 :
244 0 : if (lastVB != batch.vertices->m_Owner)
245 : {
246 0 : lastVB = batch.vertices->m_Owner;
247 0 : ENSURE(!lastVB->GetBuffer()->IsDynamic());
248 :
249 0 : deviceCommandContext->SetVertexInputLayout(vertexInputLayout);
250 :
251 0 : deviceCommandContext->SetVertexBuffer(
252 0 : 0, batch.vertices->m_Owner->GetBuffer(), 0);
253 : }
254 :
255 0 : if (lastIB != batch.indices->m_Owner)
256 : {
257 0 : lastIB = batch.indices->m_Owner;
258 0 : ENSURE(!lastIB->GetBuffer()->IsDynamic());
259 0 : deviceCommandContext->SetIndexBuffer(batch.indices->m_Owner->GetBuffer());
260 : }
261 :
262 0 : deviceCommandContext->DrawIndexed(batch.indices->m_Index, batch.indices->m_Count, 0);
263 :
264 : // bump stats
265 0 : g_Renderer.m_Stats.m_DrawCalls++;
266 0 : g_Renderer.m_Stats.m_TerrainTris += batch.indices->m_Count / 3;
267 : }
268 :
269 0 : deviceCommandContext->EndPass();
270 : }
271 : }
272 : }
273 :
274 0 : void CDecalRData::BuildVertexData()
275 : {
276 0 : PROFILE("decal build");
277 :
278 0 : const SDecal& decal = m_Decal->m_Decal;
279 :
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.
283 :
284 : ssize_t i0, j0, i1, j1;
285 0 : m_Decal->CalcVertexExtents(i0, j0, i1, j1);
286 : // Currently CalcVertexExtents might return empty rectangle, that means
287 : // we can't render it.
288 0 : if (i1 <= i0 || j1 <= j0)
289 : {
290 : // We have nothing to render.
291 0 : m_VBDecals.Reset();
292 0 : m_VBDecalsIndices.Reset();
293 0 : return;
294 : }
295 :
296 0 : CmpPtr<ICmpWaterManager> cmpWaterManager(*m_Simulation, SYSTEM_ENTITY);
297 :
298 0 : std::vector<SDecalVertex> vertices((i1 - i0 + 1) * (j1 - j0 + 1));
299 :
300 0 : for (ssize_t j = j0, idx = 0; j <= j1; ++j)
301 : {
302 0 : for (ssize_t i = i0; i <= i1; ++i, ++idx)
303 : {
304 0 : SDecalVertex& vertex = vertices[idx];
305 0 : m_Decal->m_Terrain->CalcPosition(i, j, vertex.m_Position);
306 :
307 0 : if (decal.m_Floating && cmpWaterManager)
308 : {
309 0 : vertex.m_Position.Y = std::max(
310 : vertex.m_Position.Y,
311 0 : cmpWaterManager->GetExactWaterLevel(vertex.m_Position.X, vertex.m_Position.Z));
312 : }
313 :
314 0 : m_Decal->m_Terrain->CalcNormal(i, j, vertex.m_Normal);
315 :
316 : // Map from world space back into decal texture space.
317 0 : CVector3D inv = m_Decal->GetInvTransform().Transform(vertex.m_Position);
318 0 : vertex.m_UV.X = 0.5f + (inv.X - decal.m_OffsetX) / decal.m_SizeX;
319 : // Flip V to match our texture convention.
320 0 : vertex.m_UV.Y = 0.5f - (inv.Z - decal.m_OffsetZ) / decal.m_SizeZ;
321 : }
322 : }
323 :
324 0 : if (!m_VBDecals || m_VBDecals->m_Count != vertices.size())
325 : {
326 0 : m_VBDecals = g_VBMan.AllocateChunk(
327 : sizeof(SDecalVertex), vertices.size(),
328 0 : Renderer::Backend::IBuffer::Type::VERTEX, false);
329 : }
330 0 : m_VBDecals->m_Owner->UpdateChunkVertices(m_VBDecals.Get(), vertices.data());
331 :
332 0 : std::vector<u16> indices((i1 - i0) * (j1 - j0) * 6);
333 :
334 0 : const ssize_t w = i1 - i0 + 1;
335 0 : auto itIdx = indices.begin();
336 0 : const size_t base = m_VBDecals->m_Index;
337 0 : for (ssize_t dj = 0; dj < j1 - j0; ++dj)
338 : {
339 0 : for (ssize_t di = 0; di < i1 - i0; ++di)
340 : {
341 0 : const bool dir = m_Decal->m_Terrain->GetTriangulationDir(i0 + di, j0 + dj);
342 0 : if (dir)
343 : {
344 0 : *itIdx++ = u16(((dj + 0) * w + (di + 0)) + base);
345 0 : *itIdx++ = u16(((dj + 0) * w + (di + 1)) + base);
346 0 : *itIdx++ = u16(((dj + 1) * w + (di + 0)) + base);
347 :
348 0 : *itIdx++ = u16(((dj + 0) * w + (di + 1)) + base);
349 0 : *itIdx++ = u16(((dj + 1) * w + (di + 1)) + base);
350 0 : *itIdx++ = u16(((dj + 1) * w + (di + 0)) + base);
351 : }
352 : else
353 : {
354 0 : *itIdx++ = u16(((dj + 0) * w + (di + 0)) + base);
355 0 : *itIdx++ = u16(((dj + 0) * w + (di + 1)) + base);
356 0 : *itIdx++ = u16(((dj + 1) * w + (di + 1)) + base);
357 :
358 0 : *itIdx++ = u16(((dj + 1) * w + (di + 1)) + base);
359 0 : *itIdx++ = u16(((dj + 1) * w + (di + 0)) + base);
360 0 : *itIdx++ = u16(((dj + 0) * w + (di + 0)) + base);
361 : }
362 : }
363 : }
364 :
365 : // Construct vertex buffer.
366 0 : if (!m_VBDecalsIndices || m_VBDecalsIndices->m_Count != indices.size())
367 : {
368 0 : m_VBDecalsIndices = g_VBMan.AllocateChunk(
369 : sizeof(u16), indices.size(),
370 0 : Renderer::Backend::IBuffer::Type::INDEX, false);
371 : }
372 0 : m_VBDecalsIndices->m_Owner->UpdateChunkVertices(m_VBDecalsIndices.Get(), indices.data());
373 3 : }
|