From 9766e02f32cbe309674d8fa51331b58c7af41c59 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 2 Sep 2023 23:37:45 -0500 Subject: [PATCH] Generate multiple texture atlases when first atlas is full --- scwx-qt/gl/geo_line.vert | 4 +- scwx-qt/gl/geo_texture2d.vert | 6 +- scwx-qt/gl/map_color.vert | 2 +- scwx-qt/gl/texture2d_array.frag | 4 +- scwx-qt/gl/threshold.geom | 4 +- scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp | 20 +- .../scwx/qt/gl/draw/placefile_icons.cpp | 41 +-- .../scwx/qt/gl/draw/placefile_images.cpp | 8 +- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 249 +++++++++++------- scwx-qt/source/scwx/qt/util/texture_atlas.hpp | 8 +- 10 files changed, 205 insertions(+), 141 deletions(-) diff --git a/scwx-qt/gl/geo_line.vert b/scwx-qt/gl/geo_line.vert index 490fc3eb..487b8041 100644 --- a/scwx-qt/gl/geo_line.vert +++ b/scwx-qt/gl/geo_line.vert @@ -8,14 +8,14 @@ layout (location = 0) in vec2 aLatLong; layout (location = 1) in vec2 aXYOffset; -layout (location = 2) in vec2 aTexCoord; +layout (location = 2) in vec3 aTexCoord; layout (location = 3) in vec4 aModulate; uniform mat4 uMVPMatrix; uniform mat4 uMapMatrix; uniform vec2 uMapScreenCoord; -smooth out vec2 texCoord; +smooth out vec3 texCoord; smooth out vec4 color; vec2 latLngToScreenCoordinate(in vec2 latLng) diff --git a/scwx-qt/gl/geo_texture2d.vert b/scwx-qt/gl/geo_texture2d.vert index 05a8c5d0..ee93a12a 100644 --- a/scwx-qt/gl/geo_texture2d.vert +++ b/scwx-qt/gl/geo_texture2d.vert @@ -9,7 +9,7 @@ layout (location = 0) in vec2 aLatLong; layout (location = 1) in vec2 aXYOffset; -layout (location = 2) in vec2 aTexCoord; +layout (location = 2) in vec3 aTexCoord; layout (location = 3) in vec4 aModulate; layout (location = 4) in float aAngleDeg; layout (location = 5) in int aThreshold; @@ -21,11 +21,11 @@ uniform vec2 uMapScreenCoord; out VertexData { int threshold; - vec2 texCoord; + vec3 texCoord; vec4 color; } vsOut; -smooth out vec2 texCoord; +smooth out vec3 texCoord; smooth out vec4 color; vec2 latLngToScreenCoordinate(in vec2 latLng) diff --git a/scwx-qt/gl/map_color.vert b/scwx-qt/gl/map_color.vert index cc19f8a4..b2749a02 100644 --- a/scwx-qt/gl/map_color.vert +++ b/scwx-qt/gl/map_color.vert @@ -12,7 +12,7 @@ uniform vec2 uMapScreenCoord; out VertexData { int threshold; - vec2 texCoord; + vec3 texCoord; vec4 color; } vsOut; diff --git a/scwx-qt/gl/texture2d_array.frag b/scwx-qt/gl/texture2d_array.frag index c345769a..b73bfda8 100644 --- a/scwx-qt/gl/texture2d_array.frag +++ b/scwx-qt/gl/texture2d_array.frag @@ -5,12 +5,12 @@ precision mediump float; uniform sampler2DArray uTexture; -smooth in vec2 texCoord; +smooth in vec3 texCoord; smooth in vec4 color; layout (location = 0) out vec4 fragColor; void main() { - fragColor = texture(uTexture, vec3(texCoord, 0.0)) * color; + fragColor = texture(uTexture, texCoord) * color; } diff --git a/scwx-qt/gl/threshold.geom b/scwx-qt/gl/threshold.geom index 4d9d3b53..1f64b368 100644 --- a/scwx-qt/gl/threshold.geom +++ b/scwx-qt/gl/threshold.geom @@ -8,11 +8,11 @@ uniform float uMapDistance; in VertexData { int threshold; - vec2 texCoord; + vec3 texCoord; vec4 color; } gsIn[]; -smooth out vec2 texCoord; +smooth out vec3 texCoord; smooth out vec4 color; void main() diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp index 912a5022..17d84a79 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp @@ -23,7 +23,7 @@ static constexpr size_t kNumRectangles = 1; static constexpr size_t kNumTriangles = kNumRectangles * 2; static constexpr size_t kVerticesPerTriangle = 3; static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; -static constexpr size_t kPointsPerVertex = 10; +static constexpr size_t kPointsPerVertex = 11; static constexpr size_t kBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; @@ -147,7 +147,7 @@ void GeoLine::Initialize() // aTexCoord gl.glVertexAttribPointer(2, - 2, + 3, GL_FLOAT, GL_FALSE, kPointsPerVertex * sizeof(float), @@ -160,7 +160,7 @@ void GeoLine::Initialize() GL_FLOAT, GL_FALSE, kPointsPerVertex * sizeof(float), - reinterpret_cast(6 * sizeof(float))); + reinterpret_cast(7 * sizeof(float))); gl.glEnableVertexAttribArray(3); p->dirty_ = true; @@ -264,6 +264,8 @@ void GeoLine::Impl::Update() const float oy = width_ * 0.5f * sinf(angle_); // Texture coordinates + static constexpr float r = 0.0f; + const float ls = texture_.sLeft_; const float rs = texture_.sRight_; const float tt = texture_.tTop_; @@ -289,12 +291,12 @@ void GeoLine::Impl::Update() { // // Line { - {lx, by, -ox, -oy, ls, bt, mc0, mc1, mc2, mc3}, // BL - {lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3}, // TL - {rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR - {rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR - {rx, ty, +ox, +oy, rs, tt, mc0, mc1, mc2, mc3}, // TR - {lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3} // TL + {lx, by, -ox, -oy, ls, bt, r, mc0, mc1, mc2, mc3}, // BL + {lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3}, // TL + {rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR + {rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR + {rx, ty, +ox, +oy, rs, tt, r, mc0, mc1, mc2, mc3}, // TR + {lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3} // TL }}; gl.glBufferData(GL_ARRAY_BUFFER, diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 22e94bb8..03a66c61 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -26,7 +26,7 @@ static constexpr std::size_t kNumTriangles = kNumRectangles * 2; static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; static constexpr std::size_t kPointsPerVertex = 9; -static constexpr std::size_t kPointsPerTexCoord = 2; +static constexpr std::size_t kPointsPerTexCoord = 3; static constexpr std::size_t kIconBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; static constexpr std::size_t kTextureBufferLength = @@ -208,7 +208,7 @@ void PlacefileIcons::Initialize() // aTexCoord gl.glVertexAttribPointer(2, - 2, + 3, GL_FLOAT, GL_FALSE, kPointsPerTexCoord * sizeof(float), @@ -508,12 +508,12 @@ void PlacefileIcons::Impl::UpdateTextureBuffer() textureBuffer_.end(), { // Icon - 0.0f, 0.0f, // BL - 0.0f, 0.0f, // TL - 0.0f, 0.0f, // BR - 0.0f, 0.0f, // BR - 0.0f, 0.0f, // TR - 0.0f, 0.0f // TL + 0.0f, 0.0f, 0.0f, // BL + 0.0f, 0.0f, 0.0f, // TL + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // TR + 0.0f, 0.0f, 0.0f // TL }); // clang-format on @@ -536,12 +536,12 @@ void PlacefileIcons::Impl::UpdateTextureBuffer() textureBuffer_.end(), { // Icon - 0.0f, 0.0f, // BL - 0.0f, 0.0f, // TL - 0.0f, 0.0f, // BR - 0.0f, 0.0f, // BR - 0.0f, 0.0f, // TR - 0.0f, 0.0f // TL + 0.0f, 0.0f, 0.0f, // BL + 0.0f, 0.0f, 0.0f, // TL + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // TR + 0.0f, 0.0f, 0.0f // TL }); // clang-format on @@ -559,18 +559,19 @@ void PlacefileIcons::Impl::UpdateTextureBuffer() const float rs = ls + icon.scaledWidth_; const float tt = icon.texture_.tTop_ + iconY; const float bt = tt + icon.scaledHeight_; + const float r = static_cast(icon.texture_.layerId_); // clang-format off textureBuffer_.insert( textureBuffer_.end(), { // Icon - ls, bt, // BL - ls, tt, // TL - rs, bt, // BR - rs, bt, // BR - rs, tt, // TR - ls, tt // TL + ls, bt, r, // BL + ls, tt, r, // TL + rs, bt, r, // BR + rs, bt, r, // BR + rs, tt, r, // TR + ls, tt, r // TL }); // clang-format on } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp index e796ed04..019c2b21 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp @@ -23,7 +23,7 @@ static constexpr std::size_t kNumTriangles = kNumRectangles * 2; static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; static constexpr std::size_t kPointsPerVertex = 8; -static constexpr std::size_t kPointsPerTexCoord = 2; +static constexpr std::size_t kPointsPerTexCoord = 3; static constexpr std::size_t kImageBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; static constexpr std::size_t kTextureBufferLength = @@ -177,7 +177,7 @@ void PlacefileImages::Initialize() // aTexCoord gl.glVertexAttribPointer(2, - 2, + 3, GL_FLOAT, GL_FALSE, kPointsPerTexCoord * sizeof(float), @@ -367,6 +367,8 @@ void PlacefileImages::Impl::UpdateTextureBuffer() currentImageFiles_.cbegin()->second : it->second; + const float r = static_cast(image.texture_.layerId_); + // Limit processing to groups of 3 (triangles) std::size_t numElements = di->elements_.size() - di->elements_.size() % 3; for (std::size_t i = 0; i < numElements; ++i) @@ -379,7 +381,7 @@ void PlacefileImages::Impl::UpdateTextureBuffer() const float t = image.texture_.tTop_ + (image.scaledHeight_ * element.tv_); - textureBuffer_.insert(textureBuffer_.end(), {s, t}); + textureBuffer_.insert(textureBuffer_.end(), {s, t, r}); } } } diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 7cb4d067..40f521eb 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -50,7 +50,7 @@ public: std::shared_mutex textureCacheMutex_ {}; std::unordered_map textureCache_ {}; - boost::gil::rgba8_image_t atlas_ {}; + std::vector atlasArray_ {}; std::unordered_map atlasMap_ {}; std::shared_mutex atlasMutex_ {}; @@ -95,7 +95,7 @@ bool TextureAtlas::CacheTexture(const std::string& name, return false; } -void TextureAtlas::BuildAtlas(size_t width, size_t height) +void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) { logger_->debug("Building {}x{} texture atlas", width, height); @@ -105,11 +105,13 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) return; } - std::vector>> - images; - std::vector stbrpRects; + ImageVector; + + ImageVector images {}; + std::vector stbrpRects {}; // Load images { @@ -171,104 +173,143 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) } } - // Pack images - { - logger_->trace("Packing {} images", images.size()); - - // Optimal number of nodes = width - stbrp_context stbrpContext; - std::vector stbrpNodes(width); - - stbrp_init_target(&stbrpContext, - static_cast(width), - static_cast(height), - stbrpNodes.data(), - static_cast(stbrpNodes.size())); - - // Pack loaded textures - stbrp_pack_rects( - &stbrpContext, stbrpRects.data(), static_cast(stbrpRects.size())); - } - - // Lock atlas - std::unique_lock lock(p->atlasMutex_); - - // Clear index - p->atlasMap_.clear(); - - // Clear atlas - p->atlas_.recreate(width, height); - boost::gil::rgba8_view_t atlasView = boost::gil::view(p->atlas_); - boost::gil::fill_pixels(atlasView, - boost::gil::rgba8_pixel_t {255, 0, 255, 255}); - - // Populate atlas - logger_->trace("Populating atlas"); + // GL_MAX_ARRAY_TEXTURE_LAYERS is guaranteed to be at least 256 in OpenGL 3.3 + constexpr std::size_t kMaxLayers = 256u; const float xStep = 1.0f / width; const float yStep = 1.0f / height; const float xMin = xStep * 0.5f; const float yMin = yStep * 0.5f; - for (size_t i = 0; i < images.size(); i++) + // Optimal number of nodes = width + stbrp_context stbrpContext; + std::vector stbrpNodes(width); + ImageVector unpackedImages {}; + std::vector unpackedRects {}; + + std::vector newAtlasArray {}; + std::unordered_map newAtlasMap {}; + + for (std::size_t layer = 0; layer < kMaxLayers; ++layer) { - // If the image was packed successfully - if (stbrpRects[i].was_packed != 0) + logger_->trace("Processing layer {}", layer); + + // Pack images { - // Populate the atlas - boost::gil::rgba8c_view_t imageView; + logger_->trace("Packing {} images", images.size()); - // Retrieve the image - if (std::holds_alternative( - images[i].second)) + stbrp_init_target(&stbrpContext, + static_cast(width), + static_cast(height), + stbrpNodes.data(), + static_cast(stbrpNodes.size())); + + // Pack loaded textures + stbrp_pack_rects(&stbrpContext, + stbrpRects.data(), + static_cast(stbrpRects.size())); + } + + // Clear atlas + auto& atlas = + newAtlasArray.emplace_back(boost::gil::rgba8_image_t(width, height)); + boost::gil::rgba8_view_t atlasView = boost::gil::view(atlas); + boost::gil::fill_pixels(atlasView, + boost::gil::rgba8_pixel_t {255, 0, 255, 255}); + + // Populate atlas + logger_->trace("Populating atlas"); + + for (std::size_t i = 0; i < images.size(); ++i) + { + // If the image was packed successfully + if (stbrpRects[i].was_packed != 0) { - imageView = boost::gil::const_view( - std::get(images[i].second)); + // Populate the atlas + boost::gil::rgba8c_view_t imageView; + + // Retrieve the image + if (std::holds_alternative( + images[i].second)) + { + imageView = boost::gil::const_view( + std::get(images[i].second)); + } + else if (std::holds_alternative( + images[i].second)) + { + imageView = boost::gil::const_view( + *std::get(images[i].second)); + } + + boost::gil::rgba8_view_t atlasSubView = + boost::gil::subimage_view(atlasView, + stbrpRects[i].x, + stbrpRects[i].y, + imageView.width(), + imageView.height()); + + boost::gil::copy_pixels(imageView, atlasSubView); + + // Add texture image to the index + const stbrp_coord x = stbrpRects[i].x; + const stbrp_coord y = stbrpRects[i].y; + + const float sLeft = x * xStep + xMin; + const float sRight = + sLeft + static_cast(imageView.width() - 1) / width; + const float tTop = y * yStep + yMin; + const float tBottom = + tTop + static_cast(imageView.height() - 1) / height; + + newAtlasMap.emplace( + std::piecewise_construct, + std::forward_as_tuple(images[i].first), + std::forward_as_tuple( + layer, + boost::gil::point_t {x, y}, + boost::gil::point_t {imageView.width(), imageView.height()}, + sLeft, + sRight, + tTop, + tBottom)); } - else if (std::holds_alternative( - images[i].second)) + else { - imageView = boost::gil::const_view( - *std::get(images[i].second)); + unpackedImages.push_back(std::move(images[i])); + unpackedRects.push_back(stbrpRects[i]); } + } - boost::gil::rgba8_view_t atlasSubView = - boost::gil::subimage_view(atlasView, - stbrpRects[i].x, - stbrpRects[i].y, - imageView.width(), - imageView.height()); - - boost::gil::copy_pixels(imageView, atlasSubView); - - // Add texture image to the index - const stbrp_coord x = stbrpRects[i].x; - const stbrp_coord y = stbrpRects[i].y; - - const float sLeft = x * xStep + xMin; - const float sRight = - sLeft + static_cast(imageView.width() - 1) / width; - const float tTop = y * yStep + yMin; - const float tBottom = - tTop + static_cast(imageView.height() - 1) / height; - - p->atlasMap_.emplace( - std::piecewise_construct, - std::forward_as_tuple(images[i].first), - std::forward_as_tuple( - boost::gil::point_t {x, y}, - boost::gil::point_t {imageView.width(), imageView.height()}, - sLeft, - sRight, - tTop, - tBottom)); + if (unpackedImages.empty()) + { + // All images have been packed into the texture atlas + break; + } + else if (layer == kMaxLayers - 1u) + { + // Some images were unable to be packed into the texture atlas + for (auto& image : unpackedImages) + { + logger_->warn("Unable to pack texture: {}", image.first); + } } else { - logger_->warn("Unable to pack texture: {}", images[i].first); + // Swap in unpacked images for processing the next atlas layer + images.swap(unpackedImages); + stbrpRects.swap(unpackedRects); + unpackedImages.clear(); + unpackedRects.clear(); } } + // Lock atlas + std::unique_lock lock(p->atlasMutex_); + + p->atlasArray_.swap(newAtlasArray); + p->atlasMap_.swap(newAtlasMap); + // Mark the need to buffer the atlas ++p->buildCount_; } @@ -279,19 +320,28 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) std::shared_lock lock(p->atlasMutex_); - if (p->atlas_.width() > 0u && p->atlas_.height() > 0u) + if (p->atlasArray_.size() > 0u && p->atlasArray_[0].width() > 0 && + p->atlasArray_[0].height() > 0) { - boost::gil::rgba8_view_t view = boost::gil::view(p->atlas_); - std::vector pixelData(view.width() * - view.height()); + const std::size_t numLayers = p->atlasArray_.size(); + const std::size_t width = p->atlasArray_[0].width(); + const std::size_t height = p->atlasArray_[0].height(); + const std::size_t layerSize = width * height; - boost::gil::copy_pixels( - view, - boost::gil::interleaved_view(view.width(), - view.height(), - pixelData.data(), - view.width() * - sizeof(boost::gil::rgba8_pixel_t))); + std::vector pixelData {layerSize * numLayers}; + + for (std::size_t i = 0; i < numLayers; ++i) + { + boost::gil::rgba8_view_t view = boost::gil::view(p->atlasArray_[i]); + + boost::gil::copy_pixels( + view, + boost::gil::interleaved_view(view.width(), + view.height(), + pixelData.data() + (i * layerSize), + view.width() * + sizeof(boost::gil::rgba8_pixel_t))); + } lock.unlock(); @@ -308,9 +358,9 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, - view.width(), - view.height(), - 1u, + static_cast(width), + static_cast(height), + static_cast(numLayers), 0, GL_RGBA, GL_UNSIGNED_BYTE, @@ -424,6 +474,11 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) }); } + boost::gil::write_view( + fmt::format("gil-{}.png", url.fileName().toStdString()), + image._view, + boost::gil::png_tag()); + stbi_image_free(pixelData); } else if (response.status_code == 0) diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp index 5aae76b4..5cc77889 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp @@ -18,6 +18,7 @@ struct TextureAttributes { TextureAttributes() : valid_ {false}, + layerId_ {}, position_ {}, size_ {}, sLeft_ {}, @@ -27,13 +28,15 @@ struct TextureAttributes { } - TextureAttributes(boost::gil::point_t position, + TextureAttributes(std::size_t layerId, + boost::gil::point_t position, boost::gil::point_t size, float sLeft, float sRight, float tTop, float tBottom) : valid_ {true}, + layerId_ {layerId}, position_ {position}, size_ {size}, sLeft_ {sLeft}, @@ -44,6 +47,7 @@ struct TextureAttributes } bool valid_; + std::size_t layerId_; boost::gil::point_t position_; boost::gil::point_t size_; float sLeft_; @@ -70,7 +74,7 @@ public: void RegisterTexture(const std::string& name, const std::string& path); bool CacheTexture(const std::string& name, const std::string& path); - void BuildAtlas(size_t width, size_t height); + void BuildAtlas(std::size_t width, std::size_t height); GLuint BufferAtlas(gl::OpenGLFunctions& gl); TextureAttributes GetTextureAttributes(const std::string& name);