Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache loaded tile heights to reduce flickering #89

Merged
merged 1 commit into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion engine/src/Core/HashMap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ class HashMap
return *this;
}

HashMap& operator=(HashMap&& other)
HashMap& operator=(HashMap&& other) noexcept
{
if (data != nullptr)
{
Expand Down Expand Up @@ -398,6 +398,11 @@ class HashMap
}
}

void Remove(const Iterator& iterator)
{
Remove(iterator.current);
}

void Remove(KeyValuePair* pair)
{
if (pair != &zeroPair)
Expand Down
97 changes: 56 additions & 41 deletions engine/src/Graphics/TerrainQuadTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ TerrainQuadTree::TerrainQuadTree() :
parentsToCheck(nullptr),
neighborsToCheck(nullptr),
edgeDependencies(nullptr),
tileIdToIndexMap(nullptr)
tileIdToIndexMap(nullptr),
nodeHeightCache(nullptr)
{
}

Expand All @@ -248,7 +249,8 @@ TerrainQuadTree::TerrainQuadTree(Allocator* allocator, render::Device* renderDev
parentsToCheck(allocator),
neighborsToCheck(allocator),
edgeDependencies(allocator),
tileIdToIndexMap(allocator)
tileIdToIndexMap(allocator),
nodeHeightCache(allocator)
{
}

Expand All @@ -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),
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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 <nodes>
UpdateTilesToRenderParams params{ frustum, cameraPos, renderDebug };
int rootNodeIndex = BuildQuadTree(QuadTreeNodeId{}, params);
Expand All @@ -358,14 +366,39 @@ ArrayView<const TerrainTileDrawInfo> 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<float>(UINT16_MAX) * terrainHeight + terrainBottom;
maxHeight = entry.max / static_cast<float>(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<int>(nodes.GetCount());
assert(nodeIndex <= UINT16_MAX);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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<float>(UINT16_MAX) * terrainHeight + terrainBottom;
maxHeight = tile.maxHeight / static_cast<float>(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);
Expand Down
35 changes: 21 additions & 14 deletions engine/src/Graphics/TerrainQuadTree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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;

Expand All @@ -169,6 +174,7 @@ class TerrainQuadTree
SortedArray<QuadTreeNodeId> neighborsToCheck;
HashMap<QuadTreeNodeId, EdgeTypeDependents> edgeDependencies; // Key = dependee node, Value = dependent nodes
HashMap<QuadTreeNodeId, uint32_t> tileIdToIndexMap; // Index into tileData
HashMap<QuadTreeNodeId, HeightCacheEntry> nodeHeightCache;

struct TileData
{
Expand Down Expand Up @@ -197,6 +203,7 @@ class TerrainQuadTree
float terrainBottom = 0.0f;
float terrainHeight = 0.0f;
float lodSizeFactor = 0.5f;
double currentTime = -1.0;
};

}