[Gameplay] Reduce loom cost
[0ad.git] / source / graphics / MiniMapTexture.cpp
blob5120d91889c9b3f1e33b01c69ced9c8b160c9932
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 "MiniMapTexture.h"
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/code_generation.h"
34 #include "lib/hash.h"
35 #include "lib/timer.h"
36 #include "maths/MathUtil.h"
37 #include "maths/Vector2D.h"
38 #include "ps/ConfigDB.h"
39 #include "ps/CStrInternStatic.h"
40 #include "ps/Filesystem.h"
41 #include "ps/Game.h"
42 #include "ps/Profile.h"
43 #include "ps/VideoMode.h"
44 #include "ps/World.h"
45 #include "ps/XML/Xeromyces.h"
46 #include "renderer/backend/IDevice.h"
47 #include "renderer/Renderer.h"
48 #include "renderer/RenderingOptions.h"
49 #include "renderer/SceneRenderer.h"
50 #include "renderer/WaterManager.h"
51 #include "scriptinterface/Object.h"
52 #include "simulation2/Simulation2.h"
53 #include "simulation2/components/ICmpMinimap.h"
54 #include "simulation2/components/ICmpRangeManager.h"
55 #include "simulation2/system/ParamNode.h"
57 #include <algorithm>
58 #include <array>
59 #include <cmath>
61 namespace
64 // Set max drawn entities to 64K / 4 for now, which is more than enough.
65 // 4 is the number of vertices per entity.
66 // TODO: we should be cleverer about drawing them to reduce clutter,
67 // f.e. use instancing.
68 constexpr size_t MAX_ENTITIES_DRAWN = 65536 / 4;
70 constexpr size_t MAX_ICON_COUNT = 256;
71 constexpr size_t MAX_UNIQUE_ICON_COUNT = 64;
72 constexpr size_t ICON_COMBINING_GRID_SIZE = 10;
74 constexpr size_t FINAL_TEXTURE_SIZE = 512;
76 unsigned int ScaleColor(unsigned int color, float x)
78 unsigned int r = unsigned(float(color & 0xff) * x);
79 unsigned int g = unsigned(float((color >> 8) & 0xff) * x);
80 unsigned int b = unsigned(float((color >> 16) & 0xff) * x);
81 return (0xff000000 | b | g << 8 | r << 16);
84 void DrawTexture(
85 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
86 Renderer::Backend::IVertexInputLayout* quadVertexInputLayout)
88 const float quadUVs[] =
90 0.0f, 0.0f,
91 1.0f, 0.0f,
92 1.0f, 1.0f,
94 1.0f, 1.0f,
95 0.0f, 1.0f,
96 0.0f, 0.0f
98 const float quadVertices[] =
100 -1.0f, -1.0f,
101 1.0f, -1.0f,
102 1.0f, 1.0f,
104 1.0f, 1.0f,
105 -1.0f, 1.0f,
106 -1.0f, -1.0f,
109 deviceCommandContext->SetVertexInputLayout(quadVertexInputLayout);
111 deviceCommandContext->SetVertexBufferData(
112 0, quadVertices, std::size(quadVertices) * sizeof(quadVertices[0]));
113 deviceCommandContext->SetVertexBufferData(
114 1, quadUVs, std::size(quadUVs) * sizeof(quadUVs[0]));
116 deviceCommandContext->Draw(0, 6);
119 struct MinimapUnitVertex
121 // This struct is copyable for convenience and because to move is to copy for primitives.
122 u8 r, g, b, a;
123 CVector2D position;
126 // Adds a vertex to the passed VertexArray
127 inline void AddEntity(const MinimapUnitVertex& v,
128 VertexArrayIterator<u8[4]>& attrColor,
129 VertexArrayIterator<float[2]>& attrPos,
130 const float entityRadius,
131 const bool useInstancing)
133 if (useInstancing)
135 (*attrColor)[0] = v.r;
136 (*attrColor)[1] = v.g;
137 (*attrColor)[2] = v.b;
138 (*attrColor)[3] = v.a;
139 ++attrColor;
141 (*attrPos)[0] = v.position.X;
142 (*attrPos)[1] = v.position.Y;
143 ++attrPos;
145 return;
148 const CVector2D offsets[4] =
150 {-entityRadius, 0.0f},
151 {0.0f, -entityRadius},
152 {entityRadius, 0.0f},
153 {0.0f, entityRadius}
156 for (const CVector2D& offset : offsets)
158 (*attrColor)[0] = v.r;
159 (*attrColor)[1] = v.g;
160 (*attrColor)[2] = v.b;
161 (*attrColor)[3] = v.a;
162 ++attrColor;
164 (*attrPos)[0] = v.position.X + offset.X;
165 (*attrPos)[1] = v.position.Y + offset.Y;
166 ++attrPos;
170 } // anonymous namespace
172 size_t CMiniMapTexture::CellIconKeyHash::operator()(
173 const CellIconKey& key) const
175 size_t seed = 0;
176 hash_combine(seed, key.path);
177 hash_combine(seed, key.r);
178 hash_combine(seed, key.g);
179 hash_combine(seed, key.b);
180 return seed;
183 bool CMiniMapTexture::CellIconKeyEqual::operator()(
184 const CellIconKey& lhs, const CellIconKey& rhs) const
186 return
187 lhs.path == rhs.path &&
188 lhs.r == rhs.r &&
189 lhs.g == rhs.g &&
190 lhs.b == rhs.b;
193 CMiniMapTexture::CMiniMapTexture(Renderer::Backend::IDevice* device, CSimulation2& simulation)
194 : m_Simulation(simulation), m_IndexArray(false),
195 m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true),
196 m_InstanceVertexArray(Renderer::Backend::IBuffer::Type::VERTEX, false)
198 // Register Relax NG validator.
199 CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
201 m_ShallowPassageHeight = GetShallowPassageHeight();
203 double blinkDuration = 1.0;
204 // Tests won't have config initialised
205 if (CConfigDB::IsInitialised())
207 CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
208 CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration);
210 m_HalfBlinkDuration = blinkDuration / 2.0;
212 m_AttributePos.format = Renderer::Backend::Format::R32G32_SFLOAT;
213 m_VertexArray.AddAttribute(&m_AttributePos);
215 m_AttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM;
216 m_VertexArray.AddAttribute(&m_AttributeColor);
218 m_VertexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 4);
219 m_VertexArray.Layout();
221 m_IndexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 6);
222 m_IndexArray.Layout();
223 VertexArrayIterator<u16> index = m_IndexArray.GetIterator();
224 for (size_t i = 0; i < m_IndexArray.GetNumberOfVertices(); ++i)
225 *index++ = 0;
226 m_IndexArray.Upload();
228 VertexArrayIterator<float[2]> attrPos = m_AttributePos.GetIterator<float[2]>();
229 VertexArrayIterator<u8[4]> attrColor = m_AttributeColor.GetIterator<u8[4]>();
230 for (size_t i = 0; i < m_VertexArray.GetNumberOfVertices(); ++i)
232 (*attrColor)[0] = 0;
233 (*attrColor)[1] = 0;
234 (*attrColor)[2] = 0;
235 (*attrColor)[3] = 0;
236 ++attrColor;
238 (*attrPos)[0] = -10000.0f;
239 (*attrPos)[1] = -10000.0f;
241 ++attrPos;
243 m_VertexArray.Upload();
245 const std::array<Renderer::Backend::SVertexAttributeFormat, 2> attributes{{
246 {Renderer::Backend::VertexAttributeStream::POSITION,
247 Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
248 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
249 {Renderer::Backend::VertexAttributeStream::UV0,
250 Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
251 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1}
253 m_QuadVertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
255 m_Flipped = device->GetBackend() == Renderer::Backend::Backend::VULKAN;
257 const uint32_t stride = m_VertexArray.GetStride();
258 if (device->GetCapabilities().instancing)
260 m_UseInstancing = true;
262 const size_t numberOfCircleSegments = 8;
264 m_InstanceAttributePosition.format = Renderer::Backend::Format::R32G32_SFLOAT;
265 m_InstanceVertexArray.AddAttribute(&m_InstanceAttributePosition);
267 m_InstanceVertexArray.SetNumberOfVertices(numberOfCircleSegments * 3);
268 m_InstanceVertexArray.Layout();
270 VertexArrayIterator<float[2]> attributePosition =
271 m_InstanceAttributePosition.GetIterator<float[2]>();
272 for (size_t segment = 0; segment < numberOfCircleSegments; ++segment)
274 const float currentAngle = static_cast<float>(segment) / numberOfCircleSegments * 2.0f * M_PI;
275 const float nextAngle = static_cast<float>(segment + 1) / numberOfCircleSegments * 2.0f * M_PI;
277 (*attributePosition)[0] = 0.0f;
278 (*attributePosition)[1] = 0.0f;
279 ++attributePosition;
281 (*attributePosition)[0] = std::cos(currentAngle);
282 (*attributePosition)[1] = std::sin(currentAngle);
283 ++attributePosition;
285 (*attributePosition)[0] = std::cos(nextAngle);
286 (*attributePosition)[1] = std::sin(nextAngle);
287 ++attributePosition;
290 m_InstanceVertexArray.Upload();
291 m_InstanceVertexArray.FreeBackingStore();
293 const std::array<Renderer::Backend::SVertexAttributeFormat, 3> attributes{{
294 {Renderer::Backend::VertexAttributeStream::POSITION,
295 m_InstanceAttributePosition.format, m_InstanceAttributePosition.offset,
296 m_InstanceVertexArray.GetStride(),
297 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
298 {Renderer::Backend::VertexAttributeStream::UV1,
299 m_AttributePos.format, m_AttributePos.offset, stride,
300 Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1},
301 {Renderer::Backend::VertexAttributeStream::COLOR,
302 m_AttributeColor.format, m_AttributeColor.offset, stride,
303 Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1},
305 m_EntitiesVertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
307 else
309 const std::array<Renderer::Backend::SVertexAttributeFormat, 2> entitiesAttributes{{
310 {Renderer::Backend::VertexAttributeStream::POSITION,
311 m_AttributePos.format, m_AttributePos.offset, stride,
312 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
313 {Renderer::Backend::VertexAttributeStream::COLOR,
314 m_AttributeColor.format, m_AttributeColor.offset, stride,
315 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}
317 m_EntitiesVertexInputLayout = g_Renderer.GetVertexInputLayout(entitiesAttributes);
320 CShaderDefines baseDefines;
321 baseDefines.Add(str_MINIMAP_BASE, str_1);
323 m_TerritoryTechnique = g_Renderer.GetShaderManager().LoadEffect(
324 str_minimap, baseDefines,
325 [](Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc)
327 pipelineStateDesc.blendState.enabled = true;
328 pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor =
329 Renderer::Backend::BlendFactor::SRC_ALPHA;
330 pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor =
331 Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
332 pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp =
333 Renderer::Backend::BlendOp::ADD;
334 pipelineStateDesc.blendState.colorWriteMask =
335 Renderer::Backend::ColorWriteMask::RED |
336 Renderer::Backend::ColorWriteMask::GREEN |
337 Renderer::Backend::ColorWriteMask::BLUE;
341 CMiniMapTexture::~CMiniMapTexture()
343 DestroyTextures();
346 void CMiniMapTexture::Update(const float UNUSED(deltaRealTime))
348 if (m_WaterHeight != g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight)
350 m_TerrainTextureDirty = true;
351 m_FinalTextureDirty = true;
355 void CMiniMapTexture::Render(
356 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
357 CLOSTexture& losTexture, CTerritoryTexture& territoryTexture)
359 const CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
360 if (!terrain)
361 return;
363 if (!m_TerrainTexture)
364 CreateTextures(deviceCommandContext, terrain);
366 if (m_TerrainTextureDirty)
367 RebuildTerrainTexture(deviceCommandContext, terrain);
369 RenderFinalTexture(deviceCommandContext, losTexture, territoryTexture);
372 void CMiniMapTexture::CreateTextures(
373 Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTerrain* terrain)
375 DestroyTextures();
377 m_MapSize = terrain->GetVerticesPerSide();
378 const size_t textureSize = round_up_to_pow2(static_cast<size_t>(m_MapSize));
380 const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
381 Renderer::Backend::Sampler::MakeDefaultSampler(
382 Renderer::Backend::Sampler::Filter::LINEAR,
383 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
385 Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice();
387 // Create terrain texture
388 m_TerrainTexture = backendDevice->CreateTexture2D("MiniMapTerrainTexture",
389 Renderer::Backend::ITexture::Usage::TRANSFER_DST |
390 Renderer::Backend::ITexture::Usage::SAMPLED,
391 Renderer::Backend::Format::R8G8B8A8_UNORM, textureSize, textureSize, defaultSamplerDesc);
393 // Initialise texture with solid black, for the areas we don't
394 // overwrite with uploading later.
395 std::unique_ptr<u32[]> texData = std::make_unique<u32[]>(textureSize * textureSize);
396 for (size_t i = 0; i < textureSize * textureSize; ++i)
397 texData[i] = 0xFF000000;
398 deviceCommandContext->UploadTexture(
399 m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
400 texData.get(), textureSize * textureSize * 4);
401 texData.reset();
403 m_TerrainData = std::make_unique<u32[]>((m_MapSize - 1) * (m_MapSize - 1));
405 m_FinalTexture = g_Renderer.GetTextureManager().WrapBackendTexture(
406 backendDevice->CreateTexture2D("MiniMapFinalTexture",
407 Renderer::Backend::ITexture::Usage::SAMPLED |
408 Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
409 Renderer::Backend::Format::R8G8B8A8_UNORM,
410 FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc));
412 Renderer::Backend::SColorAttachment colorAttachment{};
413 colorAttachment.texture = m_FinalTexture->GetBackendTexture();
414 colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
415 colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
416 colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
417 m_FinalTextureFramebuffer = backendDevice->CreateFramebuffer(
418 "MiniMapFinalFramebuffer", &colorAttachment, nullptr);
419 ENSURE(m_FinalTextureFramebuffer);
422 void CMiniMapTexture::DestroyTextures()
424 m_TerrainTexture.reset();
425 m_FinalTexture.reset();
426 m_TerrainData.reset();
429 void CMiniMapTexture::RebuildTerrainTexture(
430 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
431 const CTerrain* terrain)
433 const u32 x = 0;
434 const u32 y = 0;
435 const u32 width = m_MapSize - 1;
436 const u32 height = m_MapSize - 1;
438 m_WaterHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight;
439 m_TerrainTextureDirty = false;
441 for (u32 j = 0; j < height; ++j)
443 u32* dataPtr = m_TerrainData.get() + ((y + j) * width) + x;
444 for (u32 i = 0; i < width; ++i)
446 const float avgHeight = ( terrain->GetVertexGroundLevel((int)i, (int)j)
447 + terrain->GetVertexGroundLevel((int)i+1, (int)j)
448 + terrain->GetVertexGroundLevel((int)i, (int)j+1)
449 + terrain->GetVertexGroundLevel((int)i+1, (int)j+1)
450 ) / 4.0f;
452 if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight)
454 // shallow water
455 *dataPtr++ = 0xffc09870;
457 else if (avgHeight < m_WaterHeight)
459 // Set water as constant color for consistency on different maps
460 *dataPtr++ = 0xffa07850;
462 else
464 int hmap = ((int)terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
465 int val = (hmap / 3) + 170;
467 u32 color = 0xFFFFFFFF;
469 CMiniPatch* mp = terrain->GetTile(x + i, y + j);
470 if (mp)
472 CTerrainTextureEntry* tex = mp->GetTextureEntry();
473 if (tex)
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 if (!tex->GetTexture()->TryLoad())
478 m_TerrainTextureDirty = true;
480 color = tex->GetBaseColor();
484 *dataPtr++ = ScaleColor(color, float(val) / 255.0f);
489 // Upload the texture
490 deviceCommandContext->UploadTextureRegion(
491 m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
492 m_TerrainData.get(), width * height * 4, 0, 0, width, height);
495 void CMiniMapTexture::RenderFinalTexture(
496 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
497 CLOSTexture& losTexture, CTerritoryTexture& territoryTexture)
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 const double currentTime = timer_Time();
504 const bool doUpdate = (currentTime - m_LastFinalTextureUpdate > 0.5) || m_FinalTextureDirty;
505 if (!doUpdate)
506 return;
507 m_LastFinalTextureUpdate = currentTime;
508 m_FinalTextureDirty = false;
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 const float entityRadius = static_cast<float>(m_MapSize) / 128.0f * (m_UseInstancing ? 5.0 : 6.0f);
516 UpdateAndUploadEntities(deviceCommandContext, entityRadius, currentTime);
518 PROFILE3("Render minimap texture");
519 GPU_SCOPED_LABEL(deviceCommandContext, "Render minimap texture");
520 deviceCommandContext->BeginFramebufferPass(m_FinalTextureFramebuffer.get());
522 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
523 viewportRect.width = FINAL_TEXTURE_SIZE;
524 viewportRect.height = FINAL_TEXTURE_SIZE;
525 deviceCommandContext->SetViewports(1, &viewportRect);
527 const float texCoordMax = m_TerrainTexture ? static_cast<float>(m_MapSize - 1) / m_TerrainTexture->GetWidth() : 1.0f;
529 Renderer::Backend::IShaderProgram* shader = nullptr;
530 CShaderTechniquePtr tech;
532 CShaderDefines baseDefines;
533 baseDefines.Add(str_MINIMAP_BASE, str_1);
535 tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines);
536 deviceCommandContext->SetGraphicsPipelineState(
537 tech->GetGraphicsPipelineState());
538 deviceCommandContext->BeginPass();
539 shader = tech->GetShader();
541 if (m_TerrainTexture)
543 deviceCommandContext->SetTexture(
544 shader->GetBindingSlot(str_baseTex), m_TerrainTexture.get());
547 CMatrix3D baseTransform;
548 baseTransform.SetIdentity();
549 CMatrix3D baseTextureTransform;
550 baseTextureTransform.SetIdentity();
552 CMatrix3D terrainTransform;
553 terrainTransform.SetIdentity();
554 terrainTransform.Scale(texCoordMax, texCoordMax, 1.0f);
556 deviceCommandContext->SetUniform(
557 shader->GetBindingSlot(str_transform),
558 baseTransform._11, baseTransform._21, baseTransform._12, baseTransform._22);
559 deviceCommandContext->SetUniform(
560 shader->GetBindingSlot(str_textureTransform),
561 terrainTransform._11, terrainTransform._21, terrainTransform._12, terrainTransform._22);
562 deviceCommandContext->SetUniform(
563 shader->GetBindingSlot(str_translation),
564 baseTransform._14, baseTransform._24, terrainTransform._14, terrainTransform._24);
566 if (m_TerrainTexture)
567 DrawTexture(deviceCommandContext, m_QuadVertexInputLayout);
568 deviceCommandContext->EndPass();
570 deviceCommandContext->SetGraphicsPipelineState(
571 m_TerritoryTechnique->GetGraphicsPipelineState());
572 shader = m_TerritoryTechnique->GetShader();
573 deviceCommandContext->BeginPass();
575 // Draw territory boundaries
576 deviceCommandContext->SetTexture(
577 shader->GetBindingSlot(str_baseTex), territoryTexture.GetTexture());
578 deviceCommandContext->SetUniform(
579 shader->GetBindingSlot(str_transform),
580 baseTransform._11, baseTransform._21, baseTransform._12, baseTransform._22);
581 const CMatrix3D& territoryTransform = territoryTexture.GetMinimapTextureMatrix();
582 deviceCommandContext->SetUniform(
583 shader->GetBindingSlot(str_textureTransform),
584 territoryTransform._11, territoryTransform._21, territoryTransform._12, territoryTransform._22);
585 deviceCommandContext->SetUniform(
586 shader->GetBindingSlot(str_translation),
587 baseTransform._14, baseTransform._24, territoryTransform._14, territoryTransform._24);
589 DrawTexture(deviceCommandContext, m_QuadVertexInputLayout);
590 deviceCommandContext->EndPass();
592 tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap_los, CShaderDefines());
593 deviceCommandContext->SetGraphicsPipelineState(
594 tech->GetGraphicsPipelineState());
595 deviceCommandContext->BeginPass();
596 shader = tech->GetShader();
598 deviceCommandContext->SetTexture(
599 shader->GetBindingSlot(str_baseTex), losTexture.GetTexture());
600 deviceCommandContext->SetUniform(
601 shader->GetBindingSlot(str_transform),
602 baseTransform._11, baseTransform._21, baseTransform._12, baseTransform._22);
603 const CMatrix3D& losTransform = losTexture.GetMinimapTextureMatrix();
604 deviceCommandContext->SetUniform(
605 shader->GetBindingSlot(str_textureTransform),
606 losTransform._11, losTransform._21, losTransform._12, losTransform._22);
607 deviceCommandContext->SetUniform(
608 shader->GetBindingSlot(str_translation),
609 baseTransform._14, baseTransform._24, losTransform._14, losTransform._24);
611 DrawTexture(deviceCommandContext, m_QuadVertexInputLayout);
613 deviceCommandContext->EndPass();
615 if (m_EntitiesDrawn > 0)
616 DrawEntities(deviceCommandContext, entityRadius);
618 deviceCommandContext->EndFramebufferPass();
621 void CMiniMapTexture::UpdateAndUploadEntities(
622 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
623 const float entityRadius, const double& currentTime)
625 const float invTileMapSize = 1.0f / static_cast<float>(TERRAIN_TILE_SIZE * m_MapSize);
627 m_Icons.clear();
628 m_IconsCache.clear();
630 CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap);
632 VertexArrayIterator<float[2]> attrPos = m_AttributePos.GetIterator<float[2]>();
633 VertexArrayIterator<u8[4]> attrColor = m_AttributeColor.GetIterator<u8[4]>();
635 m_EntitiesDrawn = 0;
636 MinimapUnitVertex v;
637 std::vector<MinimapUnitVertex> pingingVertices;
638 pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
640 CmpPtr<ICmpRangeManager> cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
641 ENSURE(cmpRangeManager);
643 if (currentTime > m_NextBlinkTime)
645 m_BlinkState = !m_BlinkState;
646 m_NextBlinkTime = currentTime + m_HalfBlinkDuration;
649 bool iconsEnabled = false;
650 CFG_GET_VAL("gui.session.minimap.icons.enabled", iconsEnabled);
651 float iconsOpacity = 1.0f;
652 CFG_GET_VAL("gui.session.minimap.icons.opacity", iconsOpacity);
653 float iconsSizeScale = 1.0f;
654 CFG_GET_VAL("gui.session.minimap.icons.sizescale", iconsSizeScale);
656 bool iconsCountOverflow = false;
658 entity_pos_t posX, posZ;
659 for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
661 ICmpMinimap* cmpMinimap = static_cast<ICmpMinimap*>(it->second);
662 if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
664 LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, m_Simulation.GetSimContext().GetCurrentDisplayedPlayer());
665 if (vis != LosVisibility::HIDDEN)
667 v.a = 255;
668 v.position.X = posX.ToFloat();
669 v.position.Y = posZ.ToFloat();
671 // Check minimap pinging to indicate something
672 if (m_BlinkState && cmpMinimap->CheckPing(currentTime, m_PingDuration))
674 v.r = 255; // ping color is white
675 v.g = 255;
676 v.b = 255;
677 pingingVertices.push_back(v);
679 else if (m_EntitiesDrawn < MAX_ENTITIES_DRAWN)
681 AddEntity(v, attrColor, attrPos, entityRadius, m_UseInstancing);
682 ++m_EntitiesDrawn;
685 if (!iconsEnabled || !cmpMinimap->HasIcon())
686 continue;
688 const CellIconKey key{
689 cmpMinimap->GetIconPath(), v.r, v.g, v.b};
690 const u16 gridX = Clamp<u16>(
691 (v.position.X * invTileMapSize) * ICON_COMBINING_GRID_SIZE, 0, ICON_COMBINING_GRID_SIZE - 1);
692 const u16 gridY = Clamp<u16>(
693 (v.position.Y * invTileMapSize) * ICON_COMBINING_GRID_SIZE, 0, ICON_COMBINING_GRID_SIZE - 1);
694 CellIcon icon{
695 gridX, gridY, cmpMinimap->GetIconSize() * iconsSizeScale * 0.5f, v.position};
696 if (m_IconsCache.find(key) == m_IconsCache.end() && m_IconsCache.size() >= MAX_UNIQUE_ICON_COUNT)
698 iconsCountOverflow = true;
700 else
702 m_IconsCache[key].emplace_back(std::move(icon));
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 struct Cell
713 u32 count;
714 float maxHalfSize;
715 CVector2D averagePosition;
717 std::array<Cell, ICON_COMBINING_GRID_SIZE> gridRow;
718 for (auto& [key, icons] : m_IconsCache)
720 CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(
721 CTextureProperties(key.path));
722 const CColor color(key.r / 255.0f, key.g / 255.0f, key.b / 255.0f, iconsOpacity);
724 std::sort(icons.begin(), icons.end(),
725 [](const CellIcon& lhs, const CellIcon& rhs) -> bool
727 if (lhs.gridY != rhs.gridY)
728 return lhs.gridY < rhs.gridY;
729 return lhs.gridX < rhs.gridX;
732 for (auto beginIt = icons.begin(); beginIt != icons.end();)
734 auto endIt = std::next(beginIt);
735 while (endIt != icons.end() && beginIt->gridY == endIt->gridY)
736 ++endIt;
737 gridRow.fill({0, 0.0f, {}});
738 for (; beginIt != endIt; ++beginIt)
740 Cell& cell = gridRow[beginIt->gridX];
741 const float previousPositionWeight = static_cast<float>(cell.count) / (cell.count + 1);
742 cell.averagePosition = cell.averagePosition * previousPositionWeight + beginIt->worldPosition / static_cast<float>(cell.count + 1);
743 cell.maxHalfSize = std::max(cell.maxHalfSize, beginIt->halfSize);
744 ++cell.count;
746 for (const Cell& cell : gridRow)
748 if (cell.count == 0)
749 continue;
751 if (m_Icons.size() < MAX_ICON_COUNT)
753 m_Icons.emplace_back(Icon{
754 texture, color, cell.averagePosition, cell.maxHalfSize});
756 else
757 iconsCountOverflow = true;
762 if (iconsCountOverflow)
763 LOGWARNING("Too many minimap icons to draw.");
765 // Add the pinged vertices at the end, so they are drawn on top
766 for (const MinimapUnitVertex& vertex : pingingVertices)
768 AddEntity(vertex, attrColor, attrPos, entityRadius, m_UseInstancing);
769 ++m_EntitiesDrawn;
770 if (m_EntitiesDrawn == MAX_ENTITIES_DRAWN)
771 break;
774 if (m_EntitiesDrawn == MAX_ENTITIES_DRAWN)
775 ONCE(LOGERROR("Too many entities, some of them will be hidden on the minimap."));
777 if (!m_UseInstancing)
779 VertexArrayIterator<u16> index = m_IndexArray.GetIterator();
780 for (size_t entityIndex = 0; entityIndex < m_EntitiesDrawn; ++entityIndex)
782 index[entityIndex * 6 + 0] = static_cast<u16>(entityIndex * 4 + 0);
783 index[entityIndex * 6 + 1] = static_cast<u16>(entityIndex * 4 + 1);
784 index[entityIndex * 6 + 2] = static_cast<u16>(entityIndex * 4 + 2);
785 index[entityIndex * 6 + 3] = static_cast<u16>(entityIndex * 4 + 0);
786 index[entityIndex * 6 + 4] = static_cast<u16>(entityIndex * 4 + 2);
787 index[entityIndex * 6 + 5] = static_cast<u16>(entityIndex * 4 + 3);
790 m_IndexArray.Upload();
793 m_VertexArray.Upload();
795 m_VertexArray.PrepareForRendering();
797 m_VertexArray.UploadIfNeeded(deviceCommandContext);
798 if (!m_UseInstancing)
799 m_IndexArray.UploadIfNeeded(deviceCommandContext);
802 void CMiniMapTexture::DrawEntities(
803 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
804 const float entityRadius)
806 const float invTileMapSize = 1.0f / static_cast<float>(TERRAIN_TILE_SIZE * m_MapSize);
808 CShaderDefines pointDefines;
809 pointDefines.Add(str_MINIMAP_POINT, str_1);
810 if (m_UseInstancing)
811 pointDefines.Add(str_USE_GPU_INSTANCING, str_1);
812 CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines);
813 deviceCommandContext->SetGraphicsPipelineState(
814 tech->GetGraphicsPipelineState());
815 deviceCommandContext->BeginPass();
816 Renderer::Backend::IShaderProgram* shader = tech->GetShader();
818 CMatrix3D unitMatrix;
819 unitMatrix.SetIdentity();
820 // Convert world space coordinates into [0, 2].
821 const float unitScale = invTileMapSize;
822 unitMatrix.Scale(unitScale * 2.0f, unitScale * 2.0f, 1.0f);
823 // Offset the coordinates to [-1, 1].
824 unitMatrix.Translate(CVector3D(-1.0f, -1.0f, 0.0f));
825 deviceCommandContext->SetUniform(
826 shader->GetBindingSlot(str_transform),
827 unitMatrix._11, unitMatrix._21, unitMatrix._12, unitMatrix._22);
828 deviceCommandContext->SetUniform(
829 shader->GetBindingSlot(str_translation),
830 unitMatrix._14, unitMatrix._24, 0.0f, 0.0f);
832 Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
833 scissorRect.x = scissorRect.y = 1;
834 scissorRect.width = scissorRect.height = FINAL_TEXTURE_SIZE - 2;
835 deviceCommandContext->SetScissors(1, &scissorRect);
837 const uint32_t stride = m_VertexArray.GetStride();
838 const uint32_t firstVertexOffset = m_VertexArray.GetOffset() * stride;
840 deviceCommandContext->SetVertexInputLayout(m_EntitiesVertexInputLayout);
841 if (m_UseInstancing)
843 deviceCommandContext->SetVertexBuffer(
844 0, m_InstanceVertexArray.GetBuffer(), m_InstanceVertexArray.GetOffset());
845 deviceCommandContext->SetVertexBuffer(
846 1, m_VertexArray.GetBuffer(), firstVertexOffset);
848 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_width), entityRadius);
850 deviceCommandContext->DrawInstanced(0, m_InstanceVertexArray.GetNumberOfVertices(), 0, m_EntitiesDrawn);
852 else
854 deviceCommandContext->SetVertexBuffer(
855 0, m_VertexArray.GetBuffer(), firstVertexOffset);
856 deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer());
858 deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_EntitiesDrawn * 6, 0);
861 g_Renderer.GetStats().m_DrawCalls++;
863 deviceCommandContext->SetScissors(0, nullptr);
865 deviceCommandContext->EndPass();
868 // static
869 float CMiniMapTexture::GetShallowPassageHeight()
871 float shallowPassageHeight = 0.0f;
872 CParamNode externalParamNode;
873 CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
874 const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
875 if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
876 shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
877 return shallowPassageHeight;