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 "TerrainOverlay.h"
22 #include "graphics/Color.h"
23 #include "graphics/ShaderManager.h"
24 #include "graphics/ShaderProgram.h"
25 #include "graphics/Terrain.h"
27 #include "maths/MathUtil.h"
28 #include "maths/Vector2D.h"
29 #include "ps/CStrInternStatic.h"
31 #include "ps/Profile.h"
33 #include "renderer/backend/IDevice.h"
34 #include "renderer/backend/IDeviceCommandContext.h"
35 #include "renderer/Renderer.h"
36 #include "renderer/SceneRenderer.h"
37 #include "renderer/TerrainRenderer.h"
38 #include "simulation2/system/SimContext.h"
45 // Global overlay list management:
46 std::vector
<std::pair
<ITerrainOverlay
*, int>> g_TerrainOverlayList
;
48 void AdjustOverlayGraphicsPipelineState(
49 Renderer::Backend::SGraphicsPipelineStateDesc
& pipelineStateDesc
, const bool drawHidden
)
51 pipelineStateDesc
.depthStencilState
.depthTestEnabled
= !drawHidden
;
52 pipelineStateDesc
.blendState
.enabled
= true;
53 pipelineStateDesc
.blendState
.srcColorBlendFactor
= pipelineStateDesc
.blendState
.srcAlphaBlendFactor
=
54 Renderer::Backend::BlendFactor::SRC_ALPHA
;
55 pipelineStateDesc
.blendState
.dstColorBlendFactor
= pipelineStateDesc
.blendState
.dstAlphaBlendFactor
=
56 Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA
;
57 pipelineStateDesc
.blendState
.colorBlendOp
= pipelineStateDesc
.blendState
.alphaBlendOp
=
58 Renderer::Backend::BlendOp::ADD
;
59 pipelineStateDesc
.rasterizationState
.cullMode
=
60 drawHidden
? Renderer::Backend::CullMode::NONE
: Renderer::Backend::CullMode::BACK
;
63 CShaderTechniquePtr
CreateOverlayTileShaderTechnique(const bool drawHidden
)
65 return g_Renderer
.GetShaderManager().LoadEffect(
67 [drawHidden
](Renderer::Backend::SGraphicsPipelineStateDesc
& pipelineStateDesc
)
69 AdjustOverlayGraphicsPipelineState(pipelineStateDesc
, drawHidden
);
70 // To ensure that outlines are drawn on top of the terrain correctly (and
71 // don't Z-fight and flicker nastily), use detph bias to pull them towards
73 pipelineStateDesc
.rasterizationState
.depthBiasEnabled
= true;
74 pipelineStateDesc
.rasterizationState
.depthBiasConstantFactor
= -1.0f
;
75 pipelineStateDesc
.rasterizationState
.depthBiasSlopeFactor
= -1.0f
;
79 CShaderTechniquePtr
CreateOverlayOutlineShaderTechnique(const bool drawHidden
)
81 return g_Renderer
.GetShaderManager().LoadEffect(
83 [drawHidden
](Renderer::Backend::SGraphicsPipelineStateDesc
& pipelineStateDesc
)
85 AdjustOverlayGraphicsPipelineState(pipelineStateDesc
, drawHidden
);
86 pipelineStateDesc
.rasterizationState
.polygonMode
= Renderer::Backend::PolygonMode::LINE
;
90 } // anonymous namespace
92 ITerrainOverlay::ITerrainOverlay(int priority
)
94 // Add to global list of overlays
95 g_TerrainOverlayList
.emplace_back(this, priority
);
96 // Sort by overlays by priority. Do stable sort so that adding/removing
97 // overlays doesn't randomly disturb all the existing ones (which would
98 // be noticeable if they have the same priority and overlap).
99 std::stable_sort(g_TerrainOverlayList
.begin(), g_TerrainOverlayList
.end(),
100 [](const std::pair
<ITerrainOverlay
*, int>& a
, const std::pair
<ITerrainOverlay
*, int>& b
) {
101 return a
.second
< b
.second
;
105 ITerrainOverlay::~ITerrainOverlay()
107 std::vector
<std::pair
<ITerrainOverlay
*, int> >::iterator newEnd
=
108 std::remove_if(g_TerrainOverlayList
.begin(), g_TerrainOverlayList
.end(),
109 [this](const std::pair
<ITerrainOverlay
*, int>& a
) { return a
.first
== this; });
110 g_TerrainOverlayList
.erase(newEnd
, g_TerrainOverlayList
.end());
114 void ITerrainOverlay::RenderOverlaysBeforeWater(
115 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
)
117 if (g_TerrainOverlayList
.empty())
120 PROFILE3_GPU("terrain overlays (before)");
121 GPU_SCOPED_LABEL(deviceCommandContext
, "Render terrain overlays before water");
123 for (size_t i
= 0; i
< g_TerrainOverlayList
.size(); ++i
)
124 g_TerrainOverlayList
[i
].first
->RenderBeforeWater(deviceCommandContext
);
127 void ITerrainOverlay::RenderOverlaysAfterWater(
128 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
, int cullGroup
)
130 if (g_TerrainOverlayList
.empty())
133 PROFILE3_GPU("terrain overlays (after)");
134 GPU_SCOPED_LABEL(deviceCommandContext
, "Render terrain overlays after water");
136 for (size_t i
= 0; i
< g_TerrainOverlayList
.size(); ++i
)
137 g_TerrainOverlayList
[i
].first
->RenderAfterWater(deviceCommandContext
, cullGroup
);
140 //////////////////////////////////////////////////////////////////////////
142 TerrainOverlay::TerrainOverlay(
143 const CSimContext
& simContext
, int priority
/* = 100 */)
144 : ITerrainOverlay(priority
), m_Terrain(&simContext
.GetTerrain())
146 m_OverlayTechTile
= CreateOverlayTileShaderTechnique(false);
147 m_OverlayTechTileHidden
= CreateOverlayTileShaderTechnique(true);
149 m_OverlayTechOutline
= CreateOverlayOutlineShaderTechnique(false);
150 m_OverlayTechOutlineHidden
= CreateOverlayOutlineShaderTechnique(true);
152 const std::array
<Renderer::Backend::SVertexAttributeFormat
, 1> attributes
{{
153 {Renderer::Backend::VertexAttributeStream::POSITION
,
154 Renderer::Backend::Format::R32G32B32_SFLOAT
, 0, sizeof(float) * 3,
155 Renderer::Backend::VertexAttributeRate::PER_VERTEX
, 0}
158 m_VertexInputLayout
= g_Renderer
.GetVertexInputLayout(attributes
);
161 void TerrainOverlay::StartRender()
165 void TerrainOverlay::EndRender()
169 void TerrainOverlay::GetTileExtents(
170 ssize_t
& min_i_inclusive
, ssize_t
& min_j_inclusive
,
171 ssize_t
& max_i_inclusive
, ssize_t
& max_j_inclusive
)
173 // Default to whole map
174 min_i_inclusive
= min_j_inclusive
= 0;
175 max_i_inclusive
= max_j_inclusive
= m_Terrain
->GetTilesPerSide()-1;
178 void TerrainOverlay::RenderBeforeWater(
179 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
)
182 return; // should never happen, but let's play it safe
186 ssize_t min_i
, min_j
, max_i
, max_j
;
187 GetTileExtents(min_i
, min_j
, max_i
, max_j
);
188 // Clamp the min to 0, but the max to -1 - so tile -1 can never be rendered,
189 // but if unclamped_max<0 then no tiles at all will be rendered. And the same
190 // for the upper limit.
191 min_i
= Clamp
<ssize_t
>(min_i
, 0, m_Terrain
->GetTilesPerSide());
192 min_j
= Clamp
<ssize_t
>(min_j
, 0, m_Terrain
->GetTilesPerSide());
193 max_i
= Clamp
<ssize_t
>(max_i
, -1, m_Terrain
->GetTilesPerSide()-1);
194 max_j
= Clamp
<ssize_t
>(max_j
, -1, m_Terrain
->GetTilesPerSide()-1);
196 for (m_j
= min_j
; m_j
<= max_j
; ++m_j
)
197 for (m_i
= min_i
; m_i
<= max_i
; ++m_i
)
198 ProcessTile(deviceCommandContext
, m_i
, m_j
);
203 void TerrainOverlay::RenderTile(
204 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
,
205 const CColor
& color
, bool drawHidden
)
207 RenderTile(deviceCommandContext
, color
, drawHidden
, m_i
, m_j
);
210 void TerrainOverlay::RenderTile(
211 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
,
212 const CColor
& color
, bool drawHidden
, ssize_t i
, ssize_t j
)
214 // TODO: unnecessary computation calls has been removed but we should use
215 // a vertex buffer or a vertex shader with a texture.
216 // Not sure if it's possible on old OpenGL.
219 for (int di
= 0; di
< 2; ++di
)
220 for (int dj
= 0; dj
< 2; ++dj
)
221 m_Terrain
->CalcPosition(i
+ di
, j
+ dj
, pos
[di
][dj
]);
223 std::vector
<float> vertices
;
224 #define ADD(position) \
225 vertices.emplace_back((position).X); \
226 vertices.emplace_back((position).Y); \
227 vertices.emplace_back((position).Z);
229 if (m_Terrain
->GetTriangulationDir(i
, j
))
251 const CShaderTechniquePtr
& shaderTechnique
= drawHidden
? m_OverlayTechTileHidden
: m_OverlayTechTile
;
252 deviceCommandContext
->SetGraphicsPipelineState(
253 shaderTechnique
->GetGraphicsPipelineState());
254 deviceCommandContext
->BeginPass();
256 Renderer::Backend::IShaderProgram
* overlayShader
= shaderTechnique
->GetShader();
258 const CMatrix3D transform
=
259 g_Renderer
.GetSceneRenderer().GetViewCamera().GetViewProjection();
260 deviceCommandContext
->SetUniform(
261 overlayShader
->GetBindingSlot(str_transform
), transform
.AsFloatArray());
262 deviceCommandContext
->SetUniform(
263 overlayShader
->GetBindingSlot(str_color
), color
.AsFloatArray());
265 deviceCommandContext
->SetVertexInputLayout(m_VertexInputLayout
);
267 deviceCommandContext
->SetVertexBufferData(
268 0, vertices
.data(), vertices
.size() * sizeof(vertices
[0]));
270 deviceCommandContext
->Draw(0, vertices
.size() / 3);
272 deviceCommandContext
->EndPass();
275 void TerrainOverlay::RenderTileOutline(
276 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
,
277 const CColor
& color
, bool drawHidden
)
279 RenderTileOutline(deviceCommandContext
, color
, drawHidden
, m_i
, m_j
);
282 void TerrainOverlay::RenderTileOutline(
283 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
,
284 const CColor
& color
, bool drawHidden
, ssize_t i
, ssize_t j
)
286 std::vector
<float> vertices
;
288 m_Terrain->CalcPosition(i, j, position); \
289 vertices.emplace_back(position.X); \
290 vertices.emplace_back(position.Y); \
291 vertices.emplace_back(position.Z);
302 const CShaderTechniquePtr
& shaderTechnique
= drawHidden
? m_OverlayTechOutlineHidden
: m_OverlayTechOutline
;
303 deviceCommandContext
->SetGraphicsPipelineState(
304 shaderTechnique
->GetGraphicsPipelineState());
305 deviceCommandContext
->BeginPass();
307 Renderer::Backend::IShaderProgram
* overlayShader
= shaderTechnique
->GetShader();
309 const CMatrix3D transform
=
310 g_Renderer
.GetSceneRenderer().GetViewCamera().GetViewProjection();
311 deviceCommandContext
->SetUniform(
312 overlayShader
->GetBindingSlot(str_transform
), transform
.AsFloatArray());
313 deviceCommandContext
->SetUniform(
314 overlayShader
->GetBindingSlot(str_color
), color
.AsFloatArray());
316 deviceCommandContext
->SetVertexInputLayout(m_VertexInputLayout
);
318 deviceCommandContext
->SetVertexBufferData(
319 0, vertices
.data(), vertices
.size() * sizeof(vertices
[0]));
321 deviceCommandContext
->Draw(0, vertices
.size() / 3);
323 deviceCommandContext
->EndPass();
326 //////////////////////////////////////////////////////////////////////////
328 TerrainTextureOverlay::TerrainTextureOverlay(float texelsPerTile
, int priority
) :
329 ITerrainOverlay(priority
), m_TexelsPerTile(texelsPerTile
)
333 TerrainTextureOverlay::~TerrainTextureOverlay() = default;
335 void TerrainTextureOverlay::RenderAfterWater(
336 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
, int cullGroup
)
338 const CTerrain
& terrain
= g_Game
->GetWorld()->GetTerrain();
340 const ssize_t w
= static_cast<ssize_t
>(terrain
.GetTilesPerSide() * m_TexelsPerTile
);
341 const ssize_t h
= static_cast<ssize_t
>(terrain
.GetTilesPerSide() * m_TexelsPerTile
);
343 const uint32_t requiredWidth
= round_up_to_pow2(w
);
344 const uint32_t requiredHeight
= round_up_to_pow2(h
);
346 // Recreate the texture with new size if necessary
347 if (!m_Texture
|| m_Texture
->GetWidth() != requiredWidth
|| m_Texture
->GetHeight() != requiredHeight
)
349 m_Texture
= deviceCommandContext
->GetDevice()->CreateTexture2D("TerrainOverlayTexture",
350 Renderer::Backend::ITexture::Usage::TRANSFER_DST
|
351 Renderer::Backend::ITexture::Usage::SAMPLED
,
352 Renderer::Backend::Format::R8G8B8A8_UNORM
, requiredWidth
, requiredHeight
,
353 Renderer::Backend::Sampler::MakeDefaultSampler(
354 Renderer::Backend::Sampler::Filter::NEAREST
,
355 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE
));
358 u8
* data
= (u8
*)calloc(w
* h
, 4);
359 BuildTextureRGBA(data
, w
, h
);
361 deviceCommandContext
->UploadTextureRegion(
362 m_Texture
.get(), Renderer::Backend::Format::R8G8B8A8_UNORM
, data
, w
* h
* 4, 0, 0, w
, h
);
366 const CVector2D textureTransform
{
367 m_TexelsPerTile
/ (m_Texture
->GetWidth() * TERRAIN_TILE_SIZE
),
368 m_TexelsPerTile
/ (m_Texture
->GetHeight() * TERRAIN_TILE_SIZE
)};
369 g_Renderer
.GetSceneRenderer().GetTerrainRenderer().RenderTerrainOverlayTexture(
370 deviceCommandContext
, cullGroup
, textureTransform
, m_Texture
.get());
373 SColor4ub
TerrainTextureOverlay::GetColor(size_t idx
, u8 alpha
) const
375 static u8 colors
[][3] =
408 size_t c
= idx
% ARRAY_SIZE(colors
);
409 return SColor4ub(colors
[c
][0], colors
[c
][1], colors
[c
][2], alpha
);