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 "MiniMapTexture.h"
21 :
22 : #include "graphics/GameView.h"
23 : #include "graphics/LOSTexture.h"
24 : #include "graphics/MiniPatch.h"
25 : #include "graphics/ShaderManager.h"
26 : #include "graphics/ShaderProgramPtr.h"
27 : #include "graphics/Terrain.h"
28 : #include "graphics/TerrainTextureEntry.h"
29 : #include "graphics/TerrainTextureManager.h"
30 : #include "graphics/TerritoryTexture.h"
31 : #include "graphics/TextureManager.h"
32 : #include "lib/bits.h"
33 : #include "lib/hash.h"
34 : #include "lib/timer.h"
35 : #include "maths/MathUtil.h"
36 : #include "maths/Vector2D.h"
37 : #include "ps/ConfigDB.h"
38 : #include "ps/CStrInternStatic.h"
39 : #include "ps/Filesystem.h"
40 : #include "ps/Game.h"
41 : #include "ps/Profile.h"
42 : #include "ps/VideoMode.h"
43 : #include "ps/World.h"
44 : #include "ps/XML/Xeromyces.h"
45 : #include "renderer/backend/IDevice.h"
46 : #include "renderer/Renderer.h"
47 : #include "renderer/RenderingOptions.h"
48 : #include "renderer/SceneRenderer.h"
49 : #include "renderer/WaterManager.h"
50 : #include "scriptinterface/Object.h"
51 : #include "simulation2/Simulation2.h"
52 : #include "simulation2/components/ICmpMinimap.h"
53 : #include "simulation2/components/ICmpRangeManager.h"
54 : #include "simulation2/system/ParamNode.h"
55 :
56 : #include <algorithm>
57 : #include <array>
58 : #include <cmath>
59 :
60 : namespace
61 : {
62 :
63 : // Set max drawn entities to 64K / 4 for now, which is more than enough.
64 : // 4 is the number of vertices per entity.
65 : // TODO: we should be cleverer about drawing them to reduce clutter,
66 : // f.e. use instancing.
67 : constexpr size_t MAX_ENTITIES_DRAWN = 65536 / 4;
68 :
69 : constexpr size_t MAX_ICON_COUNT = 256;
70 : constexpr size_t MAX_UNIQUE_ICON_COUNT = 64;
71 : constexpr size_t ICON_COMBINING_GRID_SIZE = 10;
72 :
73 : constexpr size_t FINAL_TEXTURE_SIZE = 512;
74 :
75 0 : unsigned int ScaleColor(unsigned int color, float x)
76 : {
77 0 : unsigned int r = unsigned(float(color & 0xff) * x);
78 0 : unsigned int g = unsigned(float((color >> 8) & 0xff) * x);
79 0 : unsigned int b = unsigned(float((color >> 16) & 0xff) * x);
80 0 : return (0xff000000 | b | g << 8 | r << 16);
81 : }
82 :
83 0 : void DrawTexture(
84 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
85 : Renderer::Backend::IVertexInputLayout* quadVertexInputLayout)
86 : {
87 0 : const float quadUVs[] =
88 : {
89 : 0.0f, 0.0f,
90 : 1.0f, 0.0f,
91 : 1.0f, 1.0f,
92 :
93 : 1.0f, 1.0f,
94 : 0.0f, 1.0f,
95 : 0.0f, 0.0f
96 : };
97 0 : const float quadVertices[] =
98 : {
99 : -1.0f, -1.0f,
100 : 1.0f, -1.0f,
101 : 1.0f, 1.0f,
102 :
103 : 1.0f, 1.0f,
104 : -1.0f, 1.0f,
105 : -1.0f, -1.0f,
106 : };
107 :
108 0 : deviceCommandContext->SetVertexInputLayout(quadVertexInputLayout);
109 :
110 0 : deviceCommandContext->SetVertexBufferData(
111 0 : 0, quadVertices, std::size(quadVertices) * sizeof(quadVertices[0]));
112 0 : deviceCommandContext->SetVertexBufferData(
113 0 : 1, quadUVs, std::size(quadUVs) * sizeof(quadUVs[0]));
114 :
115 0 : deviceCommandContext->Draw(0, 6);
116 0 : }
117 :
118 0 : struct MinimapUnitVertex
119 : {
120 : // This struct is copyable for convenience and because to move is to copy for primitives.
121 : u8 r, g, b, a;
122 : CVector2D position;
123 : };
124 :
125 : // Adds a vertex to the passed VertexArray
126 0 : inline void AddEntity(const MinimapUnitVertex& v,
127 : VertexArrayIterator<u8[4]>& attrColor,
128 : VertexArrayIterator<float[2]>& attrPos,
129 : const float entityRadius,
130 : const bool useInstancing)
131 : {
132 0 : if (useInstancing)
133 : {
134 0 : (*attrColor)[0] = v.r;
135 0 : (*attrColor)[1] = v.g;
136 0 : (*attrColor)[2] = v.b;
137 0 : (*attrColor)[3] = v.a;
138 0 : ++attrColor;
139 :
140 0 : (*attrPos)[0] = v.position.X;
141 0 : (*attrPos)[1] = v.position.Y;
142 0 : ++attrPos;
143 :
144 0 : return;
145 : }
146 :
147 : const CVector2D offsets[4] =
148 : {
149 : {-entityRadius, 0.0f},
150 : {0.0f, -entityRadius},
151 : {entityRadius, 0.0f},
152 : {0.0f, entityRadius}
153 0 : };
154 :
155 0 : for (const CVector2D& offset : offsets)
156 : {
157 0 : (*attrColor)[0] = v.r;
158 0 : (*attrColor)[1] = v.g;
159 0 : (*attrColor)[2] = v.b;
160 0 : (*attrColor)[3] = v.a;
161 0 : ++attrColor;
162 :
163 0 : (*attrPos)[0] = v.position.X + offset.X;
164 0 : (*attrPos)[1] = v.position.Y + offset.Y;
165 0 : ++attrPos;
166 : }
167 : }
168 :
169 : } // anonymous namespace
170 :
171 0 : size_t CMiniMapTexture::CellIconKeyHash::operator()(
172 : const CellIconKey& key) const
173 : {
174 0 : size_t seed = 0;
175 0 : hash_combine(seed, key.path);
176 0 : hash_combine(seed, key.r);
177 0 : hash_combine(seed, key.g);
178 0 : hash_combine(seed, key.b);
179 0 : return seed;
180 : }
181 :
182 0 : bool CMiniMapTexture::CellIconKeyEqual::operator()(
183 : const CellIconKey& lhs, const CellIconKey& rhs) const
184 : {
185 : return
186 0 : lhs.path == rhs.path &&
187 0 : lhs.r == rhs.r &&
188 0 : lhs.g == rhs.g &&
189 0 : lhs.b == rhs.b;
190 : }
191 :
192 0 : CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation)
193 : : m_Simulation(simulation), m_IndexArray(false),
194 : m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true),
195 0 : m_InstanceVertexArray(Renderer::Backend::IBuffer::Type::VERTEX, false)
196 : {
197 : // Register Relax NG validator.
198 0 : CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
199 :
200 0 : m_ShallowPassageHeight = GetShallowPassageHeight();
201 :
202 0 : double blinkDuration = 1.0;
203 : // Tests won't have config initialised
204 0 : if (CConfigDB::IsInitialised())
205 : {
206 0 : CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
207 0 : CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration);
208 : }
209 0 : m_HalfBlinkDuration = blinkDuration / 2.0;
210 :
211 0 : m_AttributePos.format = Renderer::Backend::Format::R32G32_SFLOAT;
212 0 : m_VertexArray.AddAttribute(&m_AttributePos);
213 :
214 0 : m_AttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM;
215 0 : m_VertexArray.AddAttribute(&m_AttributeColor);
216 :
217 0 : m_VertexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 4);
218 0 : m_VertexArray.Layout();
219 :
220 0 : m_IndexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 6);
221 0 : m_IndexArray.Layout();
222 0 : VertexArrayIterator<u16> index = m_IndexArray.GetIterator();
223 0 : for (size_t i = 0; i < m_IndexArray.GetNumberOfVertices(); ++i)
224 0 : *index++ = 0;
225 0 : m_IndexArray.Upload();
226 :
227 0 : VertexArrayIterator<float[2]> attrPos = m_AttributePos.GetIterator<float[2]>();
228 0 : VertexArrayIterator<u8[4]> attrColor = m_AttributeColor.GetIterator<u8[4]>();
229 0 : for (size_t i = 0; i < m_VertexArray.GetNumberOfVertices(); ++i)
230 : {
231 0 : (*attrColor)[0] = 0;
232 0 : (*attrColor)[1] = 0;
233 0 : (*attrColor)[2] = 0;
234 0 : (*attrColor)[3] = 0;
235 0 : ++attrColor;
236 :
237 0 : (*attrPos)[0] = -10000.0f;
238 0 : (*attrPos)[1] = -10000.0f;
239 :
240 0 : ++attrPos;
241 : }
242 0 : m_VertexArray.Upload();
243 :
244 0 : const std::array<Renderer::Backend::SVertexAttributeFormat, 2> attributes{{
245 : {Renderer::Backend::VertexAttributeStream::POSITION,
246 : Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
247 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
248 : {Renderer::Backend::VertexAttributeStream::UV0,
249 : Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
250 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1}
251 : }};
252 0 : m_QuadVertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
253 :
254 0 : Renderer::Backend::IDevice* device = g_VideoMode.GetBackendDevice();
255 0 : m_Flipped = device->GetBackend() == Renderer::Backend::Backend::VULKAN;
256 :
257 0 : const uint32_t stride = m_VertexArray.GetStride();
258 0 : if (device->GetCapabilities().instancing)
259 : {
260 0 : m_UseInstancing = true;
261 :
262 0 : const size_t numberOfCircleSegments = 8;
263 :
264 0 : m_InstanceAttributePosition.format = Renderer::Backend::Format::R32G32_SFLOAT;
265 0 : m_InstanceVertexArray.AddAttribute(&m_InstanceAttributePosition);
266 :
267 0 : m_InstanceVertexArray.SetNumberOfVertices(numberOfCircleSegments * 3);
268 0 : m_InstanceVertexArray.Layout();
269 :
270 : VertexArrayIterator<float[2]> attributePosition =
271 0 : m_InstanceAttributePosition.GetIterator<float[2]>();
272 0 : for (size_t segment = 0; segment < numberOfCircleSegments; ++segment)
273 : {
274 0 : const float currentAngle = static_cast<float>(segment) / numberOfCircleSegments * 2.0f * M_PI;
275 0 : const float nextAngle = static_cast<float>(segment + 1) / numberOfCircleSegments * 2.0f * M_PI;
276 :
277 0 : (*attributePosition)[0] = 0.0f;
278 0 : (*attributePosition)[1] = 0.0f;
279 0 : ++attributePosition;
280 :
281 0 : (*attributePosition)[0] = std::cos(currentAngle);
282 0 : (*attributePosition)[1] = std::sin(currentAngle);
283 0 : ++attributePosition;
284 :
285 0 : (*attributePosition)[0] = std::cos(nextAngle);
286 0 : (*attributePosition)[1] = std::sin(nextAngle);
287 0 : ++attributePosition;
288 : }
289 :
290 0 : m_InstanceVertexArray.Upload();
291 0 : m_InstanceVertexArray.FreeBackingStore();
292 :
293 0 : const std::array<Renderer::Backend::SVertexAttributeFormat, 3> attributes{{
294 : {Renderer::Backend::VertexAttributeStream::POSITION,
295 0 : m_InstanceAttributePosition.format, m_InstanceAttributePosition.offset,
296 0 : m_InstanceVertexArray.GetStride(),
297 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
298 : {Renderer::Backend::VertexAttributeStream::UV1,
299 0 : m_AttributePos.format, m_AttributePos.offset, stride,
300 : Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1},
301 : {Renderer::Backend::VertexAttributeStream::COLOR,
302 0 : m_AttributeColor.format, m_AttributeColor.offset, stride,
303 : Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1},
304 0 : }};
305 0 : m_EntitiesVertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
306 : }
307 : else
308 : {
309 0 : const std::array<Renderer::Backend::SVertexAttributeFormat, 2> entitiesAttributes{{
310 : {Renderer::Backend::VertexAttributeStream::POSITION,
311 0 : m_AttributePos.format, m_AttributePos.offset, stride,
312 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
313 : {Renderer::Backend::VertexAttributeStream::COLOR,
314 0 : m_AttributeColor.format, m_AttributeColor.offset, stride,
315 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}
316 0 : }};
317 0 : m_EntitiesVertexInputLayout = g_Renderer.GetVertexInputLayout(entitiesAttributes);
318 : }
319 :
320 0 : CShaderDefines baseDefines;
321 0 : baseDefines.Add(str_MINIMAP_BASE, str_1);
322 :
323 0 : m_TerritoryTechnique = g_Renderer.GetShaderManager().LoadEffect(
324 : str_minimap, baseDefines,
325 0 : [](Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc)
326 : {
327 0 : pipelineStateDesc.blendState.enabled = true;
328 0 : pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor =
329 : Renderer::Backend::BlendFactor::SRC_ALPHA;
330 0 : pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor =
331 : Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
332 0 : pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp =
333 : Renderer::Backend::BlendOp::ADD;
334 0 : pipelineStateDesc.blendState.colorWriteMask =
335 : Renderer::Backend::ColorWriteMask::RED |
336 : Renderer::Backend::ColorWriteMask::GREEN |
337 : Renderer::Backend::ColorWriteMask::BLUE;
338 0 : });
339 0 : }
340 :
341 0 : CMiniMapTexture::~CMiniMapTexture()
342 : {
343 0 : DestroyTextures();
344 0 : }
345 :
346 0 : void CMiniMapTexture::Update(const float UNUSED(deltaRealTime))
347 : {
348 0 : if (m_WaterHeight != g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight)
349 : {
350 0 : m_TerrainTextureDirty = true;
351 0 : m_FinalTextureDirty = true;
352 : }
353 0 : }
354 :
355 0 : void CMiniMapTexture::Render(
356 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
357 : CLOSTexture& losTexture, CTerritoryTexture& territoryTexture)
358 : {
359 0 : const CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
360 0 : if (!terrain)
361 0 : return;
362 :
363 0 : if (!m_TerrainTexture)
364 0 : CreateTextures(deviceCommandContext, terrain);
365 :
366 0 : if (m_TerrainTextureDirty)
367 0 : RebuildTerrainTexture(deviceCommandContext, terrain);
368 :
369 0 : RenderFinalTexture(deviceCommandContext, losTexture, territoryTexture);
370 : }
371 :
372 0 : void CMiniMapTexture::CreateTextures(
373 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTerrain* terrain)
374 : {
375 0 : DestroyTextures();
376 :
377 0 : m_MapSize = terrain->GetVerticesPerSide();
378 0 : const size_t textureSize = round_up_to_pow2(static_cast<size_t>(m_MapSize));
379 :
380 : const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
381 : Renderer::Backend::Sampler::MakeDefaultSampler(
382 : Renderer::Backend::Sampler::Filter::LINEAR,
383 0 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
384 :
385 0 : Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice();
386 :
387 : // Create terrain texture
388 0 : m_TerrainTexture = backendDevice->CreateTexture2D("MiniMapTerrainTexture",
389 : Renderer::Backend::ITexture::Usage::TRANSFER_DST |
390 : Renderer::Backend::ITexture::Usage::SAMPLED,
391 0 : Renderer::Backend::Format::R8G8B8A8_UNORM, textureSize, textureSize, defaultSamplerDesc);
392 :
393 : // Initialise texture with solid black, for the areas we don't
394 : // overwrite with uploading later.
395 0 : std::unique_ptr<u32[]> texData = std::make_unique<u32[]>(textureSize * textureSize);
396 0 : for (size_t i = 0; i < textureSize * textureSize; ++i)
397 0 : texData[i] = 0xFF000000;
398 0 : deviceCommandContext->UploadTexture(
399 : m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
400 0 : texData.get(), textureSize * textureSize * 4);
401 0 : texData.reset();
402 :
403 0 : m_TerrainData = std::make_unique<u32[]>((m_MapSize - 1) * (m_MapSize - 1));
404 :
405 0 : m_FinalTexture = g_Renderer.GetTextureManager().WrapBackendTexture(
406 0 : backendDevice->CreateTexture2D("MiniMapFinalTexture",
407 : Renderer::Backend::ITexture::Usage::SAMPLED |
408 : Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
409 : Renderer::Backend::Format::R8G8B8A8_UNORM,
410 0 : FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc));
411 :
412 0 : Renderer::Backend::SColorAttachment colorAttachment{};
413 0 : colorAttachment.texture = m_FinalTexture->GetBackendTexture();
414 0 : colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
415 0 : colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
416 0 : colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
417 0 : m_FinalTextureFramebuffer = backendDevice->CreateFramebuffer(
418 0 : "MiniMapFinalFramebuffer", &colorAttachment, nullptr);
419 0 : ENSURE(m_FinalTextureFramebuffer);
420 0 : }
421 :
422 0 : void CMiniMapTexture::DestroyTextures()
423 : {
424 0 : m_TerrainTexture.reset();
425 0 : m_FinalTexture.reset();
426 0 : m_TerrainData.reset();
427 0 : }
428 :
429 0 : void CMiniMapTexture::RebuildTerrainTexture(
430 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
431 : const CTerrain* terrain)
432 : {
433 0 : const u32 x = 0;
434 0 : const u32 y = 0;
435 0 : const u32 width = m_MapSize - 1;
436 0 : const u32 height = m_MapSize - 1;
437 :
438 0 : m_WaterHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight;
439 0 : m_TerrainTextureDirty = false;
440 :
441 0 : for (u32 j = 0; j < height; ++j)
442 : {
443 0 : u32* dataPtr = m_TerrainData.get() + ((y + j) * width) + x;
444 0 : for (u32 i = 0; i < width; ++i)
445 : {
446 0 : const float avgHeight = ( terrain->GetVertexGroundLevel((int)i, (int)j)
447 0 : + terrain->GetVertexGroundLevel((int)i+1, (int)j)
448 0 : + terrain->GetVertexGroundLevel((int)i, (int)j+1)
449 0 : + terrain->GetVertexGroundLevel((int)i+1, (int)j+1)
450 0 : ) / 4.0f;
451 :
452 0 : if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight)
453 : {
454 : // shallow water
455 0 : *dataPtr++ = 0xffc09870;
456 : }
457 0 : else if (avgHeight < m_WaterHeight)
458 : {
459 : // Set water as constant color for consistency on different maps
460 0 : *dataPtr++ = 0xffa07850;
461 : }
462 : else
463 : {
464 0 : int hmap = ((int)terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
465 0 : int val = (hmap / 3) + 170;
466 :
467 0 : u32 color = 0xFFFFFFFF;
468 :
469 0 : CMiniPatch* mp = terrain->GetTile(x + i, y + j);
470 0 : if (mp)
471 : {
472 0 : CTerrainTextureEntry* tex = mp->GetTextureEntry();
473 0 : if (tex)
474 : {
475 : // If the texture can't be loaded yet, set the dirty flags
476 : // so we'll try regenerating the terrain texture again soon
477 0 : if (!tex->GetTexture()->TryLoad())
478 0 : m_TerrainTextureDirty = true;
479 :
480 0 : color = tex->GetBaseColor();
481 : }
482 : }
483 :
484 0 : *dataPtr++ = ScaleColor(color, float(val) / 255.0f);
485 : }
486 : }
487 : }
488 :
489 : // Upload the texture
490 0 : deviceCommandContext->UploadTextureRegion(
491 : m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
492 0 : m_TerrainData.get(), width * height * 4, 0, 0, width, height);
493 0 : }
494 :
495 0 : void CMiniMapTexture::RenderFinalTexture(
496 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
497 : CLOSTexture& losTexture, CTerritoryTexture& territoryTexture)
498 : {
499 : // only update 2x / second
500 : // (note: since units only move a few pixels per second on the minimap,
501 : // we can get away with infrequent updates; this is slow)
502 : // TODO: Update all but camera at same speed as simulation
503 0 : const double currentTime = timer_Time();
504 0 : const bool doUpdate = (currentTime - m_LastFinalTextureUpdate > 0.5) || m_FinalTextureDirty;
505 0 : if (!doUpdate)
506 0 : return;
507 0 : m_LastFinalTextureUpdate = currentTime;
508 0 : m_FinalTextureDirty = false;
509 :
510 : // We might scale entities properly in the vertex shader but it requires
511 : // additional space in the vertex buffer. So we assume that we don't need
512 : // to change an entity size so often.
513 : // Radius with instancing is lower because an entity has a more round shape.
514 0 : const float entityRadius = static_cast<float>(m_MapSize) / 128.0f * (m_UseInstancing ? 5.0 : 6.0f);
515 :
516 0 : UpdateAndUploadEntities(deviceCommandContext, entityRadius, currentTime);
517 :
518 0 : PROFILE3("Render minimap texture");
519 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Render minimap texture");
520 0 : deviceCommandContext->BeginFramebufferPass(m_FinalTextureFramebuffer.get());
521 :
522 0 : Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
523 0 : viewportRect.width = FINAL_TEXTURE_SIZE;
524 0 : viewportRect.height = FINAL_TEXTURE_SIZE;
525 0 : deviceCommandContext->SetViewports(1, &viewportRect);
526 :
527 0 : const float texCoordMax = m_TerrainTexture ? static_cast<float>(m_MapSize - 1) / m_TerrainTexture->GetWidth() : 1.0f;
528 :
529 0 : Renderer::Backend::IShaderProgram* shader = nullptr;
530 0 : CShaderTechniquePtr tech;
531 :
532 0 : CShaderDefines baseDefines;
533 0 : baseDefines.Add(str_MINIMAP_BASE, str_1);
534 :
535 0 : tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines);
536 0 : deviceCommandContext->SetGraphicsPipelineState(
537 0 : tech->GetGraphicsPipelineState());
538 0 : deviceCommandContext->BeginPass();
539 0 : shader = tech->GetShader();
540 :
541 0 : if (m_TerrainTexture)
542 : {
543 0 : deviceCommandContext->SetTexture(
544 0 : shader->GetBindingSlot(str_baseTex), m_TerrainTexture.get());
545 : }
546 :
547 0 : CMatrix3D baseTransform;
548 0 : baseTransform.SetIdentity();
549 0 : CMatrix3D baseTextureTransform;
550 0 : baseTextureTransform.SetIdentity();
551 :
552 0 : CMatrix3D terrainTransform;
553 0 : terrainTransform.SetIdentity();
554 0 : terrainTransform.Scale(texCoordMax, texCoordMax, 1.0f);
555 :
556 0 : deviceCommandContext->SetUniform(
557 0 : shader->GetBindingSlot(str_transform),
558 0 : baseTransform._11, baseTransform._21, baseTransform._12, baseTransform._22);
559 0 : deviceCommandContext->SetUniform(
560 0 : shader->GetBindingSlot(str_textureTransform),
561 0 : terrainTransform._11, terrainTransform._21, terrainTransform._12, terrainTransform._22);
562 0 : deviceCommandContext->SetUniform(
563 0 : shader->GetBindingSlot(str_translation),
564 0 : baseTransform._14, baseTransform._24, terrainTransform._14, terrainTransform._24);
565 :
566 0 : if (m_TerrainTexture)
567 0 : DrawTexture(deviceCommandContext, m_QuadVertexInputLayout);
568 0 : deviceCommandContext->EndPass();
569 :
570 0 : deviceCommandContext->SetGraphicsPipelineState(
571 0 : m_TerritoryTechnique->GetGraphicsPipelineState());
572 0 : shader = m_TerritoryTechnique->GetShader();
573 0 : deviceCommandContext->BeginPass();
574 :
575 : // Draw territory boundaries
576 0 : deviceCommandContext->SetTexture(
577 0 : shader->GetBindingSlot(str_baseTex), territoryTexture.GetTexture());
578 0 : deviceCommandContext->SetUniform(
579 0 : shader->GetBindingSlot(str_transform),
580 0 : baseTransform._11, baseTransform._21, baseTransform._12, baseTransform._22);
581 0 : const CMatrix3D& territoryTransform = territoryTexture.GetMinimapTextureMatrix();
582 0 : deviceCommandContext->SetUniform(
583 0 : shader->GetBindingSlot(str_textureTransform),
584 0 : territoryTransform._11, territoryTransform._21, territoryTransform._12, territoryTransform._22);
585 0 : deviceCommandContext->SetUniform(
586 0 : shader->GetBindingSlot(str_translation),
587 0 : baseTransform._14, baseTransform._24, territoryTransform._14, territoryTransform._24);
588 :
589 0 : DrawTexture(deviceCommandContext, m_QuadVertexInputLayout);
590 0 : deviceCommandContext->EndPass();
591 :
592 0 : tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap_los, CShaderDefines());
593 0 : deviceCommandContext->SetGraphicsPipelineState(
594 0 : tech->GetGraphicsPipelineState());
595 0 : deviceCommandContext->BeginPass();
596 0 : shader = tech->GetShader();
597 :
598 0 : deviceCommandContext->SetTexture(
599 0 : shader->GetBindingSlot(str_baseTex), losTexture.GetTexture());
600 0 : deviceCommandContext->SetUniform(
601 0 : shader->GetBindingSlot(str_transform),
602 0 : baseTransform._11, baseTransform._21, baseTransform._12, baseTransform._22);
603 0 : const CMatrix3D& losTransform = losTexture.GetMinimapTextureMatrix();
604 0 : deviceCommandContext->SetUniform(
605 0 : shader->GetBindingSlot(str_textureTransform),
606 0 : losTransform._11, losTransform._21, losTransform._12, losTransform._22);
607 0 : deviceCommandContext->SetUniform(
608 0 : shader->GetBindingSlot(str_translation),
609 0 : baseTransform._14, baseTransform._24, losTransform._14, losTransform._24);
610 :
611 0 : DrawTexture(deviceCommandContext, m_QuadVertexInputLayout);
612 :
613 0 : deviceCommandContext->EndPass();
614 :
615 0 : if (m_EntitiesDrawn > 0)
616 0 : DrawEntities(deviceCommandContext, entityRadius);
617 :
618 0 : deviceCommandContext->EndFramebufferPass();
619 : }
620 :
621 0 : void CMiniMapTexture::UpdateAndUploadEntities(
622 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
623 : const float entityRadius, const double& currentTime)
624 : {
625 0 : const float invTileMapSize = 1.0f / static_cast<float>(TERRAIN_TILE_SIZE * m_MapSize);
626 :
627 0 : m_Icons.clear();
628 0 : m_IconsCache.clear();
629 :
630 0 : CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap);
631 :
632 0 : VertexArrayIterator<float[2]> attrPos = m_AttributePos.GetIterator<float[2]>();
633 0 : VertexArrayIterator<u8[4]> attrColor = m_AttributeColor.GetIterator<u8[4]>();
634 :
635 0 : m_EntitiesDrawn = 0;
636 0 : MinimapUnitVertex v;
637 0 : std::vector<MinimapUnitVertex> pingingVertices;
638 0 : pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
639 :
640 0 : CmpPtr<ICmpRangeManager> cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
641 0 : ENSURE(cmpRangeManager);
642 :
643 0 : if (currentTime > m_NextBlinkTime)
644 : {
645 0 : m_BlinkState = !m_BlinkState;
646 0 : m_NextBlinkTime = currentTime + m_HalfBlinkDuration;
647 : }
648 :
649 0 : bool iconsEnabled = false;
650 0 : CFG_GET_VAL("gui.session.minimap.icons.enabled", iconsEnabled);
651 0 : float iconsOpacity = 1.0f;
652 0 : CFG_GET_VAL("gui.session.minimap.icons.opacity", iconsOpacity);
653 0 : float iconsSizeScale = 1.0f;
654 0 : CFG_GET_VAL("gui.session.minimap.icons.sizescale", iconsSizeScale);
655 :
656 0 : bool iconsCountOverflow = false;
657 :
658 0 : entity_pos_t posX, posZ;
659 0 : for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
660 : {
661 0 : ICmpMinimap* cmpMinimap = static_cast<ICmpMinimap*>(it->second);
662 0 : if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
663 : {
664 0 : LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, m_Simulation.GetSimContext().GetCurrentDisplayedPlayer());
665 0 : if (vis != LosVisibility::HIDDEN)
666 : {
667 0 : v.a = 255;
668 0 : v.position.X = posX.ToFloat();
669 0 : v.position.Y = posZ.ToFloat();
670 :
671 : // Check minimap pinging to indicate something
672 0 : if (m_BlinkState && cmpMinimap->CheckPing(currentTime, m_PingDuration))
673 : {
674 0 : v.r = 255; // ping color is white
675 0 : v.g = 255;
676 0 : v.b = 255;
677 0 : pingingVertices.push_back(v);
678 : }
679 : else
680 : {
681 0 : AddEntity(v, attrColor, attrPos, entityRadius, m_UseInstancing);
682 0 : ++m_EntitiesDrawn;
683 : }
684 :
685 0 : if (!iconsEnabled || !cmpMinimap->HasIcon())
686 0 : continue;
687 :
688 : const CellIconKey key{
689 0 : cmpMinimap->GetIconPath(), v.r, v.g, v.b};
690 0 : const u16 gridX = Clamp<u16>(
691 0 : (v.position.X * invTileMapSize) * ICON_COMBINING_GRID_SIZE, 0, ICON_COMBINING_GRID_SIZE - 1);
692 0 : const u16 gridY = Clamp<u16>(
693 0 : (v.position.Y * invTileMapSize) * ICON_COMBINING_GRID_SIZE, 0, ICON_COMBINING_GRID_SIZE - 1);
694 : CellIcon icon{
695 0 : gridX, gridY, cmpMinimap->GetIconSize() * iconsSizeScale * 0.5f, v.position};
696 0 : if (m_IconsCache.find(key) == m_IconsCache.end() && m_IconsCache.size() >= MAX_UNIQUE_ICON_COUNT)
697 : {
698 0 : iconsCountOverflow = true;
699 : }
700 : else
701 : {
702 0 : m_IconsCache[key].emplace_back(std::move(icon));
703 : }
704 : }
705 : }
706 : }
707 :
708 : // We need to combine too close icons with the same path, we use a grid for
709 : // that. But to save some allocations and space we store only the current
710 : // row.
711 0 : struct Cell
712 : {
713 : u32 count;
714 : float maxHalfSize;
715 : CVector2D averagePosition;
716 : };
717 0 : std::array<Cell, ICON_COMBINING_GRID_SIZE> gridRow;
718 0 : for (auto& [key, icons] : m_IconsCache)
719 : {
720 0 : CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(
721 0 : CTextureProperties(key.path));
722 0 : const CColor color(key.r / 255.0f, key.g / 255.0f, key.b / 255.0f, iconsOpacity);
723 :
724 0 : std::sort(icons.begin(), icons.end(),
725 0 : [](const CellIcon& lhs, const CellIcon& rhs) -> bool
726 : {
727 0 : if (lhs.gridY != rhs.gridY)
728 0 : return lhs.gridY < rhs.gridY;
729 0 : return lhs.gridX < rhs.gridX;
730 : });
731 :
732 0 : for (auto beginIt = icons.begin(); beginIt != icons.end();)
733 : {
734 0 : auto endIt = std::next(beginIt);
735 0 : while (endIt != icons.end() && beginIt->gridY == endIt->gridY)
736 0 : ++endIt;
737 0 : gridRow.fill({0, 0.0f, {}});
738 0 : for (; beginIt != endIt; ++beginIt)
739 : {
740 0 : Cell& cell = gridRow[beginIt->gridX];
741 0 : const float previousPositionWeight = static_cast<float>(cell.count) / (cell.count + 1);
742 0 : cell.averagePosition = cell.averagePosition * previousPositionWeight + beginIt->worldPosition / static_cast<float>(cell.count + 1);
743 0 : cell.maxHalfSize = std::max(cell.maxHalfSize, beginIt->halfSize);
744 0 : ++cell.count;
745 : }
746 0 : for (const Cell& cell : gridRow)
747 : {
748 0 : if (cell.count == 0)
749 0 : continue;
750 :
751 0 : if (m_Icons.size() < MAX_ICON_COUNT)
752 : {
753 0 : m_Icons.emplace_back(Icon{
754 0 : texture, color, cell.averagePosition, cell.maxHalfSize});
755 : }
756 : else
757 0 : iconsCountOverflow = true;
758 : }
759 : }
760 : }
761 :
762 0 : if (iconsCountOverflow)
763 0 : LOGWARNING("Too many minimap icons to draw.");
764 :
765 : // Add the pinged vertices at the end, so they are drawn on top
766 0 : for (const MinimapUnitVertex& vertex : pingingVertices)
767 : {
768 0 : AddEntity(vertex, attrColor, attrPos, entityRadius, m_UseInstancing);
769 0 : ++m_EntitiesDrawn;
770 : }
771 :
772 0 : ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
773 :
774 0 : if (!m_UseInstancing)
775 : {
776 0 : VertexArrayIterator<u16> index = m_IndexArray.GetIterator();
777 0 : for (size_t entityIndex = 0; entityIndex < m_EntitiesDrawn; ++entityIndex)
778 : {
779 0 : index[entityIndex * 6 + 0] = static_cast<u16>(entityIndex * 4 + 0);
780 0 : index[entityIndex * 6 + 1] = static_cast<u16>(entityIndex * 4 + 1);
781 0 : index[entityIndex * 6 + 2] = static_cast<u16>(entityIndex * 4 + 2);
782 0 : index[entityIndex * 6 + 3] = static_cast<u16>(entityIndex * 4 + 0);
783 0 : index[entityIndex * 6 + 4] = static_cast<u16>(entityIndex * 4 + 2);
784 0 : index[entityIndex * 6 + 5] = static_cast<u16>(entityIndex * 4 + 3);
785 : }
786 :
787 0 : m_IndexArray.Upload();
788 : }
789 :
790 0 : m_VertexArray.Upload();
791 :
792 0 : m_VertexArray.PrepareForRendering();
793 :
794 0 : m_VertexArray.UploadIfNeeded(deviceCommandContext);
795 0 : if (!m_UseInstancing)
796 0 : m_IndexArray.UploadIfNeeded(deviceCommandContext);
797 0 : }
798 :
799 0 : void CMiniMapTexture::DrawEntities(
800 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
801 : const float entityRadius)
802 : {
803 0 : const float invTileMapSize = 1.0f / static_cast<float>(TERRAIN_TILE_SIZE * m_MapSize);
804 :
805 0 : CShaderDefines pointDefines;
806 0 : pointDefines.Add(str_MINIMAP_POINT, str_1);
807 0 : if (m_UseInstancing)
808 0 : pointDefines.Add(str_USE_GPU_INSTANCING, str_1);
809 0 : CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines);
810 0 : deviceCommandContext->SetGraphicsPipelineState(
811 0 : tech->GetGraphicsPipelineState());
812 0 : deviceCommandContext->BeginPass();
813 0 : Renderer::Backend::IShaderProgram* shader = tech->GetShader();
814 :
815 0 : CMatrix3D unitMatrix;
816 0 : unitMatrix.SetIdentity();
817 : // Convert world space coordinates into [0, 2].
818 0 : const float unitScale = invTileMapSize;
819 0 : unitMatrix.Scale(unitScale * 2.0f, unitScale * 2.0f, 1.0f);
820 : // Offset the coordinates to [-1, 1].
821 0 : unitMatrix.Translate(CVector3D(-1.0f, -1.0f, 0.0f));
822 0 : deviceCommandContext->SetUniform(
823 0 : shader->GetBindingSlot(str_transform),
824 0 : unitMatrix._11, unitMatrix._21, unitMatrix._12, unitMatrix._22);
825 0 : deviceCommandContext->SetUniform(
826 0 : shader->GetBindingSlot(str_translation),
827 0 : unitMatrix._14, unitMatrix._24, 0.0f, 0.0f);
828 :
829 : Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
830 0 : scissorRect.x = scissorRect.y = 1;
831 0 : scissorRect.width = scissorRect.height = FINAL_TEXTURE_SIZE - 2;
832 0 : deviceCommandContext->SetScissors(1, &scissorRect);
833 :
834 0 : const uint32_t stride = m_VertexArray.GetStride();
835 0 : const uint32_t firstVertexOffset = m_VertexArray.GetOffset() * stride;
836 :
837 0 : deviceCommandContext->SetVertexInputLayout(m_EntitiesVertexInputLayout);
838 0 : if (m_UseInstancing)
839 : {
840 0 : deviceCommandContext->SetVertexBuffer(
841 0 : 0, m_InstanceVertexArray.GetBuffer(), m_InstanceVertexArray.GetOffset());
842 0 : deviceCommandContext->SetVertexBuffer(
843 0 : 1, m_VertexArray.GetBuffer(), firstVertexOffset);
844 :
845 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_width), entityRadius);
846 :
847 0 : deviceCommandContext->DrawInstanced(0, m_InstanceVertexArray.GetNumberOfVertices(), 0, m_EntitiesDrawn);
848 : }
849 : else
850 : {
851 0 : deviceCommandContext->SetVertexBuffer(
852 0 : 0, m_VertexArray.GetBuffer(), firstVertexOffset);
853 0 : deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer());
854 :
855 0 : deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_EntitiesDrawn * 6, 0);
856 : }
857 :
858 0 : g_Renderer.GetStats().m_DrawCalls++;
859 :
860 0 : deviceCommandContext->SetScissors(0, nullptr);
861 :
862 0 : deviceCommandContext->EndPass();
863 0 : }
864 :
865 : // static
866 0 : float CMiniMapTexture::GetShallowPassageHeight()
867 : {
868 0 : float shallowPassageHeight = 0.0f;
869 0 : CParamNode externalParamNode;
870 0 : CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
871 0 : const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
872 0 : if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
873 0 : shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
874 0 : return shallowPassageHeight;
875 3 : }
|