From bda0dc18008d18a931c90c2f0552febeba11625e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksi=20Gr=C3=B6n?= Date: Sun, 13 Oct 2024 16:31:30 +0300 Subject: [PATCH] Cache loaded tile heights to reduce flickering --- engine/src/Core/HashMap.hpp | 7 +- engine/src/Graphics/TerrainQuadTree.cpp | 97 ++++++++++++++----------- engine/src/Graphics/TerrainQuadTree.hpp | 35 +++++---- 3 files changed, 83 insertions(+), 56 deletions(-) diff --git a/engine/src/Core/HashMap.hpp b/engine/src/Core/HashMap.hpp index 5fd8bda..42d8f0a 100644 --- a/engine/src/Core/HashMap.hpp +++ b/engine/src/Core/HashMap.hpp @@ -211,7 +211,7 @@ class HashMap return *this; } - HashMap& operator=(HashMap&& other) + HashMap& operator=(HashMap&& other) noexcept { if (data != nullptr) { @@ -398,6 +398,11 @@ class HashMap } } + void Remove(const Iterator& iterator) + { + Remove(iterator.current); + } + void Remove(KeyValuePair* pair) { if (pair != &zeroPair) diff --git a/engine/src/Graphics/TerrainQuadTree.cpp b/engine/src/Graphics/TerrainQuadTree.cpp index 2355147..b43381b 100644 --- a/engine/src/Graphics/TerrainQuadTree.cpp +++ b/engine/src/Graphics/TerrainQuadTree.cpp @@ -236,7 +236,8 @@ TerrainQuadTree::TerrainQuadTree() : parentsToCheck(nullptr), neighborsToCheck(nullptr), edgeDependencies(nullptr), - tileIdToIndexMap(nullptr) + tileIdToIndexMap(nullptr), + nodeHeightCache(nullptr) { } @@ -248,7 +249,8 @@ TerrainQuadTree::TerrainQuadTree(Allocator* allocator, render::Device* renderDev parentsToCheck(allocator), neighborsToCheck(allocator), edgeDependencies(allocator), - tileIdToIndexMap(allocator) + tileIdToIndexMap(allocator), + nodeHeightCache(allocator) { } @@ -261,6 +263,7 @@ TerrainQuadTree::TerrainQuadTree(TerrainQuadTree&& other) noexcept : neighborsToCheck(std::move(other.neighborsToCheck)), edgeDependencies(std::move(other.edgeDependencies)), tileIdToIndexMap(std::move(other.tileIdToIndexMap)), + nodeHeightCache(std::move(other.nodeHeightCache)), tileData(std::move(other.tileData)), treeLevels(other.treeLevels), maxNodeLevel(other.maxNodeLevel), @@ -281,6 +284,7 @@ TerrainQuadTree& TerrainQuadTree::operator=(TerrainQuadTree&& other) noexcept neighborsToCheck = std::move(other.neighborsToCheck); edgeDependencies = std::move(other.edgeDependencies); tileIdToIndexMap = std::move(other.tileIdToIndexMap); + nodeHeightCache = std::move(other.nodeHeightCache); tileData = std::move(other.tileData); treeLevels = other.treeLevels; maxNodeLevel = other.maxNodeLevel; @@ -314,6 +318,7 @@ void TerrainQuadTree::SetHeightmap(const uint16_t* pixels, uint32_t resolution) { tileData.count = 0; tileIdToIndexMap.Clear(); + nodeHeightCache.Clear(); treeLevels = GetLevelsFromHeightmapResolution(resolution); heightmapPixels = pixels; @@ -332,6 +337,9 @@ void TerrainQuadTree::UpdateTilesToRender( drawTiles.Clear(); maxNodeLevel = 0; + // Used for cache eviction + currentTime = Time::GetRunningTime(); + // Calculates optimal set of tiles to render and updates quad tree UpdateTilesToRenderParams params{ frustum, cameraPos, renderDebug }; int rootNodeIndex = BuildQuadTree(QuadTreeNodeId{}, params); @@ -358,14 +366,39 @@ ArrayView TerrainQuadTree::GetTilesToRender() const int TerrainQuadTree::BuildQuadTree(const QuadTreeNodeId& id, const UpdateTilesToRenderParams& params) { - AABB tileBounds = FindApproximateTileBounds(id); - float width = tileBounds.extents.x * 2.0f; + auto cacheKv = nodeHeightCache.Lookup(id); + QuadTreeNodeId searchId = id; + while (cacheKv == nullptr && searchId.level != 0) + { + searchId = GetParentId(searchId); + cacheKv = nodeHeightCache.Lookup(searchId); + } + + float minHeight = terrainBottom; + float maxHeight = terrainBottom + terrainHeight; + if (cacheKv != nullptr) + { + HeightCacheEntry& entry = cacheKv->second; + minHeight = entry.min / static_cast(UINT16_MAX) * terrainHeight + terrainBottom; + maxHeight = entry.max / static_cast(UINT16_MAX) * terrainHeight + terrainBottom; + entry.lastAccessTime = currentTime; + } + + float tileScale = GetTileScale(id.level); + float tileWidth = terrainWidth * tileScale; + float halfSize = terrainWidth * 0.5f; + Vec3f tileMin(id.x * tileWidth - halfSize, minHeight, id.y * tileWidth - halfSize); + Vec3f tileSize(tileWidth, maxHeight - minHeight, tileWidth); + + AABB tileBounds; + tileBounds.extents = tileSize * 0.5f; + tileBounds.center = tileMin + tileBounds.extents; if (Intersect::FrustumAabb(params.frustum, tileBounds) == false) return -1; bool lastLevel = id.level + 1 == treeLevels; - bool tileIsSmallEnough = lastLevel || (width < (tileBounds.center - params.cameraPos).Magnitude() * lodSizeFactor); + bool tileIsSmallEnough = lastLevel || (tileWidth < (tileBounds.center - params.cameraPos).Magnitude() * lodSizeFactor); int nodeIndex = static_cast(nodes.GetCount()); assert(nodeIndex <= UINT16_MAX); @@ -596,7 +629,6 @@ void TerrainQuadTree::QuadTreeToTiles(uint16_t nodeIndex) void TerrainQuadTree::LoadTiles() { - const double currentTime = Time::GetRunningTime(); const double oldTileThresholdTime = currentTime - 10.0; // Unload tiles that haven't been used for a while @@ -707,6 +739,11 @@ void TerrainQuadTree::LoadTiles() tile.minHeight = heightData.min; tile.maxHeight = heightData.max; + auto cacheKv = nodeHeightCache.Insert(drawTile.id); + cacheKv->second.min = heightData.min; + cacheKv->second.max = heightData.max; + cacheKv->second.lastAccessTime = currentTime; + renderDevice->SetTextureSubImage2D(tileData.textureIds[tileIdx], 0, 0, 0, texResolution, texResolution, RenderTextureBaseFormat::R, RenderTextureDataType::UnsignedShort, heightData.data); @@ -716,6 +753,19 @@ void TerrainQuadTree::LoadTiles() tileData.count += 1; } } + + // Unload old height cache entries + + const double oldCacheThresholdTime = currentTime - 300.0; + + for (auto itr = nodeHeightCache.begin(), end = nodeHeightCache.end(); itr != end; ++itr) + { + if (itr->second.lastAccessTime < oldCacheThresholdTime) + { + nodeHeightCache.Remove(itr); + break; // Only remove one entry per update as removal invalidates iterators + } + } } void TerrainQuadTree::LoadTileData(const QuadTreeNodeId& id, TerrainTileHeightData& heightDataOut) @@ -756,41 +806,6 @@ void TerrainQuadTree::LoadTileData(const QuadTreeNodeId& id, TerrainTileHeightDa heightDataOut.max = max; } -AABB TerrainQuadTree::FindApproximateTileBounds(const QuadTreeNodeId& id) const -{ - auto pair = tileIdToIndexMap.Lookup(id); - QuadTreeNodeId searchId = id; - while (pair == nullptr) - { - if (searchId.level == 0) - break; - - searchId = GetParentId(searchId); - tileIdToIndexMap.Lookup(searchId); - } - - float minHeight = terrainBottom; - float maxHeight = terrainBottom + terrainHeight; - if (pair != nullptr) - { - TerrainTile& tile = tileData.tiles[pair->second]; - minHeight = tile.minHeight / static_cast(UINT16_MAX) * terrainHeight + terrainBottom; - maxHeight = tile.maxHeight / static_cast(UINT16_MAX) * terrainHeight + terrainBottom; - } - - float tileScale = GetTileScale(id.level); - float tileWidth = terrainWidth * tileScale; - float halfSize = terrainWidth * 0.5f; - Vec3f tileMin(id.x * tileWidth - halfSize, minHeight, id.y * tileWidth - halfSize); - Vec3f tileSize(tileWidth, maxHeight - minHeight, tileWidth); - - AABB bounds; - bounds.extents = tileSize * 0.5f; - bounds.center = tileMin + bounds.extents; - - return bounds; -} - render::TextureId TerrainQuadTree::GetTileHeightTexture(const QuadTreeNodeId& id) { auto pair = tileIdToIndexMap.Lookup(id); diff --git a/engine/src/Graphics/TerrainQuadTree.hpp b/engine/src/Graphics/TerrainQuadTree.hpp index 20dd684..84fa588 100644 --- a/engine/src/Graphics/TerrainQuadTree.hpp +++ b/engine/src/Graphics/TerrainQuadTree.hpp @@ -78,18 +78,6 @@ enum class TerrainEdgeType : uint8_t LeftTopSparse }; -struct TerrainQuadTreeNode -{ - QuadTreeNodeId id; - uint16_t children[4] = { 0, 0, 0, 0 }; - TerrainEdgeType edgeType = TerrainEdgeType::Regular; - - uint16_t HasChildren() const - { - return children[0] != 0 || children[1] != 0 || children[2] != 0 || children[3] != 0; - } -}; - struct TerrainTileDrawInfo { QuadTreeNodeId id; @@ -134,6 +122,18 @@ class TerrainQuadTree static float GetTileScale(uint8_t level); private: + struct TerrainQuadTreeNode + { + QuadTreeNodeId id; + uint16_t children[4] = { 0, 0, 0, 0 }; + TerrainEdgeType edgeType = TerrainEdgeType::Regular; + + uint16_t HasChildren() const + { + return children[0] != 0 || children[1] != 0 || children[2] != 0 || children[3] != 0; + } + }; + struct UpdateTilesToRenderParams { const FrustumPlanes& frustum; @@ -149,6 +149,13 @@ class TerrainQuadTree void AddDependent(uint16_t dependent); }; + struct HeightCacheEntry + { + uint16_t min; + uint16_t max; + double lastAccessTime; + }; + // Returns inserted node index (points to nodes array), or -1 if no insertion int BuildQuadTree(const QuadTreeNodeId& id, const UpdateTilesToRenderParams& params); void RestrictQuadTree(); @@ -158,8 +165,6 @@ class TerrainQuadTree void LoadTiles(); void LoadTileData(const QuadTreeNodeId& id, TerrainTileHeightData& heightDataOut); - AABB FindApproximateTileBounds(const QuadTreeNodeId& id) const; - Allocator* allocator; render::Device* renderDevice; @@ -169,6 +174,7 @@ class TerrainQuadTree SortedArray neighborsToCheck; HashMap edgeDependencies; // Key = dependee node, Value = dependent nodes HashMap tileIdToIndexMap; // Index into tileData + HashMap nodeHeightCache; struct TileData { @@ -197,6 +203,7 @@ class TerrainQuadTree float terrainBottom = 0.0f; float terrainHeight = 0.0f; float lodSizeFactor = 0.5f; + double currentTime = -1.0; }; }