From fdff25f6887498d759cbaf1e73ade55d14b93d14 Mon Sep 17 00:00:00 2001 From: Matthias Huerbe Date: Tue, 11 Aug 2026 14:56:59 +0200 Subject: [PATCH] switched GpuArrayHelper's array index to freelist - made add_layer O(1) - switch GpuArrayHelper fullness check to add_tile() sentinel - added warnings if a GpuArrayHelper is full / was silent failure - switched layer indices are now uint16_t --- gl_engine/AvalancheWarningLayer.cpp | 6 ++- gl_engine/TextureLayer.cpp | 6 ++- gl_engine/TileGeometry.cpp | 5 +- nucleus/tile/GpuArrayHelper.cpp | 57 +++++++++++++------- nucleus/tile/GpuArrayHelper.h | 14 +++-- webgpu/engine/cloud/CloudRenderer.cpp | 9 ++-- webgpu/engine/tile_mesh/TileMeshRenderer.cpp | 10 +++- 7 files changed, 73 insertions(+), 34 deletions(-) diff --git a/gl_engine/AvalancheWarningLayer.cpp b/gl_engine/AvalancheWarningLayer.cpp index e408dd55..42d65a6e 100644 --- a/gl_engine/AvalancheWarningLayer.cpp +++ b/gl_engine/AvalancheWarningLayer.cpp @@ -21,6 +21,7 @@ #include "ShaderProgram.h" #include "ShaderRegistry.h" #include "TileGeometry.h" +#include #include #include #include @@ -104,8 +105,11 @@ void AvalancheWarningLayer::update_gpu_tiles(const std::vectorupload(*tile.texture, layer_index); } } diff --git a/gl_engine/TextureLayer.cpp b/gl_engine/TextureLayer.cpp index 51506a7f..85f6db5f 100644 --- a/gl_engine/TextureLayer.cpp +++ b/gl_engine/TextureLayer.cpp @@ -22,6 +22,7 @@ #include "ShaderRegistry.h" #include "Texture.h" #include "TileGeometry.h" +#include #include #include @@ -90,8 +91,11 @@ void TextureLayer::update_gpu_tiles(const std::vector& delete Q_ASSERT(tile.id.zoom_level < 100); Q_ASSERT(tile.texture); - // find empty spot and upload texture const auto layer_index = m_gpu_array_helper.add_tile(tile.id); + if (layer_index == nucleus::tile::GpuArrayHelper::invalid_layer) { + qWarning() << "TextureLayer: texture array full, dropping remaining tiles this update"; + break; + } m_texture_array->upload(*tile.texture, layer_index); } } diff --git a/gl_engine/TileGeometry.cpp b/gl_engine/TileGeometry.cpp index 1c581606..04abc3b4 100644 --- a/gl_engine/TileGeometry.cpp +++ b/gl_engine/TileGeometry.cpp @@ -207,8 +207,11 @@ void TileGeometry::update_gpu_tiles(const std::vector& deleted_ Q_ASSERT(tile.id.zoom_level < 100); Q_ASSERT(tile.surface); - // find empty spot and upload texture const auto layer_index = m_gpu_array_helper.add_tile(tile.id); + if (layer_index == nucleus::tile::GpuArrayHelper::invalid_layer) { + qWarning() << "TileGeometry: DTM texture array full, dropping remaining tiles this update"; + break; + } m_dtm_textures->upload(*tile.surface, layer_index); } } diff --git a/nucleus/tile/GpuArrayHelper.cpp b/nucleus/tile/GpuArrayHelper.cpp index d4c51ef2..345e7449 100644 --- a/nucleus/tile/GpuArrayHelper.cpp +++ b/nucleus/tile/GpuArrayHelper.cpp @@ -23,46 +23,65 @@ namespace nucleus::tile { GpuArrayHelper::GpuArrayHelper() { } -unsigned GpuArrayHelper::add_tile(const tile::Id& id) +uint16_t GpuArrayHelper::add_tile(const tile::Id& id) { - Q_ASSERT(!m_id_to_layer.contains(id)); - const auto t = std::find(m_array.begin(), m_array.end(), tile::Id { unsigned(-1), {} }); - Q_ASSERT(t != m_array.end()); - *t = id; + const auto existing = m_id_to_layer.find(id); + Q_ASSERT(existing == m_id_to_layer.end()); + if (existing != m_id_to_layer.end()) + return existing->second; - // returns index in texture array - const auto layer = unsigned(t - m_array.begin()); + if (m_free_layers.empty()) + return invalid_layer; + + const auto layer = m_free_layers.back(); + m_free_layers.pop_back(); m_id_to_layer.emplace(id, layer); return layer; } void GpuArrayHelper::remove_tile(const tile::Id& tile_id) { - Q_ASSERT(m_id_to_layer.contains(tile_id)); - m_id_to_layer.erase(tile_id); - const auto t = std::find(m_array.begin(), m_array.end(), tile_id); - Q_ASSERT(t != m_array.end()); // removing a tile that's not here. likely there is a race. - *t = tile::Id { unsigned(-1), {} }; + const auto layer_it = m_id_to_layer.find(tile_id); + + // TODO: should this assert or be a noop? + Q_ASSERT(layer_it != m_id_to_layer.end()); + if (layer_it == m_id_to_layer.end()) + return; + + m_free_layers.push_back(layer_it->second); + m_id_to_layer.erase(layer_it); } void GpuArrayHelper::set_tile_limit(unsigned int new_limit) { - Q_ASSERT(m_array.empty()); - m_array.resize(new_limit); - std::fill(m_array.begin(), m_array.end(), tile::Id { unsigned(-1), {} }); + Q_ASSERT(m_size == 0); + Q_ASSERT(new_limit <= invalid_layer); + if (new_limit > invalid_layer) + new_limit = invalid_layer; + + m_size = new_limit; + m_free_layers.resize(new_limit); + // fill freelist + for (unsigned i = 0; i < new_limit; ++i) + m_free_layers[i] = uint16_t(new_limit - 1 - i); + + m_id_to_layer.reserve(new_limit); } -unsigned GpuArrayHelper::size() const { return unsigned(m_array.size()); } +unsigned GpuArrayHelper::size() const { return m_size; } unsigned GpuArrayHelper::n_occupied() const { return unsigned(m_id_to_layer.size()); } GpuArrayHelper::LayerInfo GpuArrayHelper::layer(Id tile_id) const { - while (!m_id_to_layer.contains(tile_id) && tile_id.zoom_level > 0) + auto it = m_id_to_layer.find(tile_id); + while (it == m_id_to_layer.end() && tile_id.zoom_level > 0) { tile_id = tile_id.parent(); - if (!m_id_to_layer.contains(tile_id)) + it = m_id_to_layer.find(tile_id); + } + if (it == m_id_to_layer.end()) return { {}, 0 }; // may be empty during startup. - return { tile_id, m_id_to_layer.at(tile_id) }; + return { tile_id, it->second }; } bool GpuArrayHelper::contains(Id tile_id) const { return m_id_to_layer.contains(tile_id); } diff --git a/nucleus/tile/GpuArrayHelper.h b/nucleus/tile/GpuArrayHelper.h index 3a93edd5..50f8ad00 100644 --- a/nucleus/tile/GpuArrayHelper.h +++ b/nucleus/tile/GpuArrayHelper.h @@ -31,13 +31,16 @@ class GpuArrayHelper { }; struct LayerInfo { tile::Id id; - unsigned index; + uint16_t index; }; + // sentinel when the storage is full + static constexpr uint16_t invalid_layer = uint16_t(-1); + GpuArrayHelper(); - /// returns index in texture array - unsigned add_tile(const tile::Id& tile_id); + /// returns index in texture array, or invalid_layer if the array is full + uint16_t add_tile(const tile::Id& tile_id); void remove_tile(const tile::Id& tile_id); void set_tile_limit(unsigned new_limit); unsigned size() const; @@ -47,8 +50,9 @@ class GpuArrayHelper { bool contains(Id tile_id) const; private: - std::vector m_array; - tile::IdMap m_id_to_layer; + unsigned m_size = 0; + std::vector m_free_layers; // freelist stack + tile::IdMap m_id_to_layer; }; } // namespace nucleus::tile diff --git a/webgpu/engine/cloud/CloudRenderer.cpp b/webgpu/engine/cloud/CloudRenderer.cpp index e46787f1..599917a3 100644 --- a/webgpu/engine/cloud/CloudRenderer.cpp +++ b/webgpu/engine/cloud/CloudRenderer.cpp @@ -22,6 +22,7 @@ #include "nucleus/camera/Definition.h" #include "nucleus/srs.h" #include "nucleus/utils/terrain_mesh_index_generator.h" +#include #include #include @@ -479,14 +480,12 @@ void CloudRenderer::update_gpu_tiles_cloud(const std::vector& Q_ASSERT(tile.id.zoom_level < 100); Q_ASSERT(tile.texture); - // Atlas is full - if (m_loaded_cloud_textures.n_occupied() >= m_loaded_cloud_textures.size()) { + const auto layer_index = m_loaded_cloud_textures.add_tile(tile.id); + if (layer_index == nucleus::tile::GpuArrayHelper::invalid_layer) { + qWarning() << "CloudRenderer: cloud atlas full, dropping remaining tiles this update"; break; } - // find empty spot and upload texture - const auto layer_index = m_loaded_cloud_textures.add_tile(tile.id); - uint32_t atlas_x = layer_index & ATLAS_MASK_XY; uint32_t atlas_y = (layer_index >> ATLAS_BITS_XY) & ATLAS_MASK_XY; uint32_t atlas_z = (layer_index >> (2 * ATLAS_BITS_XY)) & ATLAS_MASK_Z; diff --git a/webgpu/engine/tile_mesh/TileMeshRenderer.cpp b/webgpu/engine/tile_mesh/TileMeshRenderer.cpp index 9fd86993..500769bd 100644 --- a/webgpu/engine/tile_mesh/TileMeshRenderer.cpp +++ b/webgpu/engine/tile_mesh/TileMeshRenderer.cpp @@ -319,8 +319,11 @@ void TileMeshRenderer::update_gpu_tiles_height(const std::vectortexture().write(m_ctx->queue(), *tile.surface, layer_index); } } @@ -335,8 +338,11 @@ void TileMeshRenderer::update_gpu_tiles_ortho(const std::vectortexture().write(m_ctx->queue(), tile.texture->front(), uint32_t(layer_index)); } }