diff --git a/Common/Render/AtlasGen.cpp b/Common/Render/AtlasGen.cpp index 743026d6eb..95034843fe 100644 --- a/Common/Render/AtlasGen.cpp +++ b/Common/Render/AtlasGen.cpp @@ -100,12 +100,12 @@ 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) { +std::vector Bucket::Resolve(int image_width, Image *dest) { // 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); + dest->resize(image_width, 1); std::sort(data.begin(), data.end(), CompareByArea); for (int i = 0; i < (int)data.size(); i++) { if ((i + 1) % 2000 == 0) { @@ -116,10 +116,10 @@ std::vector Bucket::Resolve(int image_width, Image &dest) { if (idx > 1 && idy > 1) { assert(idx <= image_width); for (int ty = 0; ty < 2047; ty++) { - if (ty + idy + 1 > (int)dest.height()) { + if (ty + idy + 1 > (int)dest->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); + dest->resize(image_width, ty + idy + 16); } // Brute force packing. int sz = (int)data[i].w; @@ -160,7 +160,7 @@ std::vector Bucket::Resolve(int image_width, Image &dest) { // 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); + 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 ee2d4026fd..5cb96a9a41 100644 --- a/Common/Render/AtlasGen.h +++ b/Common/Render/AtlasGen.h @@ -46,6 +46,12 @@ struct Image { Image(Image &&) = default; Image &operator=(Image &&) = default; + void clear() { + dat.clear(); + w = 0; + h = 0; + } + float scale = 1.0f; int w = 0; @@ -118,7 +124,7 @@ struct Bucket { data.emplace_back(dat); } void AddImage(Image &&img, int id); - std::vector Resolve(int image_width, Image &dest); + std::vector Resolve(int image_width, Image *dest); }; AtlasImage ToAtlasImage(int id, std::string_view name, float tw, float th, const std::vector &results); diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp index 61d23028ce..0a76f82e37 100644 --- a/Common/UI/Context.cpp +++ b/Common/UI/Context.cpp @@ -46,7 +46,7 @@ void UIContext::BeginFrame() { if (uitexture_) { uitexture_->Release(); } - AtlasData data = atlasProvider_(draw_, AtlasChoice::General, 1.0f / g_display.dpi_scale_x); + AtlasData data = atlasProvider_(draw_, AtlasChoice::General, 1.0f / g_display.dpi_scale_x, atlasInvalid_); uitexture_ = data.texture; _dbg_assert_(uitexture_); ui_draw2d.SetAtlas(data.atlas); @@ -54,7 +54,7 @@ void UIContext::BeginFrame() { } if (!fontTexture_) { - AtlasData data = atlasProvider_(draw_, AtlasChoice::Font, 1.0f / g_display.dpi_scale_x); + AtlasData data = atlasProvider_(draw_, AtlasChoice::Font, 1.0f / g_display.dpi_scale_x, false); fontTexture_ = data.texture; _dbg_assert_(fontTexture_); ui_draw2d.SetFontAtlas(data.atlas); diff --git a/Common/UI/Context.h b/Common/UI/Context.h index 5826485884..4c7060cea4 100644 --- a/Common/UI/Context.h +++ b/Common/UI/Context.h @@ -54,7 +54,7 @@ struct AtlasData { Draw::Texture *texture; }; -typedef std::function UIAtlasProviderFunc; +typedef std::function UIAtlasProviderFunc; class UIContext { public: diff --git a/UI/UIAtlas.cpp b/UI/UIAtlas.cpp index 394fed4e8c..dc4f631759 100644 --- a/UI/UIAtlas.cpp +++ b/UI/UIAtlas.cpp @@ -178,7 +178,7 @@ static bool IsImageID(std::string_view id) { return GetImageIndex(id) != -1; } -Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas, float dpiScale) { +static bool GenerateUIAtlasImage(Atlas *atlas, float dpiScale, Image *dest) { Bucket bucket; // Script fully read, now read images and rasterize the fonts. @@ -368,7 +368,6 @@ Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas, float dpiS INFO_LOG(Log::G3D, " - Added %zu images to bucket in %.2f ms", bucket.data.size(), addStart.ElapsedMs()); int image_width = 512; - Image dest; Instant bucketStart = Instant::Now(); std::vector results = bucket.Resolve(image_width, dest); @@ -379,7 +378,7 @@ Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas, float dpiS std::vector genAtlasImages; genAtlasImages.reserve(ARRAY_SIZE(imageIDs)); for (int i = 0; i < ARRAY_SIZE(imageIDs); i++) { - genAtlasImages.push_back(ToAtlasImage(resultIds[i], imageIDs[i].id, (float)dest.width(), (float)dest.height(), results)); + genAtlasImages.push_back(ToAtlasImage(resultIds[i], imageIDs[i].id, (float)dest->width(), (float)dest->height(), results)); } atlas->Clear(); @@ -389,21 +388,36 @@ Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas, float dpiS // For debug, write out the atlas. if (SAVE_DEBUG_IMAGES) { - dest.SavePNG("../ui_atlas_gen.png"); + dest->SavePNG("../ui_atlas_gen.png"); } + INFO_LOG(Log::G3D, "UI atlas generated in %.2f ms, size %dx%d with %zu images\n", svgStart.ElapsedMs(), dest->width(), dest->height(), genAtlasImages.size()); + return true; +} - // Then, create the texture too. +static Image g_cachedUIAtlasImage; +static float g_cachedDpiScale = 0.0f; + +// The caller must cache the Atlas. +Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas, float dpiScale, bool invalidate) { + if (g_cachedUIAtlasImage.IsEmpty() || dpiScale != g_cachedDpiScale || invalidate) { + g_cachedUIAtlasImage.clear(); + if (!GenerateUIAtlasImage(atlas, dpiScale, &g_cachedUIAtlasImage)) { + ERROR_LOG(Log::G3D, "Failed to generate UI atlas!"); + return nullptr; + } + } + g_cachedDpiScale = dpiScale; + + // Create the texture. Draw::TextureDesc desc{}; - desc.width = image_width; - desc.height = dest.height(); + desc.width = g_cachedUIAtlasImage.width(); + desc.height = g_cachedUIAtlasImage.height(); desc.depth = 1; desc.mipLevels = 1; desc.format = Draw::DataFormat::R8G8B8A8_UNORM; desc.type = Draw::TextureType::LINEAR2D; - desc.initData.push_back((const u8 *)dest.data()); + desc.initData.push_back((const u8 *)g_cachedUIAtlasImage.data()); desc.tag = "UIAtlas"; - - INFO_LOG(Log::G3D, "UI atlas generated in %.2f ms, size %dx%d with %zu images\n", svgStart.ElapsedMs(), desc.width, desc.height, genAtlasImages.size()); return draw->CreateTexture(desc); } @@ -418,7 +432,7 @@ static void LoadAtlasMetadata(Atlas &metadata, const char *filename) { delete[] atlas_data; } -AtlasData AtlasProvider(Draw::DrawContext *draw, AtlasChoice atlas, float dpiScale) { +AtlasData AtlasProvider(Draw::DrawContext *draw, AtlasChoice atlas, float dpiScale, bool invalidate) { // Clamp the dpiScale to sane values. Might increase the range later. dpiScale = std::clamp(dpiScale, 0.5f, 4.0f); @@ -426,7 +440,7 @@ AtlasData AtlasProvider(Draw::DrawContext *draw, AtlasChoice atlas, float dpiSca case AtlasChoice::General: { // Generate the atlas from scratch. - Draw::Texture *tex = GenerateUIAtlas(draw, &ui_atlas, dpiScale); + Draw::Texture *tex = GenerateUIAtlas(draw, &ui_atlas, dpiScale, invalidate); return {&ui_atlas, tex}; } case AtlasChoice::Font: diff --git a/UI/UIAtlas.h b/UI/UIAtlas.h index 395d265bc8..e84257ba37 100644 --- a/UI/UIAtlas.h +++ b/UI/UIAtlas.h @@ -5,4 +5,4 @@ const Atlas *GetFontAtlas(); Atlas *GetUIAtlas(); -AtlasData AtlasProvider(Draw::DrawContext *draw, AtlasChoice atlas, float dpiScale); +AtlasData AtlasProvider(Draw::DrawContext *draw, AtlasChoice atlas, float dpiScale, bool invalidate); diff --git a/ext/native/tools/atlastool.cpp b/ext/native/tools/atlastool.cpp index a0d8d096b5..2f88b166eb 100644 --- a/ext/native/tools/atlastool.cpp +++ b/ext/native/tools/atlastool.cpp @@ -654,7 +654,7 @@ int GenerateFromScript(const char *script_file, const char *atlas_name, bool hig // Place things on the bitmap. printf("Resolving...\n"); - std::vector results = bucket.Resolve(image_width, dest); + std::vector results = bucket.Resolve(image_width, &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);