Merge 'remotes/trunk'
[0ad.git] / source / renderer / TerrainOverlay.cpp
blob19d1b1fdc5053a82e78f5e43438646bee4e13343
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"
26 #include "lib/bits.h"
27 #include "maths/MathUtil.h"
28 #include "maths/Vector2D.h"
29 #include "ps/CStrInternStatic.h"
30 #include "ps/Game.h"
31 #include "ps/Profile.h"
32 #include "ps/World.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"
40 #include <algorithm>
42 namespace
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(
66 str_debug_line, {},
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
72 // the camera.
73 pipelineStateDesc.rasterizationState.depthBiasEnabled = true;
74 pipelineStateDesc.rasterizationState.depthBiasConstantFactor = -1.0f;
75 pipelineStateDesc.rasterizationState.depthBiasSlopeFactor = -1.0f;
76 });
79 CShaderTechniquePtr CreateOverlayOutlineShaderTechnique(const bool drawHidden)
81 return g_Renderer.GetShaderManager().LoadEffect(
82 str_debug_line, {},
83 [drawHidden](Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc)
85 AdjustOverlayGraphicsPipelineState(pipelineStateDesc, drawHidden);
86 pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE;
87 });
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())
118 return;
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())
131 return;
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)
181 if (!m_Terrain)
182 return; // should never happen, but let's play it safe
184 StartRender();
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);
200 EndRender();
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.
218 CVector3D pos[2][2];
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))
231 ADD(pos[0][0]);
232 ADD(pos[1][0]);
233 ADD(pos[0][1]);
235 ADD(pos[1][0]);
236 ADD(pos[1][1]);
237 ADD(pos[0][1]);
239 else
241 ADD(pos[0][0]);
242 ADD(pos[1][0]);
243 ADD(pos[1][1]);
245 ADD(pos[1][1]);
246 ADD(pos[0][1]);
247 ADD(pos[0][0]);
249 #undef ADD
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;
287 #define ADD(i, j) \
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);
293 CVector3D position;
294 ADD(i, j);
295 ADD(i + 1, j);
296 ADD(i + 1, j + 1);
297 ADD(i, j);
298 ADD(i + 1, j + 1);
299 ADD(i, j + 1);
300 #undef ADD
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);
364 free(data);
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] =
377 { 255, 0, 0 },
378 { 0, 255, 0 },
379 { 0, 0, 255 },
380 { 255, 255, 0 },
381 { 255, 0, 255 },
382 { 0, 255, 255 },
383 { 255, 255, 255 },
385 { 127, 0, 0 },
386 { 0, 127, 0 },
387 { 0, 0, 127 },
388 { 127, 127, 0 },
389 { 127, 0, 127 },
390 { 0, 127, 127 },
391 { 127, 127, 127},
393 { 255, 127, 0 },
394 { 127, 255, 0 },
395 { 255, 0, 127 },
396 { 127, 0, 255},
397 { 0, 255, 127 },
398 { 0, 127, 255},
399 { 255, 127, 127},
400 { 127, 255, 127},
401 { 127, 127, 255},
403 { 127, 255, 255 },
404 { 255, 127, 255 },
405 { 255, 255, 127 },
408 size_t c = idx % ARRAY_SIZE(colors);
409 return SColor4ub(colors[c][0], colors[c][1], colors[c][2], alpha);