LCOV - code coverage report
Current view: top level - source/graphics - MiniMapTexture.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 455 0.2 %
Date: 2023-01-19 00:18:29 Functions: 2 22 9.1 %

          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 : }

Generated by: LCOV version 1.13