1 /* Copyright (C) 2023 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"
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"
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.
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
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}
97 return g_Renderer
.GetVertexInputLayout(attributes
);
100 CDecalRData::CDecalRData(CModelDecal
* decal
, CSimulation2
* simulation
)
101 : m_Decal(decal
), m_Simulation(simulation
)
106 CDecalRData::~CDecalRData() = default;
108 void CDecalRData::Update(CSimulation2
* simulation
)
110 m_Simulation
= simulation
;
111 if (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
>;
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");
147 if (material
.GetSamplers().empty() || !decal
->m_VBDecals
|| !decal
->m_VBDecalsIndices
)
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
));
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
)
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
);
182 LOGERROR("Terrain renderer failed to load shader effect (%s)\n",
183 itTechBegin
->shaderEffect
.c_str());
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
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);
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.
292 m_VBDecalsIndices
.Reset();
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(
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
, false);
330 m_VBDecals
->m_Owner
->UpdateChunkVertices(m_VBDecals
.Get(), vertices
.data());
332 std::vector
<u16
> indices((i1
- i0
) * (j1
- j0
) * 6);
334 const ssize_t w
= i1
- i0
+ 1;
335 auto itIdx
= indices
.begin();
336 const size_t base
= m_VBDecals
->m_Index
;
337 for (ssize_t dj
= 0; dj
< j1
- j0
; ++dj
)
339 for (ssize_t di
= 0; di
< i1
- i0
; ++di
)
341 const bool dir
= m_Decal
->m_Terrain
->GetTriangulationDir(i0
+ di
, j0
+ dj
);
344 *itIdx
++ = u16(((dj
+ 0) * w
+ (di
+ 0)) + base
);
345 *itIdx
++ = u16(((dj
+ 0) * w
+ (di
+ 1)) + base
);
346 *itIdx
++ = u16(((dj
+ 1) * w
+ (di
+ 0)) + base
);
348 *itIdx
++ = u16(((dj
+ 0) * w
+ (di
+ 1)) + base
);
349 *itIdx
++ = u16(((dj
+ 1) * w
+ (di
+ 1)) + base
);
350 *itIdx
++ = u16(((dj
+ 1) * w
+ (di
+ 0)) + base
);
354 *itIdx
++ = u16(((dj
+ 0) * w
+ (di
+ 0)) + base
);
355 *itIdx
++ = u16(((dj
+ 0) * w
+ (di
+ 1)) + base
);
356 *itIdx
++ = u16(((dj
+ 1) * w
+ (di
+ 1)) + base
);
358 *itIdx
++ = u16(((dj
+ 1) * w
+ (di
+ 1)) + base
);
359 *itIdx
++ = u16(((dj
+ 1) * w
+ (di
+ 0)) + base
);
360 *itIdx
++ = u16(((dj
+ 0) * w
+ (di
+ 0)) + base
);
365 // Construct vertex buffer.
366 if (!m_VBDecalsIndices
|| m_VBDecalsIndices
->m_Count
!= indices
.size())
368 m_VBDecalsIndices
= g_Renderer
.GetVertexBufferManager().AllocateChunk(
369 sizeof(u16
), indices
.size(),
370 Renderer::Backend::IBuffer::Type::INDEX
, false);
372 m_VBDecalsIndices
->m_Owner
->UpdateChunkVertices(m_VBDecalsIndices
.Get(), indices
.data());