Performs texture uploads via DeviceCommandContext interface.
[0ad.git] / source / graphics / MiniMapTexture.cpp
blob01dc3727b0af3ee146805c4170f1e40b62f39984
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"
31 #include "lib/bits.h"
32 #include "lib/timer.h"
33 #include "ps/ConfigDB.h"
34 #include "ps/CStrInternStatic.h"
35 #include "ps/Filesystem.h"
36 #include "ps/Game.h"
37 #include "ps/World.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"
49 namespace
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[] =
70 0.0f, 0.0f,
71 1.0f, 0.0f,
72 1.0f, 1.0f,
74 1.0f, 1.0f,
75 0.0f, 1.0f,
76 0.0f, 0.0f
78 const float quadVertices[] =
80 -1.0f, -1.0f, 0.0f,
81 1.0f, -1.0f, 0.0f,
82 1.0f, 1.0f, 0.0f,
84 1.0f, 1.0f, 0.0f,
85 -1.0f, 1.0f, 0.0f,
86 -1.0f, -1.0f, 0.0f
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.
99 u8 r, g, b, a;
100 float x, y;
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;
112 ++attrColor;
114 (*attrPos)[0] = v.x;
115 (*attrPos)[1] = v.y;
117 ++attrPos;
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)
154 *index++ = 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)
162 (*attrColor)[0] = 0;
163 (*attrColor)[1] = 0;
164 (*attrColor)[2] = 0;
165 (*attrColor)[3] = 0;
166 ++attrColor;
168 (*attrPos)[0] = -10000.0f;
169 (*attrPos)[1] = -10000.0f;
171 ++attrPos;
174 m_VertexArray.Upload();
177 CMiniMapTexture::~CMiniMapTexture()
179 DestroyTextures();
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();
194 if (!terrain)
195 return;
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)
209 DestroyTextures();
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);
231 texData.reset();
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)
262 const u32 x = 0;
263 const u32 y = 0;
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)
279 ) / 4.0f;
281 if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight)
283 // shallow water
284 *dataPtr++ = 0xffc09870;
286 else if (avgHeight < m_WaterHeight)
288 // Set water as constant color for consistency on different maps
289 *dataPtr++ = 0xffa07850;
291 else
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);
299 if (mp)
301 CTerrainTextureEntry* tex = mp->GetTextureEntry();
302 if (tex)
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;
332 if (doUpdate)
333 m_LastFinalTextureUpdate = currentTime;
334 else
335 return;
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);
358 tech->BeginPass();
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)
376 DrawTexture(shader);
378 glEnable(GL_BLEND);
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());
389 DrawTexture(shader);
391 glDisable(GL_BLEND);
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());
398 DrawTexture(shader);
400 tech->EndPass();
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);
407 tech->BeginPass();
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);
423 if (doUpdate)
425 VertexArrayIterator<float[2]> attrPos = m_AttributePos.GetIterator<float[2]>();
426 VertexArrayIterator<u8[4]> attrColor = m_AttributeColor.GetIterator<u8[4]>();
428 m_EntitiesDrawn = 0;
429 MinimapUnitVertex v;
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)
448 v.a = 255;
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
456 v.g = 255;
457 v.b = 255;
458 pingingVertices.push_back(v);
460 else
462 addVertex(v, attrColor, attrPos);
463 ++m_EntitiesDrawn;
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);
473 ++m_EntitiesDrawn;
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);
486 #if !CONFIG2_GLES
487 glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
488 #endif
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();
503 #if !CONFIG2_GLES
504 glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
505 #endif
506 glDisable(GL_SCISSOR_TEST);
509 tech->EndPass();
511 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
512 g_Renderer.SetViewport(oldViewPort);
515 // static
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;