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 "TerrainOverlay.h"
21 :
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"
39 :
40 : #include <algorithm>
41 :
42 : namespace
43 : {
44 :
45 : // Global overlay list management:
46 1 : std::vector<std::pair<ITerrainOverlay*, int>> g_TerrainOverlayList;
47 :
48 0 : void AdjustOverlayGraphicsPipelineState(
49 : Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc, const bool drawHidden)
50 : {
51 0 : pipelineStateDesc.depthStencilState.depthTestEnabled = !drawHidden;
52 0 : pipelineStateDesc.blendState.enabled = true;
53 0 : pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor =
54 : Renderer::Backend::BlendFactor::SRC_ALPHA;
55 0 : pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor =
56 : Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
57 0 : pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp =
58 : Renderer::Backend::BlendOp::ADD;
59 0 : pipelineStateDesc.rasterizationState.cullMode =
60 0 : drawHidden ? Renderer::Backend::CullMode::NONE : Renderer::Backend::CullMode::BACK;
61 0 : }
62 :
63 0 : CShaderTechniquePtr CreateOverlayTileShaderTechnique(const bool drawHidden)
64 : {
65 0 : return g_Renderer.GetShaderManager().LoadEffect(
66 : str_debug_line, {},
67 0 : [drawHidden](Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc)
68 0 : {
69 0 : 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 0 : pipelineStateDesc.rasterizationState.depthBiasEnabled = true;
74 0 : pipelineStateDesc.rasterizationState.depthBiasConstantFactor = -1.0f;
75 0 : pipelineStateDesc.rasterizationState.depthBiasSlopeFactor = -1.0f;
76 0 : });
77 : }
78 :
79 0 : CShaderTechniquePtr CreateOverlayOutlineShaderTechnique(const bool drawHidden)
80 : {
81 0 : return g_Renderer.GetShaderManager().LoadEffect(
82 : str_debug_line, {},
83 0 : [drawHidden](Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc)
84 0 : {
85 0 : AdjustOverlayGraphicsPipelineState(pipelineStateDesc, drawHidden);
86 0 : pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE;
87 0 : });
88 : }
89 :
90 : } // anonymous namespace
91 :
92 0 : ITerrainOverlay::ITerrainOverlay(int priority)
93 : {
94 : // Add to global list of overlays
95 0 : 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 0 : std::stable_sort(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(),
100 0 : [](const std::pair<ITerrainOverlay*, int>& a, const std::pair<ITerrainOverlay*, int>& b) {
101 0 : return a.second < b.second;
102 0 : });
103 0 : }
104 :
105 0 : ITerrainOverlay::~ITerrainOverlay()
106 : {
107 : std::vector<std::pair<ITerrainOverlay*, int> >::iterator newEnd =
108 : std::remove_if(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(),
109 0 : [this](const std::pair<ITerrainOverlay*, int>& a) { return a.first == this; });
110 0 : g_TerrainOverlayList.erase(newEnd, g_TerrainOverlayList.end());
111 0 : }
112 :
113 :
114 0 : void ITerrainOverlay::RenderOverlaysBeforeWater(
115 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
116 : {
117 0 : if (g_TerrainOverlayList.empty())
118 0 : return;
119 :
120 0 : PROFILE3_GPU("terrain overlays (before)");
121 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays before water");
122 :
123 0 : for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i)
124 0 : g_TerrainOverlayList[i].first->RenderBeforeWater(deviceCommandContext);
125 : }
126 :
127 0 : void ITerrainOverlay::RenderOverlaysAfterWater(
128 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup)
129 : {
130 0 : if (g_TerrainOverlayList.empty())
131 0 : return;
132 :
133 0 : PROFILE3_GPU("terrain overlays (after)");
134 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays after water");
135 :
136 0 : for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i)
137 0 : g_TerrainOverlayList[i].first->RenderAfterWater(deviceCommandContext, cullGroup);
138 : }
139 :
140 : //////////////////////////////////////////////////////////////////////////
141 :
142 0 : TerrainOverlay::TerrainOverlay(
143 0 : const CSimContext& simContext, int priority /* = 100 */)
144 0 : : ITerrainOverlay(priority), m_Terrain(&simContext.GetTerrain())
145 : {
146 0 : m_OverlayTechTile = CreateOverlayTileShaderTechnique(false);
147 0 : m_OverlayTechTileHidden = CreateOverlayTileShaderTechnique(true);
148 :
149 0 : m_OverlayTechOutline = CreateOverlayOutlineShaderTechnique(false);
150 0 : m_OverlayTechOutlineHidden = CreateOverlayOutlineShaderTechnique(true);
151 :
152 0 : 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}
156 : }};
157 :
158 0 : m_VertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
159 0 : }
160 :
161 0 : void TerrainOverlay::StartRender()
162 : {
163 0 : }
164 :
165 0 : void TerrainOverlay::EndRender()
166 : {
167 0 : }
168 :
169 0 : 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)
172 : {
173 : // Default to whole map
174 0 : min_i_inclusive = min_j_inclusive = 0;
175 0 : max_i_inclusive = max_j_inclusive = m_Terrain->GetTilesPerSide()-1;
176 0 : }
177 :
178 0 : void TerrainOverlay::RenderBeforeWater(
179 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
180 : {
181 0 : if (!m_Terrain)
182 0 : return; // should never happen, but let's play it safe
183 :
184 0 : StartRender();
185 :
186 : ssize_t min_i, min_j, max_i, max_j;
187 0 : 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 0 : min_i = Clamp<ssize_t>(min_i, 0, m_Terrain->GetTilesPerSide());
192 0 : min_j = Clamp<ssize_t>(min_j, 0, m_Terrain->GetTilesPerSide());
193 0 : max_i = Clamp<ssize_t>(max_i, -1, m_Terrain->GetTilesPerSide()-1);
194 0 : max_j = Clamp<ssize_t>(max_j, -1, m_Terrain->GetTilesPerSide()-1);
195 :
196 0 : for (m_j = min_j; m_j <= max_j; ++m_j)
197 0 : for (m_i = min_i; m_i <= max_i; ++m_i)
198 0 : ProcessTile(deviceCommandContext, m_i, m_j);
199 :
200 0 : EndRender();
201 : }
202 :
203 0 : void TerrainOverlay::RenderTile(
204 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
205 : const CColor& color, bool drawHidden)
206 : {
207 0 : RenderTile(deviceCommandContext, color, drawHidden, m_i, m_j);
208 0 : }
209 :
210 0 : void TerrainOverlay::RenderTile(
211 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
212 : const CColor& color, bool drawHidden, ssize_t i, ssize_t j)
213 : {
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.
217 :
218 0 : CVector3D pos[2][2];
219 0 : for (int di = 0; di < 2; ++di)
220 0 : for (int dj = 0; dj < 2; ++dj)
221 0 : m_Terrain->CalcPosition(i + di, j + dj, pos[di][dj]);
222 :
223 0 : 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);
228 :
229 0 : if (m_Terrain->GetTriangulationDir(i, j))
230 : {
231 0 : ADD(pos[0][0]);
232 0 : ADD(pos[1][0]);
233 0 : ADD(pos[0][1]);
234 :
235 0 : ADD(pos[1][0]);
236 0 : ADD(pos[1][1]);
237 0 : ADD(pos[0][1]);
238 : }
239 : else
240 : {
241 0 : ADD(pos[0][0]);
242 0 : ADD(pos[1][0]);
243 0 : ADD(pos[1][1]);
244 :
245 0 : ADD(pos[1][1]);
246 0 : ADD(pos[0][1]);
247 0 : ADD(pos[0][0]);
248 : }
249 : #undef ADD
250 :
251 0 : const CShaderTechniquePtr& shaderTechnique = drawHidden ? m_OverlayTechTileHidden : m_OverlayTechTile;
252 0 : deviceCommandContext->SetGraphicsPipelineState(
253 0 : shaderTechnique->GetGraphicsPipelineState());
254 0 : deviceCommandContext->BeginPass();
255 :
256 0 : Renderer::Backend::IShaderProgram* overlayShader = shaderTechnique->GetShader();
257 :
258 : const CMatrix3D transform =
259 0 : g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
260 0 : deviceCommandContext->SetUniform(
261 0 : overlayShader->GetBindingSlot(str_transform), transform.AsFloatArray());
262 0 : deviceCommandContext->SetUniform(
263 0 : overlayShader->GetBindingSlot(str_color), color.AsFloatArray());
264 :
265 0 : deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
266 :
267 0 : deviceCommandContext->SetVertexBufferData(
268 0 : 0, vertices.data(), vertices.size() * sizeof(vertices[0]));
269 :
270 0 : deviceCommandContext->Draw(0, vertices.size() / 3);
271 :
272 0 : deviceCommandContext->EndPass();
273 0 : }
274 :
275 0 : void TerrainOverlay::RenderTileOutline(
276 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
277 : const CColor& color, bool drawHidden)
278 : {
279 0 : RenderTileOutline(deviceCommandContext, color, drawHidden, m_i, m_j);
280 0 : }
281 :
282 0 : void TerrainOverlay::RenderTileOutline(
283 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
284 : const CColor& color, bool drawHidden, ssize_t i, ssize_t j)
285 : {
286 0 : 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);
292 :
293 0 : CVector3D position;
294 0 : ADD(i, j);
295 0 : ADD(i + 1, j);
296 0 : ADD(i + 1, j + 1);
297 0 : ADD(i, j);
298 0 : ADD(i + 1, j + 1);
299 0 : ADD(i, j + 1);
300 : #undef ADD
301 :
302 0 : const CShaderTechniquePtr& shaderTechnique = drawHidden ? m_OverlayTechOutlineHidden : m_OverlayTechOutline;
303 0 : deviceCommandContext->SetGraphicsPipelineState(
304 0 : shaderTechnique->GetGraphicsPipelineState());
305 0 : deviceCommandContext->BeginPass();
306 :
307 0 : Renderer::Backend::IShaderProgram* overlayShader = shaderTechnique->GetShader();
308 :
309 : const CMatrix3D transform =
310 0 : g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
311 0 : deviceCommandContext->SetUniform(
312 0 : overlayShader->GetBindingSlot(str_transform), transform.AsFloatArray());
313 0 : deviceCommandContext->SetUniform(
314 0 : overlayShader->GetBindingSlot(str_color), color.AsFloatArray());
315 :
316 0 : deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
317 :
318 0 : deviceCommandContext->SetVertexBufferData(
319 0 : 0, vertices.data(), vertices.size() * sizeof(vertices[0]));
320 :
321 0 : deviceCommandContext->Draw(0, vertices.size() / 3);
322 :
323 0 : deviceCommandContext->EndPass();
324 0 : }
325 :
326 : //////////////////////////////////////////////////////////////////////////
327 :
328 0 : TerrainTextureOverlay::TerrainTextureOverlay(float texelsPerTile, int priority) :
329 0 : ITerrainOverlay(priority), m_TexelsPerTile(texelsPerTile)
330 : {
331 0 : }
332 :
333 : TerrainTextureOverlay::~TerrainTextureOverlay() = default;
334 :
335 0 : void TerrainTextureOverlay::RenderAfterWater(
336 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup)
337 : {
338 0 : CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
339 :
340 0 : ssize_t w = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile);
341 0 : ssize_t h = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile);
342 :
343 0 : const uint32_t requiredWidth = round_up_to_pow2(w);
344 0 : const uint32_t requiredHeight = round_up_to_pow2(h);
345 :
346 : // Recreate the texture with new size if necessary
347 0 : if (!m_Texture || m_Texture->GetWidth() != requiredWidth || m_Texture->GetHeight() != requiredHeight)
348 : {
349 0 : 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 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
354 : Renderer::Backend::Sampler::Filter::NEAREST,
355 0 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
356 : }
357 :
358 0 : u8* data = (u8*)calloc(w * h, 4);
359 0 : BuildTextureRGBA(data, w, h);
360 :
361 0 : deviceCommandContext->UploadTextureRegion(
362 0 : m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, data, w * h * 4, 0, 0, w, h);
363 :
364 0 : free(data);
365 :
366 : const CVector2D textureTransform{
367 0 : m_TexelsPerTile / (m_Texture->GetWidth() * TERRAIN_TILE_SIZE),
368 0 : m_TexelsPerTile / (m_Texture->GetHeight() * TERRAIN_TILE_SIZE)};
369 0 : g_Renderer.GetSceneRenderer().GetTerrainRenderer().RenderTerrainOverlayTexture(
370 : deviceCommandContext, cullGroup, textureTransform, m_Texture.get());
371 0 : }
372 :
373 0 : SColor4ub TerrainTextureOverlay::GetColor(size_t idx, u8 alpha) const
374 : {
375 : static u8 colors[][3] =
376 : {
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 },
384 :
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},
392 :
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},
402 :
403 : { 127, 255, 255 },
404 : { 255, 127, 255 },
405 : { 255, 255, 127 },
406 : };
407 :
408 0 : size_t c = idx % ARRAY_SIZE(colors);
409 0 : return SColor4ub(colors[c][0], colors[c][1], colors[c][2], alpha);
410 3 : }
|