Avoid regenerating the UI Atlas if unnecessary (faster resize)

This commit is contained in:
Henrik Rydgård
2025-11-06 08:47:45 +01:00
parent 602418aa1f
commit 56d3dae334
7 changed files with 43 additions and 23 deletions
+5 -5
View File
@@ -100,12 +100,12 @@ inline bool CompareByArea(const Data& lhs, const Data& rhs) {
return lhs.w * lhs.h > rhs.w * rhs.h;
}
std::vector<Data> Bucket::Resolve(int image_width, Image &dest) {
std::vector<Data> 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<Data> 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<Data> 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;
+7 -1
View File
@@ -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<Data> Resolve(int image_width, Image &dest);
std::vector<Data> Resolve(int image_width, Image *dest);
};
AtlasImage ToAtlasImage(int id, std::string_view name, float tw, float th, const std::vector<Data> &results);
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -54,7 +54,7 @@ struct AtlasData {
Draw::Texture *texture;
};
typedef std::function<AtlasData(Draw::DrawContext *, AtlasChoice, float dpiScale)> UIAtlasProviderFunc;
typedef std::function<AtlasData(Draw::DrawContext *, AtlasChoice, float dpiScale, bool invalidate)> UIAtlasProviderFunc;
class UIContext {
public:
+26 -12
View File
@@ -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<Data> results = bucket.Resolve(image_width, dest);
@@ -379,7 +378,7 @@ Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas, float dpiS
std::vector<AtlasImage> 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:
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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<Data> results = bucket.Resolve(image_width, dest);
std::vector<Data> 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);