1 /* Copyright (C) 2022 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"
32 #include "lib/timer.h"
33 #include "ps/ConfigDB.h"
34 #include "ps/CStrInternStatic.h"
35 #include "ps/Filesystem.h"
38 #include "ps/XML/Xeromyces.h"
39 #include "renderer/Renderer.h"
40 #include "renderer/RenderingOptions.h"
41 #include "renderer/SceneRenderer.h"
42 #include "renderer/WaterManager.h"
43 #include "scriptinterface/Object.h"
44 #include "simulation2/Simulation2.h"
45 #include "simulation2/components/ICmpMinimap.h"
46 #include "simulation2/components/ICmpRangeManager.h"
47 #include "simulation2/system/ParamNode.h"
52 // Set max drawn entities to UINT16_MAX for now, which is more than enough
53 // TODO: we should be cleverer about drawing them to reduce clutter
54 const u16 MAX_ENTITIES_DRAWN
= 65535;
56 const size_t FINAL_TEXTURE_SIZE
= 512;
58 unsigned int ScaleColor(unsigned int color
, float x
)
60 unsigned int r
= unsigned(float(color
& 0xff) * x
);
61 unsigned int g
= unsigned(float((color
>> 8) & 0xff) * x
);
62 unsigned int b
= unsigned(float((color
>> 16) & 0xff) * x
);
63 return (0xff000000 | b
| g
<< 8 | r
<< 16);
66 void DrawTexture(CShaderProgramPtr shader
)
68 const float quadUVs
[] =
78 const float quadVertices
[] =
89 shader
->TexCoordPointer(GL_TEXTURE0
, 2, GL_FLOAT
, 0, quadUVs
);
90 shader
->VertexPointer(3, GL_FLOAT
, 0, quadVertices
);
91 shader
->AssertPointersBound();
93 glDrawArrays(GL_TRIANGLES
, 0, 6);
96 struct MinimapUnitVertex
98 // This struct is copyable for convenience and because to move is to copy for primitives.
103 // Adds a vertex to the passed VertexArray
104 static void inline addVertex(const MinimapUnitVertex
& v
,
105 VertexArrayIterator
<u8
[4]>& attrColor
,
106 VertexArrayIterator
<float[2]>& attrPos
)
108 (*attrColor
)[0] = v
.r
;
109 (*attrColor
)[1] = v
.g
;
110 (*attrColor
)[2] = v
.b
;
111 (*attrColor
)[3] = v
.a
;
120 } // anonymous namespace
122 CMiniMapTexture::CMiniMapTexture(CSimulation2
& simulation
)
123 : m_Simulation(simulation
), m_IndexArray(GL_STATIC_DRAW
), m_VertexArray(GL_DYNAMIC_DRAW
)
125 // Register Relax NG validator.
126 CXeromyces::AddValidator(g_VFS
, "pathfinder", "simulation/data/pathfinder.rng");
128 m_ShallowPassageHeight
= GetShallowPassageHeight();
130 double blinkDuration
= 1.0;
131 // Tests won't have config initialised
132 if (CConfigDB::IsInitialised())
134 CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration
);
135 CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration
);
137 m_HalfBlinkDuration
= blinkDuration
/ 2.0;
139 m_AttributePos
.type
= GL_FLOAT
;
140 m_AttributePos
.elems
= 2;
141 m_VertexArray
.AddAttribute(&m_AttributePos
);
143 m_AttributeColor
.type
= GL_UNSIGNED_BYTE
;
144 m_AttributeColor
.elems
= 4;
145 m_VertexArray
.AddAttribute(&m_AttributeColor
);
147 m_VertexArray
.SetNumVertices(MAX_ENTITIES_DRAWN
);
148 m_VertexArray
.Layout();
150 m_IndexArray
.SetNumVertices(MAX_ENTITIES_DRAWN
);
151 m_IndexArray
.Layout();
152 VertexArrayIterator
<u16
> index
= m_IndexArray
.GetIterator();
153 for (u16 i
= 0; i
< MAX_ENTITIES_DRAWN
; ++i
)
155 m_IndexArray
.Upload();
156 m_IndexArray
.FreeBackingStore();
158 VertexArrayIterator
<float[2]> attrPos
= m_AttributePos
.GetIterator
<float[2]>();
159 VertexArrayIterator
<u8
[4]> attrColor
= m_AttributeColor
.GetIterator
<u8
[4]>();
160 for (u16 i
= 0; i
< MAX_ENTITIES_DRAWN
; ++i
)
168 (*attrPos
)[0] = -10000.0f
;
169 (*attrPos
)[1] = -10000.0f
;
174 m_VertexArray
.Upload();
177 CMiniMapTexture::~CMiniMapTexture()
182 void CMiniMapTexture::Update(const float UNUSED(deltaRealTime
))
184 if (m_WaterHeight
!= g_Renderer
.GetSceneRenderer().GetWaterManager().m_WaterHeight
)
186 m_TerrainTextureDirty
= true;
187 m_FinalTextureDirty
= true;
191 void CMiniMapTexture::Render(Renderer::Backend::GL::CDeviceCommandContext
* deviceCommandContext
)
193 const CTerrain
* terrain
= g_Game
->GetWorld()->GetTerrain();
197 if (!m_TerrainTexture
)
198 CreateTextures(deviceCommandContext
, terrain
);
200 if (m_TerrainTextureDirty
)
201 RebuildTerrainTexture(deviceCommandContext
, terrain
);
203 RenderFinalTexture();
206 void CMiniMapTexture::CreateTextures(
207 Renderer::Backend::GL::CDeviceCommandContext
* deviceCommandContext
, const CTerrain
* terrain
)
211 m_MapSize
= terrain
->GetVerticesPerSide();
212 const size_t textureSize
= round_up_to_pow2(static_cast<size_t>(m_MapSize
));
214 const Renderer::Backend::Sampler::Desc defaultSamplerDesc
=
215 Renderer::Backend::Sampler::MakeDefaultSampler(
216 Renderer::Backend::Sampler::Filter::LINEAR
,
217 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE
);
219 // Create terrain texture
220 m_TerrainTexture
= Renderer::Backend::GL::CTexture::Create2D(
221 Renderer::Backend::Format::R8G8B8A8
, textureSize
, textureSize
, defaultSamplerDesc
);
223 // Initialise texture with solid black, for the areas we don't
224 // overwrite with uploading later.
225 std::unique_ptr
<u32
[]> texData
= std::make_unique
<u32
[]>(textureSize
* textureSize
);
226 for (size_t i
= 0; i
< textureSize
* textureSize
; ++i
)
227 texData
[i
] = 0xFF000000;
228 deviceCommandContext
->UploadTexture(
229 m_TerrainTexture
.get(), Renderer::Backend::Format::R8G8B8A8
,
230 texData
.get(), textureSize
* textureSize
* 4);
233 m_TerrainData
= std::make_unique
<u32
[]>((m_MapSize
- 1) * (m_MapSize
- 1));
235 m_FinalTexture
= Renderer::Backend::GL::CTexture::Create2D(
236 Renderer::Backend::Format::R8G8B8A8
, FINAL_TEXTURE_SIZE
, FINAL_TEXTURE_SIZE
, defaultSamplerDesc
);
238 glGenFramebuffersEXT(1, &m_FinalTextureFBO
);
239 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, m_FinalTextureFBO
);
240 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
, GL_TEXTURE_2D
, m_FinalTexture
->GetHandle(), 0);
242 GLenum status
= glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT
);
243 if (status
!= GL_FRAMEBUFFER_COMPLETE_EXT
)
245 LOGWARNING("MiniMapTexture Framebuffer object incomplete (A): 0x%04X", status
);
248 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
251 void CMiniMapTexture::DestroyTextures()
253 m_TerrainTexture
.reset();
254 m_FinalTexture
.reset();
255 m_TerrainData
.reset();
258 void CMiniMapTexture::RebuildTerrainTexture(
259 Renderer::Backend::GL::CDeviceCommandContext
* deviceCommandContext
,
260 const CTerrain
* terrain
)
264 const u32 width
= m_MapSize
- 1;
265 const u32 height
= m_MapSize
- 1;
267 m_WaterHeight
= g_Renderer
.GetSceneRenderer().GetWaterManager().m_WaterHeight
;
268 m_TerrainTextureDirty
= false;
270 for (u32 j
= 0; j
< height
; ++j
)
272 u32
* dataPtr
= m_TerrainData
.get() + ((y
+ j
) * width
) + x
;
273 for (u32 i
= 0; i
< width
; ++i
)
275 const float avgHeight
= ( terrain
->GetVertexGroundLevel((int)i
, (int)j
)
276 + terrain
->GetVertexGroundLevel((int)i
+1, (int)j
)
277 + terrain
->GetVertexGroundLevel((int)i
, (int)j
+1)
278 + terrain
->GetVertexGroundLevel((int)i
+1, (int)j
+1)
281 if (avgHeight
< m_WaterHeight
&& avgHeight
> m_WaterHeight
- m_ShallowPassageHeight
)
284 *dataPtr
++ = 0xffc09870;
286 else if (avgHeight
< m_WaterHeight
)
288 // Set water as constant color for consistency on different maps
289 *dataPtr
++ = 0xffa07850;
293 int hmap
= ((int)terrain
->GetHeightMap()[(y
+ j
) * m_MapSize
+ x
+ i
]) >> 8;
294 int val
= (hmap
/ 3) + 170;
296 u32 color
= 0xFFFFFFFF;
298 CMiniPatch
* mp
= terrain
->GetTile(x
+ i
, y
+ j
);
301 CTerrainTextureEntry
* tex
= mp
->GetTextureEntry();
304 // If the texture can't be loaded yet, set the dirty flags
305 // so we'll try regenerating the terrain texture again soon
306 if(!tex
->GetTexture()->TryLoad())
307 m_TerrainTextureDirty
= true;
309 color
= tex
->GetBaseColor();
313 *dataPtr
++ = ScaleColor(color
, float(val
) / 255.0f
);
318 // Upload the texture
319 deviceCommandContext
->UploadTextureRegion(
320 m_TerrainTexture
.get(), Renderer::Backend::Format::R8G8B8A8
,
321 m_TerrainData
.get(), width
* height
* 4, 0, 0, width
, height
);
324 void CMiniMapTexture::RenderFinalTexture()
326 // only update 2x / second
327 // (note: since units only move a few pixels per second on the minimap,
328 // we can get away with infrequent updates; this is slow)
329 // TODO: Update all but camera at same speed as simulation
330 const double currentTime
= timer_Time();
331 const bool doUpdate
= (currentTime
- m_LastFinalTextureUpdate
> 0.5) || m_FinalTextureDirty
;
333 m_LastFinalTextureUpdate
= currentTime
;
336 m_FinalTextureDirty
= false;
338 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, m_FinalTextureFBO
);
340 const SViewPort oldViewPort
= g_Renderer
.GetViewport();
341 const SViewPort viewPort
= { 0, 0, FINAL_TEXTURE_SIZE
, FINAL_TEXTURE_SIZE
};
342 g_Renderer
.SetViewport(viewPort
);
344 CmpPtr
<ICmpRangeManager
> cmpRangeManager(m_Simulation
, SYSTEM_ENTITY
);
345 ENSURE(cmpRangeManager
);
346 CLOSTexture
& losTexture
= g_Game
->GetView()->GetLOSTexture();
348 const float invTileMapSize
= 1.0f
/ static_cast<float>(TERRAIN_TILE_SIZE
* m_MapSize
);
349 const float texCoordMax
= m_TerrainTexture
? static_cast<float>(m_MapSize
- 1) / m_TerrainTexture
->GetWidth() : 1.0f
;
351 CShaderProgramPtr shader
;
352 CShaderTechniquePtr tech
;
354 CShaderDefines baseDefines
;
355 baseDefines
.Add(str_MINIMAP_BASE
, str_1
);
357 tech
= g_Renderer
.GetShaderManager().LoadEffect(str_minimap
, baseDefines
);
359 shader
= tech
->GetShader();
361 if (m_TerrainTexture
)
362 shader
->BindTexture(str_baseTex
, m_TerrainTexture
.get());
364 CMatrix3D baseTransform
;
365 baseTransform
.SetIdentity();
366 CMatrix3D baseTextureTransform
;
367 baseTextureTransform
.SetIdentity();
369 CMatrix3D terrainTransform
;
370 terrainTransform
.SetIdentity();
371 terrainTransform
.Scale(texCoordMax
, texCoordMax
, 1.0f
);
372 shader
->Uniform(str_transform
, baseTransform
);
373 shader
->Uniform(str_textureTransform
, terrainTransform
);
375 if (m_TerrainTexture
)
379 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
380 glColorMask(1, 1, 1, 0);
382 // Draw territory boundaries
383 CTerritoryTexture
& territoryTexture
= g_Game
->GetView()->GetTerritoryTexture();
385 shader
->BindTexture(str_baseTex
, territoryTexture
.GetTexture());
386 shader
->Uniform(str_transform
, baseTransform
);
387 shader
->Uniform(str_textureTransform
, territoryTexture
.GetMinimapTextureMatrix());
392 glColorMask(0, 0, 0, 1);
394 shader
->BindTexture(str_baseTex
, losTexture
.GetTexture());
395 shader
->Uniform(str_transform
, baseTransform
);
396 shader
->Uniform(str_textureTransform
, losTexture
.GetMinimapTextureMatrix());
402 glColorMask(1, 1, 1, 1);
404 CShaderDefines pointDefines
;
405 pointDefines
.Add(str_MINIMAP_POINT
, str_1
);
406 tech
= g_Renderer
.GetShaderManager().LoadEffect(str_minimap
, pointDefines
);
408 shader
= tech
->GetShader();
409 shader
->Uniform(str_transform
, baseTransform
);
410 shader
->Uniform(str_pointSize
, 9.0f
);
412 CMatrix3D unitMatrix
;
413 unitMatrix
.SetIdentity();
414 // Convert world space coordinates into [0, 2].
415 const float unitScale
= invTileMapSize
;
416 unitMatrix
.Scale(unitScale
* 2.0f
, unitScale
* 2.0f
, 1.0f
);
417 // Offset the coordinates to [-1, 1].
418 unitMatrix
.Translate(CVector3D(-1.0f
, -1.0f
, 0.0f
));
419 shader
->Uniform(str_transform
, unitMatrix
);
421 CSimulation2::InterfaceList ents
= m_Simulation
.GetEntitiesWithInterface(IID_Minimap
);
425 VertexArrayIterator
<float[2]> attrPos
= m_AttributePos
.GetIterator
<float[2]>();
426 VertexArrayIterator
<u8
[4]> attrColor
= m_AttributeColor
.GetIterator
<u8
[4]>();
430 std::vector
<MinimapUnitVertex
> pingingVertices
;
431 pingingVertices
.reserve(MAX_ENTITIES_DRAWN
/ 2);
433 if (currentTime
> m_NextBlinkTime
)
435 m_BlinkState
= !m_BlinkState
;
436 m_NextBlinkTime
= currentTime
+ m_HalfBlinkDuration
;
439 entity_pos_t posX
, posZ
;
440 for (CSimulation2::InterfaceList::const_iterator it
= ents
.begin(); it
!= ents
.end(); ++it
)
442 ICmpMinimap
* cmpMinimap
= static_cast<ICmpMinimap
*>(it
->second
);
443 if (cmpMinimap
->GetRenderData(v
.r
, v
.g
, v
.b
, posX
, posZ
))
445 LosVisibility vis
= cmpRangeManager
->GetLosVisibility(it
->first
, m_Simulation
.GetSimContext().GetCurrentDisplayedPlayer());
446 if (vis
!= LosVisibility::HIDDEN
)
449 v
.x
= posX
.ToFloat();
450 v
.y
= posZ
.ToFloat();
452 // Check minimap pinging to indicate something
453 if (m_BlinkState
&& cmpMinimap
->CheckPing(currentTime
, m_PingDuration
))
455 v
.r
= 255; // ping color is white
458 pingingVertices
.push_back(v
);
462 addVertex(v
, attrColor
, attrPos
);
469 // Add the pinged vertices at the end, so they are drawn on top
470 for (const MinimapUnitVertex
& vertex
: pingingVertices
)
472 addVertex(vertex
, attrColor
, attrPos
);
476 ENSURE(m_EntitiesDrawn
< MAX_ENTITIES_DRAWN
);
477 m_VertexArray
.Upload();
480 m_VertexArray
.PrepareForRendering();
482 if (m_EntitiesDrawn
> 0)
484 glEnable(GL_SCISSOR_TEST
);
485 glScissor(1, 1, FINAL_TEXTURE_SIZE
- 1, FINAL_TEXTURE_SIZE
- 1);
487 glEnable(GL_VERTEX_PROGRAM_POINT_SIZE
);
490 u8
* indexBase
= m_IndexArray
.Bind();
491 u8
* base
= m_VertexArray
.Bind();
492 const GLsizei stride
= (GLsizei
)m_VertexArray
.GetStride();
494 shader
->VertexPointer(2, GL_FLOAT
, stride
, base
+ m_AttributePos
.offset
);
495 shader
->ColorPointer(4, GL_UNSIGNED_BYTE
, stride
, base
+ m_AttributeColor
.offset
);
496 shader
->AssertPointersBound();
498 glDrawElements(GL_POINTS
, (GLsizei
)(m_EntitiesDrawn
), GL_UNSIGNED_SHORT
, indexBase
);
500 g_Renderer
.GetStats().m_DrawCalls
++;
501 CVertexBuffer::Unbind();
504 glDisable(GL_VERTEX_PROGRAM_POINT_SIZE
);
506 glDisable(GL_SCISSOR_TEST
);
511 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
512 g_Renderer
.SetViewport(oldViewPort
);
516 float CMiniMapTexture::GetShallowPassageHeight()
518 float shallowPassageHeight
= 0.0f
;
519 CParamNode externalParamNode
;
520 CParamNode::LoadXML(externalParamNode
, L
"simulation/data/pathfinder.xml", "pathfinder");
521 const CParamNode pathingSettings
= externalParamNode
.GetChild("Pathfinder").GetChild("PassabilityClasses");
522 if (pathingSettings
.GetChild("default").IsOk() && pathingSettings
.GetChild("default").GetChild("MaxWaterDepth").IsOk())
523 shallowPassageHeight
= pathingSettings
.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
524 return shallowPassageHeight
;