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"
33 #include "lib/code_generation.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"
42 #include "ps/Profile.h"
43 #include "ps/VideoMode.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"
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);
85 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
,
86 Renderer::Backend::IVertexInputLayout
* quadVertexInputLayout
)
88 const float quadUVs
[] =
98 const float quadVertices
[] =
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.
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
)
135 (*attrColor
)[0] = v
.r
;
136 (*attrColor
)[1] = v
.g
;
137 (*attrColor
)[2] = v
.b
;
138 (*attrColor
)[3] = v
.a
;
141 (*attrPos
)[0] = v
.position
.X
;
142 (*attrPos
)[1] = v
.position
.Y
;
148 const CVector2D offsets
[4] =
150 {-entityRadius
, 0.0f
},
151 {0.0f
, -entityRadius
},
152 {entityRadius
, 0.0f
},
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
;
164 (*attrPos
)[0] = v
.position
.X
+ offset
.X
;
165 (*attrPos
)[1] = v
.position
.Y
+ offset
.Y
;
170 } // anonymous namespace
172 size_t CMiniMapTexture::CellIconKeyHash::operator()(
173 const CellIconKey
& key
) const
176 hash_combine(seed
, key
.path
);
177 hash_combine(seed
, key
.r
);
178 hash_combine(seed
, key
.g
);
179 hash_combine(seed
, key
.b
);
183 bool CMiniMapTexture::CellIconKeyEqual::operator()(
184 const CellIconKey
& lhs
, const CellIconKey
& rhs
) const
187 lhs
.path
== rhs
.path
&&
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
)
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
)
238 (*attrPos
)[0] = -10000.0f
;
239 (*attrPos
)[1] = -10000.0f
;
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
;
281 (*attributePosition
)[0] = std::cos(currentAngle
);
282 (*attributePosition
)[1] = std::sin(currentAngle
);
285 (*attributePosition
)[0] = std::cos(nextAngle
);
286 (*attributePosition
)[1] = std::sin(nextAngle
);
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
);
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()
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();
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
)
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);
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
)
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)
452 if (avgHeight
< m_WaterHeight
&& avgHeight
> m_WaterHeight
- m_ShallowPassageHeight
)
455 *dataPtr
++ = 0xffc09870;
457 else if (avgHeight
< m_WaterHeight
)
459 // Set water as constant color for consistency on different maps
460 *dataPtr
++ = 0xffa07850;
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
);
472 CTerrainTextureEntry
* tex
= mp
->GetTextureEntry();
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
;
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
);
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]>();
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
)
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
677 pingingVertices
.push_back(v
);
679 else if (m_EntitiesDrawn
< MAX_ENTITIES_DRAWN
)
681 AddEntity(v
, attrColor
, attrPos
, entityRadius
, m_UseInstancing
);
685 if (!iconsEnabled
|| !cmpMinimap
->HasIcon())
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);
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;
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
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
)
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
);
746 for (const Cell
& cell
: gridRow
)
751 if (m_Icons
.size() < MAX_ICON_COUNT
)
753 m_Icons
.emplace_back(Icon
{
754 texture
, color
, cell
.averagePosition
, cell
.maxHalfSize
});
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
);
770 if (m_EntitiesDrawn
== MAX_ENTITIES_DRAWN
)
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
);
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
);
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
);
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();
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
;