From 915a88df60ca27698dc0093923d99db57785ab44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 15 Nov 2025 14:46:13 +0100 Subject: [PATCH] AtlasGen: Separate pack and resolve steps --- Common/Render/AtlasGen.cpp | 15 +++++++++------ Common/Render/AtlasGen.h | 7 ++++++- UI/UIAtlas.cpp | 8 ++++++-- ext/native/tools/atlastool.cpp | 6 ++++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Common/Render/AtlasGen.cpp b/Common/Render/AtlasGen.cpp index 0e5cc3ece8..83b82a8295 100644 --- a/Common/Render/AtlasGen.cpp +++ b/Common/Render/AtlasGen.cpp @@ -100,12 +100,11 @@ inline bool CompareByArea(const Data& lhs, const Data& rhs) { return lhs.w * lhs.h > rhs.w * rhs.h; } -std::vector Bucket::Resolve(int image_width, Image *dest) { +void Bucket::Pack(int image_width) { // Place all the little images - whatever they are. // Uses greedy fill algorithm. Slow but works surprisingly well, CPUs are fast. ImageU8 masq; masq.resize(image_width, 1); - dest->resize(image_width, 1); // image_width is set to the square root of the total area of all images. // We shouldn't need more than twice that in height (more likely much less). @@ -120,11 +119,10 @@ std::vector Bucket::Resolve(int image_width, Image *dest) { int idy = (int)data[i].h; if (idx > 1 && idy > 1) { assert(idx <= image_width); - for (int ty = 0; ty < maxHeight - 1; ty++) { // TODO: Maybe remove this limit? - if (ty + idy + 1 > (int)dest->height()) { + for (int ty = 0; ty < maxHeight - 1; ty++) { + if (ty + idy + 1 > (int)masq.height()) { // Every 16 lines of new space needed, grow the image. masq.resize(image_width, ty + idy + 16); - dest->resize(image_width, ty + idy + 16); } // Brute force packing. int sz = (int)data[i].w; @@ -163,11 +161,16 @@ std::vector Bucket::Resolve(int image_width, Image *dest) { // Sort the data back by ID. std::sort(data.begin(), data.end(), CompareByID); + w = image_width; + h = masq.height(); +} + +std::vector Bucket::Resolve(Image *dest) { + dest->resize(w, h); // Actually copy the image data in place, after doing the layout. for (int i = 0; i < (int)data.size(); i++) { dest->copyfrom(images[i], data[i].sx, data[i].sy, data[i].redToWhiteAlpha); } - return data; } diff --git a/Common/Render/AtlasGen.h b/Common/Render/AtlasGen.h index 5cb96a9a41..c3c2202fa9 100644 --- a/Common/Render/AtlasGen.h +++ b/Common/Render/AtlasGen.h @@ -119,12 +119,17 @@ struct Data { struct Bucket { std::vector images; std::vector data; + int w = 0; + int h = 0; + void AddItem(Image &&img, const Data &dat) { images.emplace_back(std::move(img)); data.emplace_back(dat); } void AddImage(Image &&img, int id); - std::vector Resolve(int image_width, Image *dest); + + void Pack(int image_width); + std::vector Resolve(Image *dest); }; AtlasImage ToAtlasImage(int id, std::string_view name, float tw, float th, const std::vector &results); diff --git a/UI/UIAtlas.cpp b/UI/UIAtlas.cpp index cb55c35458..377064e7e7 100644 --- a/UI/UIAtlas.cpp +++ b/UI/UIAtlas.cpp @@ -385,8 +385,12 @@ static bool GenerateUIAtlasImage(Atlas *atlas, float dpiScale, Image *dest, int int imageWidth = RoundToNextPowerOf2((int)sqrtf(area)); Instant bucketStart = Instant::Now(); - std::vector results = bucket.Resolve(imageWidth, dest); - INFO_LOG(Log::G3D, " - Bucketed %zu images in %.2f ms (final image size: %dx%d)", results.size(), bucketStart.ElapsedMs(), dest->width(), dest->height()); + bucket.Pack(imageWidth); + INFO_LOG(Log::G3D, " - Packed in %.2f ms (image size: %dx%d)", bucketStart.ElapsedMs(), bucket.w, bucket.h); + + Instant resolveStart = Instant::Now(); + std::vector results = bucket.Resolve(dest); + INFO_LOG(Log::G3D, " - Resolved %zu images in %.2f ms (final image size: %dx%d)", results.size(), resolveStart.ElapsedMs(), dest->width(), dest->height()); _dbg_assert_(!results.empty()); // Fill out the atlas structure. diff --git a/ext/native/tools/atlastool.cpp b/ext/native/tools/atlastool.cpp index 2f88b166eb..bf5f0546ce 100644 --- a/ext/native/tools/atlastool.cpp +++ b/ext/native/tools/atlastool.cpp @@ -652,9 +652,11 @@ int GenerateFromScript(const char *script_file, const char *atlas_name, bool hig // Place the subimages onto the main texture. Also writes to png. Image dest; // Place things on the bitmap. - printf("Resolving...\n"); - std::vector results = bucket.Resolve(image_width, &dest); + printf("Packing...\n"); + bucket.Pack(image_width); + printf("Resolving...\n"); + std::vector results = bucket.Resolve(&dest); if (highcolor) { printf("Writing .ZIM %ix%i RGBA8888...\n", dest.width(), dest.height()); dest.SaveZIM(image_name.c_str(), ZIM_RGBA8888 | ZIM_ZSTD_COMPRESSED);