Generate multiple texture atlases when first atlas is full

This commit is contained in:
Dan Paulat 2023-09-02 23:37:45 -05:00
parent 7198d1c7af
commit 9766e02f32
10 changed files with 205 additions and 141 deletions

View file

@ -8,14 +8,14 @@
layout (location = 0) in vec2 aLatLong; layout (location = 0) in vec2 aLatLong;
layout (location = 1) in vec2 aXYOffset; 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 = 3) in vec4 aModulate;
uniform mat4 uMVPMatrix; uniform mat4 uMVPMatrix;
uniform mat4 uMapMatrix; uniform mat4 uMapMatrix;
uniform vec2 uMapScreenCoord; uniform vec2 uMapScreenCoord;
smooth out vec2 texCoord; smooth out vec3 texCoord;
smooth out vec4 color; smooth out vec4 color;
vec2 latLngToScreenCoordinate(in vec2 latLng) vec2 latLngToScreenCoordinate(in vec2 latLng)

View file

@ -9,7 +9,7 @@
layout (location = 0) in vec2 aLatLong; layout (location = 0) in vec2 aLatLong;
layout (location = 1) in vec2 aXYOffset; 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 = 3) in vec4 aModulate;
layout (location = 4) in float aAngleDeg; layout (location = 4) in float aAngleDeg;
layout (location = 5) in int aThreshold; layout (location = 5) in int aThreshold;
@ -21,11 +21,11 @@ uniform vec2 uMapScreenCoord;
out VertexData out VertexData
{ {
int threshold; int threshold;
vec2 texCoord; vec3 texCoord;
vec4 color; vec4 color;
} vsOut; } vsOut;
smooth out vec2 texCoord; smooth out vec3 texCoord;
smooth out vec4 color; smooth out vec4 color;
vec2 latLngToScreenCoordinate(in vec2 latLng) vec2 latLngToScreenCoordinate(in vec2 latLng)

View file

@ -12,7 +12,7 @@ uniform vec2 uMapScreenCoord;
out VertexData out VertexData
{ {
int threshold; int threshold;
vec2 texCoord; vec3 texCoord;
vec4 color; vec4 color;
} vsOut; } vsOut;

View file

@ -5,12 +5,12 @@ precision mediump float;
uniform sampler2DArray uTexture; uniform sampler2DArray uTexture;
smooth in vec2 texCoord; smooth in vec3 texCoord;
smooth in vec4 color; smooth in vec4 color;
layout (location = 0) out vec4 fragColor; layout (location = 0) out vec4 fragColor;
void main() void main()
{ {
fragColor = texture(uTexture, vec3(texCoord, 0.0)) * color; fragColor = texture(uTexture, texCoord) * color;
} }

View file

@ -8,11 +8,11 @@ uniform float uMapDistance;
in VertexData in VertexData
{ {
int threshold; int threshold;
vec2 texCoord; vec3 texCoord;
vec4 color; vec4 color;
} gsIn[]; } gsIn[];
smooth out vec2 texCoord; smooth out vec3 texCoord;
smooth out vec4 color; smooth out vec4 color;
void main() void main()

View file

@ -23,7 +23,7 @@ static constexpr size_t kNumRectangles = 1;
static constexpr size_t kNumTriangles = kNumRectangles * 2; static constexpr size_t kNumTriangles = kNumRectangles * 2;
static constexpr size_t kVerticesPerTriangle = 3; static constexpr size_t kVerticesPerTriangle = 3;
static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2;
static constexpr size_t kPointsPerVertex = 10; static constexpr size_t kPointsPerVertex = 11;
static constexpr size_t kBufferLength = static constexpr size_t kBufferLength =
kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; kNumTriangles * kVerticesPerTriangle * kPointsPerVertex;
@ -147,7 +147,7 @@ void GeoLine::Initialize()
// aTexCoord // aTexCoord
gl.glVertexAttribPointer(2, gl.glVertexAttribPointer(2,
2, 3,
GL_FLOAT, GL_FLOAT,
GL_FALSE, GL_FALSE,
kPointsPerVertex * sizeof(float), kPointsPerVertex * sizeof(float),
@ -160,7 +160,7 @@ void GeoLine::Initialize()
GL_FLOAT, GL_FLOAT,
GL_FALSE, GL_FALSE,
kPointsPerVertex * sizeof(float), kPointsPerVertex * sizeof(float),
reinterpret_cast<void*>(6 * sizeof(float))); reinterpret_cast<void*>(7 * sizeof(float)));
gl.glEnableVertexAttribArray(3); gl.glEnableVertexAttribArray(3);
p->dirty_ = true; p->dirty_ = true;
@ -264,6 +264,8 @@ void GeoLine::Impl::Update()
const float oy = width_ * 0.5f * sinf(angle_); const float oy = width_ * 0.5f * sinf(angle_);
// Texture coordinates // Texture coordinates
static constexpr float r = 0.0f;
const float ls = texture_.sLeft_; const float ls = texture_.sLeft_;
const float rs = texture_.sRight_; const float rs = texture_.sRight_;
const float tt = texture_.tTop_; const float tt = texture_.tTop_;
@ -289,12 +291,12 @@ void GeoLine::Impl::Update()
{ // { //
// Line // Line
{ {
{lx, by, -ox, -oy, ls, bt, mc0, mc1, mc2, mc3}, // BL {lx, by, -ox, -oy, ls, bt, r, mc0, mc1, mc2, mc3}, // BL
{lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3}, // TL {lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3}, // TL
{rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR {rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR
{rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR {rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR
{rx, ty, +ox, +oy, rs, tt, mc0, mc1, mc2, mc3}, // TR {rx, ty, +ox, +oy, rs, tt, r, mc0, mc1, mc2, mc3}, // TR
{lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3} // TL {lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3} // TL
}}; }};
gl.glBufferData(GL_ARRAY_BUFFER, gl.glBufferData(GL_ARRAY_BUFFER,

View file

@ -26,7 +26,7 @@ static constexpr std::size_t kNumTriangles = kNumRectangles * 2;
static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerTriangle = 3;
static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2;
static constexpr std::size_t kPointsPerVertex = 9; 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 = static constexpr std::size_t kIconBufferLength =
kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; kNumTriangles * kVerticesPerTriangle * kPointsPerVertex;
static constexpr std::size_t kTextureBufferLength = static constexpr std::size_t kTextureBufferLength =
@ -208,7 +208,7 @@ void PlacefileIcons::Initialize()
// aTexCoord // aTexCoord
gl.glVertexAttribPointer(2, gl.glVertexAttribPointer(2,
2, 3,
GL_FLOAT, GL_FLOAT,
GL_FALSE, GL_FALSE,
kPointsPerTexCoord * sizeof(float), kPointsPerTexCoord * sizeof(float),
@ -508,12 +508,12 @@ void PlacefileIcons::Impl::UpdateTextureBuffer()
textureBuffer_.end(), textureBuffer_.end(),
{ {
// Icon // Icon
0.0f, 0.0f, // BL 0.0f, 0.0f, 0.0f, // BL
0.0f, 0.0f, // TL 0.0f, 0.0f, 0.0f, // TL
0.0f, 0.0f, // BR 0.0f, 0.0f, 0.0f, // BR
0.0f, 0.0f, // BR 0.0f, 0.0f, 0.0f, // BR
0.0f, 0.0f, // TR 0.0f, 0.0f, 0.0f, // TR
0.0f, 0.0f // TL 0.0f, 0.0f, 0.0f // TL
}); });
// clang-format on // clang-format on
@ -536,12 +536,12 @@ void PlacefileIcons::Impl::UpdateTextureBuffer()
textureBuffer_.end(), textureBuffer_.end(),
{ {
// Icon // Icon
0.0f, 0.0f, // BL 0.0f, 0.0f, 0.0f, // BL
0.0f, 0.0f, // TL 0.0f, 0.0f, 0.0f, // TL
0.0f, 0.0f, // BR 0.0f, 0.0f, 0.0f, // BR
0.0f, 0.0f, // BR 0.0f, 0.0f, 0.0f, // BR
0.0f, 0.0f, // TR 0.0f, 0.0f, 0.0f, // TR
0.0f, 0.0f // TL 0.0f, 0.0f, 0.0f // TL
}); });
// clang-format on // clang-format on
@ -559,18 +559,19 @@ void PlacefileIcons::Impl::UpdateTextureBuffer()
const float rs = ls + icon.scaledWidth_; const float rs = ls + icon.scaledWidth_;
const float tt = icon.texture_.tTop_ + iconY; const float tt = icon.texture_.tTop_ + iconY;
const float bt = tt + icon.scaledHeight_; const float bt = tt + icon.scaledHeight_;
const float r = static_cast<float>(icon.texture_.layerId_);
// clang-format off // clang-format off
textureBuffer_.insert( textureBuffer_.insert(
textureBuffer_.end(), textureBuffer_.end(),
{ {
// Icon // Icon
ls, bt, // BL ls, bt, r, // BL
ls, tt, // TL ls, tt, r, // TL
rs, bt, // BR rs, bt, r, // BR
rs, bt, // BR rs, bt, r, // BR
rs, tt, // TR rs, tt, r, // TR
ls, tt // TL ls, tt, r // TL
}); });
// clang-format on // clang-format on
} }

View file

@ -23,7 +23,7 @@ static constexpr std::size_t kNumTriangles = kNumRectangles * 2;
static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerTriangle = 3;
static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2;
static constexpr std::size_t kPointsPerVertex = 8; 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 = static constexpr std::size_t kImageBufferLength =
kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; kNumTriangles * kVerticesPerTriangle * kPointsPerVertex;
static constexpr std::size_t kTextureBufferLength = static constexpr std::size_t kTextureBufferLength =
@ -177,7 +177,7 @@ void PlacefileImages::Initialize()
// aTexCoord // aTexCoord
gl.glVertexAttribPointer(2, gl.glVertexAttribPointer(2,
2, 3,
GL_FLOAT, GL_FLOAT,
GL_FALSE, GL_FALSE,
kPointsPerTexCoord * sizeof(float), kPointsPerTexCoord * sizeof(float),
@ -367,6 +367,8 @@ void PlacefileImages::Impl::UpdateTextureBuffer()
currentImageFiles_.cbegin()->second : currentImageFiles_.cbegin()->second :
it->second; it->second;
const float r = static_cast<float>(image.texture_.layerId_);
// Limit processing to groups of 3 (triangles) // Limit processing to groups of 3 (triangles)
std::size_t numElements = di->elements_.size() - di->elements_.size() % 3; std::size_t numElements = di->elements_.size() - di->elements_.size() % 3;
for (std::size_t i = 0; i < numElements; ++i) for (std::size_t i = 0; i < numElements; ++i)
@ -379,7 +381,7 @@ void PlacefileImages::Impl::UpdateTextureBuffer()
const float t = const float t =
image.texture_.tTop_ + (image.scaledHeight_ * element.tv_); image.texture_.tTop_ + (image.scaledHeight_ * element.tv_);
textureBuffer_.insert(textureBuffer_.end(), {s, t}); textureBuffer_.insert(textureBuffer_.end(), {s, t, r});
} }
} }
} }

View file

@ -50,7 +50,7 @@ public:
std::shared_mutex textureCacheMutex_ {}; std::shared_mutex textureCacheMutex_ {};
std::unordered_map<std::string, boost::gil::rgba8_image_t> textureCache_ {}; std::unordered_map<std::string, boost::gil::rgba8_image_t> textureCache_ {};
boost::gil::rgba8_image_t atlas_ {}; std::vector<boost::gil::rgba8_image_t> atlasArray_ {};
std::unordered_map<std::string, TextureAttributes> atlasMap_ {}; std::unordered_map<std::string, TextureAttributes> atlasMap_ {};
std::shared_mutex atlasMutex_ {}; std::shared_mutex atlasMutex_ {};
@ -95,7 +95,7 @@ bool TextureAtlas::CacheTexture(const std::string& name,
return false; 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); logger_->debug("Building {}x{} texture atlas", width, height);
@ -105,11 +105,13 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height)
return; return;
} }
std::vector<std::pair< typedef std::vector<std::pair<
std::string, std::string,
std::variant<boost::gil::rgba8_image_t, boost::gil::rgba8_image_t*>>> std::variant<boost::gil::rgba8_image_t, boost::gil::rgba8_image_t*>>>
images; ImageVector;
std::vector<stbrp_rect> stbrpRects;
ImageVector images {};
std::vector<stbrp_rect> stbrpRects {};
// Load images // Load images
{ {
@ -171,13 +173,30 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height)
} }
} }
// Pack images // GL_MAX_ARRAY_TEXTURE_LAYERS is guaranteed to be at least 256 in OpenGL 3.3
{ constexpr std::size_t kMaxLayers = 256u;
logger_->trace("Packing {} images", images.size());
const float xStep = 1.0f / width;
const float yStep = 1.0f / height;
const float xMin = xStep * 0.5f;
const float yMin = yStep * 0.5f;
// Optimal number of nodes = width // Optimal number of nodes = width
stbrp_context stbrpContext; stbrp_context stbrpContext;
std::vector<stbrp_node> stbrpNodes(width); std::vector<stbrp_node> stbrpNodes(width);
ImageVector unpackedImages {};
std::vector<stbrp_rect> unpackedRects {};
std::vector<boost::gil::rgba8_image_t> newAtlasArray {};
std::unordered_map<std::string, TextureAttributes> newAtlasMap {};
for (std::size_t layer = 0; layer < kMaxLayers; ++layer)
{
logger_->trace("Processing layer {}", layer);
// Pack images
{
logger_->trace("Packing {} images", images.size());
stbrp_init_target(&stbrpContext, stbrp_init_target(&stbrpContext,
static_cast<int>(width), static_cast<int>(width),
@ -186,31 +205,22 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height)
static_cast<int>(stbrpNodes.size())); static_cast<int>(stbrpNodes.size()));
// Pack loaded textures // Pack loaded textures
stbrp_pack_rects( stbrp_pack_rects(&stbrpContext,
&stbrpContext, stbrpRects.data(), static_cast<int>(stbrpRects.size())); stbrpRects.data(),
static_cast<int>(stbrpRects.size()));
} }
// Lock atlas
std::unique_lock lock(p->atlasMutex_);
// Clear index
p->atlasMap_.clear();
// Clear atlas // Clear atlas
p->atlas_.recreate(width, height); auto& atlas =
boost::gil::rgba8_view_t atlasView = boost::gil::view(p->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::fill_pixels(atlasView,
boost::gil::rgba8_pixel_t {255, 0, 255, 255}); boost::gil::rgba8_pixel_t {255, 0, 255, 255});
// Populate atlas // Populate atlas
logger_->trace("Populating atlas"); logger_->trace("Populating atlas");
const float xStep = 1.0f / width; for (std::size_t i = 0; i < images.size(); ++i)
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++)
{ {
// If the image was packed successfully // If the image was packed successfully
if (stbrpRects[i].was_packed != 0) if (stbrpRects[i].was_packed != 0)
@ -252,10 +262,11 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height)
const float tBottom = const float tBottom =
tTop + static_cast<float>(imageView.height() - 1) / height; tTop + static_cast<float>(imageView.height() - 1) / height;
p->atlasMap_.emplace( newAtlasMap.emplace(
std::piecewise_construct, std::piecewise_construct,
std::forward_as_tuple(images[i].first), std::forward_as_tuple(images[i].first),
std::forward_as_tuple( std::forward_as_tuple(
layer,
boost::gil::point_t {x, y}, boost::gil::point_t {x, y},
boost::gil::point_t {imageView.width(), imageView.height()}, boost::gil::point_t {imageView.width(), imageView.height()},
sLeft, sLeft,
@ -265,10 +276,40 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height)
} }
else else
{ {
logger_->warn("Unable to pack texture: {}", images[i].first); unpackedImages.push_back(std::move(images[i]));
unpackedRects.push_back(stbrpRects[i]);
} }
} }
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
{
// 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 // Mark the need to buffer the atlas
++p->buildCount_; ++p->buildCount_;
} }
@ -279,19 +320,28 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl)
std::shared_lock lock(p->atlasMutex_); 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_); const std::size_t numLayers = p->atlasArray_.size();
std::vector<boost::gil::rgba8_pixel_t> pixelData(view.width() * const std::size_t width = p->atlasArray_[0].width();
view.height()); const std::size_t height = p->atlasArray_[0].height();
const std::size_t layerSize = width * height;
std::vector<boost::gil::rgba8_pixel_t> 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( boost::gil::copy_pixels(
view, view,
boost::gil::interleaved_view(view.width(), boost::gil::interleaved_view(view.width(),
view.height(), view.height(),
pixelData.data(), pixelData.data() + (i * layerSize),
view.width() * view.width() *
sizeof(boost::gil::rgba8_pixel_t))); sizeof(boost::gil::rgba8_pixel_t)));
}
lock.unlock(); lock.unlock();
@ -308,9 +358,9 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl)
gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, gl.glTexImage3D(GL_TEXTURE_2D_ARRAY,
0, 0,
GL_RGBA, GL_RGBA,
view.width(), static_cast<GLsizei>(width),
view.height(), static_cast<GLsizei>(height),
1u, static_cast<GLsizei>(numLayers),
0, 0,
GL_RGBA, GL_RGBA,
GL_UNSIGNED_BYTE, 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); stbi_image_free(pixelData);
} }
else if (response.status_code == 0) else if (response.status_code == 0)

View file

@ -18,6 +18,7 @@ struct TextureAttributes
{ {
TextureAttributes() : TextureAttributes() :
valid_ {false}, valid_ {false},
layerId_ {},
position_ {}, position_ {},
size_ {}, size_ {},
sLeft_ {}, 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, boost::gil::point_t size,
float sLeft, float sLeft,
float sRight, float sRight,
float tTop, float tTop,
float tBottom) : float tBottom) :
valid_ {true}, valid_ {true},
layerId_ {layerId},
position_ {position}, position_ {position},
size_ {size}, size_ {size},
sLeft_ {sLeft}, sLeft_ {sLeft},
@ -44,6 +47,7 @@ struct TextureAttributes
} }
bool valid_; bool valid_;
std::size_t layerId_;
boost::gil::point_t position_; boost::gil::point_t position_;
boost::gil::point_t size_; boost::gil::point_t size_;
float sLeft_; float sLeft_;
@ -70,7 +74,7 @@ public:
void RegisterTexture(const std::string& name, const std::string& path); void RegisterTexture(const std::string& name, const std::string& path);
bool CacheTexture(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); GLuint BufferAtlas(gl::OpenGLFunctions& gl);
TextureAttributes GetTextureAttributes(const std::string& name); TextureAttributes GetTextureAttributes(const std::string& name);