From 14278db0240aefee9af8beb960053aaaea55dce3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 3 Jun 2026 15:27:02 +0200 Subject: [PATCH] Fonts: better document the fact that ImFontAtlas::Clear()/ClearFonts() functions are unlikely to be useful nowadays + fix tex->Updates[] ever-growing if ClearFonts() is called between frames. --- docs/CHANGELOG.txt | 3 +++ imgui.h | 16 +++++++++------- imgui_draw.cpp | 43 ++++++++++++++++++++++++------------------- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 4f8cf7054..98bdb582d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,6 +60,9 @@ Other Changes: disable embedding either fonts separately. (#9407) - Tweak `CalcTextSize()` awkward width rounding/ceiling code to reduce floating-point imprecisions altering the result by 1 even at relatively small width. (#791) + - Better document the fact that ImFontAtlas::Clear()/ClearFonts() functions are + unlikely to be useful nowadays. Better recover to an edge case of mistakenly + calling ClearFonts() during rendering. - Fixed an issue where passing a manually created ImFontAtlas to CreateContext() would incorrectly destroy it in DestroyContext() when ref-count gets back to zero. (#9426) - DrawList: diff --git a/imgui.h b/imgui.h index c4828e591..378a8827a 100644 --- a/imgui.h +++ b/imgui.h @@ -3545,7 +3545,7 @@ struct ImTextureData bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. // Functions - // - If GetPixels() functions asserts while being called by your render loop, it could be caused by calling ImFontAtlas::Clear() instead of ClearFonts()? + // - If GetPixels() functions asserts while being called by your render loop, it could be caused by calling ImFontAtlas::Clear()/ClearFonts()? ImTextureData() { memset((void*)this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; } ~ImTextureData() { DestroyPixels(); } IMGUI_API void Create(ImTextureFormat format, int w, int h); @@ -3699,14 +3699,16 @@ struct ImFontAtlas IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. - IMGUI_API void RemoveFont(ImFont* font); - - IMGUI_API void Clear(); // Clear everything (fonts + textures). Don't call mid-frame! - IMGUI_API void ClearFonts(); // Clear input+output font data/glyphs. You can call this mid-frame if you load new fonts afterwards! - IMGUI_API void CompactCache(); // Compact cached glyphs and texture. + IMGUI_API void RemoveFont(ImFont* font); // Remove a font + IMGUI_API void CompactCache(); // Compact cached glyphs and texture. IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime. - // As we are transitioning toward a new font system, we expect to obsolete those soon: + // Clearing the atlas/fonts has little use nowadays, unless you want to batch remove all fonts. + // - Since 1.92, you can call ClearFonts() mid-frame, if you load new fonts afterwards. + // - As we are transitioning toward our new font system the semantic for those functions gets increasingly misleading and are often a source of issues. + // TL;DR; most likely, don't use any of those functions. We expect to obsolete/rework them. + IMGUI_API void Clear(); // Clear everything (fonts + textures). Don't call mid-frame! + IMGUI_API void ClearFonts(); // Clear input+output font data/glyphs. New fonts and textures will be recreated afterwards. IMGUI_API void ClearInputData(); // [OBSOLETE] Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. IMGUI_API void ClearTexData(); // [OBSOLETE] Clear CPU-side copy of the texture data. Saves RAM once the texture has been copied to graphics memory. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d8f9bf8e3..03376e02d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2691,6 +2691,7 @@ ImFontAtlas::~ImFontAtlas() // Calling this mid-frame will discard the CPU-side copy of the texture data which is generally unreliable as you may have textures queued for creation or updates. void ImFontAtlas::Clear() { + IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlas::Clear()\n"); bool backup_renderer_has_textures = RendererHasTextures; RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. ClearFonts(); @@ -2701,6 +2702,7 @@ void ImFontAtlas::Clear() void ImFontAtlas::ClearFonts() { // FIXME-NEWATLAS: Illegal to remove currently bound font. + IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlas::ClearFonts()\n"); IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); for (ImFont* font : Fonts) ImFontAtlasBuildNotifySetFont(this, font, NULL); @@ -2776,6 +2778,28 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere IM_ASSERT(atlas->Builder == NULL || atlas->Builder->FrameCount < frame_count); // Protection against being called twice. atlas->RendererHasTextures = renderer_has_textures; + // Update texture status and discard old textures. + // (we do this first thing to handle an edge case: if user mistakenly calls ClearFonts()+SetStatus(OK) during + // rendering, it would ImFontAtlasBuildMain() rebuilding before tex->Updates[] gets a chance to be cleared) + // (if somehow we need to move this back lower in the function, we could manually call the code to clear Updates[]). + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + // Update and remove if requested + ImTextureData* tex = atlas->TexList[tex_n]; + if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures) + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK."); + + bool remove_from_list = ImTextureDataUpdateNewFrame(tex); + if (remove_from_list) + { + IM_ASSERT(atlas->TexData != tex); + tex->DestroyPixels(); + IM_DELETE(tex); + atlas->TexList.erase(atlas->TexList.begin() + tex_n); + tex_n--; + } + } + // Check that font atlas was built or backend support texture reload in which case we can build now if (atlas->RendererHasTextures) { @@ -2815,25 +2839,6 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere builder->BakedPool.Size -= builder->BakedDiscardedCount; builder->BakedDiscardedCount = 0; } - - // Update texture status - for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) - { - // Update and remove if requested - ImTextureData* tex = atlas->TexList[tex_n]; - if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures) - IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK."); - - bool remove_from_list = ImTextureDataUpdateNewFrame(tex); - if (remove_from_list) - { - IM_ASSERT(atlas->TexData != tex); - tex->DestroyPixels(); - IM_DELETE(tex); - atlas->TexList.erase(atlas->TexList.begin() + tex_n); - tex_n--; - } - } } bool ImTextureDataUpdateNewFrame(ImTextureData* tex)