Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_gaia_ore.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_gaia_ore.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_gaia_ore.xml @@ -12,6 +12,7 @@ metal + metal.png Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_gaia_rock.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_gaia_rock.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_gaia_rock.xml @@ -12,6 +12,7 @@ stone + stone.png Index: ps/trunk/source/graphics/MiniMapTexture.h =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.h +++ ps/trunk/source/graphics/MiniMapTexture.h @@ -26,6 +26,8 @@ #include "renderer/VertexArray.h" #include +#include +#include #include class CSimulation2; @@ -117,6 +119,29 @@ bool m_BlinkState = false; std::vector m_Icons; + // We store the map as a member to avoid redundant reallocations on each + // update. We use a grid approach to combine icons by distance. + struct CellIconKey + { + std::string path; + u8 r, g, b; + }; + struct CellIconKeyHash + { + size_t operator()(const CellIconKey& key) const; + }; + struct CellIconKeyEqual + { + bool operator()(const CellIconKey& lhs, const CellIconKey& rhs) const; + }; + struct CellIcon + { + // TODO: use CVector2DI. + u16 gridX, gridY; + float halfSize; + CVector2D worldPosition; + }; + std::unordered_map, CellIconKeyHash, CellIconKeyEqual> m_IconsCache; }; #endif // INCLUDED_MINIMAPTEXTURE Index: ps/trunk/source/graphics/MiniMapTexture.cpp =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.cpp +++ ps/trunk/source/graphics/MiniMapTexture.cpp @@ -30,12 +30,15 @@ #include "graphics/TerritoryTexture.h" #include "graphics/TextureManager.h" #include "lib/bits.h" +#include "lib/hash.h" #include "lib/timer.h" +#include "maths/MathUtil.h" #include "maths/Vector2D.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" +#include "ps/Profile.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" @@ -50,6 +53,8 @@ #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/system/ParamNode.h" +#include +#include #include namespace @@ -59,11 +64,13 @@ // 4 is the number of vertices per entity. // TODO: we should be cleverer about drawing them to reduce clutter, // f.e. use instancing. -const size_t MAX_ENTITIES_DRAWN = 65536 / 4; +constexpr size_t MAX_ENTITIES_DRAWN = 65536 / 4; -const size_t MAX_ICON_COUNT = 128; +constexpr size_t MAX_ICON_COUNT = 128; +constexpr size_t MAX_UNIQUE_ICON_COUNT = 64; +constexpr size_t ICON_COMBINING_GRID_SIZE = 10; -const size_t FINAL_TEXTURE_SIZE = 512; +constexpr size_t FINAL_TEXTURE_SIZE = 512; unsigned int ScaleColor(unsigned int color, float x) { @@ -167,6 +174,27 @@ } // anonymous namespace +size_t CMiniMapTexture::CellIconKeyHash::operator()( + const CellIconKey& key) const +{ + size_t seed = 0; + hash_combine(seed, key.path); + hash_combine(seed, key.r); + hash_combine(seed, key.g); + hash_combine(seed, key.b); + return seed; +} + +bool CMiniMapTexture::CellIconKeyEqual::operator()( + const CellIconKey& lhs, const CellIconKey& rhs) const +{ + return + lhs.path == rhs.path && + lhs.r == rhs.r && + lhs.g == rhs.g && + lhs.b == rhs.b; +} + CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation) : m_Simulation(simulation), m_IndexArray(false), m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true), @@ -412,6 +440,7 @@ return; m_FinalTextureDirty = false; + PROFILE3("Render minimap texture"); GPU_SCOPED_LABEL(deviceCommandContext, "Render minimap texture"); deviceCommandContext->SetFramebuffer(m_FinalTextureFramebuffer.get()); @@ -517,6 +546,7 @@ if (doUpdate) { m_Icons.clear(); + m_IconsCache.clear(); CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap); @@ -573,24 +603,82 @@ if (!iconsEnabled || !cmpMinimap->HasIcon()) continue; + const CellIconKey key{ + cmpMinimap->GetIconPath(), v.r, v.g, v.b}; + const u16 gridX = Clamp( + (v.position.X * invTileMapSize) * ICON_COMBINING_GRID_SIZE, 0, ICON_COMBINING_GRID_SIZE - 1); + const u16 gridY = Clamp( + (v.position.Y * invTileMapSize) * ICON_COMBINING_GRID_SIZE, 0, ICON_COMBINING_GRID_SIZE - 1); + CellIcon icon{ + gridX, gridY, cmpMinimap->GetIconSize() * iconsSizeScale * 0.5f, v.position}; + if (m_IconsCache.find(key) == m_IconsCache.end() && m_IconsCache.size() >= MAX_UNIQUE_ICON_COUNT) + { + iconsCountOverflow = true; + } + else + { + m_IconsCache[key].emplace_back(std::move(icon)); + } + } + } + } + + // We need to combine too close icons with the same path, we use a grid for + // that. But to save some allocations and space we store only the current + // row. + struct Cell + { + u32 count; + float maxHalfSize; + CVector2D averagePosition; + }; + std::array gridRow; + for (auto& [key, icons] : m_IconsCache) + { + CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture( + CTextureProperties(key.path)); + const CColor color(key.r / 255.0f, key.g / 255.0f, key.b / 255.0f, iconsOpacity); + + std::sort(icons.begin(), icons.end(), + [](const CellIcon& lhs, const CellIcon& rhs) -> bool + { + if (lhs.gridY != rhs.gridY) + return lhs.gridY < rhs.gridY; + return lhs.gridX < rhs.gridX; + }); + + for (auto beginIt = icons.begin(); beginIt != icons.end();) + { + auto endIt = std::next(beginIt); + while (endIt != icons.end() && beginIt->gridY == endIt->gridY) + ++endIt; + gridRow.fill({0, 0.0f, {}}); + for (; beginIt != endIt; ++beginIt) + { + Cell& cell = gridRow[beginIt->gridX]; + const float previousPositionWeight = static_cast(cell.count) / (cell.count + 1); + cell.averagePosition = cell.averagePosition * previousPositionWeight + beginIt->worldPosition / static_cast(cell.count + 1); + cell.maxHalfSize = std::max(cell.maxHalfSize, beginIt->halfSize); + ++cell.count; + } + for (const Cell& cell : gridRow) + { + if (cell.count == 0) + continue; + if (m_Icons.size() < MAX_ICON_COUNT) { - CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture( - CTextureProperties(cmpMinimap->GetIconPath())); - const CColor color(v.r / 255.0f, v.g / 255.0f, v.b / 255.0f, iconsOpacity); m_Icons.emplace_back(Icon{ - std::move(texture), color, v.position, cmpMinimap->GetIconSize() * iconsSizeScale * 0.5f}); + texture, color, cell.averagePosition, cell.maxHalfSize}); } else - { iconsCountOverflow = true; - } } } } if (iconsCountOverflow) - LOGWARNING("Too many minimap icons to draw: %zu/%zu", m_Icons.size(), MAX_ICON_COUNT); + LOGWARNING("Too many minimap icons to draw."); // Add the pinged vertices at the end, so they are drawn on top for (const MinimapUnitVertex& vertex : pingingVertices)