From 201899b611c34d35e6e38778abab91d76b0451c0 Mon Sep 17 00:00:00 2001 From: Dylam De La Torre Date: Wed, 4 Jun 2025 23:08:19 +0200 Subject: [PATCH 001/191] Backends: OpenGL3: Fixed using non-existing features on GLES 3.20 which would push a GL error. (#8664) * GL_PRIMITIVE_RESTART is not a valid enum for glEnable&co on GLES 3.20 * GL_CONTEXT_PROFILE_MASK is not a valid enum for glGetIntegerv on GLES 3.20 --- backends/imgui_impl_opengl3.cpp | 18 ++++++++++-------- docs/CHANGELOG.txt | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 7d0b53621..8b7a2ecb5 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -22,6 +22,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664) // 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406) // 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-06-28: OpenGL: ImGui_ImplOpenGL3_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL3_DestroyFontsTexture(). (#7748) @@ -325,11 +326,6 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) if (major == 0 && minor == 0) sscanf(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "." bd->GlVersion = (GLuint)(major * 100 + minor * 10); -#if defined(GL_CONTEXT_PROFILE_MASK) - if (bd->GlVersion >= 320) - glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask); - bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0; -#endif #if defined(IMGUI_IMPL_OPENGL_ES3) bd->GlProfileIsES3 = true; @@ -338,6 +334,12 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) bd->GlProfileIsES3 = true; #endif +#if defined(GL_CONTEXT_PROFILE_MASK) + if (!bd->GlProfileIsES3 && bd->GlVersion >= 320) + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask); + bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0; +#endif + bd->UseBufferSubData = false; /* // Query vendor to enable glBufferSubData kludge @@ -439,7 +441,7 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid glDisable(GL_STENCIL_TEST); glEnable(GL_SCISSOR_TEST); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART - if (bd->GlVersion >= 310) + if (!bd->GlProfileIsES3 && bd->GlVersion >= 310) glDisable(GL_PRIMITIVE_RESTART); #endif #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE @@ -551,7 +553,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST); GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART - GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE; + GLboolean last_enable_primitive_restart = (!bd->GlProfileIsES3 && bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE; #endif // Setup desired GL state @@ -670,7 +672,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART - if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); } + if (!bd->GlProfileIsES3 && bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); } #endif #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e490133ca..059daf5b5 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -149,6 +149,8 @@ Other changes: - Backends: SDLGPU3: Made ImGui_ImplSDLGPU3_PrepareDrawData() reuse GPU Transfer Buffers which were unusually slow to recreate every frame. Much faster now. (#8534) [@ocornut, @TheMode] - Backends: SDLGPU3: added support for ImDrawCallback_ResetRenderState. (#8599) +- Backends: OpenGL3: made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor + GL_PRIMITIVE_RESTART. (#8664) [@DyXel] - Backends: DirectX10, DirectX11, DirectX12: Honor FramebufferScale to allow for custom platform backends and experiments using it (consistently with other renderer backends, even though in normal condition it is not set under Windows). (#8412) [@WSSDude] From b2f39318cb10eb51f37f393bd5c6bbe07dd0edcd Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 11 Jun 2025 13:41:06 +0200 Subject: [PATCH 002/191] Adding .cache to ignore list. (#8674) --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6cadd63b4..6f6c50cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -29,8 +29,9 @@ ipch ## Getting files created in JSON/Schemas/Catalog/ from a VS2022 update JSON/ -## Commonly used CMake directories +## Commonly used CMake directories & CMake CPM cache build*/ +.cache ## Xcode & macOS artifacts project.xcworkspace From 191a728ecca454f11ff473e285804d1f7362a83d Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 17 Mar 2025 19:13:17 +0100 Subject: [PATCH 003/191] (Breaking) added ImTextureRef struct. Changed ImDrawCmd::TextureId to TexRef. Softly breaking. May require support from language binding generator. Rebased and reworked completely on 2025/03/19. --- imgui.cpp | 30 +++++------ imgui.h | 75 +++++++++++++++++++--------- imgui_demo.cpp | 2 +- imgui_draw.cpp | 85 +++++++++++++++++--------------- imgui_internal.h | 2 +- imgui_widgets.cpp | 22 ++++----- misc/freetype/imgui_freetype.cpp | 2 +- 7 files changed, 126 insertions(+), 92 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index df41d9713..bb5819a37 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3841,12 +3841,12 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale))) continue; ImDrawList* draw_list = GetForegroundDrawList(viewport); - ImTextureID tex_id = font_atlas->TexID; - draw_list->PushTextureID(tex_id); - draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[2], uv[3], col_border); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[0], uv[1], col_fill); + ImTextureRef tex_ref = font_atlas->TexID; + draw_list->PushTexture(tex_ref); + draw_list->AddImage(tex_ref, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_ref, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_ref, pos, pos + size * scale, uv[2], uv[3], col_border); + draw_list->AddImage(tex_ref, pos, pos + size * scale, uv[0], uv[1], col_fill); if (mouse_cursor == ImGuiMouseCursor_Wait || mouse_cursor == ImGuiMouseCursor_Progress) { float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI); @@ -3854,7 +3854,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max); draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale); } - draw_list->PopTextureID(); + draw_list->PopTexture(); } } @@ -4925,7 +4925,7 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount) { draw_list->_ResetForNewFrame(); - draw_list->PushTextureID(g.IO.Fonts->TexID); + draw_list->PushTexture(g.IO.Fonts->TexID); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount; } @@ -7580,7 +7580,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Setup draw list and outer clipping rectangle IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0); - window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + window->DrawList->PushTexture(g.Font->ContainerAtlas->TexID); PushClipRect(host_rect.Min, host_rect.Max, false); // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71) @@ -8553,7 +8553,7 @@ void ImGui::PushFont(ImFont* font) font = GetDefaultFont(); g.FontStack.push_back(font); SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); + g.CurrentWindow->DrawList->_SetTexture(font->ContainerAtlas->TexID); } void ImGui::PopFont() @@ -8567,7 +8567,7 @@ void ImGui::PopFont() g.FontStack.pop_back(); ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back(); SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); + g.CurrentWindow->DrawList->_SetTexture(font->ContainerAtlas->TexID); } //----------------------------------------------------------------------------- @@ -15500,11 +15500,11 @@ void ImGui::UpdateDebugToolFlashStyleColor() DebugFlashStyleColorStop(); } -static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureRef tex_ref) { union { void* ptr; int integer; } tex_id_opaque; - memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); - if (sizeof(tex_id) >= sizeof(void*)) + memcpy(&tex_id_opaque, &tex_ref._TexID, ImMin(sizeof(void*), sizeof(tex_ref._TexID))); + if (sizeof(tex_ref._TexID) >= sizeof(void*)) ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); else ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); @@ -16245,7 +16245,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con } char texid_desc[20]; - FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TextureId); + FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TexRef); char buf[300]; ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount / 3, texid_desc, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); diff --git a/imgui.h b/imgui.h index 6d062c190..0e8f9c1ae 100644 --- a/imgui.h +++ b/imgui.h @@ -37,7 +37,7 @@ Index of this file: // [SECTION] Header mess // [SECTION] Forward declarations and basic types -// [SECTION] Texture identifier (ImTextureID) +// [SECTION] Texture identifiers (ImTextureID, ImTextureRef) // [SECTION] Dear ImGui end-user API functions // [SECTION] Flags & Enumerations // [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) @@ -301,18 +301,38 @@ struct ImVec4 IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- -// [SECTION] Texture identifier (ImTextureID) +// [SECTION] Texture identifiers (ImTextureID, ImTextureRef) //----------------------------------------------------------------------------- -// ImTexture: user data for renderer backend to identify a texture [Compile-time configurable type] +// ImTextureID: user data for renderer backend to identify a texture [Compile-time configurable type] // - To use something else than an opaque void* pointer: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file. // - This can be whatever to you want it to be! read the FAQ about ImTextureID for details. // - You can make this a structure with various constructors if you need. You will have to implement ==/!= operators. // - (note: before v1.91.4 (2024/10/08) the default type for ImTextureID was void*. Use intermediary intptr_t cast and read FAQ if you have casting warnings) #ifndef ImTextureID -typedef ImU64 ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) +typedef ImU64 ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) #endif +// ImTextureRef contains: +// - a texture/atlas pointer, typically when created by Dear ImGui itself. +// - OR a raw ImTextureID value (user/backend identifier), typically when created by user code to load images. +// There is no constructor to create a ImTextureID from a ImTextureData* as we don't expect this to be useful to the end-user. +IM_MSVC_RUNTIME_CHECKS_OFF +struct ImTextureRef +{ + ImTextureRef() { memset(this, 0, sizeof(*this)); } + ImTextureRef(ImTextureID tex_id) { memset(this, 0, sizeof(*this)); _TexID = tex_id; } +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImTextureRef(void* tex_id) { memset(this, 0, sizeof(*this)); _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID + //inline operator intptr_t() const { return (intptr_t)_TexID; } // For legacy backends casting to ImTextureID +#endif + + // Members + ImFontAtlas* _Atlas; // Texture/Atlas pointer + ImTextureID _TexID; // _OR_ Underlying user/backend texture identifier, or zero if not yet uploaded. +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] Dear ImGui end-user API functions // (Note that ImGui:: being a namespace, you can add extra ImGui:: functions in your own separate file. Please don't modify imgui source files!) @@ -567,9 +587,9 @@ namespace ImGui // - Image() pads adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side. // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. // - An obsolete version of Image(), before 1.91.9 (March 2025), had a 'tint_col' parameter which is now supported by the ImageWithBg() function. - IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1)); - IMGUI_API void ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); - IMGUI_API bool ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); + IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1)); + IMGUI_API void ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); + IMGUI_API bool ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // Widgets: Combo Box (Dropdown) // - The BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() items. @@ -3004,11 +3024,11 @@ typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* c // - VtxOffset: When 'io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset' is enabled, // this fields allow us to render meshes larger than 64K vertices while keeping 16-bit indices. // Backends made for <1.71. will typically ignore the VtxOffset fields. -// - The ClipRect/TextureId/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for). +// - The ClipRect/TexRef/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for). struct ImDrawCmd { ImVec4 ClipRect; // 4*4 // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in "viewport" coordinates - ImTextureID TextureId; // 4-8 // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImTextureRef TexRef; // 16 // User-provided texture ID. Set by user in ImFontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. unsigned int VtxOffset; // 4 // Start offset in vertex buffer. ImGuiBackendFlags_RendererHasVtxOffset: always 0, otherwise may be >0 to support meshes larger than 64K vertices with 16-bit indices. unsigned int IdxOffset; // 4 // Start offset in index buffer. unsigned int ElemCount; // 4 // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. @@ -3020,7 +3040,8 @@ struct ImDrawCmd ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) - inline ImTextureID GetTexID() const { return TextureId; } + // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! + inline ImTextureID GetTexID() const { return TexRef._TexID; } }; // Vertex layout @@ -3043,7 +3064,7 @@ IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; struct ImDrawCmdHeader { ImVec4 ClipRect; - ImTextureID TextureId; + ImTextureRef TexRef; unsigned int VtxOffset; }; @@ -3128,7 +3149,7 @@ struct ImDrawList ImDrawCmdHeader _CmdHeader; // [Internal] template of active commands. Fields should match those of CmdBuffer.back(). ImDrawListSplitter _Splitter; // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) ImVector _ClipRectStack; // [Internal] - ImVector _TextureIdStack; // [Internal] + ImVector _TextureStack; // [Internal] ImVector _CallbacksDataBuf; // [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content const char* _OwnerName; // Pointer to owner window's name for debugging @@ -3141,8 +3162,8 @@ struct ImDrawList IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) IMGUI_API void PushClipRectFullScreen(); IMGUI_API void PopClipRect(); - IMGUI_API void PushTextureID(ImTextureID texture_id); - IMGUI_API void PopTextureID(); + IMGUI_API void PushTexture(ImTextureRef tex_ref); + IMGUI_API void PopTexture(); inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } @@ -3180,12 +3201,12 @@ struct ImDrawList IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); // Image primitives - // - Read FAQ to understand what ImTextureID is. + // - Read FAQ to understand what ImTextureID/ImTextureRef are. // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle. // - "uv_min" and "uv_max" represent the normalized texture coordinates to use for those corners. Using (0,0)->(1,1) texture coordinates will generally display the entire texture. - IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); - IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); - IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); + IMGUI_API void AddImage(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageQuad(ImTextureRef tex_ref, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); // Stateful path API, add points then finish with PathFillConvex() or PathStroke() // - Important: filled shapes must always use clockwise winding order! The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. @@ -3241,6 +3262,10 @@ struct ImDrawList inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } // Write vertex with unique index // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //IMGUI_API void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.x + //IMGUI_API void PopTextureID() { PopTexture(); } // RENAMED in 1.92.x +#endif //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0) { PathEllipticalArcTo(center, ImVec2(radius_x, radius_y), rot, a_min, a_max, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) @@ -3253,9 +3278,9 @@ struct ImDrawList IMGUI_API void _PopUnusedDrawCmd(); IMGUI_API void _TryMergeDrawCmds(); IMGUI_API void _OnChangedClipRect(); - IMGUI_API void _OnChangedTextureID(); + IMGUI_API void _OnChangedTexture(); IMGUI_API void _OnChangedVtxOffset(); - IMGUI_API void _SetTextureID(ImTextureID texture_id); + IMGUI_API void _SetTexture(ImTextureRef tex_ref); IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); @@ -3412,7 +3437,11 @@ struct ImFontAtlas IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel bool IsBuilt() const { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... - void SetTexID(ImTextureID id) { TexID = id; } +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + void SetTexID(ImTextureID id){ TexID._Atlas = this; TexID._TexID = id; } // FIXME-NEWATLAS: Called by legacy backends. + void SetTexID(ImTextureRef id) { TexID = id; } // FIXME-NEWATLAS: Called by legacy backends. +#endif + //------------------------------------------- // Glyph Ranges @@ -3456,7 +3485,7 @@ struct ImFontAtlas // Input ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) - ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + ImTextureRef TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). @@ -3654,7 +3683,7 @@ struct ImGuiPlatformImeData namespace ImGui { // OBSOLETED in 1.91.9 (from February 2025) - IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- 'border_col' was removed in favor of ImGuiCol_ImageBorder. If you use 'tint_col', use ImageWithBg() instead. + IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- 'border_col' was removed in favor of ImGuiCol_ImageBorder. If you use 'tint_col', use ImageWithBg() instead. // OBSOLETED in 1.91.0 (from July 2024) static inline void PushButtonRepeat(bool repeat) { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); } static inline void PopButtonRepeat() { PopItemFlag(); } diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 3a6f3a501..790f37611 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1778,7 +1778,7 @@ static void DemoWindowWidgetsImages() // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples - ImTextureID my_tex_id = io.Fonts->TexID; + ImTextureRef my_tex_id = io.Fonts->TexID; float my_tex_w = (float)io.Fonts->TexWidth; float my_tex_h = (float)io.Fonts->TexHeight; { diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 78b0e152e..8a31b79f7 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -423,8 +423,8 @@ void ImDrawList::_ResetForNewFrame() { // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. IM_STATIC_ASSERT(offsetof(ImDrawCmd, ClipRect) == 0); - IM_STATIC_ASSERT(offsetof(ImDrawCmd, TextureId) == sizeof(ImVec4)); - IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, TexRef) == sizeof(ImVec4)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureRef)); if (_Splitter._Count > 1) _Splitter.Merge(this); @@ -437,7 +437,7 @@ void ImDrawList::_ResetForNewFrame() _VtxWritePtr = NULL; _IdxWritePtr = NULL; _ClipRectStack.resize(0); - _TextureIdStack.resize(0); + _TextureStack.resize(0); _CallbacksDataBuf.resize(0); _Path.resize(0); _Splitter.Clear(); @@ -455,7 +455,7 @@ void ImDrawList::_ClearFreeMemory() _VtxWritePtr = NULL; _IdxWritePtr = NULL; _ClipRectStack.clear(); - _TextureIdStack.clear(); + _TextureStack.clear(); _CallbacksDataBuf.clear(); _Path.clear(); _Splitter.ClearFreeMemory(); @@ -475,7 +475,7 @@ void ImDrawList::AddDrawCmd() { ImDrawCmd draw_cmd; draw_cmd.ClipRect = _CmdHeader.ClipRect; // Same as calling ImDrawCmd_HeaderCopy() - draw_cmd.TextureId = _CmdHeader.TextureId; + draw_cmd.TexRef = _CmdHeader.TexRef; draw_cmd.VtxOffset = _CmdHeader.VtxOffset; draw_cmd.IdxOffset = IdxBuffer.Size; @@ -530,10 +530,10 @@ void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t use AddDrawCmd(); // Force a new command after us (see comment below) } -// Compare ClipRect, TextureId and VtxOffset with a single memcmp() +// Compare ClipRect, TexRef and VtxOffset with a single memcmp() #define ImDrawCmd_HeaderSize (offsetof(ImDrawCmd, VtxOffset) + sizeof(unsigned int)) -#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TextureId, VtxOffset -#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TextureId, VtxOffset +#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TexRef, VtxOffset +#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TexRef, VtxOffset #define ImDrawCmd_AreSequentialIdxOffset(CMD_0, CMD_1) (CMD_0->IdxOffset + CMD_0->ElemCount == CMD_1->IdxOffset) // Try to merge two last draw commands @@ -573,12 +573,16 @@ void ImDrawList::_OnChangedClipRect() curr_cmd->ClipRect = _CmdHeader.ClipRect; } -void ImDrawList::_OnChangedTextureID() +// Operators for easy compare +static inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._Atlas == rhs._Atlas; } +static inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._Atlas != rhs._Atlas; } + +void ImDrawList::_OnChangedTexture() { // If current command is used with different settings we need to add a new command IM_ASSERT_PARANOID(CmdBuffer.Size > 0); ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; - if (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != _CmdHeader.TextureId) + if (curr_cmd->ElemCount != 0 && curr_cmd->TexRef != _CmdHeader.TexRef) { AddDrawCmd(); return; @@ -592,7 +596,7 @@ void ImDrawList::_OnChangedTextureID() CmdBuffer.pop_back(); return; } - curr_cmd->TextureId = _CmdHeader.TextureId; + curr_cmd->TexRef = _CmdHeader.TexRef; } void ImDrawList::_OnChangedVtxOffset() @@ -653,27 +657,27 @@ void ImDrawList::PopClipRect() _OnChangedClipRect(); } -void ImDrawList::PushTextureID(ImTextureID texture_id) +void ImDrawList::PushTexture(ImTextureRef tex_ref) { - _TextureIdStack.push_back(texture_id); - _CmdHeader.TextureId = texture_id; - _OnChangedTextureID(); + _TextureStack.push_back(tex_ref); + _CmdHeader.TexRef = tex_ref; + _OnChangedTexture(); } -void ImDrawList::PopTextureID() +void ImDrawList::PopTexture() { - _TextureIdStack.pop_back(); - _CmdHeader.TextureId = (_TextureIdStack.Size == 0) ? (ImTextureID)NULL : _TextureIdStack.Data[_TextureIdStack.Size - 1]; - _OnChangedTextureID(); + _TextureStack.pop_back(); + _CmdHeader.TexRef = (_TextureStack.Size == 0) ? ImTextureRef() : _TextureStack.Data[_TextureStack.Size - 1]; + _OnChangedTexture(); } // This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTextureID()/PopTextureID(). -void ImDrawList::_SetTextureID(ImTextureID texture_id) +void ImDrawList::_SetTexture(ImTextureRef tex_ref) { - if (_CmdHeader.TextureId == texture_id) + if (_CmdHeader.TexRef == tex_ref) return; - _CmdHeader.TextureId = texture_id; - _OnChangedTextureID(); + _CmdHeader.TexRef = tex_ref; + _OnChangedTexture(); } // Reserve space for a number of vertices and indices. @@ -1686,7 +1690,7 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 if (font_size == 0.0f) font_size = _Data->FontSize; - IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TextureId); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. + IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TexRef); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. ImVec4 clip_rect = _CmdHeader.ClipRect; if (cpu_fine_clip_rect) @@ -1704,39 +1708,39 @@ void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, c AddText(_Data->Font, _Data->FontSize, pos, col, text_begin, text_end); } -void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) +void ImDrawList::AddImage(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); PrimReserve(6, 4); PrimRectUV(p_min, p_max, uv_min, uv_max, col); if (push_texture_id) - PopTextureID(); + PopTexture(); } -void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col) +void ImDrawList::AddImageQuad(ImTextureRef tex_ref, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); PrimReserve(6, 4); PrimQuadUV(p1, p2, p3, p4, uv1, uv2, uv3, uv4, col); if (push_texture_id) - PopTextureID(); + PopTexture(); } -void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) +void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -1744,13 +1748,13 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi flags = FixRectCornerFlags(flags); if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { - AddImage(user_texture_id, p_min, p_max, uv_min, uv_max, col); + AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col); return; } - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); int vert_start_idx = VtxBuffer.Size; PathRect(p_min, p_max, rounding, flags); @@ -1759,7 +1763,7 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true); if (push_texture_id) - PopTextureID(); + PopTexture(); } //----------------------------------------------------------------------------- @@ -2187,7 +2191,7 @@ void ImDrawListSplitter::Merge(ImDrawList* draw_list) // If current command is used with different settings we need to add a new command ImDrawCmd* curr_cmd = &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1]; if (curr_cmd->ElemCount == 0) - ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TexRef, VtxOffset else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) draw_list->AddDrawCmd(); @@ -2213,7 +2217,7 @@ void ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx) if (curr_cmd == NULL) draw_list->AddDrawCmd(); else if (curr_cmd->ElemCount == 0) - ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TexRef, VtxOffset else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) draw_list->AddDrawCmd(); } @@ -2483,6 +2487,7 @@ ImFontAtlas::ImFontAtlas() { memset(this, 0, sizeof(*this)); TexGlyphPadding = 1; + TexID._Atlas = this; PackIdMouseCursors = PackIdLines = -1; } @@ -2882,7 +2887,7 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) ImFontAtlasBuildInit(atlas); // Clear atlas - atlas->TexID = (ImTextureID)NULL; + atlas->TexID._TexID = 0; atlas->TexWidth = atlas->TexHeight = 0; atlas->TexUvScale = ImVec2(0.0f, 0.0f); atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); diff --git a/imgui_internal.h b/imgui_internal.h index ca250e0b7..91ac0bc83 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3454,7 +3454,7 @@ namespace ImGui // Widgets IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); - IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); + IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags, float thickness = 1.0f); IMGUI_API void SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width); IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 341bf2712..1c22bd4c6 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1103,9 +1103,9 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 return held; } -// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples +// - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. -void ImGui::ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) +void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -1123,28 +1123,28 @@ void ImGui::ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, c window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); } -void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) +void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) { - ImageWithBg(user_texture_id, image_size, uv0, uv1); + ImageWithBg(tex_ref, image_size, uv0, uv1); } // 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) { ImGuiContext& g = *GImGui; PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f PushStyleColor(ImGuiCol_Border, border_col); - ImageWithBg(user_texture_id, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col); + ImageWithBg(tex_ref, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col); PopStyleColor(); PopStyleVar(); } #endif -bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +bool ImGui::ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -1166,21 +1166,21 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); return pressed; } // - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design? -bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) +bool ImGui::ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; - return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col); + return ImageButtonEx(window->GetID(str_id), tex_ref, image_size, uv0, uv1, bg_col, tint_col); } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 39a997e67..6521f83b9 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -448,7 +448,7 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u ImFontAtlasBuildInit(atlas); // Clear atlas - atlas->TexID = 0; + atlas->TexID._TexID = 0; atlas->TexWidth = atlas->TexHeight = 0; atlas->TexUvScale = ImVec2(0.0f, 0.0f); atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); From 0f0473bf1c9d6e4cd74dde883f37f87f57165096 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 29 Nov 2024 13:42:35 +0100 Subject: [PATCH 004/191] Fonts, Textures: main code for ImGuiBackendFlags_RendererHasTextures feature. # Conflicts: # imgui.h # imgui_demo.cpp --- examples/example_null/main.cpp | 7 +- imgui.cpp | 180 ++- imgui.h | 212 +++- imgui_demo.cpp | 13 +- imgui_draw.cpp | 2004 ++++++++++++++++++++---------- imgui_internal.h | 127 +- imgui_widgets.cpp | 5 +- misc/freetype/imgui_freetype.cpp | 4 +- 8 files changed, 1750 insertions(+), 802 deletions(-) diff --git a/examples/example_null/main.cpp b/examples/example_null/main.cpp index f7153cc48..460f33cab 100644 --- a/examples/example_null/main.cpp +++ b/examples/example_null/main.cpp @@ -11,9 +11,10 @@ int main(int, char**) ImGuiIO& io = ImGui::GetIO(); // Build atlas - unsigned char* tex_pixels = nullptr; - int tex_w, tex_h; - io.Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_w, &tex_h); + //unsigned char* tex_pixels = nullptr; + //int tex_w, tex_h; + //io.Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_w, &tex_h); + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; for (int n = 0; n < 20; n++) { diff --git a/imgui.cpp b/imgui.cpp index bb5819a37..5acd91c0c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1270,6 +1270,7 @@ static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt); // Misc static void UpdateFontsNewFrame(); +static void UpdateTexturesNewFrame(); static void UpdateSettings(); static int UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect); static void RenderWindowOuterBorders(ImGuiWindow* window); @@ -3841,7 +3842,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale))) continue; ImDrawList* draw_list = GetForegroundDrawList(viewport); - ImTextureRef tex_ref = font_atlas->TexID; + ImTextureRef tex_ref = font_atlas->TexRef; draw_list->PushTexture(tex_ref); draw_list->AddImage(tex_ref, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); draw_list->AddImage(tex_ref, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); @@ -4194,6 +4195,11 @@ void ImGui::Initialize() #ifdef IMGUI_HAS_DOCK #endif + // ImDrawList/ImFontAtlas are designed to function without ImGui, and 99% of it works without an ImGui context. + // But this link allows us to facilitate/handle a few edge cases better. + g.DrawListSharedData.Context = &g; + ImFontAtlasAddDrawListSharedData(g.IO.Fonts, &g.DrawListSharedData); + g.Initialized = true; } @@ -4205,6 +4211,8 @@ void ImGui::Shutdown() IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == NULL, "Forgot to shutdown Renderer backend?"); // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + if (g.IO.Fonts) + ImFontAtlasRemoveDrawListSharedData(g.IO.Fonts, &g.DrawListSharedData); if (g.IO.Fonts && g.FontAtlasOwnedByContext) { g.IO.Fonts->Locked = false; @@ -4339,7 +4347,7 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL SettingsOffset = -1; DrawList = &DrawListInst; DrawList->_OwnerName = Name; - DrawList->_Data = &Ctx->DrawListSharedData; + DrawList->_SetDrawListSharedData(&Ctx->DrawListSharedData); NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX); } @@ -4925,7 +4933,7 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount) { draw_list->_ResetForNewFrame(); - draw_list->PushTexture(g.IO.Fonts->TexID); + draw_list->PushTexture(g.IO.Fonts->TexRef); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount; } @@ -5166,6 +5174,14 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos) io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } +static void ImGui::UpdateTexturesNewFrame() +{ + // FIXME-NEWATLAS: How to reach/target all atlas? + ImGuiContext& g = *GImGui; + ImFontAtlas* atlas = g.IO.Fonts; + ImFontAtlasUpdateNewFrame(atlas); +} + // Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! static void SetupDrawListSharedData() @@ -5202,6 +5218,13 @@ void ImGui::NewFrame() CallContextHooks(&g, ImGuiContextHookType_NewFramePre); + // Check that font atlas was built or backend support texture reload in which case we can build now + ImFontAtlas* atlas = g.IO.Fonts; + if (!atlas->TexIsBuilt && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) + atlas->Build(); + else // Legacy backend + IM_ASSERT(atlas->TexIsBuilt && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); + // Check and assert for various common IO and Configuration mistakes ErrorCheckNewFrameSanityChecks(); @@ -5209,7 +5232,6 @@ void ImGui::NewFrame() UpdateSettings(); g.Time += g.IO.DeltaTime; - g.WithinFrameScope = true; g.FrameCount += 1; g.TooltipOverrideCount = 0; g.WindowsActiveCount = 0; @@ -5229,10 +5251,15 @@ void ImGui::NewFrame() // Update viewports (after processing input queue, so io.MouseHoveredViewport is set) UpdateViewportsNewFrame(); + // Update texture list (collect destroyed textures, etc.) + UpdateTexturesNewFrame(); + // Setup current font and draw list shared data SetupDrawListSharedData(); UpdateFontsNewFrame(); + g.WithinFrameScope = true; + // Mark rendering data as invalid to prevent user who may have a handle on it to use it. for (ImGuiViewportP* viewport : g.Viewports) viewport->DrawDataP.Valid = false; @@ -5810,6 +5837,11 @@ void ImGui::Render() g.IO.MetricsRenderIndices += draw_data->TotalIdxCount; } +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + ImFontAtlasDebugLogTextureRequests(g.IO.Fonts); +#endif + CallContextHooks(&g, ImGuiContextHookType_RenderPost); } @@ -7580,7 +7612,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Setup draw list and outer clipping rectangle IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0); - window->DrawList->PushTexture(g.Font->ContainerAtlas->TexID); + window->DrawList->PushTexture(g.Font->ContainerAtlas->TexRef); PushClipRect(host_rect.Min, host_rect.Max, false); // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71) @@ -8515,7 +8547,12 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) void ImGui::UpdateFontsNewFrame() { ImGuiContext& g = *GImGui; - g.IO.Fonts->Locked = true; + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + { + g.IO.Fonts->Locked = true; + for (ImFont* font : g.IO.Fonts->Fonts) + font->LockDisableLoading = true; + } SetCurrentFont(GetDefaultFont()); IM_ASSERT(g.Font->IsLoaded()); } @@ -8530,13 +8567,12 @@ void ImGui::SetCurrentFont(ImFont* font) g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale); g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; g.FontScale = g.FontSize / g.Font->FontSize; - - ImFontAtlas* atlas = g.Font->ContainerAtlas; - g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; - g.DrawListSharedData.TexUvLines = atlas->TexUvLines; g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.FontSize = g.FontSize; g.DrawListSharedData.FontScale = g.FontScale; + ImFontAtlasUpdateDrawListsSharedData(g.Font->ContainerAtlas); + if (g.CurrentWindow) + g.CurrentWindow->DrawList->_SetTexture(font->ContainerAtlas->TexRef); } // Use ImDrawList::_SetTextureID(), making our shared g.FontStack[] authoritative against window-local ImDrawList. @@ -8546,6 +8582,7 @@ void ImGui::SetCurrentFont(ImFont* font) // - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call PushTextureID()/PopTextureID() // the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem // because we have a concrete need and a test bed for multiple atlas textures. +// FIXME-NEWATLAS: perhaps we can now leverage ImFontAtlasUpdateDrawListsTextures() ? void ImGui::PushFont(ImFont* font) { ImGuiContext& g = *GImGui; @@ -8553,7 +8590,6 @@ void ImGui::PushFont(ImFont* font) font = GetDefaultFont(); g.FontStack.push_back(font); SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTexture(font->ContainerAtlas->TexID); } void ImGui::PopFont() @@ -8567,7 +8603,6 @@ void ImGui::PopFont() g.FontStack.pop_back(); ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back(); SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTexture(font->ContainerAtlas->TexID); } //----------------------------------------------------------------------------- @@ -10278,7 +10313,6 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); - IM_ASSERT(g.IO.Fonts->IsBuilt() && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations @@ -15460,10 +15494,12 @@ void ImGui::DebugTextEncoding(const char* str) Text("0x%02X", (int)(unsigned char)p[byte_index]); } TableNextColumn(); - if (GetFont()->FindGlyphNoFallback((ImWchar)c)) - TextUnformatted(p, p + c_utf8_len); - else - TextUnformatted((c == IM_UNICODE_CODEPOINT_INVALID) ? "[invalid]" : "[missing]"); + TextUnformatted(p, p + c_utf8_len); + if (GetFont()->FindGlyphNoFallback((ImWchar)c) == NULL) + { + SameLine(); + TextUnformatted("[missing]"); + } TableNextColumn(); Text("U+%04X", (int)c); p += c_utf8_len; @@ -15500,17 +15536,25 @@ void ImGui::UpdateDebugToolFlashStyleColor() DebugFlashStyleColorStop(); } -static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureRef tex_ref) +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) { union { void* ptr; int integer; } tex_id_opaque; - memcpy(&tex_id_opaque, &tex_ref._TexID, ImMin(sizeof(void*), sizeof(tex_ref._TexID))); - if (sizeof(tex_ref._TexID) >= sizeof(void*)) + memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); + if (sizeof(tex_id) >= sizeof(void*)) ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); else ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); return buf; } +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, const ImDrawCmd* cmd) +{ + char* buf_end = buf + buf_size; + if (cmd->TexRef._TexData != NULL) + buf += ImFormatString(buf, buf_end - buf, "#%03d: ", cmd->TexRef._TexData->UniqueID); + return FormatTextureIDForDebugDisplay(buf, (int)(buf_end - buf), cmd->GetTexID()); +} + // Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. static void MetricsHelpMarker(const char* desc) { @@ -15524,6 +15568,10 @@ static void MetricsHelpMarker(const char* desc) } } +#ifdef IMGUI_ENABLE_FREETYPE +namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetBackendIOForFreeType(); } +#endif + // [DEBUG] List fonts in a font atlas and display its texture void ImGui::ShowFontAtlas(ImFontAtlas* atlas) { @@ -15538,6 +15586,36 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; Checkbox("Show font preview", &cfg->ShowFontPreview); + // Font loaders + if (TreeNode("Loader", "Loader: \'%s\'", atlas->FontLoaderName ? atlas->FontLoaderName : "NULL")) + { + const ImFontLoader* loader_current = atlas->FontLoader; + BeginDisabled(!atlas->DrawListSharedData || !atlas->DrawListSharedData->RendererHasTextures); +#ifdef IMGUI_ENABLE_STB_TRUETYPE + const ImFontLoader* loader_stbtruetype = ImFontAtlasGetFontLoaderForStbTruetype(); + if (RadioButton("stb_truetype", loader_current == loader_stbtruetype)) + ImFontAtlasBuildSetupFontLoader(atlas, loader_stbtruetype); +#else + BeginDisabled(); + RadioButton("stb_truetype", false); + SetItemTooltip("Requires IMGUI_ENABLE_STB_TRUETYPE"); + EndDisabled(); +#endif + SameLine(); +#ifdef IMGUI_ENABLE_FREETYPE + const ImFontLoader* loader_freetype = ImGuiFreeType::GetBackendIOForFreeType(); + if (RadioButton("FreeType", loader_current == loader_freetype)) + ImFontAtlasBuildSetupFontLoader(atlas, loader_freetype); +#else + BeginDisabled(); + RadioButton("FreeType", false); + SetItemTooltip("Requires IMGUI_ENABLE_FREETYPE + imgui_freetype.cpp."); + EndDisabled(); +#endif + EndDisabled(); + TreePop(); + } + // Font list for (ImFont* font : atlas->Fonts) { @@ -15545,11 +15623,32 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) DebugNodeFont(font); PopID(); } - if (TreeNode("Font Atlas", "Font Atlas (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + + // Texture list + for (ImTextureData* tex : atlas->TexList) + { + PushID(tex); + DebugNodeTexture(tex); + PopID(); + } +} + +void ImGui::DebugNodeTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + if (TreeNode(tex, "Texture #%03d (%dx%d pixels)", tex->UniqueID, tex->Width, tex->Height)) { PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); - ImageWithBg(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImTextureRef tex_id; + tex_id._TexData = tex; // Don't use tex->TexID directly so first frame works. + ImageWithBg(tex_id, ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); PopStyleVar(); + + char texid_desc[20]; + Text("Format = %d", tex->Format); + Text("TexID = %s", FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->TexID)); + Text("BackendUserData = %p", tex->BackendUserData); + Text("UseColors = %d", tex->UseColors); TreePop(); } } @@ -15793,6 +15892,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } + // Details for Fonts + ImFontAtlas* atlas = g.IO.Fonts; + if (TreeNode("Fonts", "Fonts (%d), Textures (%d)", atlas->Fonts.Size, atlas->TexList.Size)) + { + ShowFontAtlas(atlas); + TreePop(); + } + // Details for Popups if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) { @@ -15829,14 +15936,6 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } - // Details for Fonts - ImFontAtlas* atlas = g.IO.Fonts; - if (TreeNode("Fonts", "Fonts (%d)", atlas->Fonts.Size)) - { - ShowFontAtlas(atlas); - TreePop(); - } - // Details for InputText if (TreeNode("InputText")) { @@ -16245,7 +16344,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con } char texid_desc[20]; - FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TexRef); + FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd); char buf[300]; ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount / 3, texid_desc, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); @@ -16379,7 +16478,7 @@ void ImGui::DebugNodeFont(ImFont* font) for (int config_i = 0; config_i < font->SourcesCount; config_i++) if (font->Sources) { - const ImFontConfig* src = &font->Sources[config_i]; + ImFontConfig* src = &font->Sources[config_i]; int oversample_h, oversample_v; ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", @@ -16390,6 +16489,10 @@ void ImGui::DebugNodeFont(ImFont* font) { if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) { + if (SmallButton("Load all")) + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base++) + font->FindGlyph((ImWchar)base); + ImDrawList* draw_list = GetWindowDrawList(); const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); const float cell_size = font->FontSize * 1; @@ -16407,7 +16510,7 @@ void ImGui::DebugNodeFont(ImFont* font) int count = 0; for (unsigned int n = 0; n < 256; n++) - if (font->FindGlyphNoFallback((ImWchar)(base + n))) + if (font->IsGlyphLoaded((ImWchar)(base + n))) count++; if (count <= 0) continue; @@ -16422,7 +16525,7 @@ void ImGui::DebugNodeFont(ImFont* font) // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); + const ImFontGlyph* glyph = font->IsGlyphLoaded((ImWchar)(base + n)) ? font->FindGlyph((ImWchar)(base + n)) : NULL; draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); if (!glyph) continue; @@ -16443,7 +16546,7 @@ void ImGui::DebugNodeFont(ImFont* font) Unindent(); } -void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) +void ImGui::DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) { Text("Codepoint: U+%04X", glyph->Codepoint); Separator(); @@ -16451,6 +16554,11 @@ void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) Text("AdvanceX: %.1f", glyph->AdvanceX); Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + if (glyph->PackId >= 0) + { + ImFontAtlasRect* r = ImFontAtlasPackGetRect(font->ContainerAtlas, glyph->PackId); + Text("PackId: %d (%dx%d rect at %d,%d)", glyph->PackId, r->w, r->h, r->x, r->y);; + } } // [DEBUG] Display contents of ImGuiStorage @@ -16727,7 +16835,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper); ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus); ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO); - //ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); + ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); diff --git a/imgui.h b/imgui.h index 0e8f9c1ae..0420b4358 100644 --- a/imgui.h +++ b/imgui.h @@ -31,6 +31,7 @@ #define IMGUI_VERSION "1.92.0 WIP" #define IMGUI_VERSION_NUM 19197 #define IMGUI_HAS_TABLE +#define IMGUI_HAS_TEXTURES // 1.92+ WIP branch with ImGuiBackendFlags_RendererHasTextures /* @@ -48,6 +49,7 @@ Index of this file: // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) // [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) // [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformImeData) @@ -169,10 +171,13 @@ struct ImDrawListSplitter; // Helper to split a draw list into differen struct ImDrawVert; // A single vertex (pos + uv + col = 20 bytes by default. Override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) struct ImFont; // Runtime data for a single font within a parent ImFontAtlas struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader -struct ImFontBuilderIO; // Opaque interface to a font builder (stb_truetype or FreeType). +struct ImFontAtlasBuilder; // Opaque storage for building a ImFontAtlas struct ImFontConfig; // Configuration data when adding a font or merging fonts struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data +struct ImFontLoader; // Opaque interface to a font loading backend (stb_truetype, FreeType etc.). +struct ImTextureData; // Specs and pixel storage for a texture used by Dear ImGui. +struct ImTextureRect; // Coordinates of a rectangle within a texture. struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) // Forward declarations: ImGui layer @@ -328,8 +333,8 @@ struct ImTextureRef #endif // Members - ImFontAtlas* _Atlas; // Texture/Atlas pointer - ImTextureID _TexID; // _OR_ Underlying user/backend texture identifier, or zero if not yet uploaded. + ImTextureData* _TexData; // Texture, generally owned by a ImFontAtlas + ImTextureID _TexID; // _OR_ Underlying texture identifier for backend, if already uploaded (otherwise pulled from _TexData) }; IM_MSVC_RUNTIME_CHECKS_RESTORE @@ -1638,6 +1643,7 @@ enum ImGuiBackendFlags_ ImGuiBackendFlags_HasMouseCursors = 1 << 1, // Backend Platform supports honoring GetMouseCursor() value to change the OS cursor shape. ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if io.ConfigNavMoveSetMousePos is set). ImGuiBackendFlags_RendererHasVtxOffset = 1 << 3, // Backend Renderer supports ImDrawCmd::VtxOffset. This enables output of large meshes (64K+ vertices) while still using 16-bit indices. + ImGuiBackendFlags_RendererHasTextures = 1 << 4, // Backend Renderer supports ImTextureData requests to create/update/destroy textures. This enables incremental texture updates and texture reloads. }; // Enumeration for PushStyleColor() / PopStyleColor() @@ -2794,6 +2800,14 @@ static inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return IM_MSVC_RUNTIME_CHECKS_RESTORE #endif +// Helpers: ImTexture ==/!= operators provided as convenience (not strictly necessary) +static inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._TexData == rhs._TexData; } +static inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._TexData != rhs._TexData; } +//#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // For legacy backends +//static inline bool operator==(ImTextureID lhs, const ImTextureRef& rhs) { return lhs == rhs._TexID && rhs._TexData == NULL; } +//static inline bool operator==(const ImTextureRef& lhs, ImTextureID rhs) { return lhs._TexID == rhs && lhs._TexData == NULL; } +//#endif + // Helpers macros to generate 32-bit encoded colors // - User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. // - Any setting other than the default will need custom backend support. The only standard backend that supports anything else than the default is DirectX9. @@ -3041,7 +3055,8 @@ struct ImDrawCmd // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! - inline ImTextureID GetTexID() const { return TexRef._TexID; } + // If for some reason you non C++ tech stack makes it difficult to call it, we may decide to separate the fields in ImDrawCmd. + inline ImTextureID GetTexID() const; }; // Vertex layout @@ -3273,6 +3288,7 @@ struct ImDrawList //inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } // OBSOLETED in 1.80 (Jan 2021) // [Internal helpers] + IMGUI_API void _SetDrawListSharedData(ImDrawListSharedData* data); IMGUI_API void _ResetForNewFrame(); IMGUI_API void _ClearFreeMemory(); IMGUI_API void _PopUnusedDrawCmd(); @@ -3309,6 +3325,73 @@ struct ImDrawData IMGUI_API void ScaleClipRects(const ImVec2& fb_scale); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than Dear ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. }; +//----------------------------------------------------------------------------- +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureDataUpdate, ImTextureData +//----------------------------------------------------------------------------- + +// We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. +enum ImTextureFormat +{ + ImTextureFormat_RGBA32, // 4 components per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + ImTextureFormat_Alpha8, // 1 component per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight +}; + +// Status of a texture +enum ImTextureStatus +{ + ImTextureStatus_OK, + ImTextureStatus_Destroyed, // Backend destroyed the texture. + ImTextureStatus_WantCreate, // Requesting backend to create the texture. Set status OK when done. + ImTextureStatus_WantUpdates, // Requesting backend to update specific blocks of pixels (write to texture portions which have never been used before). Set status OK when done. + ImTextureStatus_WantDestroy, // Requesting backend to destroy the texture. Set status to Destroyed when done. +}; + +// Coordinates of a rectangle within a texture. +// When a texture is in ImTextureStatus_WantUpdates state, we provide a list of individual rectangles to copy to GPU texture. +// You may use ImTextureData::Updates[] for the list, or ImTextureData::UpdateBox for a single bounding box. +struct ImTextureRect +{ + unsigned short x, y; // Upper-left coordinates of rectangle to update + unsigned short w, h; // Size of rectangle to update (in pixels) +}; + +// Specs and pixel storage for a texture used by Dear ImGui. +// The renderer backend will generally create a GPU-side version of this. +// Why does we store two identifiers: TexID and BackendUserData? +// - ImTextureID TexID = lower-level identifier stored in ImDrawCmd. ImDrawCmd can refer to textures not created by the backend, and for which there's no ImTextureData. +// - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. +struct IMGUI_API ImTextureData +{ + ImTextureStatus Status; // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy + ImTextureFormat Format; // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 + int Width; // Texture width + int Height; // Texture height + int BytesPerPixel; // 4 or 1 + int UniqueID; // Sequential index to facilitate identifying a texture when debugging/printing. Only unique per atlas. + unsigned char* Pixels; // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. + ImTextureID TexID; // Identifier stored in ImDrawCmd::GetTexID() and passed to backend RenderDrawData loop. + void* BackendUserData; // Convenience storage for backend. Some backends may have enough with TexID. + ImTextureRect UpdateRect; // Bounding box encompassing all individual updates. + ImVector Updates; // Array of individual updates. + int UnusedFrames; // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. Always >0 when Status==WantDestroy. + + // [Internal] + bool UseColors; // [Internal] Tell whether our texture data is known to use colors (rather than just white + alpha). + bool WantDestroyNextFrame; // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. + + // Functions + ImTextureData() { memset(this, 0, sizeof(*this)); } + ~ImTextureData() { DestroyPixels(); } + void Create(ImTextureFormat format, int w, int h); + void DestroyPixels(); + unsigned char* GetPixels() { IM_ASSERT(Pixels != NULL); return Pixels; } + unsigned char* GetPixelsAt(int x, int y) { IM_ASSERT(Pixels != NULL); return Pixels + (x + y * Width) * BytesPerPixel; } + int GetSizeInBytes() const { return Width * Height * BytesPerPixel; } + int GetPitch() const { return Width * BytesPerPixel; } + ImTextureRef GetTexRef() const { ImTextureRef tex_ref; tex_ref._TexData = (ImTextureData*)(void*)this; tex_ref._TexID = TexID; return tex_ref; } + ImTextureID GetTexID() const { return TexID; } +}; + //----------------------------------------------------------------------------- // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) //----------------------------------------------------------------------------- @@ -3338,7 +3421,8 @@ struct ImFontConfig // [Internal] char Name[40]; // Name (strictly to ease debugging) - ImFont* DstFont; + ImFont* DstFont; // Target font (as we merging fonts, multiple ImFontConfig may target the same font) + void* FontLoaderData; // Font loader opaque storage (per font config) IMGUI_API ImFontConfig(); }; @@ -3353,6 +3437,9 @@ struct ImFontGlyph float AdvanceX; // Horizontal distance to advance layout with float X0, Y0, X1, Y1; // Glyph corners float U0, V0, U1, V1; // Texture coordinates + int PackId; // [Internal] ImFontAtlasRectId value (FIXME: Cold data, could be moved elsewhere?) + + ImFontGlyph() { memset(this, 0, sizeof(*this)); PackId = -1; } }; // Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges(). @@ -3400,12 +3487,14 @@ enum ImFontAtlasFlags_ // - One or more fonts. // - Custom graphics data needed to render the shapes needed by Dear ImGui. // - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas). -// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api. -// - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you. -// - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. -// - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples) +// - If you don't call any AddFont*** functions, the default font embedded in the code will be loaded for you. +// It is the rendering backend responsibility to upload texture into your graphics API: +// - ImGui_ImplXXXX_RenderDrawData() functions generally iterate atlas->TexList[] to create/update/destroy each ImTextureData instance. +// - Backend then set ImTextureData's TexID and BackendUserData. +// - Texture id are passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. +// Legacy path: +// - Call Build() + GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. // - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. -// This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. // Common pitfalls: // - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the // atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. @@ -3423,25 +3512,30 @@ struct ImFontAtlas IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, 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, 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, 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. + + // FIXME-NEWATLAS: Clarify meaning/purpose IMGUI_API void ClearInputData(); // 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 ClearFonts(); // Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. IMGUI_API void Clear(); // Clear all input and output. + IMGUI_API void ClearCache(); // Clear cached glyphs + // Build atlas, retrieve pixel data. // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). // The pitch is always = Width * BytesPerPixels (1 or 4) // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. - IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - bool IsBuilt() const { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... + IMGUI_API void BuildGrowTexture(); + IMGUI_API void BuildCompactTexture(); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - void SetTexID(ImTextureID id){ TexID._Atlas = this; TexID._TexID = id; } // FIXME-NEWATLAS: Called by legacy backends. - void SetTexID(ImTextureRef id) { TexID = id; } // FIXME-NEWATLAS: Called by legacy backends. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { TexRef._TexData = NULL; TexRef._TexID = id; } // Called by legacy backends. + void SetTexID(ImTextureRef id) { TexRef = id; } // Called by legacy backends. #endif - + bool IsBuilt() const { return Fonts.Size > 0 && TexIsBuilt; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... //------------------------------------------- // Glyph Ranges @@ -3466,10 +3560,15 @@ struct ImFontAtlas //------------------------------------------- // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. - // - After calling Build(), you can query the rectangle position and render your pixels. - // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. - // - You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), - // so you can render e.g. custom colorful icons and use them as regular glyphs. + // You can request your rectangles to be mapped as font glyph (given a font + Unicode point), + // so you can render e.g. custom colorful icons and use them as regular glyphs. + // - If your backend supports ImGuiBackendFlags_RendererHasTextures (since 1.92.X): + // - Packing is done immediately. Returns >= on success. Return <0 on error. + // - You can render your pixels into the texture right after calling the AddCustomRectXXX functions. + // - Texture may be resized, so you cannot cache UV coordinates. // FIXME-NEWATLAS-V1: How to handle that smoothly? + // - If your backend does NOT supports ImGuiBackendFlags_RendererHasTextures (older than 1.92.X): + // - After calling Build(), you can query the rectangle position and render your pixels. + // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. IMGUI_API int AddCustomRectRegular(int width, int height); @@ -3485,34 +3584,38 @@ struct ImFontAtlas // Input ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) - ImTextureRef TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + ImTextureRef TexRef; // User data to refer to the latest texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + ImTextureFormat TexDesiredFormat; // Desired texture format (default to ImTextureFormat_RGBA32 but may be changed to ImTextureFormat_Alpha8). int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). + // Output + ImTextureData* TexData; // Current texture + ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). + // [Internal] - // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. - bool TexReady; // Set when texture was built matching current font input - bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format. - unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight - unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 - int TexWidth; // Texture width calculated during Build(). - int TexHeight; // Texture height calculated during Build(). - ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) + bool TexIsBuilt; // Set when texture was built matching current font input + bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. + ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight) ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + int TexNextUniqueID; // Next value to be stored in TexData->UniqueID + ImDrawListSharedData* DrawListSharedData; // In principle this could become an array (e.g. multiple contexts using same atlas) // [Internal] Font builder - const ImFontBuilderIO* FontBuilderIO; // Opaque interface to a font builder (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). - unsigned int FontBuilderFlags; // Shared flags (for all fonts) for custom font builder. THIS IS BUILD IMPLEMENTATION DEPENDENT. Per-font override is also available in ImFontConfig. - - // [Internal] Packing data - int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors - int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines + ImFontAtlasBuilder* Builder; // Opaque interface to our data that doesn't need to be public + const ImFontLoader* FontLoader; // Font loader opaque interface (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). Don't set directly! + const char* FontLoaderName; // Font loader name (for display e.g. in About box) == FontLoader->Name + void* FontLoaderData; // Font backend opaque storage + unsigned int FontBuilderFlags; // [FIXME: Should be called FontLoaderFlags] Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. . Per-font override is also available in ImFontConfig. + int _PackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. + int _PackedRects; // Number of packed rectangles. + float _PackNodesFactor = 1.0f; // [Obsolete] //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ @@ -3525,13 +3628,13 @@ struct ImFont { // [Internal] Members: Hot ~20/24 bytes (for CalcTextSize) ImVector IndexAdvanceX; // 12-16 // out // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). - float FallbackAdvanceX; // 4 // out // = FallbackGlyph->AdvanceX + float FallbackAdvanceX; // 4 // out // FindGlyph(FallbackChar)->AdvanceX float FontSize; // 4 // in // Height of characters/line, set during loading (don't change after loading) // [Internal] Members: Hot ~28/40 bytes (for RenderText loop) ImVector IndexLookup; // 12-16 // out // Sparse. Index glyphs by Unicode code-point. ImVector Glyphs; // 12-16 // out // All glyphs. - ImFontGlyph* FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) + int FallbackGlyphIndex; // 4 // out // Index of FontFallbackChar // [Internal] Members: Cold ~32/40 bytes // Conceptually Sources[] is the list of font sources merged to create this font. @@ -3543,18 +3646,21 @@ struct ImFont ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') float EllipsisWidth; // 4 // out // Total ellipsis Width float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 - float Scale; // 4 // in // Base font scale (1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() + float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) int MetricsTotalSurface;// 4 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) - bool DirtyLookupTables; // 1 // out // ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + bool LockDisableLoading; + ImFontConfig* LockSingleSrcConfig; // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); - IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); - float GetCharAdvance(ImWchar c) { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); // Return fallback glyph if requested glyph doesn't exists. + IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); // Return NULL if glyph doesn't exist + IMGUI_API float GetCharAdvance(ImWchar c); + IMGUI_API bool IsGlyphLoaded(ImWchar c); + IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } const char* GetDebugName() const { return Sources ? Sources->Name : ""; } @@ -3571,14 +3677,24 @@ struct ImFont #endif // [Internal] Don't use! - IMGUI_API void BuildLookupTable(); IMGUI_API void ClearOutputData(); - IMGUI_API void GrowIndex(int new_size); - IMGUI_API void AddGlyph(const ImFontConfig* src_cfg, ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); - IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst);// , bool overwrite_dst = true); // Makes 'from_codepoint' character points to 'to_codepoint' character. Currently needs to be called AFTER fonts have been built. + IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst);// , bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); + IMGUI_API ImFontGlyph* BuildLoadGlyph(ImWchar c); + IMGUI_API void BuildRegisterGlyph(ImFontConfig* src, const ImFontGlyph* glyph); + IMGUI_API void BuildGrowIndex(int new_size); + IMGUI_API void BuildClearGlyphs(); }; +// FIXME-NEWATLAS: Added indirection to avoid patching ImDrawCmd after texture updates. +inline ImTextureID ImDrawCmd::GetTexID() const +{ + ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; + if (TexRef._TexData != NULL) + IM_ASSERT(tex_id && "ImDrawCmd is referring to Atlas texture that wasn't uploaded to graphics system."); + return tex_id; +} + //----------------------------------------------------------------------------- // [SECTION] Viewports //----------------------------------------------------------------------------- @@ -3657,6 +3773,10 @@ struct ImGuiPlatformIO // Input - Interface with Renderer Backend //------------------------------------------------------------------ + // Optional: Maximum texture size supported by renderer (used to adjust how we size textures). 0 if not known. + int Renderer_TextureMaxWidth; + int Renderer_TextureMaxHeight; + // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. void* Renderer_RenderState; }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 790f37611..5d5075e81 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -576,6 +576,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors); ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", &io.BackendFlags, ImGuiBackendFlags_HasSetMousePos); ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", &io.BackendFlags, ImGuiBackendFlags_RendererHasVtxOffset); + ImGui::CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); ImGui::EndDisabled(); ImGui::TreePop(); @@ -1778,9 +1779,9 @@ static void DemoWindowWidgetsImages() // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples - ImTextureRef my_tex_id = io.Fonts->TexID; - float my_tex_w = (float)io.Fonts->TexWidth; - float my_tex_h = (float)io.Fonts->TexHeight; + ImTextureRef my_tex_id = io.Fonts->TexRef; + float my_tex_w = (float)io.Fonts->TexData->Width; + float my_tex_h = (float)io.Fonts->TexData->Height; { ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); ImVec2 pos = ImGui::GetCursorScreenPos(); @@ -8041,7 +8042,7 @@ void ImGui::ShowAboutWindow(bool* p_open) if (copy_to_clipboard) { ImGui::LogToClipboard(); - ImGui::LogText("```\n"); // Back quotes will make text appears without formatting when pasting on GitHub + ImGui::LogText("```cpp\n"); // Back quotes will make text appears without formatting when pasting on GitHub } ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); @@ -8137,8 +8138,10 @@ void ImGui::ShowAboutWindow(bool* p_open) if (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) ImGui::Text(" HasMouseCursors"); if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos) ImGui::Text(" HasSetMousePos"); if (io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) ImGui::Text(" RendererHasVtxOffset"); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) ImGui::Text(" RendererHasTextures"); ImGui::Separator(); - ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexWidth, io.Fonts->TexHeight); + ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexData->Width, io.Fonts->TexData->Height); + ImGui::Text("io.Fonts->FontLoaderName: \"%s\"", io.Fonts->FontLoaderName ? io.Fonts->FontLoaderName : "NULL"); ImGui::Text("io.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y); ImGui::Text("io.DisplayFramebufferScale: %.2f,%.2f", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); ImGui::Separator(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8a31b79f7..b1cf15ff4 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -13,7 +13,8 @@ Index of this file: // [SECTION] ImDrawData // [SECTION] Helpers ShadeVertsXXX functions // [SECTION] ImFontConfig -// [SECTION] ImFontAtlas +// [SECTION] ImFontAtlas, ImFontAtlasBuilder +// [SECTION] ImFontAtlas: backend for stb_truetype // [SECTION] ImFontAtlas: glyph ranges helpers // [SECTION] ImFontGlyphRangesBuilder // [SECTION] ImFont @@ -389,6 +390,12 @@ ImDrawListSharedData::ImDrawListSharedData() ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); } ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); + RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. +} + +ImDrawListSharedData::~ImDrawListSharedData() +{ + IM_ASSERT(DrawLists.Size == 0); } void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) @@ -409,12 +416,22 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) ImDrawList::ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); - _Data = shared_data; + _SetDrawListSharedData(shared_data); } ImDrawList::~ImDrawList() { _ClearFreeMemory(); + _SetDrawListSharedData(NULL); +} + +void ImDrawList::_SetDrawListSharedData(ImDrawListSharedData* data) +{ + if (_Data != NULL) + _Data->DrawLists.find_erase_unsorted(this); + _Data = data; + if (_Data != NULL) + _Data->DrawLists.push_back(this); } // Initialize before use in a new frame. We always have a command ready in the buffer. @@ -573,10 +590,6 @@ void ImDrawList::_OnChangedClipRect() curr_cmd->ClipRect = _CmdHeader.ClipRect; } -// Operators for easy compare -static inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._Atlas == rhs._Atlas; } -static inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._Atlas != rhs._Atlas; } - void ImDrawList::_OnChangedTexture() { // If current command is used with different settings we need to add a new command @@ -1690,8 +1703,6 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 if (font_size == 0.0f) font_size = _Data->FontSize; - IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TexRef); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. - ImVec4 clip_rect = _CmdHeader.ClipRect; if (cpu_fine_clip_rect) { @@ -2396,39 +2407,113 @@ ImFontConfig::ImFontConfig() } //----------------------------------------------------------------------------- -// [SECTION] ImFontAtlas +// [SECTION] ImTextureData +//----------------------------------------------------------------------------- +// - ImTextureData::Create() +// - ImTextureData::DestroyPixels() +//----------------------------------------------------------------------------- + +static int GetTextureFormatBytesPerPixel(ImTextureFormat format) +{ + switch (format) + { + case ImTextureFormat_Alpha8: return 1; + case ImTextureFormat_RGBA32: return 4; + } + IM_ASSERT(0); + return 0; +} + +void ImTextureData::Create(ImTextureFormat format, int w, int h) +{ + DestroyPixels(); + Format = format; + Width = w; + Height = h; + BytesPerPixel = GetTextureFormatBytesPerPixel(format); + UseColors = false; + Pixels = (unsigned char*)IM_ALLOC(Width * Height * BytesPerPixel); + IM_ASSERT(Pixels != NULL); + memset(Pixels, 0, Width * Height * BytesPerPixel); +} + +void ImTextureData::DestroyPixels() +{ + if (Pixels) + IM_FREE(Pixels); + Pixels = NULL; + UseColors = false; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas, ImFontAtlasBuilder //----------------------------------------------------------------------------- // - Default texture data encoded in ASCII +// - ImFontAtlasBuilder // - ImFontAtlas::ClearInputData() // - ImFontAtlas::ClearTexData() // - ImFontAtlas::ClearFonts() // - ImFontAtlas::Clear() -// - ImFontAtlas::GetTexDataAsAlpha8() -// - ImFontAtlas::GetTexDataAsRGBA32() +// - ImFontAtlas::ClearCache() +// - ImFontAtlas::BuildGrowTexture() +// - ImFontAtlas::BuildCompactTexture() +// - ImFontAtlasUpdateTextures() +//----------------------------------------------------------------------------- +// - ImFontAtlasTextureBlockConvertAndPostProcess() +// - ImFontAtlasTextureBlockConvert() +// - ImFontAtlasTextureBlockPostProcessMultiply() +// - ImFontAtlasTextureBlockCopy() +// - ImFontAtlasTextureBlockQueueUpload() +//----------------------------------------------------------------------------- +// - ImFontAtlas::GetTexDataAsAlpha8() [legacy] +// - ImFontAtlas::GetTexDataAsRGBA32() [legacy] +// - ImFontAtlas::Build() +//----------------------------------------------------------------------------- // - ImFontAtlas::AddFont() // - ImFontAtlas::AddFontDefault() // - ImFontAtlas::AddFontFromFileTTF() // - ImFontAtlas::AddFontFromMemoryTTF() // - ImFontAtlas::AddFontFromMemoryCompressedTTF() // - ImFontAtlas::AddFontFromMemoryCompressedBase85TTF() +//----------------------------------------------------------------------------- // - ImFontAtlas::AddCustomRectRegular() // - ImFontAtlas::AddCustomRectFontGlyph() // - ImFontAtlas::CalcCustomRectUV() // - ImFontAtlasGetMouseCursorTexData() -// - ImFontAtlas::Build() -// - ImFontAtlasBuildMultiplyCalcLookupTable() -// - ImFontAtlasBuildMultiplyRectAlpha8() -// - ImFontAtlasBuildWithStbTruetype() -// - ImFontAtlasGetBuilderForStbTruetype() -// - ImFontAtlasUpdateSourcesPointers() -// - ImFontAtlasBuildSetupFont() -// - ImFontAtlasBuildPackCustomRects() -// - ImFontAtlasBuildRender8bppRectFromString() -// - ImFontAtlasBuildRender32bppRectFromString() -// - ImFontAtlasBuildRenderDefaultTexData() -// - ImFontAtlasBuildRenderLinesTexData() +//----------------------------------------------------------------------------- +// - ImFontAtlasBuildSetupFontLoader() +// - ImFontAtlasBuildPreloadAllGlyphRanges() +// - ImFontAtlasBuildUpdatePointers() +// - ImFontAtlasBuildRenderBitmapFromString() +// - ImFontAtlasBuildUpdateBasicTexData() +// - ImFontAtlasBuildUpdateLinesTexData() +// - ImFontAtlasBuildAddFont() +// - ImFontAtlasBuildSetupFontSpecialGlyphs() +// - ImFontAtlasBuildReloadFont() +//----------------------------------------------------------------------------- +// - ImFontAtlasAddDrawListSharedData() +// - ImFontAtlasRemoveDrawListSharedData() +// - ImFontAtlasUpdateDrawListsTextures() +// - ImFontAtlasUpdateDrawListsSharedData() +//----------------------------------------------------------------------------- +// - ImFontAtlasBuildSetTexture() +// - ImFontAtlasBuildAddTexture() +// - ImFontAtlasBuildRepackTexture() +// - ImFontAtlasBuildGrowTexture() +// - ImFontAtlasBuildCompactTexture() // - ImFontAtlasBuildInit() -// - ImFontAtlasBuildFinish() +// - ImFontAtlasBuildDestroy() +//----------------------------------------------------------------------------- +// - ImFontAtlasPackInit() +// - ImFontAtlasPackAddRect() +// - ImFontAtlasPackGetRect() +//----------------------------------------------------------------------------- +// - ImFont::BuildLoadGlyph() +// - ImFont::BuildClearGlyphs() +//----------------------------------------------------------------------------- +// - ImFontAtlasDebugLogTextureRequests() +//----------------------------------------------------------------------------- +// - ImFontAtlasGetFontLoaderForStbTruetype() //----------------------------------------------------------------------------- // A work of art lies ahead! (. = white layer, X = black layer, others are blank) @@ -2483,23 +2568,29 @@ static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3 { ImVec2(109,0),ImVec2(13,15), ImVec2( 6, 7) }, // ImGuiMouseCursor_NotAllowed }; +#define IM_FONTGLYPH_INDEX_UNUSED ((ImU16)-1) // 0xFFFF +#define IM_FONTGLYPH_INDEX_NOT_FOUND ((ImU16)-2) // 0xFFFE + ImFontAtlas::ImFontAtlas() { memset(this, 0, sizeof(*this)); + TexDesiredFormat = ImTextureFormat_RGBA32; TexGlyphPadding = 1; - TexID._Atlas = this; - PackIdMouseCursors = PackIdLines = -1; + TexRef._TexData = NULL;// this; + TexNextUniqueID = 1; + _PackNodesFactor = 1.0f; + Builder = NULL; } ImFontAtlas::~ImFontAtlas() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); Clear(); } -void ImFontAtlas::ClearInputData() +void ImFontAtlas::ClearInputData() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); for (ImFontConfig& font_cfg : Sources) if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas) { @@ -2516,77 +2607,281 @@ void ImFontAtlas::ClearInputData() } Sources.clear(); CustomRects.clear(); - PackIdMouseCursors = PackIdLines = -1; // Important: we leave TexReady untouched } -void ImFontAtlas::ClearTexData() +void ImFontAtlas::ClearTexData() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - if (TexPixelsAlpha8) - IM_FREE(TexPixelsAlpha8); - if (TexPixelsRGBA32) - IM_FREE(TexPixelsRGBA32); - TexPixelsAlpha8 = NULL; - TexPixelsRGBA32 = NULL; - TexPixelsUseColors = false; - // Important: we leave TexReady untouched + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + TexList.clear(); + IM_DELETE(TexData); + TexData = NULL; + // TexData.Destroy(); + //IM_ASSERT(0); + // Important: we leave TexReady untouched } -void ImFontAtlas::ClearFonts() +void ImFontAtlas::ClearFonts() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + // FIXME-NEWATLAS: Illegal to remove currently bound font. + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); ClearInputData(); Fonts.clear_delete(); - TexReady = false; + TexIsBuilt = false; + DrawListSharedData->Font = NULL; + DrawListSharedData->FontScale = DrawListSharedData->FontSize = 0.0f; } -void ImFontAtlas::Clear() +void ImFontAtlas::Clear() { + //IM_DELETE(Builder); // FIXME-NEW-ATLAS: ClearXXX functions + const ImFontLoader* font_loader = FontLoader; + ImFontAtlasBuildSetupFontLoader(this, NULL); ClearInputData(); ClearTexData(); ClearFonts(); + ImFontAtlasBuildSetupFontLoader(this, font_loader); } -void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +void ImFontAtlas::ClearCache() { - // Build atlas on demand - if (TexPixelsAlpha8 == NULL) - Build(); - - *out_pixels = TexPixelsAlpha8; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; + int tex_w = (TexData && TexData->Status != ImTextureStatus_WantDestroy) ? TexData->Width : 0; + int tex_h = (TexData && TexData->Status != ImTextureStatus_WantDestroy) ? TexData->Height : 0; + ImFontAtlasBuildDestroy(this); + if (tex_w != 0 && tex_h != 0) + ImFontAtlasBuildAddTexture(this, tex_w, tex_h); + ImFontAtlasBuildInit(this); } -void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +void ImFontAtlas::BuildGrowTexture() { - // Convert to RGBA32 format on demand - // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp - if (!TexPixelsRGBA32) + ImFontAtlasBuildGrowTexture(this, TexData->Width, TexData->Height); +} + +void ImFontAtlas::BuildCompactTexture() +{ + ImFontAtlasBuildCompactTexture(this); +} + +static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* atlas) +{ + // [LEGACY] Copy back the ImGuiBackendFlags_RendererHasTextures flag from ImGui context. + // - This is the 1% exceptional case where that dependency if useful, to bypass an issue where otherwise at the + // time of an early call to Build(), it would be impossible for us to tell if the backend supports texture update. + // - Without this hack, we would have quite a pitfall as many legacy codebases have an early call to Build(). + // Whereas conversely, the portion of people using ImDrawList without ImGui is expected to be pathologically rare. + if (atlas->DrawListSharedData) + if (ImGuiContext* imgui_ctx = atlas->DrawListSharedData->Context) + atlas->DrawListSharedData->RendererHasTextures = (imgui_ctx->IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; +} + +// Called by NewFrame() +void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) +{ + if (atlas->TexIsBuilt && atlas->Builder->PreloadedAllGlyphsRanges) { - unsigned char* pixels = NULL; - GetTexDataAsAlpha8(&pixels, NULL, NULL); - if (pixels) - { - TexPixelsRGBA32 = (unsigned int*)IM_ALLOC((size_t)TexWidth * (size_t)TexHeight * 4); - const unsigned char* src = pixels; - unsigned int* dst = TexPixelsRGBA32; - for (int n = TexWidth * TexHeight; n > 0; n--) - *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); - } + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); + IM_ASSERT_USER_ERROR(atlas->DrawListSharedData->RendererHasTextures == false, + "Called ImFontAtlas::Build() before ImGuiBackendFlags_RendererHasTextures got set! With new backends: you don't need to call Build()."); } - *out_pixels = (unsigned char*)TexPixelsRGBA32; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + ImTextureData* tex = atlas->TexList[tex_n]; + bool remove_from_list = false; + tex->Updates.resize(0); + tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; + tex->UpdateRect.w = tex->UpdateRect.h = 0; + + if (tex->Status == ImTextureStatus_Destroyed) + { + IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == NULL); + if (tex->WantDestroyNextFrame) + remove_from_list = true; // Destroy was scheduled by us + else + tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend (e.g. freed resources mid-run) + } + else if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_WantDestroy) + { + // Request destroy. Keep bool as it allows us to keep track of things. + IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); + tex->Status = ImTextureStatus_WantDestroy; + tex->DestroyPixels(); + } + + // The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering. + // We allow the texture staying in _WantDestroy state and increment a counter which the backend can use to take its decision. + if (tex->Status == ImTextureStatus_WantDestroy) + tex->UnusedFrames++; + + // If a texture has never reached the backend, they don't need to know about it. + if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == 0 && tex->BackendUserData == NULL) + remove_from_list = true; + + // Remove + if (remove_from_list) + { + IM_DELETE(tex); + atlas->TexList.erase(atlas->TexList.begin() + tex_n); + tex_n--; + } + } } +// Source buffer may be written to (used for in-place mods). +// Post-process hooks may eventually be added here. +void ImFontAtlasTextureBlockConvertAndPostProcess(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) +{ + IM_UNUSED(atlas); + IM_UNUSED(font); + IM_UNUSED(glyph); + + // Multiply operator (legacy) + if (src->RasterizerMultiply != 1.0f) + ImFontAtlasTextureBlockPostProcessMultiply(atlas, font, src, glyph, src_pixels, src_fmt, w, h, src_pitch, src->RasterizerMultiply); + + ImFontAtlasTextureBlockConvert(src_pixels, src_fmt, src_pitch, dst, dst_fmt, dst_pitch, w, h); +} + +void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) +{ + IM_ASSERT(src_pixels != NULL && dst_pixels != NULL); + if (src_fmt == dst_fmt) + { + int line_sz = w * GetTextureFormatBytesPerPixel(src_fmt); + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + memcpy(dst_pixels, src_pixels, line_sz); + } + else if (src_fmt == ImTextureFormat_Alpha8 && dst_fmt == ImTextureFormat_RGBA32) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU8* src_p = (const ImU8*)src_pixels; + ImU32* dst_p = (ImU32*)(void*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = IM_COL32(255, 255, 255, (unsigned int)(*src_p++)); + } + } + else if (src_fmt == ImTextureFormat_RGBA32 && dst_fmt == ImTextureFormat_Alpha8) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU32* src_p = (const ImU32*)(void*)src_pixels; + ImU8* dst_p = (ImU8*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = ((*src_p++) >> IM_COL32_A_SHIFT) & 0xFF; + } + } + else + { + IM_ASSERT(0); + } +} + +void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* pixels, ImTextureFormat format, int w, int h, int pitch, float in_multiply_factor) +{ + IM_UNUSED(atlas); + IM_UNUSED(font); + IM_UNUSED(src); + IM_UNUSED(glyph); + IM_ASSERT(in_multiply_factor >= 0.0f); + IM_ASSERT_PARANOID(w <= pitch); + if (format == ImTextureFormat_Alpha8) + { + for (int ny = h; ny > 0; ny--, pixels += pitch) + { + ImU8* p = (ImU8*)pixels; + for (int nx = w; nx > 0; nx--, p++) + { + unsigned int v = ImMin((unsigned int)(*p * in_multiply_factor), (unsigned int)255); + *p = (unsigned char)v; + } + } + } + else if (format == ImTextureFormat_RGBA32) + { + for (int ny = h; ny > 0; ny--, pixels += pitch) + { + ImU32* p = (ImU32*)(void*)pixels; + for (int nx = w; nx > 0; nx--, p++) + { + unsigned int a = ImMin((unsigned int)(((*p >> IM_COL32_A_SHIFT) & 0xFF) * in_multiply_factor), (unsigned int)255); + *p = IM_COL32((*p >> IM_COL32_R_SHIFT) & 0xFF, (*p >> IM_COL32_G_SHIFT) & 0xFF, (*p >> IM_COL32_B_SHIFT) & 0xFF, a); + } + } + } + else + { + IM_ASSERT(0); + } +} + +// Convert block from one texture to another +void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h) +{ + IM_ASSERT(src_tex != dst_tex); + IM_ASSERT(src_tex->Pixels != NULL && dst_tex->Pixels != NULL); + IM_ASSERT(src_tex->Format == dst_tex->Format); + IM_ASSERT(src_x >= 0 && src_x + w <= src_tex->Width); + IM_ASSERT(src_y >= 0 && src_y + h <= src_tex->Height); + IM_ASSERT(dst_x >= 0 && dst_x + w <= dst_tex->Width); + IM_ASSERT(dst_y >= 0 && dst_y + h <= dst_tex->Height); + for (int y = 0; y < h; y++) + memcpy(dst_tex->GetPixelsAt(dst_x, dst_y + y), src_tex->GetPixelsAt(src_x, src_y + y), w * dst_tex->BytesPerPixel); +} + +// Queue texture block update for renderer backend +void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h) +{ + // Queue texture update (no need to queue if status is _WantCreate) + IM_ASSERT(atlas); + if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates) + { + IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000); + ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h }; + tex->Status = ImTextureStatus_WantUpdates; + tex->Updates.push_back(req); + int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w); + int new_y1 = ImMax(tex->UpdateRect.h == 0 ? 0 : tex->UpdateRect.y + tex->UpdateRect.h, req.y + req.h); + IM_ASSERT(new_x1 < 0x8000); + IM_ASSERT(new_y1 < 0x8000); + tex->UpdateRect.x = ImMin(tex->UpdateRect.x, req.x); + tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y); + tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x); + tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y); + } +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +static void GetTexDataAsFormat(ImFontAtlas* atlas, ImTextureFormat format, unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + ImTextureData* tex = atlas->TexData; + if (!atlas->TexIsBuilt || tex == NULL || tex->Pixels == NULL || atlas->TexDesiredFormat != format) + { + atlas->TexDesiredFormat = format; + atlas->Build(); + tex = atlas->TexData; + } + if (out_pixels) { *out_pixels = (unsigned char*)tex->Pixels; }; + if (out_width) { *out_width = tex->Width; }; + if (out_height) { *out_height = tex->Height; }; + if (out_bytes_per_pixel) { *out_bytes_per_pixel = tex->BytesPerPixel; } +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_Alpha8, out_pixels, out_width, out_height, out_bytes_per_pixel); +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_RGBA32, out_pixels, out_width, out_height, out_bytes_per_pixel); +} +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); IM_ASSERT(font_cfg->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); @@ -2614,12 +2909,15 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) // - We may support it better later and remove this rounding. new_font_cfg.SizePixels = ImTrunc(new_font_cfg.SizePixels); - // Pointers to Sources data are otherwise dangling - ImFontAtlasUpdateSourcesPointers(this); + // Pointers to Sources are otherwise dangling + ImFontAtlasBuildUpdatePointers(this); + + if (Builder != NULL) + ImFontAtlasBuildAddFont(this, &new_font_cfg); // Invalidate texture - TexReady = false; - ClearTexData(); + //TexReady = false; + //ClearTexData(); return new_font_cfg.DstFont; } @@ -2672,7 +2970,7 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); size_t data_size = 0; void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); if (!data) @@ -2694,7 +2992,7 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, // NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); IM_ASSERT(font_cfg.FontData == NULL); IM_ASSERT(font_data_size > 100 && "Incorrect value for font_data_size!"); // Heuristic to prevent accidentally passing a wrong value to font_data_size. @@ -2728,6 +3026,25 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed return font; } +// FIXME-NEWATLAS-V1: Feature is broken for now. +/* + // Register custom rectangle glyphs + for (int i = 0; i < atlas->CustomRects.Size; i++) + { + const ImFontAtlasCustomRect* r = &atlas->CustomRects[i]; + if (r->Font == NULL || r->GlyphID == 0) + continue; + + // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, PixelSnapH + IM_ASSERT(r->Font->ContainerAtlas == atlas); + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(r, &uv0, &uv1); + r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); + if (r->GlyphColored) + r->Font->Glyphs.back().Colored = 1; + } +*/ + int ImFontAtlas::AddCustomRectRegular(int width, int height) { IM_ASSERT(width > 0 && width <= 0xFFFF); @@ -2761,7 +3078,7 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const { - IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates + IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); @@ -2774,9 +3091,8 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) return false; - IM_ASSERT(atlas->PackIdMouseCursors != -1); - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); - ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->X, (float)r->Y); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, atlas->Builder->PackIdMouseCursors); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->x, (float)r->y); ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; *out_size = size; *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; @@ -2788,407 +3104,104 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso return true; } -bool ImFontAtlas::Build() +bool ImFontAtlas::Build() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); // Default font is none are specified if (Sources.Size == 0) AddFontDefault(); // Select builder - // - Note that we do not reassign to atlas->FontBuilderIO, since it is likely to point to static data which + // - Note that we do not reassign to atlas->FontLoader, since it is likely to point to static data which // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are - // using a hot-reloading scheme that messes up static data, store your own instance of ImFontBuilderIO somewhere - // and point to it instead of pointing directly to return value of the GetBuilderXXX functions. - const ImFontBuilderIO* builder_io = FontBuilderIO; - if (builder_io == NULL) + // using a hot-reloading scheme that messes up static data, store your own instance of ImFontLoader somewhere + // and point to it instead of pointing directly to return value of the GetBackendIOXXX functions. + if (FontLoader == NULL) { #ifdef IMGUI_ENABLE_FREETYPE - builder_io = ImGuiFreeType::GetBuilderForFreeType(); + ImFontAtlasBuildSetupFontLoader(this, ImGuiFreeType::GetBackendIOForFreeType()); #elif defined(IMGUI_ENABLE_STB_TRUETYPE) - builder_io = ImFontAtlasGetBuilderForStbTruetype(); + ImFontAtlasBuildSetupFontLoader(this, ImFontAtlasGetFontLoaderForStbTruetype()); #else IM_ASSERT(0); // Invalid Build function #endif } - // Build - return builder_io->FontBuilder_Build(this); + // Create initial texture size + ImFontAtlasBuildAddTexture(this, 512, 128); + ImFontAtlasBuildInit(this); + + // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(this); + if (DrawListSharedData->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures + ImFontAtlasBuildPreloadAllGlyphRanges(this); + TexIsBuilt = true; + + return true; } -void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) -{ - for (unsigned int i = 0; i < 256; i++) - { - unsigned int value = (unsigned int)(i * in_brighten_factor); - out_table[i] = value > 255 ? 255 : (value & 0xFF); - } -} - -void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) -{ - IM_ASSERT_PARANOID(w <= stride); - unsigned char* data = pixels + x + y * stride; - for (int j = h; j > 0; j--, data += stride - w) - for (int i = w; i > 0; i--, data++) - *data = table[*data]; -} - -void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v) +void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v) { // Automatically disable horizontal oversampling over size 36 *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (src->SizePixels * src->RasterizerDensity > 36.0f || src->PixelSnapH) ? 1 : 2; *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1; } -#ifdef IMGUI_ENABLE_STB_TRUETYPE -// Temporary data for one source font (multiple source fonts can be merged into one destination ImFont) -// (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.) -struct ImFontBuildSrcData +void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader) { - stbtt_fontinfo FontInfo; - stbtt_pack_range PackRange; // Hold the list of codepoints to pack (essentially points to Codepoints.Data) - stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position. - stbtt_packedchar* PackedChars; // Output glyphs - const ImWchar* SrcRanges; // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF) - int DstIndex; // Index into atlas->Fonts[] and dst_tmp_array[] - int GlyphsHighest; // Highest requested codepoint - int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font) - ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB) - ImVector GlyphsList; // Glyph codepoints list (flattened version of GlyphsSet) -}; + if (atlas->FontLoader == font_loader) + return; + IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); -// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont) -struct ImFontBuildDstData -{ - int SrcCount; // Number of source fonts targeting this destination font. - int GlyphsHighest; - int GlyphsCount; - ImBitVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font. -}; - -static void UnpackBitVectorToFlatIndexList(const ImBitVector* in, ImVector* out) -{ - IM_ASSERT(sizeof(in->Storage.Data[0]) == sizeof(int)); - const ImU32* it_begin = in->Storage.begin(); - const ImU32* it_end = in->Storage.end(); - for (const ImU32* it = it_begin; it < it_end; it++) - if (ImU32 entries_32 = *it) - for (ImU32 bit_n = 0; bit_n < 32; bit_n++) - if (entries_32 & ((ImU32)1 << bit_n)) - out->push_back((int)(((it - it_begin) << 5) + bit_n)); + ImFontAtlasBuildDestroy(atlas); + if (atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + { + atlas->FontLoader->LoaderShutdown(atlas); + IM_ASSERT(atlas->FontLoaderData == NULL); + } + atlas->FontLoader = font_loader; + atlas->FontLoaderName = font_loader ? font_loader->Name : "NULL"; + if (atlas->FontLoader && atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); + if (atlas->Builder && font_loader != NULL) + atlas->ClearCache(); } -static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) +// Preload all glyph ranges for legacy backends. +// This may lead to multiple texture creation which might be a little slower than before. +void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas) { - IM_ASSERT(atlas->Sources.Size > 0); - - ImFontAtlasBuildInit(atlas); - - // Clear atlas - atlas->TexID._TexID = 0; - atlas->TexWidth = atlas->TexHeight = 0; - atlas->TexUvScale = ImVec2(0.0f, 0.0f); - atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); - atlas->ClearTexData(); - - // Temporary storage for building - ImVector src_tmp_array; - ImVector dst_tmp_array; - src_tmp_array.resize(atlas->Sources.Size); - dst_tmp_array.resize(atlas->Fonts.Size); - memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes()); - memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes()); - - // 1. Initialize font loading structure, check font data validity - for (int src_i = 0; src_i < atlas->Sources.Size; src_i++) + atlas->Builder->PreloadedAllGlyphsRanges = true; + for (int src_n = 0; src_n < atlas->Sources.Size; src_n++) { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - IM_ASSERT(src.DstFont && (!src.DstFont->IsLoaded() || src.DstFont->ContainerAtlas == atlas)); - - // Find index from src.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices) - src_tmp.DstIndex = -1; - for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++) - if (src.DstFont == atlas->Fonts[output_i]) - src_tmp.DstIndex = output_i; - if (src_tmp.DstIndex == -1) - { - IM_ASSERT(src_tmp.DstIndex != -1); // src.DstFont not pointing within atlas->Fonts[] array? - return false; - } - // Initialize helper structure for font loading and verify that the TTF/OTF data is correct - const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src.FontData, src.FontNo); - IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); - if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)src.FontData, font_offset)) - { - IM_ASSERT(0 && "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); - return false; - } - - // Measure highest codepoints - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.SrcRanges = src.GlyphRanges ? src.GlyphRanges : atlas->GetGlyphRangesDefault(); - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - { - // Check for valid range. This may also help detect *some* dangling pointers, because a common - // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent, - // or to forget to zero-terminate the glyph range array. - IM_ASSERT(src_range[0] <= src_range[1] && "Invalid range: is your glyph range array persistent? it is zero-terminated?"); - src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]); - } - dst_tmp.SrcCount++; - dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest); + ImFontConfig* src = &atlas->Sources[src_n]; + const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); + IM_ASSERT(ranges != NULL); + for (; ranges[0]; ranges += 2) + for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 + src->DstFont->FindGlyphNoFallback((ImWchar)c); } - - // 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs. - int total_glyphs_count = 0; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.GlyphsSet.Create(src_tmp.GlyphsHighest + 1); - if (dst_tmp.GlyphsSet.Storage.empty()) - dst_tmp.GlyphsSet.Create(dst_tmp.GlyphsHighest + 1); - - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - for (unsigned int codepoint = src_range[0]; codepoint <= src_range[1]; codepoint++) - { - if (dst_tmp.GlyphsSet.TestBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option for MergeMode (e.g. MergeOverwrite==true) - continue; - if (!stbtt_FindGlyphIndex(&src_tmp.FontInfo, codepoint)) // It is actually in the font? - continue; - - // Add to avail set/counters - src_tmp.GlyphsCount++; - dst_tmp.GlyphsCount++; - src_tmp.GlyphsSet.SetBit(codepoint); - dst_tmp.GlyphsSet.SetBit(codepoint); - total_glyphs_count++; - } - } - - // 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another) - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount); - UnpackBitVectorToFlatIndexList(&src_tmp.GlyphsSet, &src_tmp.GlyphsList); - src_tmp.GlyphsSet.Clear(); - IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount); - } - for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++) - dst_tmp_array[dst_i].GlyphsSet.Clear(); - dst_tmp_array.clear(); - - // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) - // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity) - ImVector buf_rects; - ImVector buf_packedchars; - buf_rects.resize(total_glyphs_count); - buf_packedchars.resize(total_glyphs_count); - memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes()); - memset(buf_packedchars.Data, 0, (size_t)buf_packedchars.size_in_bytes()); - - // 4. Gather glyphs sizes so we can pack them in our virtual canvas. - int total_surface = 0; - int buf_rects_out_n = 0; - int buf_packedchars_out_n = 0; - const int pack_padding = atlas->TexGlyphPadding; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - - src_tmp.Rects = &buf_rects[buf_rects_out_n]; - src_tmp.PackedChars = &buf_packedchars[buf_packedchars_out_n]; - buf_rects_out_n += src_tmp.GlyphsCount; - buf_packedchars_out_n += src_tmp.GlyphsCount; - - // Automatic selection of oversampling parameters - ImFontConfig& src = atlas->Sources[src_i]; - int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(&src, &oversample_h, &oversample_v); - - // Convert our ranges in the format stb_truetype wants - src_tmp.PackRange.font_size = src.SizePixels * src.RasterizerDensity; - src_tmp.PackRange.first_unicode_codepoint_in_range = 0; - src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data; - src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size; - src_tmp.PackRange.chardata_for_range = src_tmp.PackedChars; - src_tmp.PackRange.h_oversample = (unsigned char)oversample_h; - src_tmp.PackRange.v_oversample = (unsigned char)oversample_v; - - // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) - const float scale = (src.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels * src.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -src.SizePixels * src.RasterizerDensity); - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) - { - int x0, y0, x1, y1; - const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]); - IM_ASSERT(glyph_index_in_font != 0); - stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * oversample_h, scale * oversample_v, 0, 0, &x0, &y0, &x1, &y1); - src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + pack_padding + oversample_h - 1); - src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + pack_padding + oversample_v - 1); - total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; - } - } - for (int i = 0; i < atlas->CustomRects.Size; i++) - total_surface += (atlas->CustomRects[i].Width + pack_padding) * (atlas->CustomRects[i].Height + pack_padding); - - // We need a width for the skyline algorithm, any width! - // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. - // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface. - const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1; - atlas->TexHeight = 0; - if (atlas->TexDesiredWidth > 0) - atlas->TexWidth = atlas->TexDesiredWidth; - else - atlas->TexWidth = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512; - - // 5. Start packing - // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). - const int TEX_HEIGHT_MAX = 1024 * 32; - stbtt_pack_context spc = {}; - stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, 0, NULL); - spc.padding = atlas->TexGlyphPadding; // Because we mixup stbtt_PackXXX and stbrp_PackXXX there's a bit of a hack here, not passing the value to stbtt_PackBegin() allows us to still pack a TexWidth-1 wide item. (#8107) - ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); - - // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - - stbrp_pack_rects((stbrp_context*)spc.pack_info, src_tmp.Rects, src_tmp.GlyphsCount); - - // Extend texture height and mark missing glyphs as non-packed so we won't render them. - // FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?) - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - if (src_tmp.Rects[glyph_i].was_packed) - atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h); - } - - // 7. Allocate texture - atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); - atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); - atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight); - memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); - spc.pixels = atlas->TexPixelsAlpha8; - spc.height = atlas->TexHeight; - - // 8. Render/rasterize font characters into the texture - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontConfig& src = atlas->Sources[src_i]; - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - - stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects); - - // Apply multiply operator - if (src.RasterizerMultiply != 1.0f) - { - unsigned char multiply_table[256]; - ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, src.RasterizerMultiply); - stbrp_rect* r = &src_tmp.Rects[0]; - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++, r++) - if (r->was_packed) - ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, atlas->TexPixelsAlpha8, r->x, r->y, r->w, r->h, atlas->TexWidth * 1); - } - src_tmp.Rects = NULL; - } - - // End packing - stbtt_PackEnd(&spc); - buf_rects.clear(); - - // 9. Setup ImFont and glyphs for runtime - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - // When merging fonts with MergeMode=true: - // - We can have multiple input fonts writing into a same destination font. - // - dst_font->Sources is != from src which is our source configuration. - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - ImFont* dst_font = src.DstFont; - - const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels); - int unscaled_ascent, unscaled_descent, unscaled_line_gap; - stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); - - const float ascent = ImCeil(unscaled_ascent * font_scale); - const float descent = ImFloor(unscaled_descent * font_scale); - ImFontAtlasBuildSetupFont(atlas, dst_font, &src, ascent, descent); - const float font_off_x = src.GlyphOffset.x; - const float font_off_y = src.GlyphOffset.y + IM_ROUND(dst_font->Ascent); - - const float inv_rasterization_scale = 1.0f / src.RasterizerDensity; - - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - { - // Register glyph - const int codepoint = src_tmp.GlyphsList[glyph_i]; - const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i]; - stbtt_aligned_quad q; - float unused_x = 0.0f, unused_y = 0.0f; - stbtt_GetPackedQuad(src_tmp.PackedChars, atlas->TexWidth, atlas->TexHeight, glyph_i, &unused_x, &unused_y, &q, 0); - float x0 = q.x0 * inv_rasterization_scale + font_off_x; - float y0 = q.y0 * inv_rasterization_scale + font_off_y; - float x1 = q.x1 * inv_rasterization_scale + font_off_x; - float y1 = q.y1 * inv_rasterization_scale + font_off_y; - dst_font->AddGlyph(&src, (ImWchar)codepoint, x0, y0, x1, y1, q.s0, q.t0, q.s1, q.t1, pc.xadvance * inv_rasterization_scale); - } - } - - // Cleanup - src_tmp_array.clear_destruct(); - - ImFontAtlasBuildFinish(atlas); - return true; } -const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype() +void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) { - static ImFontBuilderIO io; - io.FontBuilder_Build = ImFontAtlasBuildWithStbTruetype; - return &io; -} - -#endif // IMGUI_ENABLE_STB_TRUETYPE - -void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas) -{ - for (ImFontConfig& src : atlas->Sources) + for (int src_n = 0; src_n < atlas->Sources.Size; src_n++) { - ImFont* font = src.DstFont; - if (!src.MergeMode) + ImFontConfig* src = &atlas->Sources[src_n]; + ImFont* font = src->DstFont; + if (!src->MergeMode) { - font->Sources = &src; + font->Sources = src; font->SourcesCount = 0; } font->SourcesCount++; } } -void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) -{ - if (!font_config->MergeMode) - { - font->ClearOutputData(); - font->FontSize = font_config->SizePixels; - IM_ASSERT(font->Sources == font_config); - font->ContainerAtlas = atlas; - font->Ascent = ascent; - font->Descent = descent; - } -} - void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque) { + ImTextureData* tex = atlas->TexData; stbrp_context* pack_context = (stbrp_context*)stbrp_context_opaque; IM_ASSERT(pack_context != NULL); @@ -3213,175 +3226,828 @@ void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opa { user_rects[i].X = (unsigned short)pack_rects[i].x; user_rects[i].Y = (unsigned short)pack_rects[i].y; - IM_ASSERT(pack_rects[i].w == user_rects[i].Width + pack_padding && pack_rects[i].h == user_rects[i].Height + pack_padding); - atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + tex->Height = ImMax(tex->Height, pack_rects[i].y + pack_rects[i].h); } } -void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value) +// Render a white-colored bitmap encoded in a string +void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char) { - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned char* out_pixel = atlas->TexPixelsAlpha8 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : 0x00; -} + ImTextureData* tex = atlas->TexData; + IM_ASSERT(x >= 0 && x + w <= tex->Width); + IM_ASSERT(y >= 0 && y + h <= tex->Height); -void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value) -{ - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned int* out_pixel = atlas->TexPixelsRGBA32 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : IM_COL32_BLACK_TRANS; -} - -static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) -{ - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); - IM_ASSERT(r->IsPacked()); - - const int w = atlas->TexWidth; - if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) + switch (tex->Format) { - // White pixels only - IM_ASSERT(r->Width == 2 && r->Height == 2); - const int offset = (int)r->X + (int)r->Y * w; - if (atlas->TexPixelsAlpha8 != NULL) + case ImTextureFormat_Alpha8: + { + ImU8* out_p = tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? 0xFF : 0x00; + break; + } + case ImTextureFormat_RGBA32: + { + ImU32* out_p = (ImU32*)(void*)tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? IM_COL32_WHITE : IM_COL32_BLACK_TRANS; + break; + } + } +} + +static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_draw) +{ + ImVec2i pack_size = (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) ? ImVec2i(2, 2) : ImVec2i(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + + // Pack and store identifier so we can refresh UV coordinates on texture resize. + // FIXME-NEWATLAS: User/custom rects where user code wants to store UV coordinates will need to do the same thing. + ImFontAtlasBuilder* builder = atlas->Builder; + + if (add_and_draw) + builder->PackIdMouseCursors = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdMouseCursors); + + // Draw to texture + if (add_and_draw) + { + if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) { - atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + // 2x2 white pixels + ImFontAtlasBuildRenderBitmapFromString(atlas, r->x, r->y, 2, 2, "XX" "XX", 'X'); } else { - atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; + // 2x2 white pixels + mouse cursors + const int x_for_white = r->x; + const int x_for_black = r->x + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_white, r->y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.'); + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r->y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); } } - else - { - // White pixels and mouse cursor - IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); - const int x_for_white = r->X; - const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; - if (atlas->TexPixelsAlpha8 != NULL) - { - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', 0xFF); - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', 0xFF); - } - else - { - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', IM_COL32_WHITE); - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE); - } - } - atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); + atlas->TexUvWhitePixel = ImVec2((r->x + 0.5f) * atlas->TexUvScale.x, (r->y + 0.5f) * atlas->TexUvScale.y); } -static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) +static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_draw) { if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) return; + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + + // Pack and store identifier so we can refresh UV coordinates on texture resize. + ImFontAtlasBuilder* builder = atlas->Builder; + if (add_and_draw) + builder->PackIdLinesTexData = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdLinesTexData); + + // Register texture region for thick lines + // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines); - IM_ASSERT(r->IsPacked()); + ImTextureData* tex = atlas->TexData; for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle int y = n; int line_width = n; - int pad_left = (r->Width - line_width) / 2; - int pad_right = r->Width - (pad_left + line_width); + int pad_left = (r->w - line_width) / 2; + int pad_right = r->w - (pad_left + line_width); // Write each slice - IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels - if (atlas->TexPixelsAlpha8 != NULL) + IM_ASSERT(pad_left + line_width + pad_right == r->w && y < r->h); // Make sure we're inside the texture bounds before we start writing pixels + if (add_and_draw) { - unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = 0x00; + switch (tex->Format) + { + case ImTextureFormat_Alpha8: + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r->x, r->y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = 0x00; - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = 0xFF; + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = 0xFF; - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = 0x00; - } - else - { - unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = 0x00; + break; + } + case ImTextureFormat_RGBA32: + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r->x, r->y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = IM_COL32(255, 255, 255, 0); - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = IM_COL32_WHITE; + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = IM_COL32_WHITE; - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); + break; + } + } } // Calculate UVs for this line - ImVec2 uv0 = ImVec2((float)(r->X + pad_left - 1), (float)(r->Y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r->X + pad_left + line_width + 1), (float)(r->Y + y + 1)) * atlas->TexUvScale; + ImVec2 uv0 = ImVec2((float)(r->x + pad_left - 1), (float)(r->y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r->x + pad_left + line_width + 1), (float)(r->y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } } -// Note: this is called / shared by both the stb_truetype and the FreeType builder +//----------------------------------------------------------------------------------------------------------------------------- + +static const ImFontGlyph* LoadFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) +{ + for (int n = 0; n < candidate_chars_count; n++) + if (candidate_chars[n] != 0) + if (const ImFontGlyph* glyph = font->FindGlyphNoFallback(candidate_chars[n])) + return glyph; + return NULL; +} + +bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) +{ + ImFont* font = src->DstFont; + if (src->MergeMode == false) + { + font->ClearOutputData(); + font->FontSize = src->SizePixels; + font->ContainerAtlas = atlas; + IM_ASSERT(font->Sources == src); + } + + const ImFontLoader* font_loader = atlas->FontLoader; + if (!font_loader->FontSrcInit(atlas, src)) + return false; // FIXME-NEWATLAS: error handling + + ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, src); + return true; +} + +// Load/identify special glyphs +// (note that this is called again for fonts with MergeMode) +void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src) +{ + ImFont* font = src->DstFont; + IM_UNUSED(atlas); + + // While manipulating glyphs during init we want to restrict all searches for one source font. + font->LockSingleSrcConfig = src; + + // Setup Fallback character + // FIXME-NEWATLAS: could we use a scheme where this is lazily loaded? + const ImWchar fallback_chars[] = { font->FallbackChar, (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; + if (font->FallbackGlyphIndex == -1) + if (const ImFontGlyph* glyph = LoadFirstExistingGlyph(font, fallback_chars, IM_ARRAYSIZE(fallback_chars))) + { + font->FallbackChar = (ImWchar)glyph->Codepoint; + font->FallbackGlyphIndex = font->Glyphs.index_from_ptr(glyph); // Storing index avoid need to update pointer on growth. + font->FallbackAdvanceX = glyph->AdvanceX; + } + + // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) + ImFontGlyph* space_glyph = (ImFontGlyph*)(void*)font->FindGlyph((ImWchar)' '); + if (space_glyph != NULL) + space_glyph->Visible = false; + + // Setup Tab character. + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (font->FindGlyphNoFallback('\t') == NULL && space_glyph != NULL) + { + ImFontGlyph tab_glyph; + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX = space_glyph->AdvanceX * IM_TABSIZE; + font->BuildRegisterGlyph(font->Sources, &tab_glyph); + } + + // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). + // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. + // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. + const ImWchar ellipsis_chars[] = { src->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; + if (font->EllipsisChar == 0) + if (const ImFontGlyph* glyph = LoadFirstExistingGlyph(font, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars))) + { + font->EllipsisChar = (ImWchar)glyph->Codepoint; + font->EllipsisCharCount = 1; + font->EllipsisWidth = font->EllipsisCharStep = glyph->X1; + } + if (font->EllipsisChar == 0) + { + // FIXME-NEWATLAS-V2: We can now rasterize this into a regular character and register it! + const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; + if (const ImFontGlyph* dot_glyph = LoadFirstExistingGlyph(font, dots_chars, IM_ARRAYSIZE(dots_chars))) + { + font->EllipsisChar = (ImWchar)dot_glyph->Codepoint; + font->EllipsisCharCount = 3; + font->EllipsisCharStep = (float)(int)(dot_glyph->X1 - dot_glyph->X0) + 1.0f; + font->EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + font->EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. + } + } + + font->LockSingleSrcConfig = NULL; +} + +// Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* +void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(atlas->DrawListSharedData == NULL && data->FontAtlas == NULL); + atlas->DrawListSharedData = data; + data->FontAtlas = atlas; +} + +void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(atlas->DrawListSharedData == data && data->FontAtlas == atlas); + atlas->DrawListSharedData = data; + data->FontAtlas = NULL; +} + +// Update texture identifier in all active draw lists +void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex) +{ + ImDrawListSharedData* shared_data = atlas->DrawListSharedData; + for (ImDrawList* draw_list : shared_data->DrawLists) + { + // Replace in command-buffer + // (there is not need to replace in ImDrawListSplitter: current channel is in ImDrawList's CmdBuffer[], + // other channels will be on SetCurrentChannel() which already needs to compare CmdHeader anyhow) + if (draw_list->CmdBuffer.Size > 0 && draw_list->_CmdHeader.TexRef == old_tex) + draw_list->_SetTexture(new_tex); + + // Replace in stack + for (ImTextureRef& stacked_tex : draw_list->_TextureStack) + if (stacked_tex == old_tex) + stacked_tex = new_tex; + } +} + +// Update texture coordinates in all draw list shared context +void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) +{ + ImDrawListSharedData* shared_data = atlas->DrawListSharedData; + shared_data->FontAtlas = atlas; + shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; + shared_data->TexUvLines = atlas->TexUvLines; +} + +// Set current texture. This is mostly called from AddTexture() + to handle a failed resize. +static void ImFontAtlasBuildSetTexture(ImFontAtlas* atlas, ImTextureData* tex) +{ + ImTextureRef old_tex_ref = atlas->TexRef; + atlas->TexData = tex; + atlas->TexUvScale = ImVec2(1.0f / tex->Width, 1.0f / tex->Height); + atlas->TexRef._TexData = tex; + //atlas->TexID._TexID = tex->TexID; // <-- We intentionally don't do that and leave it 0, to allow late upload. + ImTextureRef new_tex_ref = atlas->TexRef; + ImFontAtlasUpdateDrawListsTextures(atlas, old_tex_ref, new_tex_ref); +} + +// Create a new texture, discard previous one +ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h) +{ + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex; + + // FIXME: Cannot reuse texture because old UV may have been used already (unless we remap UV). + /*if (old_tex != NULL && old_tex->Status == ImTextureStatus_WantCreate) + { + // Reuse texture not yet used by backend. + IM_ASSERT(old_tex->TexID == 0 && old_tex->BackendUserData == NULL); + old_tex->DestroyPixels(); + old_tex->Updates.clear(); + new_tex = old_tex; + old_tex = NULL; + } + else*/ + { + // Add new + new_tex = IM_NEW(ImTextureData)(); + new_tex->UniqueID = atlas->TexNextUniqueID++; + atlas->TexList.push_back(new_tex); + } + if (old_tex != NULL) + { + // Queue old as to destroy next frame + old_tex->WantDestroyNextFrame = true; + IM_ASSERT(old_tex->Status == ImTextureStatus_OK || old_tex->Status == ImTextureStatus_WantCreate || old_tex->Status == ImTextureStatus_WantUpdates); + } + + new_tex->Create(atlas->TexDesiredFormat, w, h); + new_tex->Status = ImTextureStatus_WantCreate; + + ImFontAtlasBuildSetTexture(atlas, new_tex); + + return new_tex; +} + +void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(g); // for IMGUI_DEBUG_LOG_FONT() + + ImFontAtlasBuilder* builder = atlas->Builder; + builder->LockDisableResize = true; + + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex = ImFontAtlasBuildAddTexture(atlas, w, h); + new_tex->UseColors = old_tex->UseColors; + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize+repack %dx%d => Texture #%03d: %dx%d\n", old_tex->UniqueID, old_tex->Width, old_tex->Height, new_tex->UniqueID, new_tex->Width, new_tex->Height); + + // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. + +#if 1 + // Repack + copy pixels + // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic. + ImFontAtlasPackInit(atlas); + ImVector old_rects; + old_rects.swap(builder->Rects); + for (ImFontAtlasRect& old_r : old_rects) + { + ImFontAtlasRectId new_r_id = ImFontAtlasPackAddRect(atlas, old_r.w, old_r.h); + if (new_r_id == -1) + { + // Undo, grow texture and try repacking again. + // FIXME-NEWATLAS-TESTS: This is a very rarely exercised path! It needs to be automatically tested properly. + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize failed. Will grow.\n", new_tex->UniqueID); + new_tex->WantDestroyNextFrame = true; + old_rects.swap(builder->Rects); + ImFontAtlasBuildSetTexture(atlas, old_tex); + ImFontAtlasBuildGrowTexture(atlas, w, h); + return; + } + ImFontAtlasRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); + ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h); + } + IM_ASSERT(old_rects.Size == builder->Rects.Size); + + // Patch glyphs UV + for (ImFont* font : atlas->Fonts) + for (ImFontGlyph& glyph : font->Glyphs) + if (glyph.PackId != -1) + { + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + } + + // Update other cached UV + ImFontAtlasBuildUpdateLinesTexData(atlas, false); + ImFontAtlasBuildUpdateBasicTexData(atlas, false); + +#else + // Copy previous pixels + ImFontAtlasTextureCopyBlock(atlas, old_tex, 0, 0, new_tex, 0, 0, ImMin(old_tex->Width, new_tex->Width), ImMin(old_tex->Height, new_tex->Height)); + + // Scale UV coordinates + // FIXME-NEWATLAS: Probably lossy? + ImVec2 uv_scale((float)old_tex->Width / new_tex->Width, (float)old_tex->Height / new_tex->Height); + for (ImFont* font : atlas->Fonts) + for (ImFontGlyph& glyph : font->Glyphs) + { + glyph.U0 *= uv_scale.x; + glyph.U1 *= uv_scale.x; + glyph.V0 *= uv_scale.y; + glyph.V1 *= uv_scale.y; + } + ImVec4 uv_scale4(uv_scale.x, uv_scale.y, uv_scale.x, uv_scale.y); + atlas->TexUvWhitePixel *= uv_scale; + for (ImVec4& uv : atlas->TexUvLines) + uv = uv * uv_scale4; +#endif + + builder->LockDisableResize = false; + ImFontAtlasUpdateDrawListsSharedData(atlas); +} + +void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_h) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + if (old_tex_w == -1) + old_tex_w = atlas->TexData->Width; + if (old_tex_h == -1) + old_tex_h = atlas->TexData->Height; + + // FIXME-NEWATLAS-V1: Handle atlas->TexDesiredWidth from user? + // FIXME-NEWATLAS-V1: What to do when reaching limits exposed by backend? + // FIXME-NEWATLAS-V1: does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? + IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); + + // Grow texture so it follows roughly a square. + int new_tex_w = (old_tex_h < old_tex_w) ? old_tex_w : old_tex_w * 2; + int new_tex_h = (old_tex_h < old_tex_w) ? old_tex_h * 2 : old_tex_h; + + // Handle minimum size first (for pathologically large packed rects) + new_tex_w = ImMax(new_tex_w, ImUpperPowerOfTwo(builder->MaxRectSize.x + builder->PackPadding)); + new_tex_h = ImMax(new_tex_h, ImUpperPowerOfTwo(builder->MaxRectSize.y + builder->PackPadding)); + + ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); +} + +// You should not need to call this manually! +// If you think you do, let us know and we can advise about policies auto-compact. +void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + + ImTextureData* old_tex = atlas->TexData; + int old_tex_w = old_tex->Width; + int old_tex_h = old_tex->Height; + + // FIXME-NEWATLAS: Expose atlas->TexMinWidth etc. + const int min_w = ImMax(builder->MaxRectSize.x, 512); + const int min_h = builder->MaxRectSize.y; + const int surface_sqrt = (int)sqrtf((float)atlas->_PackedSurface); + + int new_tex_w; + int new_tex_h; + if (min_w >= min_h) + { + new_tex_w = ImMax(min_w, ImUpperPowerOfTwo(surface_sqrt)); + new_tex_h = ImMax(min_h, (int)(atlas->_PackedSurface / new_tex_w)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + } + else + { + new_tex_h = ImMax(min_h, ImUpperPowerOfTwo(surface_sqrt)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + new_tex_w = ImMax(min_w, (int)(atlas->_PackedSurface / new_tex_h)); + } + + if (new_tex_w == old_tex_w && new_tex_h == old_tex_h) + return; + + ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); +} + +// Start packing over current empty texture void ImFontAtlasBuildInit(ImFontAtlas* atlas) { - // Register texture region for mouse cursors or standard white pixels - if (atlas->PackIdMouseCursors < 0) + ImFontAtlasBuilder* builder = atlas->Builder; + + const bool builder_is_new = (builder == NULL); + if (builder_is_new) { - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); - else - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(2, 2); + builder = atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); + builder->PackPadding = 1; } - // Register texture region for thick lines - // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - if (atlas->PackIdLines < 0) + ImFontAtlasPackInit(atlas); + + // Add required texture data + ImFontAtlasBuildUpdateLinesTexData(atlas, true); + ImFontAtlasBuildUpdateBasicTexData(atlas, true); + + // Register fonts + if (builder_is_new) { - if (!(atlas->Flags & ImFontAtlasFlags_NoBakedLines)) - atlas->PackIdLines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + ImFontAtlasBuildUpdatePointers(atlas); + for (ImFontConfig& cfg : atlas->Sources) + ImFontAtlasBuildAddFont(atlas, &cfg); } + + // Update UV coordinates etc. stored in bound ImDrawListSharedData instance + ImFontAtlasUpdateDrawListsSharedData(atlas); + + //atlas->TexIsBuilt = true; } -// This is called/shared by both the stb_truetype and the FreeType builder. -void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +// Destroy builder and all cached glyphs. Do not destroy actual fonts. +void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) { - // Render into our custom data blocks - IM_ASSERT(atlas->TexPixelsAlpha8 != NULL || atlas->TexPixelsRGBA32 != NULL); - ImFontAtlasBuildRenderDefaultTexData(atlas); - ImFontAtlasBuildRenderLinesTexData(atlas); + for (ImFont* font : atlas->Fonts) + font->BuildClearGlyphs(); + if (atlas->FontLoader && atlas->FontLoader->FontSrcDestroy != NULL) + for (ImFontConfig& font_cfg : atlas->Sources) + atlas->FontLoader->FontSrcDestroy(atlas, &font_cfg); - // Register custom rectangle glyphs - for (int i = 0; i < atlas->CustomRects.Size; i++) + IM_DELETE(atlas->Builder); + atlas->Builder = NULL; +} + +void ImFontAtlasPackInit(ImFontAtlas* atlas) +{ + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; + + // FIXME-NEWATLAS-V2: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 + // FIXME-NEWATLAS-V2: Experiment with number of nodes. 2024-11-05: Seems to be quite fine to reduce this. + int pack_node_count = tex->Width - builder->PackPadding; + //pack_node_count *= atlas->_PackNodesFactor; + builder->PackNodes.resize(pack_node_count); + IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); + stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width - builder->PackPadding, tex->Height - builder->PackPadding, builder->PackNodes.Data, builder->PackNodes.Size); + atlas->_PackedSurface = atlas->_PackedRects = 0; + builder->MaxRectSize = ImVec2i(0, 0); + builder->MaxRectBounds = ImVec2i(0, 0); +} + +// Important: Calling this may recreate a new texture and therefore change atlas->TexData +ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h) +{ + IM_ASSERT(w > 0 && w <= 0xFFFF); + IM_ASSERT(h > 0 && h <= 0xFFFF); + + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + builder->MaxRectSize.x = ImMax(builder->MaxRectSize.x, w); + builder->MaxRectSize.y = ImMax(builder->MaxRectSize.y, h); + + // Pack + ImFontAtlasRect r = { 0, 0, (unsigned short)w, (unsigned short)h }; + for (int attempts_remaining = 3; attempts_remaining >= 0; attempts_remaining--) { - const ImFontAtlasCustomRect* r = &atlas->CustomRects[i]; - if (r->Font == NULL || r->GlyphID == 0) - continue; + // Try packing + stbrp_rect pack_r = {}; + pack_r.w = r.w + builder->PackPadding; + pack_r.h = r.h + builder->PackPadding; + stbrp_pack_rects((stbrp_context*)(void*)&builder->PackContext, &pack_r, 1); + r.x = (unsigned short)(pack_r.x + builder->PackPadding); + r.y = (unsigned short)(pack_r.y + builder->PackPadding); + if (pack_r.was_packed) + break; - // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, PixelSnapH - IM_ASSERT(r->Font->ContainerAtlas == atlas); - ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(r, &uv0, &uv1); - r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); - if (r->GlyphColored) - r->Font->Glyphs.back().Colored = 1; + // If we ran out of attempts, return fallback + if (attempts_remaining == 0 || builder->LockDisableResize) + { + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + IMGUI_DEBUG_LOG_FONT("[font] Failed packing %dx%d rectangle. Returning fallback.\n", w, h); + return -1; + } + + // Resize atlas! (this should be a rare event) + ImFontAtlasBuildGrowTexture(atlas); } - // Build all fonts lookup tables - for (ImFont* font : atlas->Fonts) - if (font->DirtyLookupTables) - font->BuildLookupTable(); + builder->MaxRectBounds.x = ImMax(builder->MaxRectBounds.x, r.x + r.w); + builder->MaxRectBounds.y = ImMax(builder->MaxRectBounds.y, r.y + r.h); + atlas->_PackedSurface += w * h; + atlas->_PackedRects++; - atlas->TexReady = true; + builder->Rects.push_back(r); + return builder->Rects.Size - 1; } +ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + return &builder->Rects[id]; +} + +ImFontGlyph* ImFont::BuildLoadGlyph(ImWchar codepoint) +{ + if (LockDisableLoading) + return NULL; + + //char utf8_buf[5]; + //IMGUI_DEBUG_LOG("[font] BuildAddGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); + + ImFontAtlas* atlas = ContainerAtlas; + const ImFontLoader* font_loader = atlas->FontLoader; + if (!font_loader->FontAddGlyph(atlas, this, codepoint)) + { + // Mark index as not found, so we don't attempt the search twice + BuildGrowIndex(codepoint + 1); + IndexAdvanceX[codepoint] = (float)IM_FONTGLYPH_INDEX_NOT_FOUND; + IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; + return NULL; + } + + // FIXME: Add hooks for e.g. #7962 + ImFontGlyph* glyph = &Glyphs.back(); + return glyph; +} + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) +{ + // [DEBUG] Log texture update requests + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + for (ImTextureData* tex : atlas->TexList) + { + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + IM_ASSERT(tex->Updates.Size == 0); + if (tex->Status == ImTextureStatus_WantCreate) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: create %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + else if (tex->Status == ImTextureStatus_WantDestroy) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: destroy %dx%d, texid=0x%" IM_PRIX64 ", backend_data=%p\n", tex->UniqueID, tex->Width, tex->Height, tex->TexID, tex->BackendUserData); + else if (tex->Status == ImTextureStatus_WantUpdates) + { + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update %d regions, texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, tex->Updates.Size, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); + for (const ImTextureRect& r : tex->Updates) + { + IM_ASSERT(r.x >= 0 && r.y >= 0); + IM_ASSERT(r.x + r.w < tex->Width && r.y + r.h < tex->Height); + //IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update (% 4d..%-4d)->(% 4d..%-4d), texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, r.x, r.y, r.x + r.w, r.y + r.h, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); + } + } + } +} +#endif + +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas: backend for stb_truetype +//------------------------------------------------------------------------- +// (imstb_truetype.h in included near the top of this file, when IMGUI_ENABLE_STB_TRUETYPE is set) +//------------------------------------------------------------------------- + +#ifdef IMGUI_ENABLE_STB_TRUETYPE + +// One for each ConfigData +struct ImGui_ImplStbTrueType_FontSrcData +{ + stbtt_fontinfo FontInfo; + float ScaleForRasterX; // Factor in RasterizationDensity * OversampleH + float ScaleForRasterY; // Factor in RasterizationDensity * OversampleV + float ScaleForLayout; +}; + +static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplStbTrueType_FontSrcData); + IM_ASSERT(src->FontLoaderData == NULL); + + // Initialize helper structure for font loading and verify that the TTF/OTF data is correct + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src->FontData, src->FontNo); + IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); // FIXME-NEWATLAS: error handling + if (!stbtt_InitFont(&bd_font_data->FontInfo, (unsigned char*)src->FontData, font_offset)) + { + IM_ASSERT(0 && "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); + return false; + } + src->FontLoaderData = bd_font_data; + + // FIXME-NEWATLAS-V2: reevaluate sizing metrics + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); + if (src->SizePixels > 0.0f) + { + bd_font_data->ScaleForRasterX = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels * src->RasterizerDensity) * oversample_h; + bd_font_data->ScaleForRasterY = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels * src->RasterizerDensity) * oversample_v; + bd_font_data->ScaleForLayout = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels); + } + else + { + bd_font_data->ScaleForRasterX = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels * src->RasterizerDensity) * oversample_h; + bd_font_data->ScaleForRasterY = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels * src->RasterizerDensity) * oversample_v; + bd_font_data->ScaleForLayout = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels); + } + + // FIXME-NEWATLAS-V2: make use of line gap value + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + + if (src->MergeMode == false) + { + ImFont* font = src->DstFont; + font->Ascent = ImCeil(unscaled_ascent * bd_font_data->ScaleForLayout); + font->Descent = ImFloor(unscaled_descent * bd_font_data->ScaleForLayout); + } + + return true; +} + +static void ImGui_ImplStbTrueType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_DELETE(bd_font_data); + src->FontLoaderData = NULL; +} + +static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) +{ + IM_UNUSED(atlas); + + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_ASSERT(bd_font_data != NULL); + + int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + return glyph_index != 0; +} + +static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImWchar codepoint) +{ + // Search for first font which has the glyph + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = NULL; + ImFontConfig* src = NULL; + int glyph_index = 0; + int scan_count = (font->LockSingleSrcConfig != NULL) ? 1 : font->SourcesCount; + for (int src_n = 0; src_n < scan_count; src_n++, bd_font_data++) + { + src = font->LockSingleSrcConfig ? font->LockSingleSrcConfig : &font->Sources[src_n]; + bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + if (glyph_index != 0) + break; + } + if (glyph_index == 0) + return false; // Not found + + // FIXME-NEWATLAS: Handling of atlas->TexGlyphPadding? + const float scale_for_layout = bd_font_data->ScaleForLayout; // ~ (font units to pixels) + const float scale_for_raster_x = bd_font_data->ScaleForRasterX; // ~ (font units to pixels) * RasterizationDensity * OversampleH + const float scale_for_raster_y = bd_font_data->ScaleForRasterY; // ~ (font units to pixels) * RasterizationDensity * OversampleV + + // Obtain size and advance + int x0, y0, x1, y1; + int advance, lsb; + stbtt_GetGlyphBitmapBoxSubpixel(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, 0, 0, &x0, &y0, &x1, &y1); + stbtt_GetGlyphHMetrics(&bd_font_data->FontInfo, glyph_index, &advance, &lsb); + const bool is_visible = (x0 != x1 && y0 != y1); + + // Prepare glyph + ImFontGlyph glyph; + glyph.Codepoint = codepoint; + glyph.AdvanceX = advance * scale_for_layout; + + // Pack and retrieve position inside texture atlas + // (generally based on stbtt_PackFontRangesRenderIntoRects) + if (is_visible) + { + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); + const int w = (x1 - x0 + oversample_h - 1); + const int h = (y1 - y0 + oversample_v - 1); + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + font->MetricsTotalSurface += w * h; + + // Render + stbtt_GetGlyphBitmapBox(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, &x0, &y0, &x1, &y1); + ImFontAtlasBuilder* builder = atlas->Builder; + builder->TempBuffer.resize(w * h * 1); + unsigned char* bitmap_pixels = builder->TempBuffer.Data; + memset(bitmap_pixels, 0, w * h * 1); + stbtt_MakeGlyphBitmapSubpixel(&bd_font_data->FontInfo, bitmap_pixels, r->w - oversample_h + 1, r->h - oversample_v + 1, w, + scale_for_raster_x, scale_for_raster_y, 0, 0, glyph_index); + + // Oversampling + // (those functions conveniently assert if pixels are not cleared, which is another safety layer) + if (oversample_h > 1) + stbtt__h_prefilter(bitmap_pixels, r->w, r->h, r->w, oversample_h); + if (oversample_v > 1) + stbtt__v_prefilter(bitmap_pixels, r->w, r->h, r->w, oversample_v); + + float font_off_x = src->GlyphOffset.x + stbtt__oversample_shift(oversample_h); + float font_off_y = src->GlyphOffset.y + stbtt__oversample_shift(oversample_v) + IM_ROUND(font->Ascent); + float recip_h = 1.0f / (oversample_h * src->RasterizerDensity); + float recip_v = 1.0f / (oversample_v * src->RasterizerDensity); + + // Register glyph + // r->x r->y are coordinates inside texture (in pixels) + // glyph.X0, glyph.Y0 are drawing coordinates from base text position, and accounting for oversampling. + glyph.X0 = x0 * recip_h + font_off_x; + glyph.Y0 = y0 * recip_v + font_off_y; + glyph.X1 = (x0 + (int)r->w) * recip_h + font_off_x; + glyph.Y1 = (y0 + (int)r->h) * recip_v + font_off_y; + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + glyph.Visible = true; + glyph.PackId = pack_id; + font->BuildRegisterGlyph(src, &glyph); + + // Copy to texture, post-process and queue update for backend + ImTextureData* tex = atlas->TexData; + IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); + ImFontAtlasTextureBlockConvertAndPostProcess(atlas, font, src, &font->Glyphs.back(), + bitmap_pixels, ImTextureFormat_Alpha8, w, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); + } + else + { + font->BuildRegisterGlyph(src, &glyph); + } + + return true; +} + +const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() +{ + static ImFontLoader loader; + loader.Name = "stb_truetype"; + loader.FontSrcInit = ImGui_ImplStbTrueType_FontSrcInit; + loader.FontSrcDestroy = ImGui_ImplStbTrueType_FontSrcDestroy; + loader.FontSrcContainsGlyph = ImGui_ImplStbTrueType_FontSrcContainsGlyph; + loader.FontAddGlyph = ImGui_ImplStbTrueType_FontAddGlyph; + return &loader; +} + +#endif // IMGUI_ENABLE_STB_TRUETYPE + //------------------------------------------------------------------------- // [SECTION] ImFontAtlas: glyph ranges helpers //------------------------------------------------------------------------- @@ -3710,113 +4376,30 @@ ImFont::~ImFont() ClearOutputData(); } -void ImFont::ClearOutputData() +void ImFont::ClearOutputData() { FontSize = 0.0f; FallbackAdvanceX = 0.0f; Glyphs.clear(); IndexAdvanceX.clear(); IndexLookup.clear(); - FallbackGlyph = NULL; + FallbackGlyphIndex = -1; ContainerAtlas = NULL; - DirtyLookupTables = true; Ascent = Descent = 0.0f; MetricsTotalSurface = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); } -static ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) +void ImFont::BuildClearGlyphs() { - for (int n = 0; n < candidate_chars_count; n++) - if (font->FindGlyphNoFallback(candidate_chars[n]) != NULL) - return candidate_chars[n]; - return 0; -} - -void ImFont::BuildLookupTable() -{ - int max_codepoint = 0; - for (int i = 0; i != Glyphs.Size; i++) - max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); - - // Build lookup table - IM_ASSERT(Glyphs.Size > 0 && "Font has not loaded glyph!"); - IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved + FallbackAdvanceX = 0.0f; + Glyphs.clear(); IndexAdvanceX.clear(); IndexLookup.clear(); - DirtyLookupTables = false; + FallbackGlyphIndex = 0; + MetricsTotalSurface = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); - GrowIndex(max_codepoint + 1); - for (int i = 0; i < Glyphs.Size; i++) - { - int codepoint = (int)Glyphs[i].Codepoint; - IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; - IndexLookup[codepoint] = (ImU16)i; - - // Mark 4K page as used - const int page_n = codepoint / 8192; - Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); - } - - // Create a glyph to handle TAB - // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) - if (FindGlyph((ImWchar)' ')) - { - if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times (FIXME: Flaky) - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& tab_glyph = Glyphs.back(); - tab_glyph = *FindGlyph((ImWchar)' '); - tab_glyph.Codepoint = '\t'; - tab_glyph.AdvanceX *= IM_TABSIZE; - IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; - IndexLookup[(int)tab_glyph.Codepoint] = (ImU16)(Glyphs.Size - 1); - } - - // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons) - if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)' ')) - glyph->Visible = false; - if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)'\t')) - glyph->Visible = false; - - // Setup Fallback character - const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) - { - FallbackChar = FindFirstExistingGlyph(this, fallback_chars, IM_ARRAYSIZE(fallback_chars)); - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) - { - FallbackGlyph = &Glyphs.back(); - FallbackChar = (ImWchar)FallbackGlyph->Codepoint; - } - } - FallbackAdvanceX = FallbackGlyph->AdvanceX; - for (int i = 0; i < max_codepoint + 1; i++) - if (IndexAdvanceX[i] < 0.0f) - IndexAdvanceX[i] = FallbackAdvanceX; - - // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). - // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. - // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. - const ImWchar ellipsis_chars[] = { Sources->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; - const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; - if (EllipsisChar == 0) - EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars)); - const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); - if (EllipsisChar != 0) - { - EllipsisCharCount = 1; - EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1; - } - else if (dot_char != 0) - { - const ImFontGlyph* dot_glyph = FindGlyph(dot_char); - EllipsisChar = dot_char; - EllipsisCharCount = 3; - EllipsisCharStep = (float)(int)(dot_glyph->X1 - dot_glyph->X0) + 1.0f; - EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. - } + // Don't clear BuilderData } // API is designed this way to avoid exposing the 8K page size @@ -3832,7 +4415,7 @@ bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) return true; } -void ImFont::GrowIndex(int new_size) +void ImFont::BuildGrowIndex(int new_size) { IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); if (new_size <= IndexLookup.Size) @@ -3844,50 +4427,43 @@ void ImFont::GrowIndex(int new_size) // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. // Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). // 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. -void ImFont::AddGlyph(const ImFontConfig* src, ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +void ImFont::BuildRegisterGlyph(ImFontConfig* src, const ImFontGlyph* in_glyph) { + int glyph_idx = Glyphs.Size; + Glyphs.push_back(*in_glyph); + ImFontGlyph& glyph = Glyphs[glyph_idx]; + IM_ASSERT(Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. + if (src != NULL) { // Clamp & recenter if needed - const float advance_x_original = advance_x; - advance_x = ImClamp(advance_x, src->GlyphMinAdvanceX, src->GlyphMaxAdvanceX); - if (advance_x != advance_x_original) + float advance_x = ImClamp(glyph.AdvanceX, src->GlyphMinAdvanceX, src->GlyphMaxAdvanceX); + if (advance_x != glyph.AdvanceX) { - float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f; - x0 += char_off_x; - x1 += char_off_x; + float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - glyph.AdvanceX) * 0.5f) : (advance_x - glyph.AdvanceX) * 0.5f; + glyph.X0 += char_off_x; + glyph.X1 += char_off_x; } // Snap to pixel if (src->PixelSnapH) advance_x = IM_ROUND(advance_x); - // Bake extra spacing - advance_x += src->GlyphExtraAdvanceX; + // Bake spacing + glyph.AdvanceX = advance_x + src->GlyphExtraAdvanceX; } + if (glyph.Colored) + ContainerAtlas->TexPixelsUseColors = ContainerAtlas->TexData->UseColors = true; - int glyph_idx = Glyphs.Size; - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& glyph = Glyphs[glyph_idx]; - glyph.Codepoint = (unsigned int)codepoint; - glyph.Visible = (x0 != x1) && (y0 != y1); - glyph.Colored = false; - glyph.X0 = x0; - glyph.Y0 = y0; - glyph.X1 = x1; - glyph.Y1 = y1; - glyph.U0 = u0; - glyph.V0 = v0; - glyph.U1 = u1; - glyph.V1 = v1; - glyph.AdvanceX = advance_x; - IM_ASSERT(Glyphs.Size < 0xFFFF); // IndexLookup[] hold 16-bit values and -1 is reserved. + // Update lookup tables + int codepoint = glyph.Codepoint; + BuildGrowIndex(codepoint + 1); + IndexAdvanceX[codepoint] = glyph.AdvanceX; + IndexLookup[codepoint] = (ImU16)glyph_idx; - // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) - // We use (U1-U0)*TexWidth instead of X1-X0 to account for oversampling. - float pad = ContainerAtlas->TexGlyphPadding + 0.99f; - DirtyLookupTables = true; - MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + pad) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + pad); + // Mark 4K page as used + const int page_n = codepoint / 8192; + Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); } void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst) @@ -3900,30 +4476,76 @@ void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool ove if (to_codepoint >= index_size && from_codepoint >= index_size) // both 'from_codepoint' and 'to_codepoint' don't exist -> no-op return; - GrowIndex(from_codepoint + 1); + BuildGrowIndex(from_codepoint + 1); IndexLookup[from_codepoint] = (to_codepoint < index_size) ? IndexLookup.Data[to_codepoint] : (ImU16)-1; IndexAdvanceX[from_codepoint] = (to_codepoint < index_size) ? IndexAdvanceX.Data[to_codepoint] : 1.0f; } -// Find glyph, return fallback if missing +// Find glyph, load if necessary, return fallback if missing ImFontGlyph* ImFont::FindGlyph(ImWchar c) { - if (c >= (size_t)IndexLookup.Size) - return FallbackGlyph; - const ImU16 i = IndexLookup.Data[c]; - if (i == (ImU16)-1) - return FallbackGlyph; - return &Glyphs.Data[i]; + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return &Glyphs.Data[FallbackGlyphIndex]; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + ImFontGlyph* glyph = BuildLoadGlyph(c); + return glyph ? glyph : &Glyphs.Data[FallbackGlyphIndex]; } +// Attempt to load but when missing, return NULL instead of FallbackGlyph ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) { - if (c >= (size_t)IndexLookup.Size) - return NULL; - const ImU16 i = IndexLookup.Data[c]; - if (i == (ImU16)-1) - return NULL; - return &Glyphs.Data[i]; + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return NULL; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + ImFontGlyph* glyph = BuildLoadGlyph(c); + return glyph; +} + +bool ImFont::IsGlyphLoaded(ImWchar c) +{ + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return false; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return true; + } + return false; +} + +// This is not fast query +bool ImFont::IsGlyphInFont(ImWchar c) +{ + ImFontAtlas* atlas = ContainerAtlas; + for (int src_n = 0; src_n < SourcesCount; src_n++) + if (atlas->FontLoader->FontSrcContainsGlyph(atlas, &Sources[src_n], c)) + return true; + return false; +} + +float ImFont::GetCharAdvance(ImWchar c) +{ + if (c < (size_t)IndexAdvanceX.Size) + { + const float x = IndexAdvanceX.Data[c]; + if (x >= 0.0f) + return x; + if (x == (float)IM_FONTGLYPH_INDEX_NOT_FOUND) // FIXME-NEWATLAS: could bake in index + return FallbackAdvanceX; + } + const ImFontGlyph* glyph = BuildLoadGlyph(c); + return glyph ? glyph->AdvanceX : FallbackAdvanceX; } // Trim trailing space and find beginning of next line @@ -3991,7 +4613,9 @@ const char* ImFont::CalcWordWrapPosition(float size, const char* text, const cha } } - const float char_width = ImFontGetCharAdvanceX(this, c); + // FIXME-NEWATLAS-V1: Measure perf, inline etc. + //const float char_width = ImFontGetCharAdvanceX(this, c); + const float char_width = GetCharAdvance((ImWchar)c); // ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX); if (ImCharIsBlankW(c)) { if (inside_word) @@ -4096,7 +4720,9 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons continue; } - const float char_width = ImFontGetCharAdvanceX(this, c) * scale; + // FIXME-NEWATLAS-V1: Measure perf, inline etc. + //const float char_width = ImFontGetCharAdvanceX(this, c) * scale; + const float char_width = GetCharAdvance((ImWchar)c) /* (int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX)*/ * scale; if (line_width + char_width >= max_width) { s = prev_s; diff --git a/imgui_internal.h b/imgui_internal.h index 91ac0bc83..3b650c9d5 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -37,6 +37,7 @@ Index of this file: // [SECTION] Tab bar, Tab item support // [SECTION] Table support // [SECTION] ImGui internal API +// [SECTION] ImFontLoader // [SECTION] ImFontAtlas internal API // [SECTION] Test Engine specific hooks (imgui_test_engine) @@ -140,6 +141,8 @@ struct ImGuiTextIndex; // Maintain a line index for a text buffer. // ImDrawList/ImFontAtlas struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImFontAtlasRect; // Packed rectangle (same as ImTextureRect) +struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas // ImGui struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) @@ -530,6 +533,14 @@ struct ImVec1 constexpr ImVec1(float _x) : x(_x) { } }; +// Helper: ImVec2i (2D vector, integer) +struct ImVec2i +{ + int x, y; + constexpr ImVec2i() : x(0), y(0) {} + constexpr ImVec2i(int _x, int _y) : x(_x), y(_y) {} +}; + // Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) struct ImVec2ih { @@ -789,8 +800,9 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag // You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. struct IMGUI_API ImDrawListSharedData { - ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) + ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current/default font (optional, for simplified AddText overload) float FontSize; // Current/default font size (optional, for simplified AddText overload) float FontScale; // Current/default font scale (== FontSize / Font->FontSize) @@ -800,13 +812,17 @@ struct IMGUI_API ImDrawListSharedData ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImVector TempBuffer; // Temporary write buffer + ImVector DrawLists; // All draw lists associated to this ImDrawListSharedData + ImGuiContext* Context; // [OPTIONAL] Link to Dear ImGui context. 99% of ImDrawList/ImFontAtlas can function without an ImGui context, but this facilitate handling one legacy edge case. // Lookup tables ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) + bool RendererHasTextures; // Copy of (GetIO().BackendFlags & ImGuiBackendFlags_RendererHasTextures). ImDrawListSharedData(); + ~ImDrawListSharedData(); void SetCircleTessellationMaxError(float max_error); }; @@ -2085,6 +2101,7 @@ struct ImGuiContext float FontScale; // == FontSize / Font->FontSize float CurrentDpiScale; // Current window/viewport DpiScale ImDrawListSharedData DrawListSharedData; + ImVectorTextures; double Time; int FrameCount; int FrameCountEnded; @@ -3561,6 +3578,7 @@ namespace ImGui IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); + IMGUI_API void DebugNodeTexture(ImTextureData* tex); IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); @@ -3593,31 +3611,102 @@ namespace ImGui } // namespace ImGui +//----------------------------------------------------------------------------- +// [SECTION] ImFontLoader +//----------------------------------------------------------------------------- + +// Hooks and storage for a given font backend. +// This structure is likely to evolve as we add support for incremental atlas updates. +// Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public. +struct ImFontLoader +{ + const char* Name; + bool (*LoaderInit)(ImFontAtlas* atlas); + void (*LoaderShutdown)(ImFontAtlas* atlas); + bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); + void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); + bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); + bool (*FontAddGlyph)(ImFontAtlas* atlas, ImFont* font, ImWchar codepoint); + + ImFontLoader() { memset(this, 0, sizeof(*this)); } +}; + +#ifdef IMGUI_ENABLE_STB_TRUETYPE +IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); +#endif + //----------------------------------------------------------------------------- // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- -// This structure is likely to evolve as we add support for incremental atlas updates. -// Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public. -struct ImFontBuilderIO +// Packed rectangle (same as ImTextureRect) +struct ImFontAtlasRect { - bool (*FontBuilder_Build)(ImFontAtlas* atlas); + unsigned short x, y; + unsigned short w, h; +}; +typedef int ImFontAtlasRectId; // <0 when invalid + +// Internal storage for incrementally packing and building a ImFontAtlas +struct stbrp_context_opaque { char data[80]; }; +struct stbrp_node; +struct ImFontAtlasBuilder +{ + stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file. + ImVector PackNodes; + int PackPadding; // Generally 1 to avoid bilinear filtering issues. + ImVector Rects; + ImVector TempBuffer; // Misc scratch buffer + ImVec2i MaxRectSize; // Largest rectangle to pack (defacto used as a "minimum texture size") + ImVec2i MaxRectBounds; // Bottom-right most used pixels + bool LockDisableResize; // Disable resizing texture + bool PreloadedAllGlyphsRanges; // Set when missing ImGuiBackendFlags_RendererHasTextures features forces atlas to preload everything. + + // Custom rectangle identifiers + ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. + ImFontAtlasRectId PackIdLinesTexData; + + ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); } }; -// Helper for font builder -#ifdef IMGUI_ENABLE_STB_TRUETYPE -IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype(); +// FIXME-NEWATLAS: Cleanup +IMGUI_API void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader); +IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char); + +IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas); + +IMGUI_API ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); +IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); + +IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy +IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); + +IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); +IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h); +IMGUI_API ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); + +IMGUI_API void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex); +IMGUI_API void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasTextureBlockConvertAndPostProcess(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* pixels, ImTextureFormat format, int w, int h, int pitch, float in_multiply_factor); +IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +IMGUI_API void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas); #endif -IMGUI_API void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, float ascent, float descent); -IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque); -IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); -IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); -IMGUI_API void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); IMGUI_API bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 1c22bd4c6..186b639b5 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3984,7 +3984,8 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c if (c == '\r') continue; - const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale; + // FIXME-NEWATLAS-V1: Measure perf, inline etc. + const float char_width = font->GetCharAdvance((ImWchar)c) * scale;// ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX)* scale; line_width += char_width; } @@ -4322,7 +4323,7 @@ void ImGui::PushPasswordFont() out_font->Ascent = in_font->Ascent; out_font->Descent = in_font->Descent; out_font->ContainerAtlas = in_font->ContainerAtlas; - out_font->FallbackGlyph = glyph; + out_font->FallbackGlyphIndex = in_font->Glyphs.index_from_ptr(glyph); // FIXME: broken out_font->FallbackAdvanceX = glyph->AdvanceX; IM_ASSERT(out_font->Glyphs.Size == 0 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0); PushFont(out_font); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 6521f83b9..1a268365b 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -166,7 +166,7 @@ namespace // NB: No ctor/dtor, explicitly call Init()/Shutdown() struct FreeTypeFont { - bool InitFont(FT_Library ft_library, const ImFontConfig& src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. + bool InitFont(FT_Library ft_library, ImFontConfig& src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. void CloseFont(); void SetPixelHeight(int pixel_height); // Change font pixel size. All following calls to RasterizeGlyph() will use this size const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint); @@ -185,7 +185,7 @@ namespace float InvRasterizationDensity; }; - bool FreeTypeFont::InitFont(FT_Library ft_library, const ImFontConfig& src, unsigned int extra_font_builder_flags) + bool FreeTypeFont::InitFont(FT_Library ft_library, ImFontConfig& src, unsigned int extra_font_builder_flags) { FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)src.FontData, (uint32_t)src.FontDataSize, (uint32_t)src.FontNo, &Face); if (error != 0) From 2cde9125d6286c878c5e00d4a9ff9c85e108aa8d Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 17 Jan 2025 18:06:25 +0100 Subject: [PATCH 005/191] Fonts: Selecting font config source list done by shared code. --- imgui.h | 10 +++++----- imgui_draw.cpp | 24 +++++++++++++++--------- imgui_internal.h | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/imgui.h b/imgui.h index 0420b4358..753fffe92 100644 --- a/imgui.h +++ b/imgui.h @@ -3631,19 +3631,19 @@ struct ImFont float FallbackAdvanceX; // 4 // out // FindGlyph(FallbackChar)->AdvanceX float FontSize; // 4 // in // Height of characters/line, set during loading (don't change after loading) - // [Internal] Members: Hot ~28/40 bytes (for RenderText loop) + // [Internal] Members: Hot ~28/36 bytes (for RenderText loop) ImVector IndexLookup; // 12-16 // out // Sparse. Index glyphs by Unicode code-point. ImVector Glyphs; // 12-16 // out // All glyphs. int FallbackGlyphIndex; // 4 // out // Index of FontFallbackChar - // [Internal] Members: Cold ~32/40 bytes + // [Internal] Members: Cold ~32/40/60 bytes // Conceptually Sources[] is the list of font sources merged to create this font. - ImFontAtlas* ContainerAtlas; // 4-8 // out // What we has been loaded into - ImFontConfig* Sources; // 4-8 // in // Pointer within ContainerAtlas->Sources[], to SourcesCount instances short SourcesCount; // 2 // in // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont. short EllipsisCharCount; // 1 // out // 1 or 3 ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') + ImFontConfig* Sources; // 4-8 // in // Pointer within ContainerAtlas->Sources[], to SourcesCount instances + ImFontAtlas* ContainerAtlas; // 4-8 // out // What we has been loaded into float EllipsisWidth; // 4 // out // Total ellipsis Width float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() @@ -3651,7 +3651,7 @@ struct ImFont int MetricsTotalSurface;// 4 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. bool LockDisableLoading; - ImFontConfig* LockSingleSrcConfig; + short LockSingleSrcConfigIdx; // Methods IMGUI_API ImFont(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b1cf15ff4..a497663b9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3394,10 +3394,12 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src) { ImFont* font = src->DstFont; + const int cfg_idx_in_font = (int)(src - font->Sources); + IM_ASSERT(cfg_idx_in_font >= 0 && cfg_idx_in_font < font->SourcesCount); IM_UNUSED(atlas); // While manipulating glyphs during init we want to restrict all searches for one source font. - font->LockSingleSrcConfig = src; + font->LockSingleSrcConfigIdx = (short)cfg_idx_in_font; // Setup Fallback character // FIXME-NEWATLAS: could we use a scheme where this is lazily loaded? @@ -3448,8 +3450,7 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* sr font->EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + font->EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. } } - - font->LockSingleSrcConfig = NULL; + font->LockSingleSrcConfigIdx = -1; } // Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* @@ -3809,10 +3810,15 @@ ImFontGlyph* ImFont::BuildLoadGlyph(ImWchar codepoint) //char utf8_buf[5]; //IMGUI_DEBUG_LOG("[font] BuildAddGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); - ImFontAtlas* atlas = ContainerAtlas; + + // Load from single source or all sources? + int srcs_count = (LockSingleSrcConfigIdx != -1) ? 1 : SourcesCount; + ImFontConfig* srcs = (LockSingleSrcConfigIdx != -1) ? &Sources[LockSingleSrcConfigIdx] : Sources; + + // Call backend const ImFontLoader* font_loader = atlas->FontLoader; - if (!font_loader->FontAddGlyph(atlas, this, codepoint)) + if (!font_loader->FontAddGlyph(atlas, this, srcs, srcs_count, codepoint)) { // Mark index as not found, so we don't attempt the search twice BuildGrowIndex(codepoint + 1); @@ -3937,16 +3943,15 @@ static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFon return glyph_index != 0; } -static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImWchar codepoint) +static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* srcs, int srcs_count, ImWchar codepoint) { // Search for first font which has the glyph ImGui_ImplStbTrueType_FontSrcData* bd_font_data = NULL; ImFontConfig* src = NULL; int glyph_index = 0; - int scan_count = (font->LockSingleSrcConfig != NULL) ? 1 : font->SourcesCount; - for (int src_n = 0; src_n < scan_count; src_n++, bd_font_data++) + for (int src_n = 0; src_n < srcs_count; src_n++) { - src = font->LockSingleSrcConfig ? font->LockSingleSrcConfig : &font->Sources[src_n]; + src = &srcs[src_n]; bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); if (glyph_index != 0) @@ -4369,6 +4374,7 @@ ImFont::ImFont() { memset(this, 0, sizeof(*this)); Scale = 1.0f; + LockSingleSrcConfigIdx = -1; } ImFont::~ImFont() diff --git a/imgui_internal.h b/imgui_internal.h index 3b650c9d5..27d1ca8e1 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3626,7 +3626,7 @@ struct ImFontLoader bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); - bool (*FontAddGlyph)(ImFontAtlas* atlas, ImFont* font, ImWchar codepoint); + bool (*FontAddGlyph)(ImFontAtlas* atlas, ImFont* font, ImFontConfig* srcs, int srcs_count, ImWchar codepoint); ImFontLoader() { memset(this, 0, sizeof(*this)); } }; From ee357aaddf438e93127eed68e9be6456ca5130a3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jan 2025 15:35:32 +0100 Subject: [PATCH 006/191] Textures: Add ImTextureUserID_Invalid + introducing SetTexID(). Which gives us room for potentially updating ImDrawData during render. --- imgui.h | 21 +++++++++++++++------ imgui_draw.cpp | 6 +++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/imgui.h b/imgui.h index 753fffe92..e51280e44 100644 --- a/imgui.h +++ b/imgui.h @@ -318,6 +318,11 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE typedef ImU64 ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) #endif +// Define this to another value if you need value of 0 to be valid. +#ifndef ImTextureID_Invalid +#define ImTextureID_Invalid ((ImTextureID)0) +#endif + // ImTextureRef contains: // - a texture/atlas pointer, typically when created by Dear ImGui itself. // - OR a raw ImTextureID value (user/backend identifier), typically when created by user code to load images. @@ -3362,21 +3367,19 @@ struct ImTextureRect // - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. struct IMGUI_API ImTextureData { - ImTextureStatus Status; // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy + ImTextureStatus Status; // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify! ImTextureFormat Format; // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 int Width; // Texture width int Height; // Texture height int BytesPerPixel; // 4 or 1 int UniqueID; // Sequential index to facilitate identifying a texture when debugging/printing. Only unique per atlas. unsigned char* Pixels; // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. - ImTextureID TexID; // Identifier stored in ImDrawCmd::GetTexID() and passed to backend RenderDrawData loop. + ImTextureID TexID; // Always use SetTexID() to modify! Identifier stored in ImDrawCmd::GetTexID() and passed to backend RenderDrawData loop. void* BackendUserData; // Convenience storage for backend. Some backends may have enough with TexID. ImTextureRect UpdateRect; // Bounding box encompassing all individual updates. ImVector Updates; // Array of individual updates. int UnusedFrames; // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. Always >0 when Status==WantDestroy. - - // [Internal] - bool UseColors; // [Internal] Tell whether our texture data is known to use colors (rather than just white + alpha). + bool UseColors; // Tell whether our texture data is known to use colors (rather than just white + alpha). bool WantDestroyNextFrame; // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. // Functions @@ -3390,6 +3393,10 @@ struct IMGUI_API ImTextureData int GetPitch() const { return Width * BytesPerPixel; } ImTextureRef GetTexRef() const { ImTextureRef tex_ref; tex_ref._TexData = (ImTextureData*)(void*)this; tex_ref._TexID = TexID; return tex_ref; } ImTextureID GetTexID() const { return TexID; } + + // Called by Renderer backend + void SetTexID(ImTextureID tex_id) { TexID = tex_id; } // Call after creating or destroying the texture. Never modify TexID directly! + void SetStatus(ImTextureStatus status) { Status = status; } // Call after honoring a request. Never modify Status directly! }; //----------------------------------------------------------------------------- @@ -3689,9 +3696,11 @@ struct ImFont // FIXME-NEWATLAS: Added indirection to avoid patching ImDrawCmd after texture updates. inline ImTextureID ImDrawCmd::GetTexID() const { + // If you are getting this assert: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) + // must iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; if (TexRef._TexData != NULL) - IM_ASSERT(tex_id && "ImDrawCmd is referring to Atlas texture that wasn't uploaded to graphics system."); + IM_ASSERT(tex_id != ImTextureID_Invalid && "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!"); return tex_id; } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a497663b9..1e66a3b9c 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2695,7 +2695,7 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) if (tex->Status == ImTextureStatus_Destroyed) { - IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == NULL); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL); if (tex->WantDestroyNextFrame) remove_from_list = true; // Destroy was scheduled by us else @@ -2715,7 +2715,7 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) tex->UnusedFrames++; // If a texture has never reached the backend, they don't need to know about it. - if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == 0 && tex->BackendUserData == NULL) + if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) remove_from_list = true; // Remove @@ -3518,7 +3518,7 @@ ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h) /*if (old_tex != NULL && old_tex->Status == ImTextureStatus_WantCreate) { // Reuse texture not yet used by backend. - IM_ASSERT(old_tex->TexID == 0 && old_tex->BackendUserData == NULL); + IM_ASSERT(old_tex->TexID == ImTextureID_Invalid && old_tex->BackendUserData == NULL); old_tex->DestroyPixels(); old_tex->Updates.clear(); new_tex = old_tex; From a21a2e855b74d92b33172fc8b7d880a8f6e09d71 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 31 Jan 2025 19:12:58 +0100 Subject: [PATCH 007/191] Textures: Single Textures[] array allows backend to not have to care about atlases. # Conflicts: # imgui.h --- imgui.cpp | 15 +++++++++++++++ imgui.h | 15 ++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5acd91c0c..dd6b939e5 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1271,6 +1271,7 @@ static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt); // Misc static void UpdateFontsNewFrame(); static void UpdateTexturesNewFrame(); +static void UpdateTexturesEndFrame(); static void UpdateSettings(); static int UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect); static void RenderWindowOuterBorders(ImGuiWindow* window); @@ -5182,6 +5183,18 @@ static void ImGui::UpdateTexturesNewFrame() ImFontAtlasUpdateNewFrame(atlas); } +// Build a single texture list +// We want to avoid user reading from atlas->TexList[] in order to facilitate better support for multiple atlases. +static void ImGui::UpdateTexturesEndFrame() +{ + ImGuiContext& g = *GImGui; + ImFontAtlas* atlas = g.IO.Fonts; + g.PlatformIO.Textures.resize(0); + g.PlatformIO.Textures.reserve(atlas->TexList.Size); + for (ImTextureData* tex : atlas->TexList) + g.PlatformIO.Textures.push_back(tex); +} + // Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! static void SetupDrawListSharedData() @@ -5759,6 +5772,8 @@ void ImGui::EndFrame() g.Windows.swap(g.WindowsTempSortBuffer); g.IO.MetricsActiveWindows = g.WindowsActiveCount; + UpdateTexturesEndFrame(); + // Unlock font atlas g.IO.Fonts->Locked = false; diff --git a/imgui.h b/imgui.h index e51280e44..936c177fe 100644 --- a/imgui.h +++ b/imgui.h @@ -3496,7 +3496,7 @@ enum ImFontAtlasFlags_ // - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas). // - If you don't call any AddFont*** functions, the default font embedded in the code will be loaded for you. // It is the rendering backend responsibility to upload texture into your graphics API: -// - ImGui_ImplXXXX_RenderDrawData() functions generally iterate atlas->TexList[] to create/update/destroy each ImTextureData instance. +// - ImGui_ImplXXXX_RenderDrawData() functions generally iterate platform_io->Textures[] to create/update/destroy each ImTextureData instance. // - Backend then set ImTextureData's TexID and BackendUserData. // - Texture id are passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. // Legacy path: @@ -3597,11 +3597,9 @@ struct ImFontAtlas int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). - // Output - ImTextureData* TexData; // Current texture - ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). - // [Internal] + ImTextureData* TexData; // Current texture + ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetPlatformIO().Textures[] instead! bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. bool TexIsBuilt; // Set when texture was built matching current font input bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. @@ -3788,6 +3786,13 @@ struct ImGuiPlatformIO // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. void* Renderer_RenderState; + + //------------------------------------------------------------------ + // Output + //------------------------------------------------------------------ + + // Textures list (the list is updated by calling ImGui::EndFrame or ImGui::Render) + ImVector Textures; // Texture list (most often Textures.Size == 1). }; // (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function. Handler is called during EndFrame(). From 208705368efdb9da4c45b377dc9bc0c5ea8458b6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 7 Mar 2025 16:14:11 +0100 Subject: [PATCH 008/191] Textures: Adding a RefCount to textures so backend can avoid destroying them on shutdown if atlas is shared. --- imgui.cpp | 19 ++++++++++++++----- imgui.h | 5 +++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index dd6b939e5..7821841d4 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3942,6 +3942,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) Font = NULL; FontSize = FontBaseSize = FontScale = CurrentDpiScale = 0.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + IO.Fonts->RefCount++; Time = 0.0f; FrameCount = 0; FrameCountEnded = FrameCountRendered = -1; @@ -4212,12 +4213,15 @@ void ImGui::Shutdown() IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == NULL, "Forgot to shutdown Renderer backend?"); // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) - if (g.IO.Fonts) - ImFontAtlasRemoveDrawListSharedData(g.IO.Fonts, &g.DrawListSharedData); - if (g.IO.Fonts && g.FontAtlasOwnedByContext) + if (ImFontAtlas* atlas = g.IO.Fonts) { - g.IO.Fonts->Locked = false; - IM_DELETE(g.IO.Fonts); + ImFontAtlasRemoveDrawListSharedData(atlas, &g.DrawListSharedData); + atlas->RefCount--; + if (g.FontAtlasOwnedByContext) + { + atlas->Locked = false; + IM_DELETE(atlas); + } } g.IO.Fonts = NULL; g.DrawListSharedData.TempBuffer.clear(); @@ -5192,7 +5196,12 @@ static void ImGui::UpdateTexturesEndFrame() g.PlatformIO.Textures.resize(0); g.PlatformIO.Textures.reserve(atlas->TexList.Size); for (ImTextureData* tex : atlas->TexList) + { + // We provide this information so backends can decide whether to destroy textures. + // This means in practice that if N imgui contexts are created with a shared atlas, we assume all of them have a backend initialized. + tex->RefCount = (unsigned short)atlas->RefCount; g.PlatformIO.Textures.push_back(tex); + } } // Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. diff --git a/imgui.h b/imgui.h index 936c177fe..b9e8f93bc 100644 --- a/imgui.h +++ b/imgui.h @@ -3379,6 +3379,7 @@ struct IMGUI_API ImTextureData ImTextureRect UpdateRect; // Bounding box encompassing all individual updates. ImVector Updates; // Array of individual updates. int UnusedFrames; // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. Always >0 when Status==WantDestroy. + unsigned short RefCount; // Number of contexts using this texture. bool UseColors; // Tell whether our texture data is known to use colors (rather than just white + alpha). bool WantDestroyNextFrame; // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. @@ -3591,13 +3592,12 @@ struct ImFontAtlas // Input ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) - ImTextureRef TexRef; // User data to refer to the latest texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. - int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. ImTextureFormat TexDesiredFormat; // Desired texture format (default to ImTextureFormat_RGBA32 but may be changed to ImTextureFormat_Alpha8). int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). // [Internal] + ImTextureRef TexRef; // User data to refer to the latest texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. ImTextureData* TexData; // Current texture ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetPlatformIO().Textures[] instead! bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. @@ -3618,6 +3618,7 @@ struct ImFontAtlas const char* FontLoaderName; // Font loader name (for display e.g. in About box) == FontLoader->Name void* FontLoaderData; // Font backend opaque storage unsigned int FontBuilderFlags; // [FIXME: Should be called FontLoaderFlags] Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. . Per-font override is also available in ImFontConfig. + int RefCount; // Number of contexts using this atlas int _PackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. int _PackedRects; // Number of packed rectangles. float _PackNodesFactor = 1.0f; From c20e160e0f22b35fcff66393abe03254c6f00bf1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 9 May 2025 21:41:01 +0200 Subject: [PATCH 009/191] Textures: added texture list pointer in ImDrawData. # Conflicts: # imgui.h --- imgui.cpp | 1 + imgui.h | 10 ++++++---- imgui_draw.cpp | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7821841d4..ce2b06462 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5588,6 +5588,7 @@ static void InitViewportDrawData(ImGuiViewportP* viewport) draw_data->DisplaySize = viewport->Size; draw_data->FramebufferScale = io.DisplayFramebufferScale; draw_data->OwnerViewport = viewport; + draw_data->Textures = &ImGui::GetPlatformIO().Textures; } // Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering. diff --git a/imgui.h b/imgui.h index b9e8f93bc..c95f5ac57 100644 --- a/imgui.h +++ b/imgui.h @@ -366,7 +366,7 @@ namespace ImGui IMGUI_API void NewFrame(); // start a new Dear ImGui frame, you can submit any command from this point until Render()/EndFrame(). IMGUI_API void EndFrame(); // ends the Dear ImGui frame. automatically called by Render(). If you don't need to render data (skipping rendering) you may call EndFrame() without Render()... but you'll have wasted CPU already! If you don't need to render, better to not create any windows and not call NewFrame() at all! IMGUI_API void Render(); // ends the Dear ImGui frame, finalize the draw data. You can then get call GetDrawData(). - IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. + IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). Call ImGui_ImplXXXX_RenderDrawData() function in your Renderer Backend to render. // Demo, Debug, Information IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create Demo window. demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! @@ -3313,7 +3313,7 @@ struct ImDrawList struct ImDrawData { bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. - int CmdListsCount; // Number of ImDrawList* to render (should always be == CmdLists.size) + int CmdListsCount; // Number of ImDrawList* to render. (== CmdLists.Size). Exists for legacy reason. int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. @@ -3321,6 +3321,7 @@ struct ImDrawData ImVec2 DisplaySize; // Size of the viewport to render (== GetMainViewport()->Size for the main viewport, == io.DisplaySize in most single-viewport applications) ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Based on io.DisplayFramebufferScale. Generally (1,1) on normal display, (2,2) on OSX with Retina display. ImGuiViewport* OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). + ImVector* Textures; // List of textures to update. Most of the times the list is shared by all ImDrawData, has only 1 texture and it doesn't need any update. This almost always points to ImGui::GetPlatformIO().Textures[]. May be overriden or set to NULL if you want to manually update textures. // Functions ImDrawData() { Clear(); } @@ -3599,7 +3600,7 @@ struct ImFontAtlas // [Internal] ImTextureRef TexRef; // User data to refer to the latest texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. ImTextureData* TexData; // Current texture - ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetPlatformIO().Textures[] instead! + ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetDrawData().Textures[]/GetPlatformIO().Textures[] instead! bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. bool TexIsBuilt; // Set when texture was built matching current font input bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. @@ -3793,7 +3794,8 @@ struct ImGuiPlatformIO //------------------------------------------------------------------ // Textures list (the list is updated by calling ImGui::EndFrame or ImGui::Render) - ImVector Textures; // Texture list (most often Textures.Size == 1). + // The ImGui_ImplXXXX_RenderDrawData() function of each backend generally access this via ImDrawData::Textures which points to this. The array is available here mostly because backends will want to destroy textures on shutdown. + ImVector Textures; // List of textures used by Dear ImGui (most often 1). }; // (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function. Handler is called during EndFrame(). diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 1e66a3b9c..0bde7a116 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2244,6 +2244,7 @@ void ImDrawData::Clear() CmdLists.resize(0); // The ImDrawList are NOT owned by ImDrawData but e.g. by ImGuiContext, so we don't clear them. DisplayPos = DisplaySize = FramebufferScale = ImVec2(0.0f, 0.0f); OwnerViewport = NULL; + Textures = NULL; } // Important: 'out_list' is generally going to be draw_data->CmdLists, but may be another temporary list From 372fd27e714893b0c80e9fe0dc554c0caabd3933 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 27 Nov 2024 18:46:02 +0100 Subject: [PATCH 010/191] Backends: DirectX11: added ImGuiBackendFlags_RendererHasTextures support. # Conflicts: # backends/imgui_impl_dx11.cpp --- backends/imgui_impl_dx11.cpp | 114 +++++++++++++++++++++++------------ backends/imgui_impl_dx11.h | 6 +- 2 files changed, 82 insertions(+), 38 deletions(-) diff --git a/backends/imgui_impl_dx11.cpp b/backends/imgui_impl_dx11.cpp index f6a7f35f6..14e74e9b2 100644 --- a/backends/imgui_impl_dx11.cpp +++ b/backends/imgui_impl_dx11.cpp @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -16,6 +17,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: DirectX11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. // 2025-05-07: DirectX11: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows). // 2025-01-06: DirectX11: Expose VertexConstantBuffer in ImGui_ImplDX11_RenderState. Reset projection matrix in ImDrawCallback_ResetRenderState handler. // 2024-10-07: DirectX11: Changed default texture sampler to Clamp instead of Repeat/Wrap. @@ -51,6 +53,12 @@ #endif // DirectX11 data +struct ImGui_ImplDX11_Texture +{ + ID3D11Texture2D* pTexture; + ID3D11ShaderResourceView* pTextureView; +}; + struct ImGui_ImplDX11_Data { ID3D11Device* pd3dDevice; @@ -63,7 +71,6 @@ struct ImGui_ImplDX11_Data ID3D11Buffer* pVertexConstantBuffer; ID3D11PixelShader* pPixelShader; ID3D11SamplerState* pFontSampler; - ID3D11ShaderResourceView* pFontTextureView; ID3D11RasterizerState* pRasterizerState; ID3D11BlendState* pBlendState; ID3D11DepthStencilState* pDepthStencilState; @@ -153,6 +160,13 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); ID3D11DeviceContext* device = bd->pd3dDeviceContext; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplDX11_UpdateTexture(tex); + // Create and grow vertex/index buffers if needed if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) { @@ -320,21 +334,39 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release(); } -static void ImGui_ImplDX11_CreateFontsTexture() +static void ImGui_ImplDX11_DestroyTexture(ImTextureData* tex) { - // Build texture atlas - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + ImGui_ImplDX11_Texture* backend_tex = (ImGui_ImplDX11_Texture*)tex->BackendUserData; + if (backend_tex == nullptr) + return; + IM_ASSERT(backend_tex->pTextureView == (ID3D11ShaderResourceView*)(intptr_t)tex->TexID); + backend_tex->pTextureView->Release(); + backend_tex->pTexture->Release(); + IM_DELETE(backend_tex); - // Upload texture to graphics system + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + tex->BackendUserData = nullptr; +} + +void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex) +{ + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + if (tex->Status == ImTextureStatus_WantCreate) { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + unsigned int* pixels = (unsigned int*)tex->GetPixels(); + ImGui_ImplDX11_Texture* backend_tex = IM_NEW(ImGui_ImplDX11_Texture)(); + + // Create texture D3D11_TEXTURE2D_DESC desc; ZeroMemory(&desc, sizeof(desc)); - desc.Width = width; - desc.Height = height; + desc.Width = (UINT)tex->Width; + desc.Height = (UINT)tex->Height; desc.MipLevels = 1; desc.ArraySize = 1; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; @@ -342,14 +374,12 @@ static void ImGui_ImplDX11_CreateFontsTexture() desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; desc.CPUAccessFlags = 0; - - ID3D11Texture2D* pTexture = nullptr; D3D11_SUBRESOURCE_DATA subResource; subResource.pSysMem = pixels; subResource.SysMemPitch = desc.Width * 4; subResource.SysMemSlicePitch = 0; - bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); - IM_ASSERT(pTexture != nullptr); + bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &backend_tex->pTexture); + IM_ASSERT(backend_tex->pTexture != nullptr && "Backend failed to create texture!"); // Create texture view D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; @@ -358,23 +388,29 @@ static void ImGui_ImplDX11_CreateFontsTexture() srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0; - bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView); - pTexture->Release(); + bd->pd3dDevice->CreateShaderResourceView(backend_tex->pTexture, &srvDesc, &backend_tex->pTextureView); + IM_ASSERT(backend_tex->pTextureView != nullptr && "Backend failed to create texture!"); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)backend_tex->pTextureView); + tex->SetStatus(ImTextureStatus_OK); + tex->BackendUserData = backend_tex; } - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView); -} - -static void ImGui_ImplDX11_DestroyFontsTexture() -{ - ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); - if (bd->pFontTextureView) + else if (tex->Status == ImTextureStatus_WantUpdates) { - bd->pFontTextureView->Release(); - bd->pFontTextureView = nullptr; - ImGui::GetIO().Fonts->SetTexID(0); // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well. + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + ImGui_ImplDX11_Texture* backend_tex = (ImGui_ImplDX11_Texture*)tex->BackendUserData; + IM_ASSERT(backend_tex->pTextureView == (ID3D11ShaderResourceView*)(intptr_t)tex->TexID); + for (ImTextureRect& r : tex->Updates) + { + D3D11_BOX box = { (UINT)r.x, (UINT)r.y, (UINT)0, (UINT)(r.x + r.w), (UINT)(r.y + r .h), (UINT)1 }; + bd->pd3dDeviceContext->UpdateSubresource(backend_tex->pTexture, 0, &box, tex->GetPixelsAt(r.x, r.y), (UINT)tex->GetPitch(), 0); + } + tex->SetStatus(ImTextureStatus_OK); } + if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + ImGui_ImplDX11_DestroyTexture(tex); } bool ImGui_ImplDX11_CreateDeviceObjects() @@ -382,8 +418,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects() ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); if (!bd->pd3dDevice) return false; - if (bd->pFontSampler) - ImGui_ImplDX11_InvalidateDeviceObjects(); + ImGui_ImplDX11_InvalidateDeviceObjects(); // By using D3DCompile() from / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) // If you would like to use this DX11 sample code but remove this dependency you can: @@ -542,8 +577,6 @@ bool ImGui_ImplDX11_CreateDeviceObjects() bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler); } - ImGui_ImplDX11_CreateFontsTexture(); - return true; } @@ -553,7 +586,10 @@ void ImGui_ImplDX11_InvalidateDeviceObjects() if (!bd->pd3dDevice) return; - ImGui_ImplDX11_DestroyFontsTexture(); + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplDX11_DestroyTexture(tex); if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; } if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } @@ -578,6 +614,10 @@ bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_co io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx11"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; // Get factory from device IDXGIDevice* pDXGIDevice = nullptr; @@ -612,7 +652,7 @@ void ImGui_ImplDX11_Shutdown() if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); } io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } @@ -621,7 +661,7 @@ void ImGui_ImplDX11_NewFrame() ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX11_Init()?"); - if (!bd->pFontSampler) + if (!bd->pVertexShader) ImGui_ImplDX11_CreateDeviceObjects(); } diff --git a/backends/imgui_impl_dx11.h b/backends/imgui_impl_dx11.h index 772c1b920..c120bf093 100644 --- a/backends/imgui_impl_dx11.h +++ b/backends/imgui_impl_dx11.h @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -33,6 +34,9 @@ IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data); IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex); + // [BETA] Selected render state data shared with callbacks. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX11_RenderDrawData() call. // (Please open an issue if you feel you need access to more data) From 75efba7ec76729d34692187f97b7218c46d51a21 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 27 Nov 2024 18:47:21 +0100 Subject: [PATCH 011/191] Backends: DirectX9: added ImGuiBackendFlags_RendererHasTextures support # Conflicts: # backends/imgui_impl_dx9.cpp --- backends/imgui_impl_dx9.cpp | 104 ++++++++++++++++++++++++++---------- backends/imgui_impl_dx9.h | 6 ++- 2 files changed, 81 insertions(+), 29 deletions(-) diff --git a/backends/imgui_impl_dx9.cpp b/backends/imgui_impl_dx9.cpp index 2cc8681a6..2617988e7 100644 --- a/backends/imgui_impl_dx9.cpp +++ b/backends/imgui_impl_dx9.cpp @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -16,6 +17,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. // 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575) // 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. @@ -50,7 +52,6 @@ struct ImGui_ImplDX9_Data LPDIRECT3DDEVICE9 pd3dDevice; LPDIRECT3DVERTEXBUFFER9 pVB; LPDIRECT3DINDEXBUFFER9 pIB; - LPDIRECT3DTEXTURE9 FontTexture; int VertexBufferSize; int IndexBufferSize; bool HasRgbaSupport; @@ -163,6 +164,13 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); LPDIRECT3DDEVICE9 device = bd->pd3dDevice; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplDX9_UpdateTexture(tex); + // Create and grow buffers if needed if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) { @@ -322,6 +330,10 @@ bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx9"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096; bd->pd3dDevice = device; bd->pd3dDevice->AddRef(); @@ -340,12 +352,12 @@ void ImGui_ImplDX9_Shutdown() if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } // Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices) -static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h) +static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h) { #ifndef IMGUI_USE_BGRA_PACKED_COLOR ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); @@ -366,28 +378,61 @@ static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, ImU32* src, int } } -static bool ImGui_ImplDX9_CreateFontsTexture() +void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex) { - // Build texture atlas - ImGuiIO& io = ImGui::GetIO(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); - unsigned char* pixels; - int width, height, bytes_per_pixel; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel); - // Upload texture to graphics system - bd->FontTexture = nullptr; - if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0) - return false; - D3DLOCKED_RECT tex_locked_rect; - if (bd->FontTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK) - return false; - ImGui_ImplDX9_CopyTextureRegion(io.Fonts->TexPixelsUseColors, (ImU32*)pixels, width * bytes_per_pixel, (ImU32*)tex_locked_rect.pBits, (int)tex_locked_rect.Pitch, width, height); - bd->FontTexture->UnlockRect(0); + if (tex->Status == ImTextureStatus_WantCreate) + { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + LPDIRECT3DTEXTURE9 dx_tex = nullptr; + HRESULT hr = bd->pd3dDevice->CreateTexture(tex->Width, tex->Height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &dx_tex, nullptr); + if (hr < 0) + { + IM_ASSERT(hr >= 0 && "Backend failed to create texture!"); + return; + } - // Store our identifier - io.Fonts->SetTexID((ImTextureID)bd->FontTexture); - return true; + D3DLOCKED_RECT locked_rect; + if (dx_tex->LockRect(0, &locked_rect, nullptr, 0) == D3D_OK) + { + ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixels(), tex->Width * 4, (ImU32*)locked_rect.pBits, (ImU32)locked_rect.Pitch, tex->Width, tex->Height); + dx_tex->UnlockRect(0); + } + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)dx_tex); + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)(intptr_t)tex->TexID; + RECT update_rect = { (LONG)tex->UpdateRect.x, (LONG)tex->UpdateRect.y, (LONG)(tex->UpdateRect.x + tex->UpdateRect.w), (LONG)(tex->UpdateRect.y + tex->UpdateRect.h) }; + D3DLOCKED_RECT locked_rect; + if (backend_tex->LockRect(0, &locked_rect, &update_rect, 0) == D3D_OK) + for (ImTextureRect& r : tex->Updates) + ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixelsAt(r.x, r.y), tex->Width * 4, + (ImU32*)locked_rect.pBits + (r.x - update_rect.left) + (r.y - update_rect.top) * (locked_rect.Pitch / 4), (int)locked_rect.Pitch, r.w, r.h); + backend_tex->UnlockRect(0); + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy) + { + LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID; + if (backend_tex == nullptr) + return; + IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex); + backend_tex->Release(); + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + } } bool ImGui_ImplDX9_CreateDeviceObjects() @@ -395,8 +440,6 @@ bool ImGui_ImplDX9_CreateDeviceObjects() ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); if (!bd || !bd->pd3dDevice) return false; - if (!ImGui_ImplDX9_CreateFontsTexture()) - return false; return true; } @@ -405,18 +448,23 @@ void ImGui_ImplDX9_InvalidateDeviceObjects() ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); if (!bd || !bd->pd3dDevice) return; + + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + { + tex->SetStatus(ImTextureStatus_WantDestroy); + ImGui_ImplDX9_UpdateTexture(tex); + } if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; } if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } - if (bd->FontTexture) { bd->FontTexture->Release(); bd->FontTexture = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. } void ImGui_ImplDX9_NewFrame() { ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?"); - - if (!bd->FontTexture) - ImGui_ImplDX9_CreateDeviceObjects(); + IM_UNUSED(bd); } //----------------------------------------------------------------------------- diff --git a/backends/imgui_impl_dx9.h b/backends/imgui_impl_dx9.h index b693054d9..600f0a750 100644 --- a/backends/imgui_impl_dx9.h +++ b/backends/imgui_impl_dx9.h @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -30,4 +31,7 @@ IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data); IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex); + #endif // #ifndef IMGUI_DISABLE From 2d2b1bc1cc17f86c7a2253fcce4ceef41c238d96 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 5 Dec 2024 13:05:27 +0100 Subject: [PATCH 012/191] Backends: DirectX10: added ImGuiBackendFlags_RendererHasTextures support. # Conflicts: # backends/imgui_impl_dx10.cpp # backends/imgui_impl_dx10.h --- backends/imgui_impl_dx10.cpp | 112 ++++++++++++++++++++++++----------- backends/imgui_impl_dx10.h | 6 +- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/backends/imgui_impl_dx10.cpp b/backends/imgui_impl_dx10.cpp index dd06e0e7f..4dc5f884e 100644 --- a/backends/imgui_impl_dx10.cpp +++ b/backends/imgui_impl_dx10.cpp @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -15,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: DirectX10: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. // 2025-05-07: DirectX10: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows). // 2025-01-06: DirectX10: Expose selected render state in ImGui_ImplDX10_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks. // 2024-10-07: DirectX10: Changed default texture sampler to Clamp instead of Repeat/Wrap. @@ -49,6 +51,12 @@ #endif // DirectX10 data +struct ImGui_ImplDX10_Texture +{ + ID3D10Texture2D* pTexture; + ID3D10ShaderResourceView* pTextureView; +}; + struct ImGui_ImplDX10_Data { ID3D10Device* pd3dDevice; @@ -60,7 +68,6 @@ struct ImGui_ImplDX10_Data ID3D10Buffer* pVertexConstantBuffer; ID3D10PixelShader* pPixelShader; ID3D10SamplerState* pFontSampler; - ID3D10ShaderResourceView* pFontTextureView; ID3D10RasterizerState* pRasterizerState; ID3D10BlendState* pBlendState; ID3D10DepthStencilState* pDepthStencilState; @@ -147,6 +154,13 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); ID3D10Device* device = bd->pd3dDevice; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplDX10_UpdateTexture(tex); + // Create and grow vertex/index buffers if needed if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) { @@ -304,21 +318,39 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release(); } -static void ImGui_ImplDX10_CreateFontsTexture() +static void ImGui_ImplDX10_DestroyTexture(ImTextureData* tex) { - // Build texture atlas - ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); - ImGuiIO& io = ImGui::GetIO(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + ImGui_ImplDX10_Texture* backend_tex = (ImGui_ImplDX10_Texture*)tex->BackendUserData; + if (backend_tex == nullptr) + return; + IM_ASSERT(backend_tex->pTextureView == (ID3D10ShaderResourceView*)(intptr_t)tex->TexID); + backend_tex->pTexture->Release(); + backend_tex->pTextureView->Release(); + IM_DELETE(backend_tex); - // Upload texture to graphics system + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + tex->BackendUserData = nullptr; +} + +void ImGui_ImplDX10_UpdateTexture(ImTextureData* tex) +{ + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + if (tex->Status == ImTextureStatus_WantCreate) { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + unsigned int* pixels = (unsigned int*)tex->GetPixels(); + ImGui_ImplDX10_Texture* backend_tex = IM_NEW(ImGui_ImplDX10_Texture)(); + + // Create texture D3D10_TEXTURE2D_DESC desc; ZeroMemory(&desc, sizeof(desc)); - desc.Width = width; - desc.Height = height; + desc.Width = (UINT)tex->Width; + desc.Height = (UINT)tex->Height; desc.MipLevels = 1; desc.ArraySize = 1; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; @@ -327,13 +359,12 @@ static void ImGui_ImplDX10_CreateFontsTexture() desc.BindFlags = D3D10_BIND_SHADER_RESOURCE; desc.CPUAccessFlags = 0; - ID3D10Texture2D* pTexture = nullptr; D3D10_SUBRESOURCE_DATA subResource; subResource.pSysMem = pixels; subResource.SysMemPitch = desc.Width * 4; subResource.SysMemSlicePitch = 0; - bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); - IM_ASSERT(pTexture != nullptr); + bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &backend_tex->pTexture); + IM_ASSERT(backend_tex->pTexture != nullptr && "Backend failed to create texture!"); // Create texture view D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc; @@ -342,23 +373,29 @@ static void ImGui_ImplDX10_CreateFontsTexture() srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; srv_desc.Texture2D.MipLevels = desc.MipLevels; srv_desc.Texture2D.MostDetailedMip = 0; - bd->pd3dDevice->CreateShaderResourceView(pTexture, &srv_desc, &bd->pFontTextureView); - pTexture->Release(); + bd->pd3dDevice->CreateShaderResourceView(backend_tex->pTexture, &srv_desc, &backend_tex->pTextureView); + IM_ASSERT(backend_tex->pTextureView != nullptr && "Backend failed to create texture!"); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)backend_tex->pTextureView); + tex->SetStatus(ImTextureStatus_OK); + tex->BackendUserData = backend_tex; } - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView); -} - -static void ImGui_ImplDX10_DestroyFontsTexture() -{ - ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); - if (bd->pFontTextureView) + else if (tex->Status == ImTextureStatus_WantUpdates) { - bd->pFontTextureView->Release(); - bd->pFontTextureView = nullptr; - ImGui::GetIO().Fonts->SetTexID(0); // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + ImGui_ImplDX10_Texture* backend_tex = (ImGui_ImplDX10_Texture*)tex->BackendUserData; + IM_ASSERT(backend_tex->pTextureView == (ID3D10ShaderResourceView*)(intptr_t)tex->TexID); + for (ImTextureRect& r : tex->Updates) + { + D3D10_BOX box = { (UINT)r.x, (UINT)r.y, (UINT)0, (UINT)(r.x + r.w), (UINT)(r.y + r.h), (UINT)1 }; + bd->pd3dDevice->UpdateSubresource(backend_tex->pTexture, 0, &box, tex->GetPixelsAt(r.x, r.y), (UINT)tex->GetPitch(), 0); + } + tex->SetStatus(ImTextureStatus_OK); } + if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + ImGui_ImplDX10_DestroyTexture(tex); } bool ImGui_ImplDX10_CreateDeviceObjects() @@ -366,8 +403,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); if (!bd->pd3dDevice) return false; - if (bd->pFontSampler) - ImGui_ImplDX10_InvalidateDeviceObjects(); + ImGui_ImplDX10_InvalidateDeviceObjects(); // By using D3DCompile() from / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) // If you would like to use this DX10 sample code but remove this dependency you can: @@ -526,8 +562,6 @@ bool ImGui_ImplDX10_CreateDeviceObjects() bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler); } - ImGui_ImplDX10_CreateFontsTexture(); - return true; } @@ -537,8 +571,10 @@ void ImGui_ImplDX10_InvalidateDeviceObjects() if (!bd->pd3dDevice) return; - ImGui_ImplDX10_DestroyFontsTexture(); - + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplDX10_DestroyTexture(tex); if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; } if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; } @@ -562,6 +598,10 @@ bool ImGui_ImplDX10_Init(ID3D10Device* device) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx10"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION; // Get factory from device IDXGIDevice* pDXGIDevice = nullptr; @@ -592,7 +632,7 @@ void ImGui_ImplDX10_Shutdown() if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } diff --git a/backends/imgui_impl_dx10.h b/backends/imgui_impl_dx10.h index d9f29987d..38ecbdfe3 100644 --- a/backends/imgui_impl_dx10.h +++ b/backends/imgui_impl_dx10.h @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -31,6 +32,9 @@ IMGUI_IMPL_API void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data); IMGUI_IMPL_API bool ImGui_ImplDX10_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX10_InvalidateDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplDX10_UpdateTexture(ImTextureData* tex); + // [BETA] Selected render state data shared with callbacks. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX10_RenderDrawData() call. // (Please open an issue if you feel you need access to more data) From eefe5d5aac1a9040020855c3bb918fe469cf4a38 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 27 Nov 2024 18:46:09 +0100 Subject: [PATCH 013/191] Backends: DirectX12: added ImGuiBackendFlags_RendererHasTextures support. # Conflicts: # backends/imgui_impl_dx12.cpp --- backends/imgui_impl_dx12.cpp | 233 ++++++++++++++++++++++++----------- backends/imgui_impl_dx12.h | 6 +- 2 files changed, 165 insertions(+), 74 deletions(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index a373e8eec..ea7e3b881 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // The aim of imgui_impl_dx12.h/.cpp is to be usable in your engine without any modification. @@ -19,6 +20,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: DirectX12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. // 2025-05-07: DirectX12: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows). // 2025-02-24: DirectX12: Fixed an issue where ImGui_ImplDX12_Init() signature change from 2024-11-15 combined with change from 2025-01-15 made legacy ImGui_ImplDX12_Init() crash. (#8429) // 2025-01-15: DirectX12: Texture upload use the command queue provided in ImGui_ImplDX12_InitInfo instead of creating its own. @@ -184,6 +186,13 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) return; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplDX12_UpdateTexture(tex); + // FIXME: We are assuming that this only gets called once per frame! ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); bd->frameIndex = bd->frameIndex + 1; @@ -316,18 +325,39 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL platform_io.Renderer_RenderState = nullptr; } -static void ImGui_ImplDX12_CreateFontsTexture() +static void ImGui_ImplDX12_DestroyTexture(ImTextureData* tex) { - // Build texture atlas - ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplDX12_Texture* backend_tex = (ImGui_ImplDX12_Texture*)tex->BackendUserData; + if (backend_tex == nullptr) + return; + IM_ASSERT(backend_tex->hFontSrvGpuDescHandle.ptr == (UINT64)tex->TexID); ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + bd->InitInfo.SrvDescriptorFreeFn(&bd->InitInfo, backend_tex->hFontSrvCpuDescHandle, backend_tex->hFontSrvGpuDescHandle); + SafeRelease(backend_tex->pTextureResource); + backend_tex->hFontSrvCpuDescHandle.ptr = 0; + backend_tex->hFontSrvGpuDescHandle.ptr = 0; + IM_DELETE(backend_tex); - // Upload texture to graphics system - ImGui_ImplDX12_Texture* font_tex = &bd->FontTexture; + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + tex->BackendUserData = nullptr; +} + +void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) +{ + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + bool need_barrier_before_copy = true; // Do we need a resource barrier before we copy new data in? + + if (tex->Status == ImTextureStatus_WantCreate) { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + ImGui_ImplDX12_Texture* backend_tex = IM_NEW(ImGui_ImplDX12_Texture)(); + bd->InitInfo.SrvDescriptorAllocFn(&bd->InitInfo, &backend_tex->hFontSrvCpuDescHandle, &backend_tex->hFontSrvGpuDescHandle); // Allocate a desctriptor handle + D3D12_HEAP_PROPERTIES props = {}; props.Type = D3D12_HEAP_TYPE_DEFAULT; props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; @@ -337,8 +367,8 @@ static void ImGui_ImplDX12_CreateFontsTexture() ZeroMemory(&desc, sizeof(desc)); desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; desc.Alignment = 0; - desc.Width = width; - desc.Height = height; + desc.Width = tex->Width; + desc.Height = tex->Height; desc.DepthOrArraySize = 1; desc.MipLevels = 1; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; @@ -351,8 +381,47 @@ static void ImGui_ImplDX12_CreateFontsTexture() bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&pTexture)); - UINT upload_pitch = (width * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); - UINT upload_size = height * upload_pitch; + // Create SRV + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; + ZeroMemory(&srvDesc, sizeof(srvDesc)); + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, backend_tex->hFontSrvCpuDescHandle); + SafeRelease(backend_tex->pTextureResource); + backend_tex->pTextureResource = pTexture; + + // Store identifiers + tex->SetTexID((ImTextureID)backend_tex->hFontSrvGpuDescHandle.ptr); + tex->BackendUserData = backend_tex; + need_barrier_before_copy = false; // Because this is a newly-created texture it will be in D3D12_RESOURCE_STATE_COMMON and thus we don't need a barrier + // We don't set tex->Status to ImTextureStatus_OK to let the code fallthrough below. + } + + if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) + { + ImGui_ImplDX12_Texture* backend_tex = (ImGui_ImplDX12_Texture*)tex->BackendUserData; + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + + // We could use the smaller rect on _WantCreate but using the full rect allows us to clear the texture. + // FIXME-OPT: Uploading single box even when using ImTextureStatus_WantUpdates. Could use tex->Updates[] + // - Copy all blocks contiguously in upload buffer. + // - Barrier before copy, submit all CopyTextureRegion(), barrier after copy. + const int upload_x = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.x; + const int upload_y = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.y; + const int upload_w = (tex->Status == ImTextureStatus_WantCreate) ? tex->Width : tex->UpdateRect.w; + const int upload_h = (tex->Status == ImTextureStatus_WantCreate) ? tex->Height : tex->UpdateRect.h; + + // Update full texture or selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions. + UINT upload_pitch_src = upload_w * tex->BytesPerPixel; + UINT upload_pitch_dst = (upload_pitch_src + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); + UINT upload_size = upload_pitch_dst * upload_h; + + D3D12_RESOURCE_DESC desc; + ZeroMemory(&desc, sizeof(desc)); desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Alignment = 0; desc.Width = upload_size; @@ -365,47 +434,19 @@ static void ImGui_ImplDX12_CreateFontsTexture() desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; desc.Flags = D3D12_RESOURCE_FLAG_NONE; + D3D12_HEAP_PROPERTIES props; + memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES)); props.Type = D3D12_HEAP_TYPE_UPLOAD; props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + // FIXME-OPT: Can upload buffer be reused? ID3D12Resource* uploadBuffer = nullptr; HRESULT hr = bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)); IM_ASSERT(SUCCEEDED(hr)); - void* mapped = nullptr; - D3D12_RANGE range = { 0, upload_size }; - hr = uploadBuffer->Map(0, &range, &mapped); - IM_ASSERT(SUCCEEDED(hr)); - for (int y = 0; y < height; y++) - memcpy((void*) ((uintptr_t) mapped + y * upload_pitch), pixels + y * width * 4, width * 4); - uploadBuffer->Unmap(0, &range); - - D3D12_TEXTURE_COPY_LOCATION srcLocation = {}; - D3D12_TEXTURE_COPY_LOCATION dstLocation = {}; - { - srcLocation.pResource = uploadBuffer; - srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; - srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - srcLocation.PlacedFootprint.Footprint.Width = width; - srcLocation.PlacedFootprint.Footprint.Height = height; - srcLocation.PlacedFootprint.Footprint.Depth = 1; - srcLocation.PlacedFootprint.Footprint.RowPitch = upload_pitch; - - dstLocation.pResource = pTexture; - dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - dstLocation.SubresourceIndex = 0; - } - - D3D12_RESOURCE_BARRIER barrier = {}; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = pTexture; - barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; - + // Create temporary command list and execute immediately ID3D12Fence* fence = nullptr; hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); IM_ASSERT(SUCCEEDED(hr)); @@ -413,16 +454,63 @@ static void ImGui_ImplDX12_CreateFontsTexture() HANDLE event = ::CreateEvent(0, 0, 0, 0); IM_ASSERT(event != nullptr); + // FIXME-OPT: Create once and reuse? ID3D12CommandAllocator* cmdAlloc = nullptr; hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc)); IM_ASSERT(SUCCEEDED(hr)); + // FIXME-OPT: Can be use the one from user? (pass ID3D12GraphicsCommandList* to ImGui_ImplDX12_UpdateTextures) ID3D12GraphicsCommandList* cmdList = nullptr; hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList)); IM_ASSERT(SUCCEEDED(hr)); - cmdList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, nullptr); - cmdList->ResourceBarrier(1, &barrier); + // Copy to upload buffer + void* mapped = nullptr; + D3D12_RANGE range = { 0, upload_size }; + hr = uploadBuffer->Map(0, &range, &mapped); + IM_ASSERT(SUCCEEDED(hr)); + for (int y = 0; y < upload_h; y++) + memcpy((void*)((uintptr_t)mapped + y * upload_pitch_dst), tex->GetPixelsAt(upload_x, upload_y + y), upload_pitch_src); + uploadBuffer->Unmap(0, &range); + + if (need_barrier_before_copy) + { + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = backend_tex->pTextureResource; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; + cmdList->ResourceBarrier(1, &barrier); + } + + D3D12_TEXTURE_COPY_LOCATION srcLocation = {}; + D3D12_TEXTURE_COPY_LOCATION dstLocation = {}; + { + srcLocation.pResource = uploadBuffer; + srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srcLocation.PlacedFootprint.Footprint.Width = upload_w; + srcLocation.PlacedFootprint.Footprint.Height = upload_h; + srcLocation.PlacedFootprint.Footprint.Depth = 1; + srcLocation.PlacedFootprint.Footprint.RowPitch = upload_pitch_dst; + dstLocation.pResource = backend_tex->pTextureResource; + dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dstLocation.SubresourceIndex = 0; + } + cmdList->CopyTextureRegion(&dstLocation, upload_x, upload_y, 0, &srcLocation, nullptr); + + { + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = backend_tex->pTextureResource; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + cmdList->ResourceBarrier(1, &barrier); + } hr = cmdList->Close(); IM_ASSERT(SUCCEEDED(hr)); @@ -432,6 +520,10 @@ static void ImGui_ImplDX12_CreateFontsTexture() hr = cmdQueue->Signal(fence, 1); IM_ASSERT(SUCCEEDED(hr)); + // FIXME-OPT: Suboptimal? + // - To remove this may need to create NumFramesInFlight x ImGui_ImplDX12_FrameContext in backend data (mimick docking version) + // - Store per-frame in flight: upload buffer? + // - Where do cmdList and cmdAlloc fit? fence->SetEventOnCompletion(1, event); ::WaitForSingleObject(event, INFINITE); @@ -440,22 +532,11 @@ static void ImGui_ImplDX12_CreateFontsTexture() ::CloseHandle(event); fence->Release(); uploadBuffer->Release(); - - // Create texture view - D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; - ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = desc.MipLevels; - srvDesc.Texture2D.MostDetailedMip = 0; - srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; - bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, font_tex->hFontSrvCpuDescHandle); - SafeRelease(font_tex->pTextureResource); - font_tex->pTextureResource = pTexture; + tex->SetStatus(ImTextureStatus_OK); } - // Store our identifier - io.Fonts->SetTexID((ImTextureID)font_tex->hFontSrvGpuDescHandle.ptr); + if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames >= (int)bd->numFramesInFlight) + ImGui_ImplDX12_DestroyTexture(tex); } bool ImGui_ImplDX12_CreateDeviceObjects() @@ -687,8 +768,6 @@ bool ImGui_ImplDX12_CreateDeviceObjects() if (result_pipeline_state != S_OK) return false; - ImGui_ImplDX12_CreateFontsTexture(); - return true; } @@ -704,12 +783,10 @@ void ImGui_ImplDX12_InvalidateDeviceObjects() SafeRelease(bd->pRootSignature); SafeRelease(bd->pPipelineState); - // Free SRV descriptor used by texture - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplDX12_Texture* font_tex = &bd->FontTexture; - bd->InitInfo.SrvDescriptorFreeFn(&bd->InitInfo, font_tex->hFontSrvCpuDescHandle, font_tex->hFontSrvGpuDescHandle); - SafeRelease(font_tex->pTextureResource); - io.Fonts->SetTexID(0); // We copied bd->hFontSrvGpuDescHandle to io.Fonts->TexID so let's clear that as well. + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplDX12_DestroyTexture(tex); for (UINT i = 0; i < bd->numFramesInFlight; i++) { @@ -741,6 +818,16 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx12"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + ImGui_ImplDX12_InitPlatformInterface(); + + // Create a dummy ImGui_ImplDX12_ViewportData holder for the main viewport, + // Since this is created and managed by the application, we will only use the ->Resources[] fields. + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + main_viewport->RendererUserData = IM_NEW(ImGui_ImplDX12_ViewportData)(bd->numFramesInFlight); +>>>>>>> dda12fbd9a (Backends: DirectX12: added ImGuiBackendFlags_RendererHasTextures support.) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (init_info->SrvDescriptorAllocFn == nullptr) @@ -763,10 +850,7 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info) }; } #endif - - // Allocate 1 SRV descriptor for the font texture IM_ASSERT(init_info->SrvDescriptorAllocFn != nullptr && init_info->SrvDescriptorFreeFn != nullptr); - init_info->SrvDescriptorAllocFn(&bd->InitInfo, &bd->FontTexture.hFontSrvCpuDescHandle, &bd->FontTexture.hFontSrvGpuDescHandle); // Create buffers with a default size (they will later be grown as needed) bd->frameIndex = UINT_MAX; @@ -806,6 +890,9 @@ bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FO bool ret = ImGui_ImplDX12_Init(&init_info); ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); bd->commandQueueOwned = true; + ImGuiIO& io = ImGui::GetIO(); + io.BackendFlags &= ~ImGuiBackendFlags_RendererHasTextures; // Using legacy ImGui_ImplDX12_Init() call with 1 SRV descriptor we cannot support multiple textures. + return ret; } #endif @@ -822,7 +909,7 @@ void ImGui_ImplDX12_Shutdown() io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } diff --git a/backends/imgui_impl_dx12.h b/backends/imgui_impl_dx12.h index a79de7b13..4ff510455 100644 --- a/backends/imgui_impl_dx12.h +++ b/backends/imgui_impl_dx12.h @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // The aim of imgui_impl_dx12.h/.cpp is to be usable in your engine without any modification. @@ -63,6 +64,9 @@ IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex); + // [BETA] Selected render state data shared with callbacks. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX12_RenderDrawData() call. // (Please open an issue if you feel you need access to more data) From dbb91a574f35e24697754f6344455fd43b97d46d Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 27 Nov 2024 18:47:14 +0100 Subject: [PATCH 014/191] Backends: OpenGL3: added ImGuiBackendFlags_RendererHasTextures support. + Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture(). --- backends/imgui_impl_opengl3.cpp | 137 ++++++++++++++++++--------- backends/imgui_impl_opengl3.h | 8 +- backends/imgui_impl_opengl3_loader.h | 8 +- 3 files changed, 104 insertions(+), 49 deletions(-) diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 8b7a2ecb5..3f6c08756 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -4,8 +4,9 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // About WebGL/ES: // - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. @@ -22,6 +23,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture(). // 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664) // 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406) // 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. @@ -231,7 +233,7 @@ struct ImGui_ImplOpenGL3_Data bool GlProfileIsES3; bool GlProfileIsCompat; GLint GlProfileMask; - GLuint FontTexture; + GLint MaxTextureSize; GLuint ShaderHandle; GLint AttribLocationTex; // Uniforms location GLint AttribLocationProjMtx; @@ -244,6 +246,7 @@ struct ImGui_ImplOpenGL3_Data bool HasPolygonMode; bool HasClipOrigin; bool UseBufferSubData; + ImVector TempBuffer; ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -326,6 +329,7 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) if (major == 0 && minor == 0) sscanf(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "." bd->GlVersion = (GLuint)(major * 100 + minor * 10); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &bd->MaxTextureSize); #if defined(IMGUI_IMPL_OPENGL_ES3) bd->GlProfileIsES3 = true; @@ -359,6 +363,10 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) if (bd->GlVersion >= 320) io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. #endif + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = (int)bd->MaxTextureSize; // Store GLSL version string so we can refer to it later in case we recreate shaders. // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure. @@ -411,7 +419,7 @@ void ImGui_ImplOpenGL3_Shutdown() ImGui_ImplOpenGL3_DestroyDeviceObjects(); io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } @@ -424,8 +432,6 @@ void ImGui_ImplOpenGL3_NewFrame() if (!bd->ShaderHandle) ImGui_ImplOpenGL3_CreateDeviceObjects(); - if (!bd->FontTexture) - ImGui_ImplOpenGL3_CreateFontsTexture(); } static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) @@ -517,6 +523,13 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplOpenGL3_UpdateTexture(tex); + // Backup GL state GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); glActiveTexture(GL_TEXTURE0); @@ -685,50 +698,82 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) (void)bd; // Not all compilation paths use this } -bool ImGui_ImplOpenGL3_CreateFontsTexture() +static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex) { - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID; + glDeleteTextures(1, &gl_tex_id); - // Build texture atlas - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. - - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) - GLint last_texture; - GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); - GL_CALL(glGenTextures(1, &bd->FontTexture)); - GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture)); - GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); - GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); -#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); -#endif - GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); - - // Store identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); - - // Restore state - GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); - - return true; + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); } -void ImGui_ImplOpenGL3_DestroyFontsTexture() +void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) { - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - if (bd->FontTexture) + if (tex->Status == ImTextureStatus_WantCreate) { - glDeleteTextures(1, &bd->FontTexture); - io.Fonts->SetTexID(0); - bd->FontTexture = 0; + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + const void* pixels = tex->GetPixels(); + GLuint gl_texture_id = 0; + + // Upload texture to graphics system + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + GLint last_texture; + GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + GL_CALL(glGenTextures(1, &gl_texture_id)); + GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_texture_id)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); +#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); +#endif + GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)gl_texture_id); + tex->SetStatus(ImTextureStatus_OK); + + // Restore state + GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + GLint last_texture; + GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + + GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID; + GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_tex_id)); +#if 0// GL_UNPACK_ROW_LENGTH // Not on WebGL/ES + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width)); + for (ImTextureRect& r : tex->Updates) + GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y))); + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); +#else + // GL ES doesn't have GL_UNPACK_ROW_LENGTH, so we need to (A) copy to a contiguous buffer or (B) upload line by line. + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + for (ImTextureRect& r : tex->Updates) + { + const int src_pitch = r.w * tex->BytesPerPixel; + bd->TempBuffer.resize(r.h * src_pitch); + char* out_p = bd->TempBuffer.Data; + for (int y = 0; y < r.h; y++, out_p += src_pitch) + memcpy(out_p, tex->GetPixelsAt(r.x, r.y + y), src_pitch); + IM_ASSERT(out_p == bd->TempBuffer.end()); + GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, bd->TempBuffer.Data)); + } +#endif + tex->SetStatus(ImTextureStatus_OK); + GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); // Restore state + } + else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + ImGui_ImplOpenGL3_DestroyTexture(tex); } // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. @@ -951,8 +996,6 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects() glGenBuffers(1, &bd->VboHandle); glGenBuffers(1, &bd->ElementsHandle); - ImGui_ImplOpenGL3_CreateFontsTexture(); - // Restore modified GL state glBindTexture(GL_TEXTURE_2D, last_texture); glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); @@ -972,7 +1015,11 @@ void ImGui_ImplOpenGL3_DestroyDeviceObjects() if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; } if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; } if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; } - ImGui_ImplOpenGL3_DestroyFontsTexture(); + + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplOpenGL3_DestroyTexture(tex); } //----------------------------------------------------------------------------- diff --git a/backends/imgui_impl_opengl3.h b/backends/imgui_impl_opengl3.h index 5de51cfdd..b72b5c887 100644 --- a/backends/imgui_impl_opengl3.h +++ b/backends/imgui_impl_opengl3.h @@ -4,8 +4,9 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // About WebGL/ES: // - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. @@ -36,11 +37,12 @@ IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); // (Optional) Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); -IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex); + // Configuration flags to add in your imconfig file: //#define IMGUI_IMPL_OPENGL_ES2 // Enable ES 2 (Auto-detected on Emscripten) //#define IMGUI_IMPL_OPENGL_ES3 // Enable ES 3 (Auto-detected on iOS/Android) diff --git a/backends/imgui_impl_opengl3_loader.h b/backends/imgui_impl_opengl3_loader.h index d6ffa5a2d..4ca053603 100644 --- a/backends/imgui_impl_opengl3_loader.h +++ b/backends/imgui_impl_opengl3_loader.h @@ -167,6 +167,7 @@ typedef khronos_uint8_t GLubyte; #define GL_SCISSOR_TEST 0x0C11 #define GL_UNPACK_ROW_LENGTH 0x0CF2 #define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAX_TEXTURE_SIZE 0x0D33 #define GL_TEXTURE_2D 0x0DE1 #define GL_UNSIGNED_BYTE 0x1401 #define GL_UNSIGNED_SHORT 0x1403 @@ -224,11 +225,13 @@ typedef khronos_float_t GLclampf; typedef double GLclampd; #define GL_TEXTURE_BINDING_2D 0x8069 typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture); typedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures); typedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices); +GLAPI void APIENTRY glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture); GLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures); GLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures); @@ -478,7 +481,7 @@ GL3W_API GL3WglProc imgl3wGetProcAddress(const char *proc); /* gl3w internal state */ union ImGL3WProcs { - GL3WglProc ptr[59]; + GL3WglProc ptr[60]; struct { PFNGLACTIVETEXTUREPROC ActiveTexture; PFNGLATTACHSHADERPROC AttachShader; @@ -534,6 +537,7 @@ union ImGL3WProcs { PFNGLSHADERSOURCEPROC ShaderSource; PFNGLTEXIMAGE2DPROC TexImage2D; PFNGLTEXPARAMETERIPROC TexParameteri; + PFNGLTEXSUBIMAGE2DPROC TexSubImage2D; PFNGLUNIFORM1IPROC Uniform1i; PFNGLUNIFORMMATRIX4FVPROC UniformMatrix4fv; PFNGLUSEPROGRAMPROC UseProgram; @@ -599,6 +603,7 @@ GL3W_API extern union ImGL3WProcs imgl3wProcs; #define glShaderSource imgl3wProcs.gl.ShaderSource #define glTexImage2D imgl3wProcs.gl.TexImage2D #define glTexParameteri imgl3wProcs.gl.TexParameteri +#define glTexSubImage2D imgl3wProcs.gl.TexSubImage2D #define glUniform1i imgl3wProcs.gl.Uniform1i #define glUniformMatrix4fv imgl3wProcs.gl.UniformMatrix4fv #define glUseProgram imgl3wProcs.gl.UseProgram @@ -894,6 +899,7 @@ static const char *proc_names[] = { "glShaderSource", "glTexImage2D", "glTexParameteri", + "glTexSubImage2D", "glUniform1i", "glUniformMatrix4fv", "glUseProgram", From 0430c55b84edc8097be1064ad8aafb842c3774ae Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 18 Dec 2024 14:05:55 +0100 Subject: [PATCH 015/191] Backends: OpenGL2: added ImGuiBackendFlags_RendererHasTextures support. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture(). --- backends/imgui_impl_opengl2.cpp | 121 +++++++++++++++++++------------- backends/imgui_impl_opengl2.h | 8 ++- 2 files changed, 78 insertions(+), 51 deletions(-) diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 2a255be4c..5e9df1715 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -2,7 +2,8 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). // Missing features or Issues: // [ ] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). @@ -24,6 +25,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture(). // 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-06-28: OpenGL: ImGui_ImplOpenGL2_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL2_DestroyFontsTexture(). (#7748) // 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. @@ -81,8 +83,6 @@ // OpenGL data struct ImGui_ImplOpenGL2_Data { - GLuint FontTexture; - ImGui_ImplOpenGL2_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -104,6 +104,7 @@ bool ImGui_ImplOpenGL2_Init() ImGui_ImplOpenGL2_Data* bd = IM_NEW(ImGui_ImplOpenGL2_Data)(); io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_opengl2"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. return true; } @@ -117,6 +118,7 @@ void ImGui_ImplOpenGL2_Shutdown() ImGui_ImplOpenGL2_DestroyDeviceObjects(); io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } @@ -124,11 +126,7 @@ void ImGui_ImplOpenGL2_NewFrame() { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplOpenGL2_Init()?"); - - if (!bd->FontTexture) - ImGui_ImplOpenGL2_CreateDeviceObjects(); - if (!bd->FontTexture) - ImGui_ImplOpenGL2_CreateFontsTexture(); + IM_UNUSED(bd); } static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height) @@ -186,6 +184,13 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) if (fb_width == 0 || fb_height == 0) return; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplOpenGL2_UpdateTexture(tex); + // Backup GL state GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); @@ -259,57 +264,77 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, last_tex_env_mode); } -bool ImGui_ImplOpenGL2_CreateFontsTexture() +void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) { - // Build texture atlas - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. - - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) - GLint last_texture; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); - glGenTextures(1, &bd->FontTexture); - glBindTexture(GL_TEXTURE_2D, bd->FontTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); - - // Restore state - glBindTexture(GL_TEXTURE_2D, last_texture); - - return true; -} - -void ImGui_ImplOpenGL2_DestroyFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); - if (bd->FontTexture) + if (tex->Status == ImTextureStatus_WantCreate) { - glDeleteTextures(1, &bd->FontTexture); - io.Fonts->SetTexID(0); - bd->FontTexture = 0; + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + const void* pixels = tex->GetPixels(); + GLuint gl_texture_id = 0; + + // Upload texture to graphics system + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + GLint last_texture; + GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + GL_CALL(glGenTextures(1, &gl_texture_id)); + GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_texture_id)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)); + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); + GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)gl_texture_id); + tex->SetStatus(ImTextureStatus_OK); + + // Restore state + GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + GLint last_texture; + GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + + GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID; + GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_tex_id)); + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width)); + for (ImTextureRect& r : tex->Updates) + GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y))); + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); + GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); // Restore state + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy) + { + GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID; + glDeleteTextures(1, &gl_tex_id); + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); } } bool ImGui_ImplOpenGL2_CreateDeviceObjects() { - return ImGui_ImplOpenGL2_CreateFontsTexture(); + return true; } void ImGui_ImplOpenGL2_DestroyDeviceObjects() { - ImGui_ImplOpenGL2_DestroyFontsTexture(); + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + { + tex->SetStatus(ImTextureStatus_WantDestroy); + ImGui_ImplOpenGL2_UpdateTexture(tex); + } } //----------------------------------------------------------------------------- diff --git a/backends/imgui_impl_opengl2.h b/backends/imgui_impl_opengl2.h index 5832a1765..014a03aca 100644 --- a/backends/imgui_impl_opengl2.h +++ b/backends/imgui_impl_opengl2.h @@ -2,7 +2,8 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // Missing features or Issues: // [ ] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). @@ -33,9 +34,10 @@ IMGUI_IMPL_API void ImGui_ImplOpenGL2_NewFrame(); IMGUI_IMPL_API void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data); // Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplOpenGL2_CreateFontsTexture(); -IMGUI_IMPL_API void ImGui_ImplOpenGL2_DestroyFontsTexture(); IMGUI_IMPL_API bool ImGui_ImplOpenGL2_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplOpenGL2_DestroyDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex); + #endif // #ifndef IMGUI_DISABLE From abe294bfd0bfff325bbaa000f2d688085f1d4696 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 27 Nov 2024 18:47:28 +0100 Subject: [PATCH 016/191] Backends: Vulkan: added ImGuiBackendFlags_RendererHasTextures support. # Conflicts: # backends/imgui_impl_vulkan.cpp --- backends/imgui_impl_vulkan.cpp | 453 ++++++++++++++++++--------------- backends/imgui_impl_vulkan.h | 13 +- 2 files changed, 250 insertions(+), 216 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 10648c3a4..74c7d791b 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions. +// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. @@ -26,7 +27,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2025-05-07- Vulkan: Fixed validation errors during window detach in multi-viewport mode. (#8600, #8176) +// 2025-06-11: Vulkan: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplVulkan_CreateFontsTexture() and ImGui_ImplVulkan_DestroyFontsTexture(). +// 2025-05-07: Vulkan: Fixed validation errors during window detach in multi-viewport mode. (#8600, #8176) // 2025-05-07: Vulkan: Load dynamic rendering functions using vkGetDeviceProcAddr() + try both non-KHR and KHR versions. (#8600, #8326, #8365) // 2025-04-07: Vulkan: Deep-copy ImGui_ImplVulkan_InitInfo::PipelineRenderingCreateInfo's pColorAttachmentFormats buffer when set, in order to reduce common user-error of specifying a pointer to data that gets out of scope. (#8282) // 2025-02-14: *BREAKING CHANGE*: Added uint32_t api_version to ImGui_ImplVulkan_LoadFunctions(). @@ -251,7 +253,6 @@ struct ImGui_ImplVulkan_Data VkDescriptorPool DescriptorPool; // Texture management - ImGui_ImplVulkan_Texture FontTexture; VkSampler TexSampler; VkCommandPool TexCommandPool; VkCommandBuffer TexCommandBuffer; @@ -502,6 +503,13 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm if (fb_width <= 0 || fb_height <= 0) return; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplVulkan_UpdateTexture(tex); + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; if (pipeline == VK_NULL_HANDLE) @@ -638,220 +646,222 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm vkCmdSetScissor(command_buffer, 0, 1, &scissor); } -bool ImGui_ImplVulkan_CreateFontsTexture() +static void ImGui_ImplVulkan_DestroyTexture(ImTextureData* tex) { - ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplVulkan_Texture* backend_tex = (ImGui_ImplVulkan_Texture*)tex->BackendUserData; + if (backend_tex == nullptr) + return; + IM_ASSERT(backend_tex->DescriptorSet == (VkDescriptorSet)tex->TexID); + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); + vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); + vkDestroyImage(v->Device, backend_tex->Image, v->Allocator); + vkFreeMemory(v->Device, backend_tex->Memory, v->Allocator); + IM_DELETE(backend_tex); + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + tex->BackendUserData = nullptr; +} + +void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex) +{ + if (tex->Status == ImTextureStatus_OK) + return; ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; VkResult err; - // Destroy existing texture (if any) - if (bd->FontTexture.DescriptorSet) + if (tex->Status == ImTextureStatus_WantCreate) { - vkQueueWaitIdle(v->Queue); - ImGui_ImplVulkan_DestroyFontsTexture(); + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + ImGui_ImplVulkan_Texture* backend_tex = IM_NEW(ImGui_ImplVulkan_Texture)(); + + // Create the Image: + { + VkImageCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.extent.width = tex->Width; + info.extent.height = tex->Height; + info.extent.depth = 1; + info.mipLevels = 1; + info.arrayLayers = 1; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + err = vkCreateImage(v->Device, &info, v->Allocator, &backend_tex->Image); + check_vk_result(err); + VkMemoryRequirements req; + vkGetImageMemoryRequirements(v->Device, backend_tex->Image, &req); + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = IM_MAX(v->MinAllocationSize, req.size); + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &backend_tex->Memory); + check_vk_result(err); + err = vkBindImageMemory(v->Device, backend_tex->Image, backend_tex->Memory, 0); + check_vk_result(err); + } + + // Create the Image View: + { + VkImageViewCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = backend_tex->Image; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + info.subresourceRange.levelCount = 1; + info.subresourceRange.layerCount = 1; + err = vkCreateImageView(v->Device, &info, v->Allocator, &backend_tex->ImageView); + check_vk_result(err); + } + + // Create the Descriptor Set + backend_tex->DescriptorSet = ImGui_ImplVulkan_AddTexture(bd->TexSampler, backend_tex->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // Store identifiers + tex->SetTexID((ImTextureID)backend_tex->DescriptorSet); + tex->BackendUserData = backend_tex; } - // Create command pool/buffer - if (bd->TexCommandPool == VK_NULL_HANDLE) + if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) { - VkCommandPoolCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - info.flags = 0; - info.queueFamilyIndex = v->QueueFamily; - vkCreateCommandPool(v->Device, &info, v->Allocator, &bd->TexCommandPool); - } - if (bd->TexCommandBuffer == VK_NULL_HANDLE) - { - VkCommandBufferAllocateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - info.commandPool = bd->TexCommandPool; - info.commandBufferCount = 1; - err = vkAllocateCommandBuffers(v->Device, &info, &bd->TexCommandBuffer); + ImGui_ImplVulkan_Texture* backend_tex = (ImGui_ImplVulkan_Texture*)tex->BackendUserData; + + // Update full texture or selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions. + // We could use the smaller rect on _WantCreate but using the full rect allows us to clear the texture. + const int upload_x = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.x; + const int upload_y = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.y; + const int upload_w = (tex->Status == ImTextureStatus_WantCreate) ? tex->Width : tex->UpdateRect.w; + const int upload_h = (tex->Status == ImTextureStatus_WantCreate) ? tex->Height : tex->UpdateRect.h; + + // Create the Upload Buffer: + VkDeviceMemory upload_buffer_memory; + + VkBuffer upload_buffer; + VkDeviceSize upload_pitch = upload_w * tex->BytesPerPixel; + VkDeviceSize upload_size = upload_h * upload_pitch; + { + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = upload_size; + buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &upload_buffer); + check_vk_result(err); + VkMemoryRequirements req; + vkGetBufferMemoryRequirements(v->Device, upload_buffer, &req); + bd->BufferMemoryAlignment = (bd->BufferMemoryAlignment > req.alignment) ? bd->BufferMemoryAlignment : req.alignment; + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = IM_MAX(v->MinAllocationSize, req.size); + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &upload_buffer_memory); + check_vk_result(err); + err = vkBindBufferMemory(v->Device, upload_buffer, upload_buffer_memory, 0); + check_vk_result(err); + } + + // Upload to Buffer: + { + char* map = nullptr; + err = vkMapMemory(v->Device, upload_buffer_memory, 0, upload_size, 0, (void**)(&map)); + check_vk_result(err); + for (int y = 0; y < upload_h; y++) + memcpy(map + upload_pitch * y, tex->GetPixelsAt(upload_x, upload_y + y), (size_t)upload_pitch); + VkMappedMemoryRange range[1] = {}; + range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range[0].memory = upload_buffer_memory; + range[0].size = upload_size; + err = vkFlushMappedMemoryRanges(v->Device, 1, range); + check_vk_result(err); + vkUnmapMemory(v->Device, upload_buffer_memory); + } + + // Start command buffer + { + err = vkResetCommandPool(v->Device, bd->TexCommandPool, 0); + check_vk_result(err); + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + err = vkBeginCommandBuffer(bd->TexCommandBuffer, &begin_info); + check_vk_result(err); + } + + // Copy to Image: + { + VkImageMemoryBarrier copy_barrier[1] = {}; + copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].image = backend_tex->Image; + copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy_barrier[0].subresourceRange.levelCount = 1; + copy_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(bd->TexCommandBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, copy_barrier); + + VkBufferImageCopy region = {}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent.width = upload_w; + region.imageExtent.height = upload_h; + region.imageExtent.depth = 1; + region.imageOffset.x = upload_x; + region.imageOffset.y = upload_y; + vkCmdCopyBufferToImage(bd->TexCommandBuffer, upload_buffer, backend_tex->Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + VkImageMemoryBarrier use_barrier[1] = {}; + use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].image = backend_tex->Image; + use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + use_barrier[0].subresourceRange.levelCount = 1; + use_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(bd->TexCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, use_barrier); + } + + // End command buffer + { + VkSubmitInfo end_info = {}; + end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + end_info.commandBufferCount = 1; + end_info.pCommandBuffers = &bd->TexCommandBuffer; + err = vkEndCommandBuffer(bd->TexCommandBuffer); + check_vk_result(err); + err = vkQueueSubmit(v->Queue, 1, &end_info, VK_NULL_HANDLE); + check_vk_result(err); + } + + err = vkQueueWaitIdle(v->Queue); // FIXME-OPT: Suboptimal! check_vk_result(err); + vkDestroyBuffer(v->Device, upload_buffer, v->Allocator); + vkFreeMemory(v->Device, upload_buffer_memory, v->Allocator); + + tex->SetStatus(ImTextureStatus_OK); } - // Start command buffer - { - err = vkResetCommandPool(v->Device, bd->TexCommandPool, 0); - check_vk_result(err); - VkCommandBufferBeginInfo begin_info = {}; - begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - err = vkBeginCommandBuffer(bd->TexCommandBuffer, &begin_info); - check_vk_result(err); - } - - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - size_t upload_size = width * height * 4 * sizeof(char); - - // Create the Image: - ImGui_ImplVulkan_Texture* backend_tex = &bd->FontTexture; - { - VkImageCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - info.imageType = VK_IMAGE_TYPE_2D; - info.format = VK_FORMAT_R8G8B8A8_UNORM; - info.extent.width = width; - info.extent.height = height; - info.extent.depth = 1; - info.mipLevels = 1; - info.arrayLayers = 1; - info.samples = VK_SAMPLE_COUNT_1_BIT; - info.tiling = VK_IMAGE_TILING_OPTIMAL; - info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - err = vkCreateImage(v->Device, &info, v->Allocator, &backend_tex->Image); - check_vk_result(err); - VkMemoryRequirements req; - vkGetImageMemoryRequirements(v->Device, backend_tex->Image, &req); - VkMemoryAllocateInfo alloc_info = {}; - alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - alloc_info.allocationSize = IM_MAX(v->MinAllocationSize, req.size); - alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); - err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &backend_tex->Memory); - check_vk_result(err); - err = vkBindImageMemory(v->Device, backend_tex->Image, backend_tex->Memory, 0); - check_vk_result(err); - } - - // Create the Image View: - { - VkImageViewCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - info.image = backend_tex->Image; - info.viewType = VK_IMAGE_VIEW_TYPE_2D; - info.format = VK_FORMAT_R8G8B8A8_UNORM; - info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - info.subresourceRange.levelCount = 1; - info.subresourceRange.layerCount = 1; - err = vkCreateImageView(v->Device, &info, v->Allocator, &backend_tex->ImageView); - check_vk_result(err); - } - - // Create the Descriptor Set: - backend_tex->DescriptorSet = ImGui_ImplVulkan_AddTexture(bd->TexSampler, backend_tex->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - // Create the Upload Buffer: - VkDeviceMemory upload_buffer_memory; - VkBuffer upload_buffer; - { - VkBufferCreateInfo buffer_info = {}; - buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - buffer_info.size = upload_size; - buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &upload_buffer); - check_vk_result(err); - VkMemoryRequirements req; - vkGetBufferMemoryRequirements(v->Device, upload_buffer, &req); - bd->BufferMemoryAlignment = (bd->BufferMemoryAlignment > req.alignment) ? bd->BufferMemoryAlignment : req.alignment; - VkMemoryAllocateInfo alloc_info = {}; - alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - alloc_info.allocationSize = IM_MAX(v->MinAllocationSize, req.size); - alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); - err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &upload_buffer_memory); - check_vk_result(err); - err = vkBindBufferMemory(v->Device, upload_buffer, upload_buffer_memory, 0); - check_vk_result(err); - } - - // Upload to Buffer: - { - char* map = nullptr; - err = vkMapMemory(v->Device, upload_buffer_memory, 0, upload_size, 0, (void**)(&map)); - check_vk_result(err); - memcpy(map, pixels, upload_size); - VkMappedMemoryRange range[1] = {}; - range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; - range[0].memory = upload_buffer_memory; - range[0].size = upload_size; - err = vkFlushMappedMemoryRanges(v->Device, 1, range); - check_vk_result(err); - vkUnmapMemory(v->Device, upload_buffer_memory); - } - - // Copy to Image: - { - VkImageMemoryBarrier copy_barrier[1] = {}; - copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - copy_barrier[0].image = backend_tex->Image; - copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copy_barrier[0].subresourceRange.levelCount = 1; - copy_barrier[0].subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(bd->TexCommandBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, copy_barrier); - - VkBufferImageCopy region = {}; - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.layerCount = 1; - region.imageExtent.width = width; - region.imageExtent.height = height; - region.imageExtent.depth = 1; - vkCmdCopyBufferToImage(bd->TexCommandBuffer, upload_buffer, backend_tex->Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - - VkImageMemoryBarrier use_barrier[1] = {}; - use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - use_barrier[0].image = backend_tex->Image; - use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - use_barrier[0].subresourceRange.levelCount = 1; - use_barrier[0].subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(bd->TexCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, use_barrier); - } - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)backend_tex->DescriptorSet); - - // End command buffer - VkSubmitInfo end_info = {}; - end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - end_info.commandBufferCount = 1; - end_info.pCommandBuffers = &bd->TexCommandBuffer; - err = vkEndCommandBuffer(bd->TexCommandBuffer); - check_vk_result(err); - err = vkQueueSubmit(v->Queue, 1, &end_info, VK_NULL_HANDLE); - check_vk_result(err); - - err = vkQueueWaitIdle(v->Queue); - check_vk_result(err); - - vkDestroyBuffer(v->Device, upload_buffer, v->Allocator); - vkFreeMemory(v->Device, upload_buffer_memory, v->Allocator); - - return true; -} - -// You probably never need to call this, as it is called by ImGui_ImplVulkan_CreateFontsTexture() and ImGui_ImplVulkan_Shutdown(). -void ImGui_ImplVulkan_DestroyFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; - - ImGui_ImplVulkan_Texture* backend_tex = &bd->FontTexture; - - if (backend_tex->DescriptorSet) - { - ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); - backend_tex->DescriptorSet = VK_NULL_HANDLE; - io.Fonts->SetTexID(0); - } - if (backend_tex->ImageView) { vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); backend_tex->ImageView = VK_NULL_HANDLE; } - if (backend_tex->Image) { vkDestroyImage(v->Device, backend_tex->Image, v->Allocator); backend_tex->Image = VK_NULL_HANDLE; } - if (backend_tex->Memory) { vkFreeMemory(v->Device, backend_tex->Memory, v->Allocator); backend_tex->Memory = VK_NULL_HANDLE; } + if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames >= (int)bd->VulkanInitInfo.ImageCount) + ImGui_ImplVulkan_DestroyTexture(tex); } static void ImGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAllocationCallbacks* allocator) @@ -1066,6 +1076,26 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, v->RenderPass, v->MSAASamples, &bd->Pipeline, v->Subpass); + // Create command pool/buffer for texture upload + if (!bd->TexCommandPool) + { + VkCommandPoolCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + info.flags = 0; + info.queueFamilyIndex = v->QueueFamily; + err = vkCreateCommandPool(v->Device, &info, v->Allocator, &bd->TexCommandPool); + check_vk_result(err); + } + if (!bd->TexCommandBuffer) + { + VkCommandBufferAllocateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + info.commandPool = bd->TexCommandPool; + info.commandBufferCount = 1; + err = vkAllocateCommandBuffers(v->Device, &info, &bd->TexCommandBuffer); + check_vk_result(err); + } + return true; } @@ -1074,7 +1104,11 @@ void ImGui_ImplVulkan_DestroyDeviceObjects() ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; ImGui_ImplVulkan_DestroyWindowRenderBuffers(v->Device, &bd->MainWindowRenderBuffers, v->Allocator); - ImGui_ImplVulkan_DestroyFontsTexture(); + + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplVulkan_DestroyTexture(tex); if (bd->TexCommandBuffer) { vkFreeCommandBuffers(v->Device, bd->TexCommandPool, 1, &bd->TexCommandBuffer); bd->TexCommandBuffer = VK_NULL_HANDLE; } if (bd->TexCommandPool) { vkDestroyCommandPool(v->Device, bd->TexCommandPool, v->Allocator); bd->TexCommandPool = VK_NULL_HANDLE; } @@ -1175,6 +1209,7 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_vulkan"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. IM_ASSERT(info->Instance != VK_NULL_HANDLE); IM_ASSERT(info->PhysicalDevice != VK_NULL_HANDLE); @@ -1219,7 +1254,7 @@ void ImGui_ImplVulkan_Shutdown() io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } @@ -1227,9 +1262,7 @@ void ImGui_ImplVulkan_NewFrame() { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplVulkan_Init()?"); - - if (!bd->FontTexture.DescriptorSet) - ImGui_ImplVulkan_CreateFontsTexture(); + IM_UNUSED(bd); } void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index c3f40e138..4baa98335 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions. +// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. @@ -61,9 +62,8 @@ #define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING #endif -// Current version of the backend use 1 descriptor for the font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture(). -// It is expected that as early as Q1 2025 the backend will use a few more descriptors. Use this value + number of desired calls to ImGui_ImplVulkan_AddTexture(). -#define IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE (1) // Minimum per atlas +// Backend uses a small number of descriptors per font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture(). +#define IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE (8) // Minimum per atlas // Initialization data, for ImGui_ImplVulkan_Init() // [Please zero-clear before use!] @@ -112,10 +112,11 @@ IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown(); IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(); IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE); -IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture(); -IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontsTexture(); IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated) +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex); + // Register a texture (VkDescriptorSet == ImTextureID) // FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem // Please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions. From 9fa65cd190017e7a5a838a719a1b9fe05664f4a6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 5 Dec 2024 15:52:52 +0100 Subject: [PATCH 017/191] Backends: SDL_Renderer2: added ImGuiBackendFlags_RendererHasTextures support. --- backends/imgui_impl_sdlrenderer2.cpp | 102 ++++++++++++++++----------- backends/imgui_impl_sdlrenderer2.h | 10 +-- 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/backends/imgui_impl_sdlrenderer2.cpp b/backends/imgui_impl_sdlrenderer2.cpp index bfaeb9138..dbdeb5c11 100644 --- a/backends/imgui_impl_sdlrenderer2.cpp +++ b/backends/imgui_impl_sdlrenderer2.cpp @@ -10,8 +10,9 @@ // and it might be difficult to step out of those boundaries. // Implemented features: -// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -23,6 +24,7 @@ // - Introduction, links and more at the top of imgui.cpp // CHANGELOG +// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer2_CreateFontsTexture() and ImGui_ImplSDLRenderer2_DestroyFontsTexture(). // 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color. // 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer2_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks. // 2024-05-14: *BREAKING CHANGE* ImGui_ImplSDLRenderer3_RenderDrawData() requires SDL_Renderer* passed as parameter. @@ -54,7 +56,7 @@ struct ImGui_ImplSDLRenderer2_Data { SDL_Renderer* Renderer; // Main viewport's renderer - SDL_Texture* FontTexture; + ImGui_ImplSDLRenderer2_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -78,6 +80,7 @@ bool ImGui_ImplSDLRenderer2_Init(SDL_Renderer* renderer) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_sdlrenderer2"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. bd->Renderer = renderer; @@ -94,7 +97,7 @@ void ImGui_ImplSDLRenderer2_Shutdown() io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } @@ -110,9 +113,7 @@ void ImGui_ImplSDLRenderer2_NewFrame() { ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer2_Init()?"); - - if (!bd->FontTexture) - ImGui_ImplSDLRenderer2_CreateDeviceObjects(); + IM_UNUSED(bd); } void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer) @@ -133,6 +134,13 @@ void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* if (fb_width == 0 || fb_height == 0) return; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplSDLRenderer2_UpdateTexture(tex); + // Backup SDL_Renderer state that will be modified to restore it afterwards struct BackupSDLRendererState { @@ -218,55 +226,67 @@ void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* SDL_RenderSetClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr); } -// Called by Init/NewFrame/Shutdown -bool ImGui_ImplSDLRenderer2_CreateFontsTexture() +void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex) { - ImGuiIO& io = ImGui::GetIO(); ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData(); - // Build texture atlas - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. - - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) - bd->FontTexture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, width, height); - if (bd->FontTexture == nullptr) + if (tex->Status == ImTextureStatus_WantCreate) { - SDL_Log("error creating texture"); - return false; + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + + // Create texture + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height); + IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!"); + SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch()); + SDL_SetTextureBlendMode(sdl_texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureScaleMode(sdl_texture, SDL_ScaleModeLinear); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)sdl_texture); + tex->SetStatus(ImTextureStatus_OK); } - SDL_UpdateTexture(bd->FontTexture, nullptr, pixels, 4 * width); - SDL_SetTextureBlendMode(bd->FontTexture, SDL_BLENDMODE_BLEND); - SDL_SetTextureScaleMode(bd->FontTexture, SDL_ScaleModeLinear); - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); - - return true; -} - -void ImGui_ImplSDLRenderer2_DestroyFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData(); - if (bd->FontTexture) + else if (tex->Status == ImTextureStatus_WantUpdates) { - io.Fonts->SetTexID(0); - SDL_DestroyTexture(bd->FontTexture); - bd->FontTexture = nullptr; + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID; + for (ImTextureRect& r : tex->Updates) + { + SDL_Rect sdl_r = { r.x, r.y, r.w, r.h }; + SDL_UpdateTexture(sdl_texture, &sdl_r, tex->GetPixelsAt(r.x, r.y), tex->GetPitch()); + } + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy) + { + SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID; + if (sdl_texture == nullptr) + return; + SDL_DestroyTexture(sdl_texture); + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); } } -bool ImGui_ImplSDLRenderer2_CreateDeviceObjects() +void ImGui_ImplSDLRenderer2_CreateDeviceObjects() { - return ImGui_ImplSDLRenderer2_CreateFontsTexture(); } void ImGui_ImplSDLRenderer2_DestroyDeviceObjects() { - ImGui_ImplSDLRenderer2_DestroyFontsTexture(); + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + { + tex->SetStatus(ImTextureStatus_WantDestroy); + ImGui_ImplSDLRenderer2_UpdateTexture(tex); + } } //----------------------------------------------------------------------------- diff --git a/backends/imgui_impl_sdlrenderer2.h b/backends/imgui_impl_sdlrenderer2.h index a0337d314..cf127c1f7 100644 --- a/backends/imgui_impl_sdlrenderer2.h +++ b/backends/imgui_impl_sdlrenderer2.h @@ -10,8 +10,9 @@ // and it might be difficult to step out of those boundaries. // Implemented features: -// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -35,11 +36,12 @@ IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_NewFrame(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer); // Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer2_CreateFontsTexture(); -IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_DestroyFontsTexture(); -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer2_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_DestroyDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex); + // [BETA] Selected render state data shared with callbacks. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplSDLRenderer2_RenderDrawData() call. // (Please open an issue if you feel you need access to more data) From e538883a20bd5896f33f1ee2717b171a593347d3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 5 Dec 2024 15:57:32 +0100 Subject: [PATCH 018/191] Backends: SDL_Renderer3: added ImGuiBackendFlags_RendererHasTextures support. --- backends/imgui_impl_sdlrenderer3.cpp | 101 ++++++++++++++++----------- backends/imgui_impl_sdlrenderer3.h | 10 +-- 2 files changed, 66 insertions(+), 45 deletions(-) diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index 42fefa0cd..ef6f0441c 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -10,8 +10,9 @@ // and it might be difficult to step out of those boundaries. // Implemented features: -// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -23,6 +24,7 @@ // - Introduction, links and more at the top of imgui.cpp // CHANGELOG +// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer3_CreateFontsTexture() and ImGui_ImplSDLRenderer3_DestroyFontsTexture(). // 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color. // 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer3_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks. // 2024-07-01: Update for SDL3 api changes: SDL_RenderGeometryRaw() uint32 version was removed (SDL#9009). @@ -51,7 +53,6 @@ struct ImGui_ImplSDLRenderer3_Data { SDL_Renderer* Renderer; // Main viewport's renderer - SDL_Texture* FontTexture; ImVector ColorBuffer; ImGui_ImplSDLRenderer3_Data() { memset((void*)this, 0, sizeof(*this)); } @@ -77,6 +78,7 @@ bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_sdlrenderer3"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. bd->Renderer = renderer; @@ -93,7 +95,7 @@ void ImGui_ImplSDLRenderer3_Shutdown() io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } @@ -109,9 +111,7 @@ void ImGui_ImplSDLRenderer3_NewFrame() { ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer3_Init()?"); - - if (!bd->FontTexture) - ImGui_ImplSDLRenderer3_CreateDeviceObjects(); + IM_UNUSED(bd); } // https://github.com/libsdl-org/SDL/issues/9009 @@ -152,6 +152,13 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* if (fb_width == 0 || fb_height == 0) return; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplSDLRenderer3_UpdateTexture(tex); + // Backup SDL_Renderer state that will be modified to restore it afterwards struct BackupSDLRendererState { @@ -235,55 +242,67 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* SDL_SetRenderClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr); } -// Called by Init/NewFrame/Shutdown -bool ImGui_ImplSDLRenderer3_CreateFontsTexture() +void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) { - ImGuiIO& io = ImGui::GetIO(); ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); - // Build texture atlas - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. - - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) - bd->FontTexture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, width, height); - if (bd->FontTexture == nullptr) + if (tex->Status == ImTextureStatus_WantCreate) { - SDL_Log("error creating texture"); - return false; + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + + // Create texture + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height); + IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!"); + SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch()); + SDL_SetTextureBlendMode(sdl_texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_LINEAR); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)sdl_texture); + tex->SetStatus(ImTextureStatus_OK); } - SDL_UpdateTexture(bd->FontTexture, nullptr, pixels, 4 * width); - SDL_SetTextureBlendMode(bd->FontTexture, SDL_BLENDMODE_BLEND); - SDL_SetTextureScaleMode(bd->FontTexture, SDL_SCALEMODE_LINEAR); - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); - - return true; -} - -void ImGui_ImplSDLRenderer3_DestroyFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); - if (bd->FontTexture) + else if (tex->Status == ImTextureStatus_WantUpdates) { - io.Fonts->SetTexID(0); - SDL_DestroyTexture(bd->FontTexture); - bd->FontTexture = nullptr; + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID; + for (ImTextureRect& r : tex->Updates) + { + SDL_Rect sdl_r = { r.x, r.y, r.w, r.h }; + SDL_UpdateTexture(sdl_texture, &sdl_r, tex->GetPixelsAt(r.x, r.y), tex->GetPitch()); + } + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy) + { + SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID; + if (sdl_texture == nullptr) + return; + SDL_DestroyTexture(sdl_texture); + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); } } -bool ImGui_ImplSDLRenderer3_CreateDeviceObjects() +void ImGui_ImplSDLRenderer3_CreateDeviceObjects() { - return ImGui_ImplSDLRenderer3_CreateFontsTexture(); } void ImGui_ImplSDLRenderer3_DestroyDeviceObjects() { - ImGui_ImplSDLRenderer3_DestroyFontsTexture(); + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + { + tex->SetStatus(ImTextureStatus_WantDestroy); + ImGui_ImplSDLRenderer3_UpdateTexture(tex); + } } //----------------------------------------------------------------------------- diff --git a/backends/imgui_impl_sdlrenderer3.h b/backends/imgui_impl_sdlrenderer3.h index 3473bcc77..170560fdc 100644 --- a/backends/imgui_impl_sdlrenderer3.h +++ b/backends/imgui_impl_sdlrenderer3.h @@ -10,8 +10,9 @@ // and it might be difficult to step out of those boundaries. // Implemented features: -// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -35,11 +36,12 @@ IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_NewFrame(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer); // Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_CreateFontsTexture(); -IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyFontsTexture(); -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex); + // [BETA] Selected render state data shared with callbacks. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplSDLRenderer3_RenderDrawData() call. // (Please open an issue if you feel you need access to more data) From 16fe666e364785c0a9b534ea18aa5fa62ab06a36 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 16 Jan 2025 12:32:56 +0100 Subject: [PATCH 019/191] Backends: SDLGPU3: added ImGuiBackendFlags_RendererHasTextures support. # Conflicts: # backends/imgui_impl_sdlgpu3.cpp # backends/imgui_impl_sdlgpu3.h --- backends/imgui_impl_sdlgpu3.cpp | 203 +++++++++++++++++++------------- backends/imgui_impl_sdlgpu3.h | 9 +- 2 files changed, 128 insertions(+), 84 deletions(-) diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 4bcc5bdfc..92515602b 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/backends/imgui_impl_sdlgpu3.cpp @@ -3,7 +3,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use simply cast a reference to your SDL_GPUTextureSamplerBinding to ImTextureID. -// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ @@ -21,6 +22,7 @@ // Calling the function is MANDATORY, otherwise the ImGui will not upload neither the vertex nor the index buffer for the GPU. See imgui_impl_sdlgpu3.cpp for more info. // CHANGELOG +// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLGPU3_CreateFontsTexture() and ImGui_ImplSDLGPU3_DestroyFontsTexture(). // 2025-04-28: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2025-03-30: Made ImGui_ImplSDLGPU3_PrepareDrawData() reuse GPU Transfer Buffers which were unusually slow to recreate every frame. Much faster now. // 2025-03-21: Fixed typo in function name Imgui_ImplSDLGPU3_PrepareDrawData() -> ImGui_ImplSDLGPU3_PrepareDrawData(). @@ -33,6 +35,11 @@ #include "imgui_impl_sdlgpu3_shaders.h" // SDL_GPU Data +struct ImGui_ImplSDLGPU3_Texture +{ + SDL_GPUTexture* Texture = nullptr; + SDL_GPUTextureSamplerBinding TextureSamplerBinding = { nullptr, nullptr }; +}; // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplSDLGPU3_RenderDrawData() struct ImGui_ImplSDLGPU3_FrameData @@ -50,14 +57,12 @@ struct ImGui_ImplSDLGPU3_Data ImGui_ImplSDLGPU3_InitInfo InitInfo; // Graphics pipeline & shaders - SDL_GPUShader* VertexShader = nullptr; - SDL_GPUShader* FragmentShader = nullptr; - SDL_GPUGraphicsPipeline* Pipeline = nullptr; - - // Font data - SDL_GPUSampler* FontSampler = nullptr; - SDL_GPUTexture* FontTexture = nullptr; - SDL_GPUTextureSamplerBinding FontBinding = { nullptr, nullptr }; + SDL_GPUShader* VertexShader = nullptr; + SDL_GPUShader* FragmentShader = nullptr; + SDL_GPUGraphicsPipeline* Pipeline = nullptr; + SDL_GPUSampler* TexSampler = nullptr; + SDL_GPUTransferBuffer* TexTransferBuffer = nullptr; + uint32_t TexTransferBufferSize = 0; // Frame data for main window ImGui_ImplSDLGPU3_FrameData MainWindowFrameData; @@ -154,6 +159,13 @@ void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuff if (fb_width <= 0 || fb_height <= 0 || draw_data->TotalVtxCount <= 0) return; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplSDLGPU3_UpdateTexture(tex); + ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_FrameData* fd = &bd->MainWindowFrameData; @@ -281,91 +293,117 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe SDL_SetGPUScissor(render_pass, &scissor_rect); } -void ImGui_ImplSDLGPU3_CreateFontsTexture() +static void ImGui_ImplSDLGPU3_DestroyTexture(ImTextureData* tex) +{ + ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); + ImGui_ImplSDLGPU3_Texture* backend_tex = (ImGui_ImplSDLGPU3_Texture*)tex->BackendUserData; + if (backend_tex == nullptr) + return; + SDL_GPUTextureSamplerBinding* binding = (SDL_GPUTextureSamplerBinding*)(intptr_t)tex->BackendUserData; + IM_ASSERT(backend_tex->Texture == binding->texture); + SDL_ReleaseGPUTexture(bd->InitInfo.Device, backend_tex->Texture); + IM_DELETE(backend_tex); + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + tex->BackendUserData = nullptr; +} + +void ImGui_ImplSDLGPU3_UpdateTexture(ImTextureData* tex) { - ImGuiIO& io = ImGui::GetIO(); ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; - // Destroy existing texture (if any) - if (bd->FontTexture) + if (tex->Status == ImTextureStatus_WantCreate) { - SDL_WaitForGPUIdle(v->Device); - ImGui_ImplSDLGPU3_DestroyFontsTexture(); - } + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + ImGui_ImplSDLGPU3_Texture* backend_tex = IM_NEW(ImGui_ImplSDLGPU3_Texture)(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - uint32_t upload_size = width * height * 4 * sizeof(char); - - // Create the Image: - { + // Create texture SDL_GPUTextureCreateInfo texture_info = {}; - texture_info.type = SDL_GPU_TEXTURETYPE_2D; + texture_info.type = SDL_GPU_TEXTURETYPE_2D; texture_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; texture_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; - texture_info.width = width; - texture_info.height = height; + texture_info.width = tex->Width; + texture_info.height = tex->Height; texture_info.layer_count_or_depth = 1; texture_info.num_levels = 1; texture_info.sample_count = SDL_GPU_SAMPLECOUNT_1; - bd->FontTexture = SDL_CreateGPUTexture(v->Device, &texture_info); - IM_ASSERT(bd->FontTexture && "Failed to create font texture, call SDL_GetError() for more info"); + backend_tex->Texture = SDL_CreateGPUTexture(v->Device, &texture_info); + backend_tex->TextureSamplerBinding.texture = backend_tex->Texture; + backend_tex->TextureSamplerBinding.sampler = bd->TexSampler; + IM_ASSERT(backend_tex->Texture && "Failed to create font texture, call SDL_GetError() for more info"); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)&backend_tex->TextureSamplerBinding); + tex->BackendUserData = backend_tex; } - // Assign the texture to the TextureSamplerBinding - bd->FontBinding.texture = bd->FontTexture; - - // Create all the upload structures and upload: + if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) { - SDL_GPUTransferBufferCreateInfo transferbuffer_info = {}; - transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; - transferbuffer_info.size = upload_size; + ImGui_ImplSDLGPU3_Texture* backend_tex = (ImGui_ImplSDLGPU3_Texture*)tex->BackendUserData; + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); - SDL_GPUTransferBuffer* transferbuffer = SDL_CreateGPUTransferBuffer(v->Device, &transferbuffer_info); - IM_ASSERT(transferbuffer != nullptr && "Failed to create font transfer buffer, call SDL_GetError() for more information"); + // Update full texture or selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions. + // We could use the smaller rect on _WantCreate but using the full rect allows us to clear the texture. + const int upload_x = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.x; + const int upload_y = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.y; + const int upload_w = (tex->Status == ImTextureStatus_WantCreate) ? tex->Width : tex->UpdateRect.w; + const int upload_h = (tex->Status == ImTextureStatus_WantCreate) ? tex->Height : tex->UpdateRect.h; + uint32_t upload_pitch = upload_w * tex->BytesPerPixel; + uint32_t upload_size = upload_w * upload_h * tex->BytesPerPixel; - void* texture_ptr = SDL_MapGPUTransferBuffer(v->Device, transferbuffer, false); - memcpy(texture_ptr, pixels, upload_size); - SDL_UnmapGPUTransferBuffer(v->Device, transferbuffer); + // Create transfer buffer + if (bd->TexTransferBufferSize < upload_size) + { + SDL_ReleaseGPUTransferBuffer(v->Device, bd->TexTransferBuffer); + SDL_GPUTransferBufferCreateInfo transferbuffer_info = {}; + transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + transferbuffer_info.size = upload_size + 1024; + bd->TexTransferBufferSize = upload_size + 1024; + bd->TexTransferBuffer = SDL_CreateGPUTransferBuffer(v->Device, &transferbuffer_info); + IM_ASSERT(bd->TexTransferBuffer != nullptr && "Failed to create font transfer buffer, call SDL_GetError() for more information"); + } + + // Copy to transfer buffer + { + void* texture_ptr = SDL_MapGPUTransferBuffer(v->Device, bd->TexTransferBuffer, false); + for (int y = 0; y < upload_h; y++) + memcpy((void*)((uintptr_t)texture_ptr + y * upload_pitch), tex->GetPixelsAt(upload_x, upload_y + y), upload_pitch); + SDL_UnmapGPUTransferBuffer(v->Device, bd->TexTransferBuffer); + } SDL_GPUTextureTransferInfo transfer_info = {}; transfer_info.offset = 0; - transfer_info.transfer_buffer = transferbuffer; + transfer_info.transfer_buffer = bd->TexTransferBuffer; SDL_GPUTextureRegion texture_region = {}; - texture_region.texture = bd->FontTexture; - texture_region.w = width; - texture_region.h = height; + texture_region.texture = backend_tex->Texture; + texture_region.x = (Uint32)upload_x; + texture_region.y = (Uint32)upload_y; + texture_region.w = (Uint32)upload_w; + texture_region.h = (Uint32)upload_h; texture_region.d = 1; - SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(v->Device); - SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd); - SDL_UploadToGPUTexture(copy_pass, &transfer_info, &texture_region, false); - SDL_EndGPUCopyPass(copy_pass); - SDL_SubmitGPUCommandBuffer(cmd); - SDL_ReleaseGPUTransferBuffer(v->Device, transferbuffer); - } + // Upload + { + SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(v->Device); + SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd); + SDL_UploadToGPUTexture(copy_pass, &transfer_info, &texture_region, false); + SDL_EndGPUCopyPass(copy_pass); + SDL_SubmitGPUCommandBuffer(cmd); + } - // Store our identifier - io.Fonts->SetTexID((ImTextureID)&bd->FontBinding); -} - -// You probably never need to call this, as it is called by ImGui_ImplSDLGPU3_CreateFontsTexture() and ImGui_ImplSDLGPU3_Shutdown(). -void ImGui_ImplSDLGPU3_DestroyFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); - ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; - if (bd->FontTexture) - { - SDL_ReleaseGPUTexture(v->Device, bd->FontTexture); - bd->FontBinding.texture = nullptr; - bd->FontTexture = nullptr; + tex->SetStatus(ImTextureStatus_OK); } - io.Fonts->SetTexID(0); + if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + ImGui_ImplSDLGPU3_DestroyTexture(tex); } static void ImGui_ImplSDLGPU3_CreateShaders() @@ -517,7 +555,9 @@ void ImGui_ImplSDLGPU3_CreateDeviceObjects() ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; - if (!bd->FontSampler) + ImGui_ImplSDLGPU3_DestroyDeviceObjects(); + + if (bd->TexSampler == nullptr) { // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. SDL_GPUSamplerCreateInfo sampler_info = {}; @@ -534,13 +574,11 @@ void ImGui_ImplSDLGPU3_CreateDeviceObjects() sampler_info.max_anisotropy = 1.0f; sampler_info.enable_compare = false; - bd->FontSampler = SDL_CreateGPUSampler(v->Device, &sampler_info); - bd->FontBinding.sampler = bd->FontSampler; - IM_ASSERT(bd->FontSampler != nullptr && "Failed to create font sampler, call SDL_GetError() for more information"); + bd->TexSampler = SDL_CreateGPUSampler(v->Device, &sampler_info); + IM_ASSERT(bd->TexSampler != nullptr && "Failed to create font sampler, call SDL_GetError() for more information"); } ImGui_ImplSDLGPU3_CreateGraphicsPipeline(); - ImGui_ImplSDLGPU3_CreateFontsTexture(); } void ImGui_ImplSDLGPU3_DestroyFrameData() @@ -564,12 +602,16 @@ void ImGui_ImplSDLGPU3_DestroyDeviceObjects() ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_DestroyFrameData(); - ImGui_ImplSDLGPU3_DestroyFontsTexture(); - if (bd->VertexShader) { SDL_ReleaseGPUShader(v->Device, bd->VertexShader); bd->VertexShader = nullptr;} - if (bd->FragmentShader) { SDL_ReleaseGPUShader(v->Device, bd->FragmentShader); bd->FragmentShader = nullptr;} - if (bd->FontSampler) { SDL_ReleaseGPUSampler(v->Device, bd->FontSampler); bd->FontSampler = nullptr;} - if (bd->Pipeline) { SDL_ReleaseGPUGraphicsPipeline(v->Device, bd->Pipeline); bd->Pipeline = nullptr;} + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplSDLGPU3_DestroyTexture(tex); + if (bd->TexTransferBuffer) { SDL_ReleaseGPUTransferBuffer(v->Device, bd->TexTransferBuffer); bd->TexTransferBuffer = nullptr; } + if (bd->VertexShader) { SDL_ReleaseGPUShader(v->Device, bd->VertexShader); bd->VertexShader = nullptr; } + if (bd->FragmentShader) { SDL_ReleaseGPUShader(v->Device, bd->FragmentShader); bd->FragmentShader = nullptr; } + if (bd->TexSampler) { SDL_ReleaseGPUSampler(v->Device, bd->TexSampler); bd->TexSampler = nullptr; } + if (bd->Pipeline) { SDL_ReleaseGPUGraphicsPipeline(v->Device, bd->Pipeline); bd->Pipeline = nullptr; } } bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info) @@ -583,6 +625,7 @@ bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_sdlgpu3"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. IM_ASSERT(info->Device != nullptr); IM_ASSERT(info->ColorTargetFormat != SDL_GPU_TEXTUREFORMAT_INVALID); @@ -601,7 +644,7 @@ void ImGui_ImplSDLGPU3_Shutdown() ImGui_ImplSDLGPU3_DestroyDeviceObjects(); io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } @@ -610,10 +653,8 @@ void ImGui_ImplSDLGPU3_NewFrame() ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLGPU3_Init()?"); - if (!bd->FontSampler) + if (!bd->TexSampler) ImGui_ImplSDLGPU3_CreateDeviceObjects(); - if (!bd->FontTexture) - ImGui_ImplSDLGPU3_CreateFontsTexture(); } #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdlgpu3.h b/backends/imgui_impl_sdlgpu3.h index c6d5a8e5d..380855b29 100644 --- a/backends/imgui_impl_sdlgpu3.h +++ b/backends/imgui_impl_sdlgpu3.h @@ -3,7 +3,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use simply cast a reference to your SDL_GPUTextureSamplerBinding to ImTextureID. -// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ @@ -41,9 +42,11 @@ IMGUI_IMPL_API void ImGui_ImplSDLGPU3_NewFrame(); IMGUI_IMPL_API void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer); IMGUI_IMPL_API void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, SDL_GPUGraphicsPipeline* pipeline = nullptr); +// Use if you want to reset your rendering device without losing Dear ImGui state. IMGUI_IMPL_API void ImGui_ImplSDLGPU3_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplSDLGPU3_DestroyDeviceObjects(); -IMGUI_IMPL_API void ImGui_ImplSDLGPU3_CreateFontsTexture(); -IMGUI_IMPL_API void ImGui_ImplSDLGPU3_DestroyFontsTexture(); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplSDLGPU3_UpdateTexture(ImTextureData* tex); #endif // #ifndef IMGUI_DISABLE From ee8941e0de53fb745e7508e71ec5a00ddf2ec3de Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jan 2025 10:38:11 +0100 Subject: [PATCH 020/191] Backends: Allegro5: added ImGuiBackendFlags_RendererHasTextures support. --- backends/imgui_impl_allegro5.cpp | 135 +++++++++++++++++++------------ backends/imgui_impl_allegro5.h | 6 +- 2 files changed, 90 insertions(+), 51 deletions(-) diff --git a/backends/imgui_impl_allegro5.cpp b/backends/imgui_impl_allegro5.cpp index 9db3e5198..b5f8ca00a 100644 --- a/backends/imgui_impl_allegro5.cpp +++ b/backends/imgui_impl_allegro5.cpp @@ -2,7 +2,8 @@ // (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.) // Implemented features: -// [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Clipboard support (from Allegro 5.1.12). // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. @@ -20,6 +21,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLGPU3_CreateFontsTexture() and ImGui_ImplSDLGPU3_DestroyFontsTexture(). // 2025-02-18: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursor support. // 2025-01-06: Avoid calling al_set_mouse_cursor() repeatedly since it appears to leak on on X11 (#8256). // 2024-08-22: moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO: @@ -137,6 +139,13 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) return; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplAllegro5_UpdateTexture(tex); + // Backup Allegro state that will be modified ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); ALLEGRO_TRANSFORM last_transform = *al_get_current_transform(); @@ -232,43 +241,7 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) bool ImGui_ImplAllegro5_CreateDeviceObjects() { - // Build texture atlas ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); - ImGuiIO& io = ImGui::GetIO(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - // Create texture - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) - int flags = al_get_new_bitmap_flags(); - int fmt = al_get_new_bitmap_format(); - al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP | ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR); - al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE); - ALLEGRO_BITMAP* img = al_create_bitmap(width, height); - al_set_new_bitmap_flags(flags); - al_set_new_bitmap_format(fmt); - if (!img) - return false; - - ALLEGRO_LOCKED_REGION* locked_img = al_lock_bitmap(img, al_get_bitmap_format(img), ALLEGRO_LOCK_WRITEONLY); - if (!locked_img) - { - al_destroy_bitmap(img); - return false; - } - memcpy(locked_img->data, pixels, sizeof(int) * width * height); - al_unlock_bitmap(img); - - // Convert software texture to hardware texture. - ALLEGRO_BITMAP* cloned_img = al_clone_bitmap(img); - al_destroy_bitmap(img); - if (!cloned_img) - return false; - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)cloned_img); - bd->Texture = cloned_img; // Create an invisible mouse cursor // Because al_hide_mouse_cursor() seems to mess up with the actual inputs.. @@ -279,16 +252,81 @@ bool ImGui_ImplAllegro5_CreateDeviceObjects() return true; } +void ImGui_ImplAllegro5_UpdateTexture(ImTextureData* tex) +{ + if (tex->Status == ImTextureStatus_WantCreate) + { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + + // Create texture + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + const int new_bitmap_flags = al_get_new_bitmap_flags(); + int new_bitmap_format = al_get_new_bitmap_format(); + al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP | ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR); + al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE); + ALLEGRO_BITMAP* cpu_bitmap = al_create_bitmap(tex->Width, tex->Height); + al_set_new_bitmap_flags(new_bitmap_flags); + al_set_new_bitmap_format(new_bitmap_format); + IM_ASSERT(cpu_bitmap != nullptr && "Backend failed to create texture!"); + + // Upload pixels + ALLEGRO_LOCKED_REGION* locked_region = al_lock_bitmap(cpu_bitmap, al_get_bitmap_format(cpu_bitmap), ALLEGRO_LOCK_WRITEONLY); + IM_ASSERT(locked_region != nullptr && "Backend failed to create texture!"); + memcpy(locked_region->data, tex->GetPixels(), tex->GetSizeInBytes()); + al_unlock_bitmap(cpu_bitmap); + + // Convert software texture to hardware texture. + ALLEGRO_BITMAP* gpu_bitmap = al_clone_bitmap(cpu_bitmap); + al_destroy_bitmap(cpu_bitmap); + IM_ASSERT(gpu_bitmap != nullptr && "Backend failed to create texture!"); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)gpu_bitmap); + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + ImTextureRect r_bb = tex->UpdateRect; // Bounding box encompassing all individual updates + ALLEGRO_BITMAP* gpu_bitmap = (ALLEGRO_BITMAP*)(intptr_t)tex->TexID; + ALLEGRO_LOCKED_REGION* locked_region = al_lock_bitmap_region(gpu_bitmap, r_bb.x, r_bb.y, r_bb.w, r_bb.h, al_get_bitmap_format(gpu_bitmap), ALLEGRO_LOCK_WRITEONLY); + IM_ASSERT(locked_region && "Backend failed to update texture!"); + for (ImTextureRect& r : tex->Updates) + for (int y = 0; y < r.h; y++) + memcpy((unsigned char*)locked_region->data + locked_region->pitch * (r.y - r_bb.y + y) + (r.x - r_bb.x) * tex->BytesPerPixel, // dst + tex->GetPixelsAt(r.x, r.y + y), r.w * tex->BytesPerPixel); // src, block pitch + al_unlock_bitmap(gpu_bitmap); + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy) + { + ALLEGRO_BITMAP* backend_tex = (ALLEGRO_BITMAP*)(intptr_t)tex->TexID; + if (backend_tex) + al_destroy_bitmap(backend_tex); + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + } +} + void ImGui_ImplAllegro5_InvalidateDeviceObjects() { - ImGuiIO& io = ImGui::GetIO(); ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); - if (bd->Texture) - { - io.Fonts->SetTexID(0); - al_destroy_bitmap(bd->Texture); - bd->Texture = nullptr; - } + + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + { + tex->SetStatus(ImTextureStatus_WantDestroy); + ImGui_ImplAllegro5_UpdateTexture(tex); + } + + // Destroy mouse cursor if (bd->MouseCursorInvisible) { al_destroy_mouse_cursor(bd->MouseCursorInvisible); @@ -439,6 +477,7 @@ bool ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display) io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = io.BackendRendererName = "imgui_impl_allegro5"; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. bd->Display = display; bd->LastCursor = ALLEGRO_SYSTEM_MOUSE_CURSOR_NONE; @@ -478,7 +517,7 @@ void ImGui_ImplAllegro5_Shutdown() io.BackendPlatformName = io.BackendRendererName = nullptr; io.BackendPlatformUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; + io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } @@ -608,12 +647,8 @@ void ImGui_ImplAllegro5_NewFrame() ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplAllegro5_Init()?"); - if (!bd->Texture) - ImGui_ImplAllegro5_CreateDeviceObjects(); - - ImGuiIO& io = ImGui::GetIO(); - // Setup display size (every frame to accommodate for window resizing) + ImGuiIO& io = ImGui::GetIO(); int w, h; w = al_get_display_width(bd->Display); h = al_get_display_height(bd->Display); diff --git a/backends/imgui_impl_allegro5.h b/backends/imgui_impl_allegro5.h index 356ec7d0a..421bbf129 100644 --- a/backends/imgui_impl_allegro5.h +++ b/backends/imgui_impl_allegro5.h @@ -2,7 +2,8 @@ // (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.) // Implemented features: -// [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Clipboard support (from Allegro 5.1.12). // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. @@ -36,4 +37,7 @@ IMGUI_IMPL_API bool ImGui_ImplAllegro5_ProcessEvent(ALLEGRO_EVENT* event); IMGUI_IMPL_API bool ImGui_ImplAllegro5_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplAllegro5_InvalidateDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplAllegro5_UpdateTexture(ImTextureData* tex); + #endif // #ifndef IMGUI_DISABLE From 26c017d5ea73a7b52271c26438c9dd15b2b46c81 Mon Sep 17 00:00:00 2001 From: thedmd Date: Tue, 17 Dec 2024 00:42:57 +0100 Subject: [PATCH 021/191] Backends: Metal: added ImGuiBackendFlags_RendererHasTextures support. # Conflicts: # backends/imgui_impl_metal.h # backends/imgui_impl_metal.mm --- backends/imgui_impl_metal.h | 13 ++- backends/imgui_impl_metal.mm | 173 +++++++++++++++++++++++------------ 2 files changed, 122 insertions(+), 64 deletions(-) diff --git a/backends/imgui_impl_metal.h b/backends/imgui_impl_metal.h index d46998269..2a9a26a02 100644 --- a/backends/imgui_impl_metal.h +++ b/backends/imgui_impl_metal.h @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. OSX) // Implemented features: -// [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -35,11 +36,12 @@ IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id commandEncoder); // Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(id device); -IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture(); IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id device); IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); + #endif //----------------------------------------------------------------------------- @@ -62,11 +64,12 @@ IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, MTL::RenderCommandEncoder* commandEncoder); // Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device); -IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture(); IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device); IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); + #endif #endif diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 1cd2308d1..b1decb2bb 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -2,8 +2,9 @@ // This needs to be used along with a Platform Backend (e.g. OSX) // Implemented features: -// [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -15,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplMetal_CreateFontsTexture() and ImGui_ImplMetal_DestroyFontsTexture(). // 2025-02-03: Metal: Crash fix. (#8367) // 2024-01-08: Metal: Fixed memory leaks when using metal-cpp (#8276, #8166) or when using multiple contexts (#7419). // 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'. @@ -59,6 +61,11 @@ - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; @end +@interface MetalTexture : NSObject +@property (nonatomic, strong) id metalTexture; +- (instancetype)initWithTexture:(id)metalTexture; +@end + // A singleton that stores long-lived objects that are needed by the Metal // renderer backend. Stores the render pipeline state cache and the default // font texture, and manages the reusable buffer cache. @@ -67,7 +74,6 @@ @property (nonatomic, strong) id depthStencilState; @property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient @property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors -@property (nonatomic, strong, nullable) id fontTexture; @property (nonatomic, strong) NSMutableArray* bufferCache; @property (nonatomic, assign) double lastBufferCachePurge; - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; @@ -110,11 +116,6 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, } -bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device) -{ - return ImGui_ImplMetal_CreateFontsTexture((__bridge id)(device)); -} - bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) { return ImGui_ImplMetal_CreateDeviceObjects((__bridge id)(device)); @@ -134,6 +135,7 @@ bool ImGui_ImplMetal_Init(id device) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_metal"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. bd->SharedMetalContext = [[MetalContext alloc] init]; bd->SharedMetalContext.device = device; @@ -152,7 +154,7 @@ void ImGui_ImplMetal_Shutdown() ImGuiIO& io = ImGui::GetIO(); io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); } void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) @@ -168,7 +170,7 @@ void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); } -static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, id commandBuffer, +static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id commandBuffer, id commandEncoder, id renderPipelineState, MetalBuffer* vertexBuffer, size_t vertexBufferOffset) { @@ -184,17 +186,17 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, idDisplaySize.x * drawData->FramebufferScale.x), - .height = (double)(drawData->DisplaySize.y * drawData->FramebufferScale.y), + .width = (double)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x), + .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), .znear = 0.0, .zfar = 1.0 }; [commandEncoder setViewport:viewport]; - float L = drawData->DisplayPos.x; - float R = drawData->DisplayPos.x + drawData->DisplaySize.x; - float T = drawData->DisplayPos.y; - float B = drawData->DisplayPos.y + drawData->DisplaySize.y; + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; float N = (float)viewport.znear; float F = (float)viewport.zfar; const float ortho_projection[4][4] = @@ -213,17 +215,24 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, id commandBuffer, id commandEncoder) +void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); MetalContext* ctx = bd->SharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(drawData->DisplaySize.x * drawData->FramebufferScale.x); - int fb_height = (int)(drawData->DisplaySize.y * drawData->FramebufferScale.y); - if (fb_width <= 0 || fb_height <= 0 || drawData->CmdListsCount == 0) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdListsCount == 0) return; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplMetal_UpdateTexture(tex); + // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame // The hit rate for this cache should be very near 100%. id renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor]; @@ -236,23 +245,23 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id c ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState; } - size_t vertexBufferLength = (size_t)drawData->TotalVtxCount * sizeof(ImDrawVert); - size_t indexBufferLength = (size_t)drawData->TotalIdxCount * sizeof(ImDrawIdx); + size_t vertexBufferLength = (size_t)draw_data->TotalVtxCount * sizeof(ImDrawVert); + size_t indexBufferLength = (size_t)draw_data->TotalIdxCount * sizeof(ImDrawIdx); MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device]; MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; - ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); + ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); // Will project scissor/clipping rectangles into framebuffer space - ImVec2 clip_off = drawData->DisplayPos; // (0,0) unless using multi-viewports - ImVec2 clip_scale = drawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) // Render command lists size_t vertexBufferOffset = 0; size_t indexBufferOffset = 0; - for (int n = 0; n < drawData->CmdListsCount; n++) + for (int n = 0; n < draw_data->CmdListsCount; n++) { - const ImDrawList* draw_list = drawData->CmdLists[n]; + const ImDrawList* draw_list = draw_data->CmdLists[n]; memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); @@ -265,7 +274,7 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id c // User callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); + ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); else pcmd->UserCallback(draw_list, pcmd); } @@ -325,42 +334,71 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id c }]; } -bool ImGui_ImplMetal_CreateFontsTexture(id device) +static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - ImGuiIO& io = ImGui::GetIO(); + MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData); + if (backend_tex == nullptr) + return; + IM_ASSERT(backend_tex.metalTexture == (__bridge id)(void*)(intptr_t)tex->TexID); + backend_tex.metalTexture = nil; - // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here. - // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth. - // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures. - // You can make that change in your implementation. - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm - width:(NSUInteger)width - height:(NSUInteger)height - mipmapped:NO]; - textureDescriptor.usage = MTLTextureUsageShaderRead; -#if TARGET_OS_OSX || TARGET_OS_MACCATALYST - textureDescriptor.storageMode = MTLStorageModeManaged; -#else - textureDescriptor.storageMode = MTLStorageModeShared; -#endif - id texture = [device newTextureWithDescriptor:textureDescriptor]; - [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)width, (NSUInteger)height) mipmapLevel:0 withBytes:pixels bytesPerRow:(NSUInteger)width * 4]; - bd->SharedMetalContext.fontTexture = texture; - io.Fonts->SetTexID((ImTextureID)(intptr_t)(__bridge void*)bd->SharedMetalContext.fontTexture); // ImTextureID == ImU64 - - return (bd->SharedMetalContext.fontTexture != nil); + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + tex->BackendUserData = nullptr; } -void ImGui_ImplMetal_DestroyFontsTexture() +void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - ImGuiIO& io = ImGui::GetIO(); - bd->SharedMetalContext.fontTexture = nil; - io.Fonts->SetTexID(0); + if (tex->Status == ImTextureStatus_WantCreate) + { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + + // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here. + // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth. + // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures. + // You can make that change in your implementation. + MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm + width:(NSUInteger)tex->Width + height:(NSUInteger)tex->Height + mipmapped:NO]; + textureDescriptor.usage = MTLTextureUsageShaderRead; + #if TARGET_OS_OSX || TARGET_OS_MACCATALYST + textureDescriptor.storageMode = MTLStorageModeManaged; + #else + textureDescriptor.storageMode = MTLStorageModeShared; + #endif + id texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor]; + [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4]; + MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture]; + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)texture); + tex->SetStatus(ImTextureStatus_OK); + tex->BackendUserData = (__bridge_retained void*)(backend_tex); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + MetalTexture* backend_tex = (__bridge MetalTexture*)(tex->BackendUserData); + for (ImTextureRect& r : tex->Updates) + { + [backend_tex.metalTexture replaceRegion:MTLRegionMake2D((NSUInteger)r.x, (NSUInteger)r.y, (NSUInteger)r.w, (NSUInteger)r.h) + mipmapLevel:0 + withBytes:tex->GetPixelsAt(r.x, r.y) + bytesPerRow:(NSUInteger)tex->Width * 4]; + } + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + { + ImGui_ImplMetal_DestroyTexture(tex); + } } bool ImGui_ImplMetal_CreateDeviceObjects(id device) @@ -373,14 +411,19 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) #ifdef IMGUI_IMPL_METAL_CPP [depthStencilDescriptor release]; #endif - ImGui_ImplMetal_CreateFontsTexture(device); + return true; } void ImGui_ImplMetal_DestroyDeviceObjects() { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - ImGui_ImplMetal_DestroyFontsTexture(); + + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplMetal_DestroyTexture(tex); + [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; } @@ -446,6 +489,18 @@ void ImGui_ImplMetal_DestroyDeviceObjects() @end +#pragma mark - MetalTexture implementation + +@implementation MetalTexture +- (instancetype)initWithTexture:(id)metalTexture +{ + if ((self = [super init])) + self.metalTexture = metalTexture; + return self; +} + +@end + #pragma mark - MetalContext implementation @implementation MetalContext From 08e1e7681e79da420ae6a433c99f1a890817c031 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 28 Nov 2024 15:26:52 +0100 Subject: [PATCH 022/191] imgui_freetype: Added Freetype implementation for new architecture. --- misc/freetype/imgui_freetype.cpp | 291 ++++++++++++++++++++++++------- misc/freetype/imgui_freetype.h | 8 +- 2 files changed, 228 insertions(+), 71 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 1a268365b..1125364bf 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -2,10 +2,12 @@ // (code) // Get the latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype -// Original code by @vuhdo (Aleksei Skriabin). Improvements by @mikesart. Maintained since 2019 by @ocornut. +// Original code by @vuhdo (Aleksei Skriabin) in 2017, with improvements by @mikesart. +// Maintained since 2019 by @ocornut. // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025/XX/XX: refactored for the new ImFontLoader architecture, and ImGuiBackendFlags_RendererHasTextures support. // 2024/10/17: added plutosvg support for SVG Fonts (seems faster/better than lunasvg). Enable by using '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG'. (#7927) // 2023/11/13: added support for ImFontConfig::RasterizationDensity field for scaling render density without scaling metrics. // 2023/08/01: added support for SVG fonts, enable by using '#define IMGUI_ENABLE_FREETYPE_LUNASVG'. (#6591) @@ -151,10 +153,19 @@ namespace bool IsColored; // The glyph is colored }; - // Font parameters and metrics. - struct FontInfo + // Stored in ImFontAtlas::FontLoaderData + struct ImGui_ImplFreeType_Data { - uint32_t PixelHeight; // Size this font was generated with. + FT_Library Library; + FT_MemoryRec_ MemoryManager; + + ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); } + }; + + // Font parameters and metrics. + struct ImGui_ImplFreeType_FontInfo + { + float PixelHeight; // Size this font was generated with. float Ascender; // The pixel extents above the baseline in pixels (typically positive). float Descender; // The extents below the baseline in pixels (typically negative). float LineSpacing; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate. @@ -162,40 +173,39 @@ namespace float MaxAdvanceWidth; // This field gives the maximum horizontal cursor advance for all glyphs in the font. }; - // FreeType glyph rasterizer. - // NB: No ctor/dtor, explicitly call Init()/Shutdown() - struct FreeTypeFont + // Stored in ImFontConfig::FontLoaderData + struct ImGui_ImplFreeType_FontSrcData { - bool InitFont(FT_Library ft_library, ImFontConfig& src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. - void CloseFont(); - void SetPixelHeight(int pixel_height); // Change font pixel size. All following calls to RasterizeGlyph() will use this size - const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint); - const FT_Bitmap* RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info); - void BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = nullptr); - FreeTypeFont() { memset((void*)this, 0, sizeof(*this)); } - ~FreeTypeFont() { CloseFont(); } + bool InitFont(FT_Library ft_library, ImFontConfig* src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. + void CloseFont(); + void SetPixelHeight(float pixel_height); // Change font pixel size. + const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint); + const FT_Bitmap* RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info); + void BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = nullptr); + ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); } + ~ImGui_ImplFreeType_FontSrcData() { CloseFont(); } - // [Internals] - FontInfo Info; // Font descriptor of the current font. - FT_Face Face; - unsigned int UserFlags; // = ImFontConfig::RasterizerFlags - FT_Int32 LoadFlags; - FT_Render_Mode RenderMode; - float RasterizationDensity; - float InvRasterizationDensity; + // Members + ImGui_ImplFreeType_FontInfo Info; // Font descriptor of the current font. + FT_Face FtFace; + unsigned int UserFlags; // = ImFontConfig::RasterizerFlags + FT_Int32 LoadFlags; + FT_Render_Mode RenderMode; + float RasterizationDensity; + float InvRasterizationDensity; }; - bool FreeTypeFont::InitFont(FT_Library ft_library, ImFontConfig& src, unsigned int extra_font_builder_flags) + bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, ImFontConfig* src, unsigned int extra_font_builder_flags) { - FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)src.FontData, (uint32_t)src.FontDataSize, (uint32_t)src.FontNo, &Face); + FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)src->FontData, (uint32_t)src->FontDataSize, (uint32_t)src->FontNo, &FtFace); if (error != 0) return false; - error = FT_Select_Charmap(Face, FT_ENCODING_UNICODE); + error = FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE); if (error != 0) return false; // Convert to FreeType flags (NB: Bold and Oblique are processed separately) - UserFlags = src.FontBuilderFlags | extra_font_builder_flags; + UserFlags = src->FontBuilderFlags | extra_font_builder_flags; LoadFlags = 0; if ((UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) == 0) @@ -222,40 +232,42 @@ namespace if (UserFlags & ImGuiFreeTypeBuilderFlags_LoadColor) LoadFlags |= FT_LOAD_COLOR; - RasterizationDensity = src.RasterizerDensity; + RasterizationDensity = src->RasterizerDensity; InvRasterizationDensity = 1.0f / RasterizationDensity; memset(&Info, 0, sizeof(Info)); - SetPixelHeight((uint32_t)src.SizePixels); + SetPixelHeight(src->SizePixels); return true; } - void FreeTypeFont::CloseFont() + void ImGui_ImplFreeType_FontSrcData::CloseFont() { - if (Face) + if (FtFace) { - FT_Done_Face(Face); - Face = nullptr; + FT_Done_Face(FtFace); + FtFace = nullptr; } } - void FreeTypeFont::SetPixelHeight(int pixel_height) + void ImGui_ImplFreeType_FontSrcData::SetPixelHeight(float pixel_height) { - // Vuhdo: I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height' + // Vuhdo (2017): "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height' // is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me. - // NB: FT_Set_Pixel_Sizes() doesn't seem to get us the same result. + // FT_Set_Pixel_Sizes() doesn't seem to get us the same result." + // (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL) FT_Size_RequestRec req; req.type = (UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM; req.width = 0; req.height = (uint32_t)(pixel_height * 64 * RasterizationDensity); req.horiResolution = 0; req.vertResolution = 0; - FT_Request_Size(Face, &req); + FT_Request_Size(FtFace, &req); + // Note: To handle multiple sizes later, we may need to use FT_New_Size(), FT_Activate_Size() // Update font info - FT_Size_Metrics metrics = Face->size->metrics; - Info.PixelHeight = (uint32_t)(pixel_height * InvRasterizationDensity); + FT_Size_Metrics metrics = FtFace->size->metrics; + Info.PixelHeight = pixel_height * InvRasterizationDensity; Info.Ascender = (float)FT_CEIL(metrics.ascender) * InvRasterizationDensity; Info.Descender = (float)FT_CEIL(metrics.descender) * InvRasterizationDensity; Info.LineSpacing = (float)FT_CEIL(metrics.height) * InvRasterizationDensity; @@ -263,9 +275,9 @@ namespace Info.MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * InvRasterizationDensity; } - const FT_Glyph_Metrics* FreeTypeFont::LoadGlyph(uint32_t codepoint) + const FT_Glyph_Metrics* ImGui_ImplFreeType_FontSrcData::LoadGlyph(uint32_t codepoint) { - uint32_t glyph_index = FT_Get_Char_Index(Face, codepoint); + uint32_t glyph_index = FT_Get_Char_Index(FtFace, codepoint); if (glyph_index == 0) return nullptr; @@ -274,12 +286,12 @@ namespace // - https://github.com/ocornut/imgui/issues/4567 // - https://github.com/ocornut/imgui/issues/4566 // You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version. - FT_Error error = FT_Load_Glyph(Face, glyph_index, LoadFlags); + FT_Error error = FT_Load_Glyph(FtFace, glyph_index, LoadFlags); if (error) return nullptr; // Need an outline for this to work - FT_GlyphSlot slot = Face->glyph; + FT_GlyphSlot slot = FtFace->glyph; #if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG) IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP || slot->format == FT_GLYPH_FORMAT_SVG); #else @@ -304,25 +316,25 @@ namespace return &slot->metrics; } - const FT_Bitmap* FreeTypeFont::RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info) + const FT_Bitmap* ImGui_ImplFreeType_FontSrcData::RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info) { - FT_GlyphSlot slot = Face->glyph; + FT_GlyphSlot slot = FtFace->glyph; FT_Error error = FT_Render_Glyph(slot, RenderMode); if (error != 0) return nullptr; - FT_Bitmap* ft_bitmap = &Face->glyph->bitmap; + FT_Bitmap* ft_bitmap = &FtFace->glyph->bitmap; out_glyph_info->Width = (int)ft_bitmap->width; out_glyph_info->Height = (int)ft_bitmap->rows; - out_glyph_info->OffsetX = Face->glyph->bitmap_left; - out_glyph_info->OffsetY = -Face->glyph->bitmap_top; + out_glyph_info->OffsetX = FtFace->glyph->bitmap_left; + out_glyph_info->OffsetY = -FtFace->glyph->bitmap_top; out_glyph_info->AdvanceX = (float)slot->advance.x / FT_SCALEFACTOR; out_glyph_info->IsColored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); return ft_bitmap; } - void FreeTypeFont::BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch, unsigned char* multiply_table) + void ImGui_ImplFreeType_FontSrcData::BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch, unsigned char* multiply_table) { IM_ASSERT(ft_bitmap != nullptr); const uint32_t w = ft_bitmap->width; @@ -398,6 +410,8 @@ namespace } } // namespace +#if 0 + #ifndef STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) #ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION #define STBRP_ASSERT(x) do { IM_ASSERT(x); } while (0) @@ -764,6 +778,7 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u return true; } +#endif // FreeType memory allocation callbacks static void* FreeType_Alloc(FT_Memory /*memory*/, long size) @@ -799,47 +814,189 @@ static void* FreeType_Realloc(FT_Memory /*memory*/, long cur_size, long new_size return block; } -static bool ImFontAtlasBuildWithFreeType(ImFontAtlas* atlas) +bool ImGui_ImplFreeType_LoaderInit(ImFontAtlas* atlas) { + IM_ASSERT(atlas->FontLoaderData == NULL); + ImGui_ImplFreeType_Data* bd = IM_NEW(ImGui_ImplFreeType_Data)(); + // FreeType memory management: https://www.freetype.org/freetype2/docs/design/design-4.html - FT_MemoryRec_ memory_rec = {}; - memory_rec.user = nullptr; - memory_rec.alloc = &FreeType_Alloc; - memory_rec.free = &FreeType_Free; - memory_rec.realloc = &FreeType_Realloc; + bd->MemoryManager.user = nullptr; + bd->MemoryManager.alloc = &FreeType_Alloc; + bd->MemoryManager.free = &FreeType_Free; + bd->MemoryManager.realloc = &FreeType_Realloc; // https://www.freetype.org/freetype2/docs/reference/ft2-module_management.html#FT_New_Library - FT_Library ft_library; - FT_Error error = FT_New_Library(&memory_rec, &ft_library); + FT_Error error = FT_New_Library(&bd->MemoryManager, &bd->Library); if (error != 0) + { + IM_DELETE(bd); return false; + } // If you don't call FT_Add_Default_Modules() the rest of code may work, but FreeType won't use our custom allocator. - FT_Add_Default_Modules(ft_library); + FT_Add_Default_Modules(bd->Library); #ifdef IMGUI_ENABLE_FREETYPE_LUNASVG // Install svg hooks for FreeType // https://freetype.org/freetype2/docs/reference/ft2-properties.html#svg-hooks // https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html#svg_fonts SVG_RendererHooks hooks = { ImGuiLunasvgPortInit, ImGuiLunasvgPortFree, ImGuiLunasvgPortRender, ImGuiLunasvgPortPresetSlot }; - FT_Property_Set(ft_library, "ot-svg", "svg-hooks", &hooks); + FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", &hooks); #endif // IMGUI_ENABLE_FREETYPE_LUNASVG #ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG // With plutosvg, use provided hooks - FT_Property_Set(ft_library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks()); + FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks()); #endif // IMGUI_ENABLE_FREETYPE_PLUTOSVG - bool ret = ImFontAtlasBuildWithFreeTypeEx(ft_library, atlas, atlas->FontBuilderFlags); - FT_Done_Library(ft_library); + // Store our data + atlas->FontLoaderData = (void*)bd; - return ret; + return true; } -const ImFontBuilderIO* ImGuiFreeType::GetBuilderForFreeType() +void ImGui_ImplFreeType_LoaderShutdown(ImFontAtlas* atlas) { - static ImFontBuilderIO io; - io.FontBuilder_Build = ImFontAtlasBuildWithFreeType; - return &io; + ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData; + IM_ASSERT(bd != NULL); + FT_Done_Library(bd->Library); + IM_DELETE(bd); + atlas->FontLoaderData = NULL; +} + +bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) +{ + ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData; + ImGui_ImplFreeType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplFreeType_FontSrcData); + IM_ASSERT(src->FontLoaderData == NULL); + src->FontLoaderData = bd_font_data; + + if (!bd_font_data->InitFont(bd->Library, src, atlas->FontBuilderFlags)) + return false; + + if (src->MergeMode == false) + { + ImFont* font = src->DstFont; + font->Ascent = bd_font_data->Info.Ascender; + font->Descent = bd_font_data->Info.Descender; + } + return true; +} + +void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; + IM_DELETE(bd_font_data); + src->FontLoaderData = NULL; +} + +bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* srcs, int srcs_count, ImWchar codepoint) +{ + // Search for first font which has the glyph + ImGui_ImplFreeType_FontSrcData* bd_font_data = NULL; + ImFontConfig* src = NULL; + uint32_t glyph_index = 0; + for (int src_n = 0; src_n < srcs_count; src_n++) + { + src = &srcs[src_n]; + bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; + glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint); + if (glyph_index != 0) + break; + } + if (glyph_index == 0) + return false; // Not found + + const FT_Glyph_Metrics* metrics = bd_font_data->LoadGlyph(codepoint); + if (metrics == NULL) + return false; + + // Render glyph into a bitmap (currently held by FreeType) + FT_Face face = bd_font_data->FtFace; + FT_GlyphSlot slot = face->glyph; + FT_Error error = FT_Render_Glyph(slot, bd_font_data->RenderMode); + if (error != 0) + return false; + + const FT_Bitmap* ft_bitmap = &slot->bitmap; + if (ft_bitmap == nullptr) + return false; + + const int w = (int)ft_bitmap->width; + const int h = (int)ft_bitmap->rows; + const bool is_visible = (w != 0 && h != 0); + + // Prepare glyph + ImFontGlyph glyph = {}; + glyph.Codepoint = codepoint; + glyph.AdvanceX = (slot->advance.x / FT_SCALEFACTOR) * bd_font_data->InvRasterizationDensity; + + // Pack and retrieve position inside texture atlas + if (is_visible) + { + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + font->MetricsTotalSurface += w * h; + + // Render pixels to our temporary buffer + atlas->Builder->TempBuffer.resize(w * h * 4); + uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data; + bd_font_data->BlitGlyph(ft_bitmap, temp_buffer, w, nullptr);// multiply_enabled ? multiply_table : nullptr); + + float font_off_x = src->GlyphOffset.x; + float font_off_y = src->GlyphOffset.y + IM_ROUND(font->Ascent); + float recip_h = 1.0f / src->RasterizerDensity; + float recip_v = 1.0f / src->RasterizerDensity; + + // Register glyph + float glyph_off_x = (float)face->glyph->bitmap_left; + float glyph_off_y = (float)-face->glyph->bitmap_top; + glyph.X0 = glyph_off_x * recip_h + font_off_x; + glyph.Y0 = glyph_off_y * recip_v + font_off_y; + glyph.X1 = (glyph_off_x + w) * recip_h + font_off_x; + glyph.Y1 = (glyph_off_y + h) * recip_v + font_off_y; + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + glyph.PackId = pack_id; + glyph.Visible = true; + glyph.Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); + font->BuildRegisterGlyph(src, &glyph); + + // Copy to texture, post-process and queue update for backend + ImTextureData* tex = atlas->TexData; + IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); + ImFontAtlasTextureBlockConvertAndPostProcess(atlas, font, src, &font->Glyphs.back(), + temp_buffer, ImTextureFormat_RGBA32, w * 4, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); + } + else + { + font->BuildRegisterGlyph(src, &glyph); + } + return true; +} + +bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) +{ + IM_UNUSED(atlas); + ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; + int glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint); + return glyph_index != 0; +} + +const ImFontLoader* ImGuiFreeType::GetFontLoader() +{ + static ImFontLoader loader; + loader.Name = "freetype"; + loader.LoaderInit = ImGui_ImplFreeType_LoaderInit; + loader.LoaderShutdown = ImGui_ImplFreeType_LoaderShutdown; + loader.FontSrcInit = ImGui_ImplFreeType_FontSrcInit; + loader.FontSrcDestroy = ImGui_ImplFreeType_FontSrcDestroy; + loader.FontSrcContainsGlyph = ImGui_ImplFreetype_FontSrcContainsGlyph; + loader.FontAddGlyph = ImGui_ImplFreeType_FontAddGlyph; + return &loader; } void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data) diff --git a/misc/freetype/imgui_freetype.h b/misc/freetype/imgui_freetype.h index 9d367074e..2d7b3a34f 100644 --- a/misc/freetype/imgui_freetype.h +++ b/misc/freetype/imgui_freetype.h @@ -14,7 +14,7 @@ // Forward declarations struct ImFontAtlas; -struct ImFontBuilderIO; +struct ImFontLoader; // Hinting greatly impacts visuals (and glyph sizes). // - By default, hinting is enabled and the font's native hinter is preferred over the auto-hinter. @@ -41,9 +41,9 @@ namespace ImGuiFreeType { // This is automatically assigned when using '#define IMGUI_ENABLE_FREETYPE'. // If you need to dynamically select between multiple builders: - // - you can manually assign this builder with 'atlas->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' - // - prefer deep-copying this into your own ImFontBuilderIO instance if you use hot-reloading that messes up static data. - IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); + // - you can manually assign this builder with 'atlas->FontLoader = ImGuiFreeType::GetFontLoader()' + // - prefer deep-copying this into your own ImFontLoader instance if you use hot-reloading that messes up static data. + IMGUI_API const ImFontLoader* GetFontLoader(); // Override allocators. By default ImGuiFreeType will use IM_ALLOC()/IM_FREE() // However, as FreeType does lots of allocations we provide a way for the user to redirect it to a separate memory heap if desired. From 1269467fa0789c927c70c4013d5ed5477b1ec49c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 28 Nov 2024 15:27:17 +0100 Subject: [PATCH 023/191] imgui_freetype: Removing old code. --- misc/freetype/imgui_freetype.cpp | 454 +------------------------------ 1 file changed, 14 insertions(+), 440 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 1125364bf..a8dfa950e 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -142,24 +142,12 @@ namespace // | | // |------------- advanceX ----------->| - // A structure that describe a glyph. - struct GlyphInfo - { - int Width; // Glyph's width in pixels. - int Height; // Glyph's height in pixels. - FT_Int OffsetX; // The distance from the origin ("pen position") to the left of the glyph. - FT_Int OffsetY; // The distance from the origin to the top of the glyph. This is usually a value < 0. - float AdvanceX; // The distance from the origin to the origin of the next glyph. This is usually a value > 0. - bool IsColored; // The glyph is colored - }; - // Stored in ImFontAtlas::FontLoaderData struct ImGui_ImplFreeType_Data { - FT_Library Library; - FT_MemoryRec_ MemoryManager; - - ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); } + FT_Library Library; + FT_MemoryRec_ MemoryManager; + ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); } }; // Font parameters and metrics. @@ -180,8 +168,7 @@ namespace void CloseFont(); void SetPixelHeight(float pixel_height); // Change font pixel size. const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint); - const FT_Bitmap* RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info); - void BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = nullptr); + void BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch); ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); } ~ImGui_ImplFreeType_FontSrcData() { CloseFont(); } @@ -316,25 +303,7 @@ namespace return &slot->metrics; } - const FT_Bitmap* ImGui_ImplFreeType_FontSrcData::RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info) - { - FT_GlyphSlot slot = FtFace->glyph; - FT_Error error = FT_Render_Glyph(slot, RenderMode); - if (error != 0) - return nullptr; - - FT_Bitmap* ft_bitmap = &FtFace->glyph->bitmap; - out_glyph_info->Width = (int)ft_bitmap->width; - out_glyph_info->Height = (int)ft_bitmap->rows; - out_glyph_info->OffsetX = FtFace->glyph->bitmap_left; - out_glyph_info->OffsetY = -FtFace->glyph->bitmap_top; - out_glyph_info->AdvanceX = (float)slot->advance.x / FT_SCALEFACTOR; - out_glyph_info->IsColored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); - - return ft_bitmap; - } - - void ImGui_ImplFreeType_FontSrcData::BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch, unsigned char* multiply_table) + void ImGui_ImplFreeType_FontSrcData::BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch) { IM_ASSERT(ft_bitmap != nullptr); const uint32_t w = ft_bitmap->width; @@ -346,24 +315,13 @@ namespace { case FT_PIXEL_MODE_GRAY: // Grayscale image, 1 byte per pixel. { - if (multiply_table == nullptr) - { - for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) - for (uint32_t x = 0; x < w; x++) - dst[x] = IM_COL32(255, 255, 255, src[x]); - } - else - { - for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) - for (uint32_t x = 0; x < w; x++) - dst[x] = IM_COL32(255, 255, 255, multiply_table[src[x]]); - } + for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) + for (uint32_t x = 0; x < w; x++) + dst[x] = IM_COL32(255, 255, 255, src[x]); break; } case FT_PIXEL_MODE_MONO: // Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB. { - uint8_t color0 = multiply_table ? multiply_table[0] : 0; - uint8_t color1 = multiply_table ? multiply_table[255] : 255; for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) { uint8_t bits = 0; @@ -372,7 +330,7 @@ namespace { if ((x & 7) == 0) bits = *bits_ptr++; - dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? color1 : color0); + dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? 255 : 0); } } break; @@ -381,26 +339,12 @@ namespace { // FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good. #define DE_MULTIPLY(color, alpha) ImMin((ImU32)(255.0f * (float)color / (float)(alpha + FLT_MIN) + 0.5f), 255u) - if (multiply_table == nullptr) - { - for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) - for (uint32_t x = 0; x < w; x++) - { - uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3]; - dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a); - } - } - else - { - for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) + for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) + for (uint32_t x = 0; x < w; x++) { - for (uint32_t x = 0; x < w; x++) - { - uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3]; - dst[x] = IM_COL32(multiply_table[DE_MULTIPLY(r, a)], multiply_table[DE_MULTIPLY(g, a)], multiply_table[DE_MULTIPLY(b, a)], multiply_table[a]); - } + uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3]; + dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a); } - } #undef DE_MULTIPLY break; } @@ -410,376 +354,6 @@ namespace } } // namespace -#if 0 - -#ifndef STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) -#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION -#define STBRP_ASSERT(x) do { IM_ASSERT(x); } while (0) -#define STBRP_STATIC -#define STB_RECT_PACK_IMPLEMENTATION -#endif -#ifdef IMGUI_STB_RECT_PACK_FILENAME -#include IMGUI_STB_RECT_PACK_FILENAME -#else -#include "imstb_rectpack.h" -#endif -#endif - -struct ImFontBuildSrcGlyphFT -{ - GlyphInfo Info; - uint32_t Codepoint; - unsigned int* BitmapData; // Point within one of the dst_tmp_bitmap_buffers[] array - - ImFontBuildSrcGlyphFT() { memset((void*)this, 0, sizeof(*this)); } -}; - -struct ImFontBuildSrcDataFT -{ - FreeTypeFont Font; - stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position. - const ImWchar* SrcRanges; // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF) - int DstIndex; // Index into atlas->Fonts[] and dst_tmp_array[] - int GlyphsHighest; // Highest requested codepoint - int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font) - ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB) - ImVector GlyphsList; -}; - -// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont) -struct ImFontBuildDstDataFT -{ - int SrcCount; // Number of source fonts targeting this destination font. - int GlyphsHighest; - int GlyphsCount; - ImBitVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font. -}; - -bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, unsigned int extra_flags) -{ - IM_ASSERT(atlas->Sources.Size > 0); - - ImFontAtlasBuildInit(atlas); - - // Clear atlas - atlas->TexID._TexID = 0; - atlas->TexWidth = atlas->TexHeight = 0; - atlas->TexUvScale = ImVec2(0.0f, 0.0f); - atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); - atlas->ClearTexData(); - - // Temporary storage for building - bool src_load_color = false; - ImVector src_tmp_array; - ImVector dst_tmp_array; - src_tmp_array.resize(atlas->Sources.Size); - dst_tmp_array.resize(atlas->Fonts.Size); - memset((void*)src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes()); - memset((void*)dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes()); - - // 1. Initialize font loading structure, check font data validity - for (int src_i = 0; src_i < atlas->Sources.Size; src_i++) - { - ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - FreeTypeFont& font_face = src_tmp.Font; - IM_ASSERT(src.DstFont && (!src.DstFont->IsLoaded() || src.DstFont->ContainerAtlas == atlas)); - - // Find index from src.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices) - src_tmp.DstIndex = -1; - for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++) - if (src.DstFont == atlas->Fonts[output_i]) - src_tmp.DstIndex = output_i; - IM_ASSERT(src_tmp.DstIndex != -1); // src.DstFont not pointing within atlas->Fonts[] array? - if (src_tmp.DstIndex == -1) - return false; - - // Load font - if (!font_face.InitFont(ft_library, src, extra_flags)) - return false; - - // Measure highest codepoints - src_load_color |= (src.FontBuilderFlags & ImGuiFreeTypeBuilderFlags_LoadColor) != 0; - ImFontBuildDstDataFT& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.SrcRanges = src.GlyphRanges ? src.GlyphRanges : atlas->GetGlyphRangesDefault(); - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - { - // Check for valid range. This may also help detect *some* dangling pointers, because a common - // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent, - // or to forget to zero-terminate the glyph range array. - IM_ASSERT(src_range[0] <= src_range[1] && "Invalid range: is your glyph range array persistent? it is zero-terminated?"); - src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]); - } - dst_tmp.SrcCount++; - dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest); - } - - // 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs. - int total_glyphs_count = 0; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; - ImFontBuildDstDataFT& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.GlyphsSet.Create(src_tmp.GlyphsHighest + 1); - if (dst_tmp.GlyphsSet.Storage.empty()) - dst_tmp.GlyphsSet.Create(dst_tmp.GlyphsHighest + 1); - - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - for (int codepoint = src_range[0]; codepoint <= (int)src_range[1]; codepoint++) - { - if (dst_tmp.GlyphsSet.TestBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option (e.g. MergeOverwrite) - continue; - uint32_t glyph_index = FT_Get_Char_Index(src_tmp.Font.Face, codepoint); // It is actually in the font? (FIXME-OPT: We are not storing the glyph_index..) - if (glyph_index == 0) - continue; - - // Add to avail set/counters - src_tmp.GlyphsCount++; - dst_tmp.GlyphsCount++; - src_tmp.GlyphsSet.SetBit(codepoint); - dst_tmp.GlyphsSet.SetBit(codepoint); - total_glyphs_count++; - } - } - - // 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another) - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; - src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount); - - IM_ASSERT(sizeof(src_tmp.GlyphsSet.Storage.Data[0]) == sizeof(ImU32)); - const ImU32* it_begin = src_tmp.GlyphsSet.Storage.begin(); - const ImU32* it_end = src_tmp.GlyphsSet.Storage.end(); - for (const ImU32* it = it_begin; it < it_end; it++) - if (ImU32 entries_32 = *it) - for (ImU32 bit_n = 0; bit_n < 32; bit_n++) - if (entries_32 & ((ImU32)1 << bit_n)) - { - ImFontBuildSrcGlyphFT src_glyph; - src_glyph.Codepoint = (ImWchar)(((it - it_begin) << 5) + bit_n); - //src_glyph.GlyphIndex = 0; // FIXME-OPT: We had this info in the previous step and lost it.. - src_tmp.GlyphsList.push_back(src_glyph); - } - src_tmp.GlyphsSet.Clear(); - IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount); - } - for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++) - dst_tmp_array[dst_i].GlyphsSet.Clear(); - dst_tmp_array.clear(); - - // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) - // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity) - ImVector buf_rects; - buf_rects.resize(total_glyphs_count); - memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes()); - - // Allocate temporary rasterization data buffers. - // We could not find a way to retrieve accurate glyph size without rendering them. - // (e.g. slot->metrics->width not always matching bitmap->width, especially considering the Oblique transform) - // We allocate in chunks of 256 KB to not waste too much extra memory ahead. Hopefully users of FreeType won't mind the temporary allocations. - const int BITMAP_BUFFERS_CHUNK_SIZE = 256 * 1024; - int buf_bitmap_current_used_bytes = 0; - ImVector buf_bitmap_buffers; - buf_bitmap_buffers.push_back((unsigned char*)IM_ALLOC(BITMAP_BUFFERS_CHUNK_SIZE)); - - // 4. Gather glyphs sizes so we can pack them in our virtual canvas. - // 8. Render/rasterize font characters into the texture - int total_surface = 0; - int buf_rects_out_n = 0; - const int pack_padding = atlas->TexGlyphPadding; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - - src_tmp.Rects = &buf_rects[buf_rects_out_n]; - buf_rects_out_n += src_tmp.GlyphsCount; - - // Compute multiply table if requested - const bool multiply_enabled = (src.RasterizerMultiply != 1.0f); - unsigned char multiply_table[256]; - if (multiply_enabled) - ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, src.RasterizerMultiply); - - // Gather the sizes of all rectangles we will need to pack - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) - { - ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i]; - - const FT_Glyph_Metrics* metrics = src_tmp.Font.LoadGlyph(src_glyph.Codepoint); - if (metrics == nullptr) - continue; - - // Render glyph into a bitmap (currently held by FreeType) - const FT_Bitmap* ft_bitmap = src_tmp.Font.RenderGlyphAndGetInfo(&src_glyph.Info); - if (ft_bitmap == nullptr) - continue; - - // Allocate new temporary chunk if needed - const int bitmap_size_in_bytes = src_glyph.Info.Width * src_glyph.Info.Height * 4; - if (buf_bitmap_current_used_bytes + bitmap_size_in_bytes > BITMAP_BUFFERS_CHUNK_SIZE) - { - buf_bitmap_current_used_bytes = 0; - buf_bitmap_buffers.push_back((unsigned char*)IM_ALLOC(BITMAP_BUFFERS_CHUNK_SIZE)); - } - IM_ASSERT(buf_bitmap_current_used_bytes + bitmap_size_in_bytes <= BITMAP_BUFFERS_CHUNK_SIZE); // We could probably allocate custom-sized buffer instead. - - // Blit rasterized pixels to our temporary buffer and keep a pointer to it. - src_glyph.BitmapData = (unsigned int*)(buf_bitmap_buffers.back() + buf_bitmap_current_used_bytes); - buf_bitmap_current_used_bytes += bitmap_size_in_bytes; - src_tmp.Font.BlitGlyph(ft_bitmap, src_glyph.BitmapData, src_glyph.Info.Width, multiply_enabled ? multiply_table : nullptr); - - src_tmp.Rects[glyph_i].w = (stbrp_coord)(src_glyph.Info.Width + pack_padding); - src_tmp.Rects[glyph_i].h = (stbrp_coord)(src_glyph.Info.Height + pack_padding); - total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; - } - } - for (int i = 0; i < atlas->CustomRects.Size; i++) - total_surface += (atlas->CustomRects[i].Width + pack_padding) * (atlas->CustomRects[i].Height + pack_padding); - - // We need a width for the skyline algorithm, any width! - // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. - // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface. - const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1; - atlas->TexHeight = 0; - if (atlas->TexDesiredWidth > 0) - atlas->TexWidth = atlas->TexDesiredWidth; - else - atlas->TexWidth = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512; - - // 5. Start packing - // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). - const int TEX_HEIGHT_MAX = 1024 * 32; - const int num_nodes_for_packing_algorithm = atlas->TexWidth - atlas->TexGlyphPadding; - ImVector pack_nodes; - pack_nodes.resize(num_nodes_for_packing_algorithm); - stbrp_context pack_context; - stbrp_init_target(&pack_context, atlas->TexWidth - atlas->TexGlyphPadding, TEX_HEIGHT_MAX - atlas->TexGlyphPadding, pack_nodes.Data, pack_nodes.Size); - ImFontAtlasBuildPackCustomRects(atlas, &pack_context); - - // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - - stbrp_pack_rects(&pack_context, src_tmp.Rects, src_tmp.GlyphsCount); - - // Extend texture height and mark missing glyphs as non-packed so we won't render them. - // FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?) - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - if (src_tmp.Rects[glyph_i].was_packed) - atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h); - } - - // 7. Allocate texture - atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); - atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); - if (src_load_color) - { - size_t tex_size = (size_t)atlas->TexWidth * atlas->TexHeight * 4; - atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(tex_size); - memset(atlas->TexPixelsRGBA32, 0, tex_size); - } - else - { - size_t tex_size = (size_t)atlas->TexWidth * atlas->TexHeight * 1; - atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(tex_size); - memset(atlas->TexPixelsAlpha8, 0, tex_size); - } - - // 8. Copy rasterized font characters back into the main texture - // 9. Setup ImFont and glyphs for runtime - bool tex_use_colors = false; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; - - // When merging fonts with MergeMode=true: - // - We can have multiple input fonts writing into a same destination font. - // - dst_font->Sources is != from src which is our source configuration. - ImFontConfig& src = atlas->Sources[src_i]; - ImFont* dst_font = src.DstFont; - - const float ascent = src_tmp.Font.Info.Ascender; - const float descent = src_tmp.Font.Info.Descender; - ImFontAtlasBuildSetupFont(atlas, dst_font, &src, ascent, descent); - - if (src_tmp.GlyphsCount == 0) - continue; - const float font_off_x = src.GlyphOffset.x; - const float font_off_y = src.GlyphOffset.y + IM_ROUND(dst_font->Ascent); - - const int padding = atlas->TexGlyphPadding; - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - { - ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i]; - stbrp_rect& pack_rect = src_tmp.Rects[glyph_i]; - IM_ASSERT(pack_rect.was_packed); - if (pack_rect.w == 0 && pack_rect.h == 0) - continue; - - GlyphInfo& info = src_glyph.Info; - IM_ASSERT(info.Width + padding <= pack_rect.w); - IM_ASSERT(info.Height + padding <= pack_rect.h); - const int tx = pack_rect.x + padding; - const int ty = pack_rect.y + padding; - - // Register glyph - float x0 = info.OffsetX * src_tmp.Font.InvRasterizationDensity + font_off_x; - float y0 = info.OffsetY * src_tmp.Font.InvRasterizationDensity + font_off_y; - float x1 = x0 + info.Width * src_tmp.Font.InvRasterizationDensity; - float y1 = y0 + info.Height * src_tmp.Font.InvRasterizationDensity; - float u0 = (tx) / (float)atlas->TexWidth; - float v0 = (ty) / (float)atlas->TexHeight; - float u1 = (tx + info.Width) / (float)atlas->TexWidth; - float v1 = (ty + info.Height) / (float)atlas->TexHeight; - dst_font->AddGlyph(&src, (ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, info.AdvanceX * src_tmp.Font.InvRasterizationDensity); - - ImFontGlyph* dst_glyph = &dst_font->Glyphs.back(); - IM_ASSERT(dst_glyph->Codepoint == src_glyph.Codepoint); - if (src_glyph.Info.IsColored) - dst_glyph->Colored = tex_use_colors = true; - - // Blit from temporary buffer to final texture - size_t blit_src_stride = (size_t)src_glyph.Info.Width; - size_t blit_dst_stride = (size_t)atlas->TexWidth; - unsigned int* blit_src = src_glyph.BitmapData; - if (atlas->TexPixelsAlpha8 != nullptr) - { - unsigned char* blit_dst = atlas->TexPixelsAlpha8 + (ty * blit_dst_stride) + tx; - for (int y = 0; y < info.Height; y++, blit_dst += blit_dst_stride, blit_src += blit_src_stride) - for (int x = 0; x < info.Width; x++) - blit_dst[x] = (unsigned char)((blit_src[x] >> IM_COL32_A_SHIFT) & 0xFF); - } - else - { - unsigned int* blit_dst = atlas->TexPixelsRGBA32 + (ty * blit_dst_stride) + tx; - for (int y = 0; y < info.Height; y++, blit_dst += blit_dst_stride, blit_src += blit_src_stride) - for (int x = 0; x < info.Width; x++) - blit_dst[x] = blit_src[x]; - } - } - - src_tmp.Rects = nullptr; - } - atlas->TexPixelsUseColors = tex_use_colors; - - // Cleanup - for (int buf_i = 0; buf_i < buf_bitmap_buffers.Size; buf_i++) - IM_FREE(buf_bitmap_buffers[buf_i]); - src_tmp_array.clear_destruct(); - - ImFontAtlasBuildFinish(atlas); - - return true; -} -#endif - // FreeType memory allocation callbacks static void* FreeType_Alloc(FT_Memory /*memory*/, long size) { @@ -941,7 +515,7 @@ bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontCon // Render pixels to our temporary buffer atlas->Builder->TempBuffer.resize(w * h * 4); uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data; - bd_font_data->BlitGlyph(ft_bitmap, temp_buffer, w, nullptr);// multiply_enabled ? multiply_table : nullptr); + bd_font_data->BlitGlyph(ft_bitmap, temp_buffer, w); float font_off_x = src->GlyphOffset.x; float font_off_y = src->GlyphOffset.y + IM_ROUND(font->Ascent); From 0f553c57bd130cbb3207ec493583030c1044bd97 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 29 Nov 2024 14:28:16 +0100 Subject: [PATCH 024/191] Fonts: AddFont() actually does the work, so we can handle errors & return an accurate return value. --- imgui.h | 1 + imgui_draw.cpp | 51 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/imgui.h b/imgui.h index c95f5ac57..8161fb4b4 100644 --- a/imgui.h +++ b/imgui.h @@ -3529,6 +3529,7 @@ struct ImFontAtlas IMGUI_API void Clear(); // Clear all input and output. IMGUI_API void ClearCache(); // Clear cached glyphs + IMGUI_API void BuildInit(); // Build atlas, retrieve pixel data. // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0bde7a116..3a9290296 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2887,6 +2887,10 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); IM_ASSERT(font_cfg->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); + // Lazily create builder on the first call to AddFont + if (Builder == NULL) + BuildInit(); + // Create new font if (!font_cfg->MergeMode) Fonts.push_back(IM_NEW(ImFont)); @@ -2912,9 +2916,19 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) // Pointers to Sources are otherwise dangling ImFontAtlasBuildUpdatePointers(this); - - if (Builder != NULL) - ImFontAtlasBuildAddFont(this, &new_font_cfg); + if (!ImFontAtlasBuildAddFont(this, &new_font_cfg)) + { + // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) + if (new_font_cfg.FontDataOwnedByAtlas) + IM_FREE(new_font_cfg.FontData); + Sources.pop_back(); + if (!font_cfg->MergeMode) + { + IM_DELETE(Fonts.back()); + Fonts.pop_back(); + } + return NULL; + } // Invalidate texture //TexReady = false; @@ -3105,14 +3119,8 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso return true; } -bool ImFontAtlas::Build() +void ImFontAtlas::BuildInit() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - - // Default font is none are specified - if (Sources.Size == 0) - AddFontDefault(); - // Select builder // - Note that we do not reassign to atlas->FontLoader, since it is likely to point to static data which // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are @@ -3132,6 +3140,18 @@ bool ImFontAtlas::Build() // Create initial texture size ImFontAtlasBuildAddTexture(this, 512, 128); ImFontAtlasBuildInit(this); +} + +bool ImFontAtlas::Build() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + + if (Builder == NULL) + BuildInit(); + + // Default font is none are specified + if (Sources.Size == 0) + AddFontDefault(); // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs ImFontAtlasBuildUpdateRendererHasTexturesFromContext(this); @@ -3384,7 +3404,7 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) const ImFontLoader* font_loader = atlas->FontLoader; if (!font_loader->FontSrcInit(atlas, src)) - return false; // FIXME-NEWATLAS: error handling + return false; ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, src); return true; @@ -3887,10 +3907,14 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* // Initialize helper structure for font loading and verify that the TTF/OTF data is correct const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src->FontData, src->FontNo); - IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); // FIXME-NEWATLAS: error handling + if (font_offset < 0) + { + IM_ASSERT_USER_ERROR(0, "stbtt_GetFontOffsetForIndex(): FontData is incorrect, or FontNo cannot be found."); + return false; + } if (!stbtt_InitFont(&bd_font_data->FontInfo, (unsigned char*)src->FontData, font_offset)) { - IM_ASSERT(0 && "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); + IM_ASSERT_USER_ERROR(0, "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); return false; } src->FontLoaderData = bd_font_data; @@ -3954,6 +3978,7 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, { src = &srcs[src_n]; bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_ASSERT(bd_font_data); glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); if (glyph_index != 0) break; From b670f799d5e3078477843a044e6fb99a383bbf6f Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 29 Nov 2024 15:16:22 +0100 Subject: [PATCH 025/191] Fonts: use TexGlyphPadding. Fixed packing issues. Removed old code. --- imgui_draw.cpp | 51 +++++++++++++----------------------------------- imgui_internal.h | 3 +-- 2 files changed, 15 insertions(+), 39 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3a9290296..089fc0c5d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3582,7 +3582,6 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. -#if 1 // Repack + copy pixels // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic. ImFontAtlasPackInit(atlas); @@ -3623,27 +3622,6 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) ImFontAtlasBuildUpdateLinesTexData(atlas, false); ImFontAtlasBuildUpdateBasicTexData(atlas, false); -#else - // Copy previous pixels - ImFontAtlasTextureCopyBlock(atlas, old_tex, 0, 0, new_tex, 0, 0, ImMin(old_tex->Width, new_tex->Width), ImMin(old_tex->Height, new_tex->Height)); - - // Scale UV coordinates - // FIXME-NEWATLAS: Probably lossy? - ImVec2 uv_scale((float)old_tex->Width / new_tex->Width, (float)old_tex->Height / new_tex->Height); - for (ImFont* font : atlas->Fonts) - for (ImFontGlyph& glyph : font->Glyphs) - { - glyph.U0 *= uv_scale.x; - glyph.U1 *= uv_scale.x; - glyph.V0 *= uv_scale.y; - glyph.V1 *= uv_scale.y; - } - ImVec4 uv_scale4(uv_scale.x, uv_scale.y, uv_scale.x, uv_scale.y); - atlas->TexUvWhitePixel *= uv_scale; - for (ImVec4& uv : atlas->TexUvLines) - uv = uv * uv_scale4; -#endif - builder->LockDisableResize = false; ImFontAtlasUpdateDrawListsSharedData(atlas); } @@ -3666,8 +3644,9 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ int new_tex_h = (old_tex_h < old_tex_w) ? old_tex_h * 2 : old_tex_h; // Handle minimum size first (for pathologically large packed rects) - new_tex_w = ImMax(new_tex_w, ImUpperPowerOfTwo(builder->MaxRectSize.x + builder->PackPadding)); - new_tex_h = ImMax(new_tex_h, ImUpperPowerOfTwo(builder->MaxRectSize.y + builder->PackPadding)); + const int pack_padding = atlas->TexGlyphPadding; + new_tex_w = ImMax(new_tex_w, ImUpperPowerOfTwo(builder->MaxRectSize.x + pack_padding)); + new_tex_h = ImMax(new_tex_h, ImUpperPowerOfTwo(builder->MaxRectSize.y + pack_padding)); ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); } @@ -3717,10 +3696,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) const bool builder_is_new = (builder == NULL); if (builder_is_new) - { builder = atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); - builder->PackPadding = 1; - } ImFontAtlasPackInit(atlas); @@ -3762,11 +3738,12 @@ void ImFontAtlasPackInit(ImFontAtlas* atlas) // FIXME-NEWATLAS-V2: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 // FIXME-NEWATLAS-V2: Experiment with number of nodes. 2024-11-05: Seems to be quite fine to reduce this. - int pack_node_count = tex->Width - builder->PackPadding; + //int pack_padding = atlas->TexGlyphPadding; + int pack_node_count = tex->Width; //pack_node_count *= atlas->_PackNodesFactor; builder->PackNodes.resize(pack_node_count); IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); - stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width - builder->PackPadding, tex->Height - builder->PackPadding, builder->PackNodes.Data, builder->PackNodes.Size); + stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width, tex->Height, builder->PackNodes.Data, builder->PackNodes.Size); atlas->_PackedSurface = atlas->_PackedRects = 0; builder->MaxRectSize = ImVec2i(0, 0); builder->MaxRectBounds = ImVec2i(0, 0); @@ -3779,6 +3756,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h) IM_ASSERT(h > 0 && h <= 0xFFFF); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + const int pack_padding = atlas->TexGlyphPadding; builder->MaxRectSize.x = ImMax(builder->MaxRectSize.x, w); builder->MaxRectSize.y = ImMax(builder->MaxRectSize.y, h); @@ -3788,11 +3766,11 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h) { // Try packing stbrp_rect pack_r = {}; - pack_r.w = r.w + builder->PackPadding; - pack_r.h = r.h + builder->PackPadding; + pack_r.w = w + pack_padding; + pack_r.h = h + pack_padding; stbrp_pack_rects((stbrp_context*)(void*)&builder->PackContext, &pack_r, 1); - r.x = (unsigned short)(pack_r.x + builder->PackPadding); - r.y = (unsigned short)(pack_r.y + builder->PackPadding); + r.x = (unsigned short)pack_r.x; + r.y = (unsigned short)pack_r.y; if (pack_r.was_packed) break; @@ -3809,9 +3787,9 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h) ImFontAtlasBuildGrowTexture(atlas); } - builder->MaxRectBounds.x = ImMax(builder->MaxRectBounds.x, r.x + r.w); - builder->MaxRectBounds.y = ImMax(builder->MaxRectBounds.y, r.y + r.h); - atlas->_PackedSurface += w * h; + builder->MaxRectBounds.x = ImMax(builder->MaxRectBounds.x, r.x + r.w + pack_padding); + builder->MaxRectBounds.y = ImMax(builder->MaxRectBounds.y, r.y + r.h + pack_padding); + atlas->_PackedSurface += (w + pack_padding) * (h + pack_padding); atlas->_PackedRects++; builder->Rects.push_back(r); @@ -3986,7 +3964,6 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, if (glyph_index == 0) return false; // Not found - // FIXME-NEWATLAS: Handling of atlas->TexGlyphPadding? const float scale_for_layout = bd_font_data->ScaleForLayout; // ~ (font units to pixels) const float scale_for_raster_x = bd_font_data->ScaleForRasterX; // ~ (font units to pixels) * RasterizationDensity * OversampleH const float scale_for_raster_y = bd_font_data->ScaleForRasterY; // ~ (font units to pixels) * RasterizationDensity * OversampleV diff --git a/imgui_internal.h b/imgui_internal.h index 27d1ca8e1..4e8384bee 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3654,10 +3654,9 @@ struct ImFontAtlasBuilder { stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file. ImVector PackNodes; - int PackPadding; // Generally 1 to avoid bilinear filtering issues. ImVector Rects; ImVector TempBuffer; // Misc scratch buffer - ImVec2i MaxRectSize; // Largest rectangle to pack (defacto used as a "minimum texture size") + ImVec2i MaxRectSize; // Largest rectangle to pack (de-facto used as a "minimum texture size") ImVec2i MaxRectBounds; // Bottom-right most used pixels bool LockDisableResize; // Disable resizing texture bool PreloadedAllGlyphsRanges; // Set when missing ImGuiBackendFlags_RendererHasTextures features forces atlas to preload everything. From 4f27792ffeffa2a922cc6e188a19c08506476fe7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 29 Nov 2024 17:22:33 +0100 Subject: [PATCH 026/191] (Breaking) Removed atlas->TexDesiredWidth now unnecessary (github 327) --- docs/FONTS.md | 1 - imgui.h | 1 + imgui_draw.cpp | 5 ++--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/FONTS.md b/docs/FONTS.md index e9fbcbe42..514af0799 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -60,7 +60,6 @@ Some solutions: - Reduce glyphs ranges by calculating them from source localization data. You can use the `ImFontGlyphRangesBuilder` for this purpose and rebuilding your atlas between frames when new characters are needed. This will be the biggest win! - Set `io.Fonts.Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;` to disable rounding the texture height to the next power of two. -- Set `io.Fonts.TexDesiredWidth` to specify a texture width to reduce maximum texture height (see comment in `ImFontAtlas::Build()` function). Future versions of Dear ImGui should solve this problem. diff --git a/imgui.h b/imgui.h index 8161fb4b4..7a57732e6 100644 --- a/imgui.h +++ b/imgui.h @@ -3626,6 +3626,7 @@ struct ImFontAtlas float _PackNodesFactor = 1.0f; // [Obsolete] + //int TexDesiredWidth; // OBSOLETED in 1.91.5 (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ }; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 089fc0c5d..a15bf0a60 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3634,9 +3634,8 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ if (old_tex_h == -1) old_tex_h = atlas->TexData->Height; - // FIXME-NEWATLAS-V1: Handle atlas->TexDesiredWidth from user? - // FIXME-NEWATLAS-V1: What to do when reaching limits exposed by backend? - // FIXME-NEWATLAS-V1: does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? + // FIXME-NEWATLAS-V2: What to do when reaching limits exposed by backend? + // FIXME-NEWATLAS-V2: does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); // Grow texture so it follows roughly a square. From 43cc3fc8b1a426cdf5cfa9a420c670ca3526ede6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 29 Nov 2024 17:34:52 +0100 Subject: [PATCH 027/191] Fonts: optimization bake FallbackAdvanceX into IndexAdvanceX[]. --- imgui_draw.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a15bf0a60..c461accde 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3820,7 +3820,7 @@ ImFontGlyph* ImFont::BuildLoadGlyph(ImWchar codepoint) { // Mark index as not found, so we don't attempt the search twice BuildGrowIndex(codepoint + 1); - IndexAdvanceX[codepoint] = (float)IM_FONTGLYPH_INDEX_NOT_FOUND; + IndexAdvanceX[codepoint] = FallbackAdvanceX; IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; return NULL; } @@ -4546,11 +4546,10 @@ float ImFont::GetCharAdvance(ImWchar c) { if (c < (size_t)IndexAdvanceX.Size) { + // Missing glyphs fitting inside index will have stored FallbackAdvanceX already. const float x = IndexAdvanceX.Data[c]; if (x >= 0.0f) return x; - if (x == (float)IM_FONTGLYPH_INDEX_NOT_FOUND) // FIXME-NEWATLAS: could bake in index - return FallbackAdvanceX; } const ImFontGlyph* glyph = BuildLoadGlyph(c); return glyph ? glyph->AdvanceX : FallbackAdvanceX; From ac13683c269fb0d77635c6f7328ea0d13ec757a2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 2 Dec 2024 21:07:26 +0100 Subject: [PATCH 028/191] Fonts: ImFontAtlas accept DrawListSharedData not being set. --- imgui_draw.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c461accde..ae63798f9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2629,8 +2629,11 @@ void ImFontAtlas::ClearFonts() ClearInputData(); Fonts.clear_delete(); TexIsBuilt = false; - DrawListSharedData->Font = NULL; - DrawListSharedData->FontScale = DrawListSharedData->FontSize = 0.0f; + if (DrawListSharedData) + { + DrawListSharedData->Font = NULL; + DrawListSharedData->FontScale = DrawListSharedData->FontSize = 0.0f; + } } void ImFontAtlas::Clear() @@ -3155,7 +3158,7 @@ bool ImFontAtlas::Build() // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs ImFontAtlasBuildUpdateRendererHasTexturesFromContext(this); - if (DrawListSharedData->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures + if (DrawListSharedData && DrawListSharedData->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures ImFontAtlasBuildPreloadAllGlyphRanges(this); TexIsBuilt = true; @@ -3493,6 +3496,8 @@ void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedDat void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex) { ImDrawListSharedData* shared_data = atlas->DrawListSharedData; + if (shared_data == NULL) + return; for (ImDrawList* draw_list : shared_data->DrawLists) { // Replace in command-buffer @@ -3512,6 +3517,8 @@ void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) { ImDrawListSharedData* shared_data = atlas->DrawListSharedData; + if (shared_data == NULL) + return; shared_data->FontAtlas = atlas; shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; shared_data->TexUvLines = atlas->TexUvLines; From 076a1ab85cd0ad18b6c07fe08100c480f0cf3cf1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 4 Dec 2024 16:43:46 +0100 Subject: [PATCH 029/191] Fonts: Misc amends, remove _PackNodesFactor, comments. --- imgui.h | 1 - imgui_draw.cpp | 9 +++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/imgui.h b/imgui.h index 7a57732e6..b733544ac 100644 --- a/imgui.h +++ b/imgui.h @@ -3623,7 +3623,6 @@ struct ImFontAtlas int RefCount; // Number of contexts using this atlas int _PackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. int _PackedRects; // Number of packed rectangles. - float _PackNodesFactor = 1.0f; // [Obsolete] //int TexDesiredWidth; // OBSOLETED in 1.91.5 (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ae63798f9..d100d7e08 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2579,7 +2579,6 @@ ImFontAtlas::ImFontAtlas() TexGlyphPadding = 1; TexRef._TexData = NULL;// this; TexNextUniqueID = 1; - _PackNodesFactor = 1.0f; Builder = NULL; } @@ -3742,11 +3741,8 @@ void ImFontAtlasPackInit(ImFontAtlas* atlas) ImTextureData* tex = atlas->TexData; ImFontAtlasBuilder* builder = atlas->Builder; - // FIXME-NEWATLAS-V2: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 - // FIXME-NEWATLAS-V2: Experiment with number of nodes. 2024-11-05: Seems to be quite fine to reduce this. - //int pack_padding = atlas->TexGlyphPadding; + // In theory we could decide to reduce the number of nodes, e.g. halve them, and waste a little texture space, but it doesn't seem worth it. int pack_node_count = tex->Width; - //pack_node_count *= atlas->_PackNodesFactor; builder->PackNodes.resize(pack_node_count); IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width, tex->Height, builder->PackNodes.Data, builder->PackNodes.Size); @@ -3756,6 +3752,7 @@ void ImFontAtlasPackInit(ImFontAtlas* atlas) } // Important: Calling this may recreate a new texture and therefore change atlas->TexData +// FIXME-NEWATLAS-V2: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h) { IM_ASSERT(w > 0 && w <= 0xFFFF); @@ -3857,7 +3854,7 @@ void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) for (const ImTextureRect& r : tex->Updates) { IM_ASSERT(r.x >= 0 && r.y >= 0); - IM_ASSERT(r.x + r.w < tex->Width && r.y + r.h < tex->Height); + IM_ASSERT(r.x + r.w <= tex->Width && r.y + r.h <= tex->Height); // In theory should subtract PackPadding but it's currently part of atlas and mid-frame change would wreck assert. //IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update (% 4d..%-4d)->(% 4d..%-4d), texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, r.x, r.y, r.x + r.w, r.y + r.h, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); } } From a6c78019266caf640bb8ddae7db60f42677b821a Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 4 Dec 2024 19:26:45 +0100 Subject: [PATCH 030/191] Fonts: Measured and tweaked CalcTextSize() computation to minimize cost in our stress tests. --- imgui_draw.cpp | 35 ++++++++++++++++++++++++++--------- imgui_widgets.cpp | 4 +--- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d100d7e08..c21925de5 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3834,6 +3834,15 @@ ImFontGlyph* ImFont::BuildLoadGlyph(ImWchar codepoint) return glyph; } +// The point of this indirection is to not be inlined in debug mode in order to not bloat inner loop.b +IM_MSVC_RUNTIME_CHECKS_OFF +static float BuildLoadGlyphGetAdvanceOrFallback(ImFont* font, unsigned int codepoint) +{ + ImFontGlyph* glyph = font->BuildLoadGlyph((ImWchar)codepoint); + return glyph ? glyph->AdvanceX : font->FallbackAdvanceX; +} +IM_MSVC_RUNTIME_CHECKS_RESTORE + #ifndef IMGUI_DISABLE_DEBUG_TOOLS void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) { @@ -4546,18 +4555,23 @@ bool ImFont::IsGlyphInFont(ImWchar c) return false; } +// This is manually inlined in CalcTextSizeA() and CalcWordWrapPositionA(), with a non-inline call to BuildLoadGlyphGetAdvanceOrFallback(). +IM_MSVC_RUNTIME_CHECKS_OFF float ImFont::GetCharAdvance(ImWchar c) { - if (c < (size_t)IndexAdvanceX.Size) + if ((int)c < IndexAdvanceX.Size) { // Missing glyphs fitting inside index will have stored FallbackAdvanceX already. const float x = IndexAdvanceX.Data[c]; if (x >= 0.0f) return x; } + + // Same as BuildLoadGlyphGetAdvanceOrFallback(): const ImFontGlyph* glyph = BuildLoadGlyph(c); return glyph ? glyph->AdvanceX : FallbackAdvanceX; } +IM_MSVC_RUNTIME_CHECKS_RESTORE // Trim trailing space and find beginning of next line static inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end) @@ -4569,8 +4583,6 @@ static inline const char* CalcWordWrapNextLineStartA(const char* text, const cha return text; } -#define ImFontGetCharAdvanceX(_FONT, _CH) ((int)(_CH) < (_FONT)->IndexAdvanceX.Size ? (_FONT)->IndexAdvanceX.Data[_CH] : (_FONT)->FallbackAdvanceX) - // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) @@ -4624,9 +4636,11 @@ const char* ImFont::CalcWordWrapPosition(float size, const char* text, const cha } } - // FIXME-NEWATLAS-V1: Measure perf, inline etc. - //const float char_width = ImFontGetCharAdvanceX(this, c); - const float char_width = GetCharAdvance((ImWchar)c); // ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX); + // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' + float char_width = (c < (unsigned int)IndexAdvanceX.Size) ? IndexAdvanceX.Data[c] : -1.0f; + if (char_width < 0.0f) + char_width = BuildLoadGlyphGetAdvanceOrFallback(this, c); + if (ImCharIsBlankW(c)) { if (inside_word) @@ -4731,9 +4745,12 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons continue; } - // FIXME-NEWATLAS-V1: Measure perf, inline etc. - //const float char_width = ImFontGetCharAdvanceX(this, c) * scale; - const float char_width = GetCharAdvance((ImWchar)c) /* (int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX)*/ * scale; + // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' + float char_width = (c < (unsigned int)IndexAdvanceX.Size) ? IndexAdvanceX.Data[c] : -1.0f; + if (char_width < 0.0f) + char_width = BuildLoadGlyphGetAdvanceOrFallback(this, c); + char_width *= scale; + if (line_width + char_width >= max_width) { s = prev_s; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 186b639b5..47f6f7187 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3984,9 +3984,7 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c if (c == '\r') continue; - // FIXME-NEWATLAS-V1: Measure perf, inline etc. - const float char_width = font->GetCharAdvance((ImWchar)c) * scale;// ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX)* scale; - line_width += char_width; + line_width += font->GetCharAdvance((ImWchar)c) * scale; } if (text_size.x < line_width) From 553b1c301df1c4ffbb9fe5c47bfa41ea86d750b6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 5 Dec 2024 18:52:30 +0100 Subject: [PATCH 031/191] Fonts: repack without full reload, discard rectangle, fixed CustomRect api with stable id, remove public BuildInit(). --- imgui.h | 5 +- imgui_draw.cpp | 207 ++++++++++++++++++++++++++++++++++++----------- imgui_internal.h | 23 +++++- 3 files changed, 182 insertions(+), 53 deletions(-) diff --git a/imgui.h b/imgui.h index b733544ac..3013eb9e0 100644 --- a/imgui.h +++ b/imgui.h @@ -3529,7 +3529,6 @@ struct ImFontAtlas IMGUI_API void Clear(); // Clear all input and output. IMGUI_API void ClearCache(); // Clear cached glyphs - IMGUI_API void BuildInit(); // Build atlas, retrieve pixel data. // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). @@ -3583,7 +3582,7 @@ struct ImFontAtlas // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. IMGUI_API int AddCustomRectRegular(int width, int height); IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); - ImFontAtlasCustomRect* GetCustomRectByIndex(int index) { IM_ASSERT(index >= 0); return &CustomRects[index]; } + IMGUI_API ImFontAtlasCustomRect* GetCustomRectByIndex(int index); // [Internal] IMGUI_API void CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; @@ -3608,8 +3607,8 @@ struct ImFontAtlas ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight) ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. - ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. ImVector Sources; // Source/configuration data + //ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines int TexNextUniqueID; // Next value to be stored in TexData->UniqueID ImDrawListSharedData* DrawListSharedData; // In principle this could become an array (e.g. multiple contexts using same atlas) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c21925de5..48f99cdf1 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2606,7 +2606,7 @@ void ImFontAtlas::ClearInputData() font->SourcesCount = 0; } Sources.clear(); - CustomRects.clear(); + //CustomRects.clear(); // Important: we leave TexReady untouched } @@ -2646,6 +2646,7 @@ void ImFontAtlas::Clear() ImFontAtlasBuildSetupFontLoader(this, font_loader); } +// FIXME-NEWATLAS: Too widespread purpose. Clarify each call site in current WIP demo. void ImFontAtlas::ClearCache() { int tex_w = (TexData && TexData->Status != ImTextureStatus_WantDestroy) ? TexData->Width : 0; @@ -2891,7 +2892,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) // Lazily create builder on the first call to AddFont if (Builder == NULL) - BuildInit(); + ImFontAtlasBuildInit(this); // Create new font if (!font_cfg->MergeMode) @@ -3066,11 +3067,24 @@ int ImFontAtlas::AddCustomRectRegular(int width, int height) { IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - ImFontAtlasCustomRect r; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index + + if (DrawListSharedData && DrawListSharedData->RendererHasTextures) + { + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(this, r_id); + ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + return r_id; + } + else + { + // FIXME-NEWATLAS-V1: Unfinished + ImFontAtlasCustomRect r; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + //CustomRects.push_back(r); + //return CustomRects.Size - 1; // Return index + return -1; + } } int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) @@ -3089,8 +3103,18 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int r.GlyphAdvanceX = advance_x; r.GlyphOffset = offset; r.Font = font; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index + //CustomRects.push_back(r); + //return CustomRects.Size - 1; // Return index + return -1; +} + +ImFontAtlasCustomRect* ImFontAtlas::GetCustomRectByIndex(int idx) +{ + IM_STATIC_ASSERT(offsetof(ImFontAtlasCustomRect, X) == offsetof(ImFontAtlasRect, x)); + IM_STATIC_ASSERT(offsetof(ImFontAtlasCustomRect, Y) == offsetof(ImFontAtlasRect, y)); + IM_STATIC_ASSERT(offsetof(ImFontAtlasCustomRect, Width) == offsetof(ImFontAtlasRect, w)); + IM_STATIC_ASSERT(offsetof(ImFontAtlasCustomRect, Height) == offsetof(ImFontAtlasRect, h)); + return (ImFontAtlasCustomRect*)(void*)ImFontAtlasPackGetRect(this, idx); } void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const @@ -3121,35 +3145,12 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso return true; } -void ImFontAtlas::BuildInit() -{ - // Select builder - // - Note that we do not reassign to atlas->FontLoader, since it is likely to point to static data which - // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are - // using a hot-reloading scheme that messes up static data, store your own instance of ImFontLoader somewhere - // and point to it instead of pointing directly to return value of the GetBackendIOXXX functions. - if (FontLoader == NULL) - { -#ifdef IMGUI_ENABLE_FREETYPE - ImFontAtlasBuildSetupFontLoader(this, ImGuiFreeType::GetBackendIOForFreeType()); -#elif defined(IMGUI_ENABLE_STB_TRUETYPE) - ImFontAtlasBuildSetupFontLoader(this, ImFontAtlasGetFontLoaderForStbTruetype()); -#else - IM_ASSERT(0); // Invalid Build function -#endif - } - - // Create initial texture size - ImFontAtlasBuildAddTexture(this, 512, 128); - ImFontAtlasBuildInit(this); -} - bool ImFontAtlas::Build() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); if (Builder == NULL) - BuildInit(); + ImFontAtlasBuildInit(this); // Default font is none are specified if (Sources.Size == 0) @@ -3222,6 +3223,8 @@ void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) } } +// FIXME-NEWATLAS: Unused +#if 0 void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque) { ImTextureData* tex = atlas->TexData; @@ -3253,6 +3256,7 @@ void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opa tex->Height = ImMax(tex->Height, pack_rects[i].y + pack_rects[i].h); } } +#endif // Render a white-colored bitmap encoded in a string void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char) @@ -3476,6 +3480,28 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* sr font->LockSingleSrcConfigIdx = -1; } +void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font) +{ + for (ImFontGlyph& glyph : font->Glyphs) + if (glyph.PackId >= 0) + ImFontAtlasPackDiscardRect(atlas, glyph.PackId); + font->BuildClearGlyphs(); + + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = (ImFontConfig*)(void*)&font->Sources[src_n]; + IM_ASSERT(src->SizePixels > 0.0f); + + // Reload font in backend + if (atlas->FontLoader && atlas->FontLoader->FontSrcDestroy != NULL) + atlas->FontLoader->FontSrcDestroy(atlas, src); + if (atlas->FontLoader && atlas->FontLoader->FontSrcInit != NULL) + atlas->FontLoader->FontSrcInit(atlas, src); + + ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, src); // Technically this is called for each source sub-font, tho 99.9% of the time the first one fills everything. + } +} + // Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) { @@ -3588,29 +3614,40 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. - // Repack + copy pixels + // Repack, lose discarded rectangle, copy pixels // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic. ImFontAtlasPackInit(atlas); ImVector old_rects; + ImVector old_index = builder->RectsIndex; old_rects.swap(builder->Rects); - for (ImFontAtlasRect& old_r : old_rects) + + for (ImFontAtlasRectEntry& index_entry : builder->RectsIndex) { - ImFontAtlasRectId new_r_id = ImFontAtlasPackAddRect(atlas, old_r.w, old_r.h); + if (index_entry.Used == false) + continue; + ImFontAtlasRect& old_r = old_rects[index_entry.TargetIndex]; + if (old_r.w == 0 && old_r.h == 0) + continue; + ImFontAtlasRectId new_r_id = ImFontAtlasPackAddRect(atlas, old_r.w, old_r.h, &index_entry); if (new_r_id == -1) { // Undo, grow texture and try repacking again. // FIXME-NEWATLAS-TESTS: This is a very rarely exercised path! It needs to be automatically tested properly. IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize failed. Will grow.\n", new_tex->UniqueID); new_tex->WantDestroyNextFrame = true; - old_rects.swap(builder->Rects); + builder->Rects.swap(old_rects); + builder->RectsIndex = old_index; ImFontAtlasBuildSetTexture(atlas, old_tex); - ImFontAtlasBuildGrowTexture(atlas, w, h); + ImFontAtlasBuildGrowTexture(atlas, w, h); // Recurse return; } + IM_ASSERT(new_r_id == builder->RectsIndex.index_from_ptr(&index_entry)); ImFontAtlasRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h); } - IM_ASSERT(old_rects.Size == builder->Rects.Size); + IM_ASSERT(old_rects.Size == builder->Rects.Size + builder->RectsDiscardedCount); + builder->RectsDiscardedCount = 0; + builder->RectsDiscardedSurface = 0; // Patch glyphs UV for (ImFont* font : atlas->Fonts) @@ -3645,6 +3682,7 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); // Grow texture so it follows roughly a square. + // FIXME-NEWATLAS-V1: Take account of RectsDiscardedSurface: may not need to grow. int new_tex_w = (old_tex_h < old_tex_w) ? old_tex_w : old_tex_w * 2; int new_tex_h = (old_tex_h < old_tex_w) ? old_tex_h * 2 : old_tex_h; @@ -3669,14 +3707,15 @@ void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) // FIXME-NEWATLAS: Expose atlas->TexMinWidth etc. const int min_w = ImMax(builder->MaxRectSize.x, 512); const int min_h = builder->MaxRectSize.y; - const int surface_sqrt = (int)sqrtf((float)atlas->_PackedSurface); + const int surface_approx = atlas->_PackedSurface - builder->RectsDiscardedSurface; // Expected surface after repack + const int surface_sqrt = (int)sqrtf((float)surface_approx); int new_tex_w; int new_tex_h; if (min_w >= min_h) { new_tex_w = ImMax(min_w, ImUpperPowerOfTwo(surface_sqrt)); - new_tex_h = ImMax(min_h, (int)(atlas->_PackedSurface / new_tex_w)); + new_tex_h = ImMax(min_h, (int)((surface_approx + new_tex_w - 1) / new_tex_w)); if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) new_tex_h = ImUpperPowerOfTwo(new_tex_h); } @@ -3685,10 +3724,10 @@ void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) new_tex_h = ImMax(min_h, ImUpperPowerOfTwo(surface_sqrt)); if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) new_tex_h = ImUpperPowerOfTwo(new_tex_h); - new_tex_w = ImMax(min_w, (int)(atlas->_PackedSurface / new_tex_h)); + new_tex_w = ImMax(min_w, (int)((surface_approx + new_tex_h - 1) / new_tex_h)); } - if (new_tex_w == old_tex_w && new_tex_h == old_tex_h) + if (builder->RectsDiscardedCount == 0 && new_tex_w == old_tex_w && new_tex_h == old_tex_h) return; ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); @@ -3699,6 +3738,25 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) { ImFontAtlasBuilder* builder = atlas->Builder; + // Select Backend + // - Note that we do not reassign to atlas->FontBackendIO, since it is likely to point to static data which + // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are + // using a hot-reloading scheme that messes up static data, store your own instance of ImFontBackendIO somewhere + // and point to it instead of pointing directly to return value of the GetBackendIOXXX functions. + if (atlas->FontLoader == NULL) + { +#ifdef IMGUI_ENABLE_FREETYPE + ImFontAtlasBuildSetupFontLoader(atlas, ImGuiFreeType::GetFontLoader()); +#elif defined(IMGUI_ENABLE_STB_TRUETYPE) + ImFontAtlasBuildSetupFontLoader(atlas, ImFontAtlasGetFontLoaderForStbTruetype()); +#else + IM_ASSERT(0); // Invalid Build function +#endif + } + // Create initial texture size + if (atlas->TexData == NULL) + ImFontAtlasBuildAddTexture(atlas, 512, 128); + const bool builder_is_new = (builder == NULL); if (builder_is_new) builder = atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); @@ -3736,7 +3794,7 @@ void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) atlas->Builder = NULL; } -void ImFontAtlasPackInit(ImFontAtlas* atlas) +void ImFontAtlasPackInit(ImFontAtlas * atlas) { ImTextureData* tex = atlas->TexData; ImFontAtlasBuilder* builder = atlas->Builder; @@ -3751,9 +3809,52 @@ void ImFontAtlasPackInit(ImFontAtlas* atlas) builder->MaxRectBounds = ImVec2i(0, 0); } +// This is essentially a free-list pattern, it may be nice to wrap it into a dedicated type. +static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int rect_idx) +{ + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + int index_idx; + ImFontAtlasRectEntry* index_entry; + if (builder->RectsIndexFreeListStart < 0) + { + builder->RectsIndex.resize(builder->RectsIndex.Size + 1); + index_idx = builder->RectsIndex.Size - 1; + index_entry = &builder->RectsIndex[index_idx]; + } + else + { + index_idx = builder->RectsIndexFreeListStart; + index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->Used == false); + builder->RectsIndexFreeListStart = index_entry->TargetIndex; + } + index_entry->TargetIndex = rect_idx; + index_entry->Used = 1; + return (ImFontAtlasRectId)index_idx; +} + +// This is expected to be called in batches and followed by a repack +void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + IM_ASSERT(id >= 0); + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; + IM_ASSERT(index_entry->Used && index_entry->TargetIndex >= 0); + + ImFontAtlasRect* rect = ImFontAtlasPackGetRect(atlas, id); + index_entry->Used = false; + index_entry->TargetIndex = builder->RectsIndexFreeListStart; + + const int pack_padding = atlas->TexGlyphPadding; + builder->RectsIndexFreeListStart = id; + builder->RectsDiscardedCount++; + builder->RectsDiscardedSurface += (rect->w + pack_padding) * (rect->h + pack_padding); + rect->w = rect->h = 0; // Clear rectangle so it won't be packed again +} + // Important: Calling this may recreate a new texture and therefore change atlas->TexData // FIXME-NEWATLAS-V2: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 -ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h) +ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry) { IM_ASSERT(w > 0 && w <= 0xFFFF); IM_ASSERT(h > 0 && h <= 0xFFFF); @@ -3796,13 +3897,25 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h) atlas->_PackedRects++; builder->Rects.push_back(r); - return builder->Rects.Size - 1; + if (overwrite_entry != NULL) + { + // Write into an existing entry instead of adding one (used during repack) + IM_ASSERT(overwrite_entry->Used); + overwrite_entry->TargetIndex = builder->Rects.Size - 1; + return builder->RectsIndex.index_from_ptr(overwrite_entry); + } + else + { + return ImFontAtlasPackAllocRectEntry(atlas, builder->Rects.Size - 1); + } } ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; - return &builder->Rects[id]; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; + IM_ASSERT(index_entry->Used); + return &builder->Rects[index_entry->TargetIndex]; } ImFontGlyph* ImFont::BuildLoadGlyph(ImWchar codepoint) diff --git a/imgui_internal.h b/imgui_internal.h index 4e8384bee..67a4b50cb 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -142,6 +142,7 @@ struct ImGuiTextIndex; // Maintain a line index for a text buffer. struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances struct ImFontAtlasRect; // Packed rectangle (same as ImTextureRect) +struct ImFontAtlasRectEntry; // Packed rectangle lookup entry struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas // ImGui @@ -3639,7 +3640,6 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- -// Packed rectangle (same as ImTextureRect) struct ImFontAtlasRect { unsigned short x, y; @@ -3647,6 +3647,17 @@ struct ImFontAtlasRect }; typedef int ImFontAtlasRectId; // <0 when invalid +// Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) +// User are returned ImFontAtlasRectId values which are meant to be persistent. +// We handle this with an indirection. While Rects[] may be in theory shuffled, compacted etc., RectsIndex[] cannot it is keyed by ImFontAtlasRectId. +// RectsIndex[] is used both as an index into Rects[] and an index into itself. This is basically a free-list. See ImFontAtlasBuildAllocRectIndexEntry() code. +// Having this also makes it easier to e.g. sort rectangles during repack. +struct ImFontAtlasRectEntry +{ + int TargetIndex : 31; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. + unsigned int Used : 1; +}; + // Internal storage for incrementally packing and building a ImFontAtlas struct stbrp_context_opaque { char data[80]; }; struct stbrp_node; @@ -3655,7 +3666,11 @@ struct ImFontAtlasBuilder stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file. ImVector PackNodes; ImVector Rects; + ImVector RectsIndex; // ImFontAtlasRectId -> index into Rects[] ImVector TempBuffer; // Misc scratch buffer + int RectsIndexFreeListStart;// First unused entry + int RectsDiscardedCount; + int RectsDiscardedSurface; ImVec2i MaxRectSize; // Largest rectangle to pack (de-facto used as a "minimum texture size") ImVec2i MaxRectBounds; // Bottom-right most used pixels bool LockDisableResize; // Disable resizing texture @@ -3665,7 +3680,7 @@ struct ImFontAtlasBuilder ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. ImFontAtlasRectId PackIdLinesTexData; - ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); } + ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); RectsIndexFreeListStart = -1; } }; // FIXME-NEWATLAS: Cleanup @@ -3683,11 +3698,13 @@ IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); -IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h); +IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); +IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); From a51a26e2aa9717cc77d8a91416e31f611d4b72db Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 11 Dec 2024 19:21:10 +0100 Subject: [PATCH 032/191] Fonts: use a structure for post-processing - easier to pass things around and iterate on. --- imgui_draw.cpp | 45 +++++++++++++------------------- imgui_internal.h | 21 +++++++++++++-- misc/freetype/imgui_freetype.cpp | 5 ++-- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 48f99cdf1..10e69644f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2460,8 +2460,8 @@ void ImTextureData::DestroyPixels() // - ImFontAtlas::BuildCompactTexture() // - ImFontAtlasUpdateTextures() //----------------------------------------------------------------------------- -// - ImFontAtlasTextureBlockConvertAndPostProcess() // - ImFontAtlasTextureBlockConvert() +// - ImFontAtlasTextureBlockPostProcess() // - ImFontAtlasTextureBlockPostProcessMultiply() // - ImFontAtlasTextureBlockCopy() // - ImFontAtlasTextureBlockQueueUpload() @@ -2734,17 +2734,11 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) // Source buffer may be written to (used for in-place mods). // Post-process hooks may eventually be added here. -void ImFontAtlasTextureBlockConvertAndPostProcess(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) +void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data) { - IM_UNUSED(atlas); - IM_UNUSED(font); - IM_UNUSED(glyph); - // Multiply operator (legacy) - if (src->RasterizerMultiply != 1.0f) - ImFontAtlasTextureBlockPostProcessMultiply(atlas, font, src, glyph, src_pixels, src_fmt, w, h, src_pitch, src->RasterizerMultiply); - - ImFontAtlasTextureBlockConvert(src_pixels, src_fmt, src_pitch, dst, dst_fmt, dst_pitch, w, h); + if (data->FontSrc->RasterizerMultiply != 1.0f) + ImFontAtlasTextureBlockPostProcessMultiply(data, data->FontSrc->RasterizerMultiply); } void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) @@ -2782,34 +2776,30 @@ void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFo } } -void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* pixels, ImTextureFormat format, int w, int h, int pitch, float in_multiply_factor) +void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor) { - IM_UNUSED(atlas); - IM_UNUSED(font); - IM_UNUSED(src); - IM_UNUSED(glyph); - IM_ASSERT(in_multiply_factor >= 0.0f); - IM_ASSERT_PARANOID(w <= pitch); - if (format == ImTextureFormat_Alpha8) + unsigned char* pixels = data->Pixels; + int pitch = data->Pitch; + if (data->Format == ImTextureFormat_Alpha8) { - for (int ny = h; ny > 0; ny--, pixels += pitch) + for (int ny = data->Height; ny > 0; ny--, pixels += pitch) { ImU8* p = (ImU8*)pixels; - for (int nx = w; nx > 0; nx--, p++) + for (int nx = data->Width; nx > 0; nx--, p++) { - unsigned int v = ImMin((unsigned int)(*p * in_multiply_factor), (unsigned int)255); + unsigned int v = ImMin((unsigned int)(*p * multiply_factor), (unsigned int)255); *p = (unsigned char)v; } } } - else if (format == ImTextureFormat_RGBA32) + else if (data->Format == ImTextureFormat_RGBA32) { - for (int ny = h; ny > 0; ny--, pixels += pitch) + for (int ny = data->Height; ny > 0; ny--, pixels += pitch) { ImU32* p = (ImU32*)(void*)pixels; - for (int nx = w; nx > 0; nx--, p++) + for (int nx = data->Width; nx > 0; nx--, p++) { - unsigned int a = ImMin((unsigned int)(((*p >> IM_COL32_A_SHIFT) & 0xFF) * in_multiply_factor), (unsigned int)255); + unsigned int a = ImMin((unsigned int)(((*p >> IM_COL32_A_SHIFT) & 0xFF) * multiply_factor), (unsigned int)255); *p = IM_COL32((*p >> IM_COL32_R_SHIFT) & 0xFF, (*p >> IM_COL32_G_SHIFT) & 0xFF, (*p >> IM_COL32_B_SHIFT) & 0xFF, a); } } @@ -4156,8 +4146,9 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, // Copy to texture, post-process and queue update for backend ImTextureData* tex = atlas->TexData; IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); - ImFontAtlasTextureBlockConvertAndPostProcess(atlas, font, src, &font->Glyphs.back(), - bitmap_pixels, ImTextureFormat_Alpha8, w, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); + ImFontAtlasTextureBlockConvert(bitmap_pixels, ImTextureFormat_Alpha8, w, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); + ImFontAtlasPostProcessData pp_data = { atlas, font, src, &font->Glyphs.back(), tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h }; + ImFontAtlasTextureBlockPostProcess(&pp_data); ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } else diff --git a/imgui_internal.h b/imgui_internal.h index 67a4b50cb..2c97cae01 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -144,6 +144,7 @@ struct ImDrawListSharedData; // Data shared between all ImDrawList instan struct ImFontAtlasRect; // Packed rectangle (same as ImTextureRect) struct ImFontAtlasRectEntry; // Packed rectangle lookup entry struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas +struct ImFontAtlasPostProcessData; // Data available to potential post-process functions // ImGui struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) @@ -3658,6 +3659,22 @@ struct ImFontAtlasRectEntry unsigned int Used : 1; }; +// Data available to potential post-process functions +struct ImFontAtlasPostProcessData +{ + ImFontAtlas* FontAtlas; + ImFont* Font; + ImFontConfig* FontSrc; + ImFontGlyph* Glyph; + + // Pixel data + unsigned char* Pixels; + ImTextureFormat Format; + int Pitch; + int Width; + int Height; +}; + // Internal storage for incrementally packing and building a ImFontAtlas struct stbrp_context_opaque { char data[80]; }; struct stbrp_node; @@ -3714,9 +3731,9 @@ IMGUI_API void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* at IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasTextureBlockConvertAndPostProcess(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); -IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* pixels, ImTextureFormat format, int w, int h, int pitch, float in_multiply_factor); +IMGUI_API void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data); +IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor); IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index a8dfa950e..efe8612ba 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -541,8 +541,9 @@ bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontCon // Copy to texture, post-process and queue update for backend ImTextureData* tex = atlas->TexData; IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); - ImFontAtlasTextureBlockConvertAndPostProcess(atlas, font, src, &font->Glyphs.back(), - temp_buffer, ImTextureFormat_RGBA32, w * 4, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); + ImFontAtlasTextureBlockConvert(temp_buffer, ImTextureFormat_RGBA32, w * 4, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); + ImFontAtlasPostProcessData pp_data = { atlas, font, src, &font->Glyphs.back(), tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h }; + ImFontAtlasTextureBlockPostProcess(&pp_data); ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } else From ef1521b472147c372d4239cda454a8275b201361 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Dec 2024 14:40:45 +0100 Subject: [PATCH 033/191] Fonts: fix for password fields --- imgui_widgets.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 47f6f7187..c632b45a4 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4321,9 +4321,12 @@ void ImGui::PushPasswordFont() out_font->Ascent = in_font->Ascent; out_font->Descent = in_font->Descent; out_font->ContainerAtlas = in_font->ContainerAtlas; - out_font->FallbackGlyphIndex = in_font->Glyphs.index_from_ptr(glyph); // FIXME: broken + out_font->Glyphs.resize(0); + out_font->Glyphs.push_back(*glyph); + out_font->FallbackGlyphIndex = 0; out_font->FallbackAdvanceX = glyph->AdvanceX; - IM_ASSERT(out_font->Glyphs.Size == 0 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0); + out_font->LockDisableLoading = true; + IM_ASSERT(out_font->Glyphs.Size == 1 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0); PushFont(out_font); } From 4399599de9c10f4adee0b1e484ceb3b5e9d2e399 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Dec 2024 15:21:22 +0100 Subject: [PATCH 034/191] Fonts: ClearCache(), ImFontAtlasBuildGetTextureSizeEstimate(), tweak clearing functions. --- imgui.h | 8 +++--- imgui_draw.cpp | 66 ++++++++++++++++++++++++++++++------------------ imgui_internal.h | 1 + 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/imgui.h b/imgui.h index 3013eb9e0..3ce13eb88 100644 --- a/imgui.h +++ b/imgui.h @@ -3523,12 +3523,12 @@ struct ImFontAtlas IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, 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. // FIXME-NEWATLAS: Clarify meaning/purpose - IMGUI_API void ClearInputData(); // 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 ClearFonts(); // Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). - IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. IMGUI_API void Clear(); // Clear all input and output. - IMGUI_API void ClearCache(); // Clear cached glyphs + // As we are transitioning toward a new font system, we expect to obsolete those soon: + 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 ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). + IMGUI_API void ClearTexData(); // [OBSOLETE] Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. // Build atlas, retrieve pixel data. // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 10e69644f..631cb22e6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2569,8 +2569,9 @@ static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3 { ImVec2(109,0),ImVec2(13,15), ImVec2( 6, 7) }, // ImGuiMouseCursor_NotAllowed }; -#define IM_FONTGLYPH_INDEX_UNUSED ((ImU16)-1) // 0xFFFF -#define IM_FONTGLYPH_INDEX_NOT_FOUND ((ImU16)-2) // 0xFFFE +#define IM_FONTGLYPH_INDEX_UNUSED ((ImU16)-1) // 0xFFFF +#define IM_FONTGLYPH_INDEX_NOT_FOUND ((ImU16)-2) // 0xFFFE +#define IM_FONTATLAS_DEFAULT_TEXTURE_SIZE ImVec2i(512, 128) ImFontAtlas::ImFontAtlas() { @@ -2600,11 +2601,14 @@ void ImFontAtlas::ClearInputData() // When clearing this we lose access to the font name and other information used to build the font. for (ImFont* font : Fonts) + { if (font->Sources >= Sources.Data && font->Sources < Sources.Data + Sources.Size) { font->Sources = NULL; font->SourcesCount = 0; } + font->LockDisableLoading = true; + } Sources.clear(); //CustomRects.clear(); // Important: we leave TexReady untouched @@ -2616,15 +2620,14 @@ void ImFontAtlas::ClearTexData() TexList.clear(); IM_DELETE(TexData); TexData = NULL; - // TexData.Destroy(); - //IM_ASSERT(0); - // Important: we leave TexReady untouched + // Important: we leave TexReady untouched } void ImFontAtlas::ClearFonts() { // FIXME-NEWATLAS: Illegal to remove currently bound font. IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + ImFontAtlasBuildDestroy(this); ClearInputData(); Fonts.clear_delete(); TexIsBuilt = false; @@ -2637,7 +2640,7 @@ void ImFontAtlas::ClearFonts() void ImFontAtlas::Clear() { - //IM_DELETE(Builder); // FIXME-NEW-ATLAS: ClearXXX functions + //IM_DELETE(Builder); // FIXME-NEW-ATLAS: Clarify ClearXXX functions const ImFontLoader* font_loader = FontLoader; ImFontAtlasBuildSetupFontLoader(this, NULL); ClearInputData(); @@ -2649,11 +2652,9 @@ void ImFontAtlas::Clear() // FIXME-NEWATLAS: Too widespread purpose. Clarify each call site in current WIP demo. void ImFontAtlas::ClearCache() { - int tex_w = (TexData && TexData->Status != ImTextureStatus_WantDestroy) ? TexData->Width : 0; - int tex_h = (TexData && TexData->Status != ImTextureStatus_WantDestroy) ? TexData->Height : 0; + ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(this); ImFontAtlasBuildDestroy(this); - if (tex_w != 0 && tex_h != 0) - ImFontAtlasBuildAddTexture(this, tex_w, tex_h); + ImFontAtlasBuildAddTexture(this, new_tex_size.x, new_tex_size.y); ImFontAtlasBuildInit(this); } @@ -3139,6 +3140,13 @@ bool ImFontAtlas::Build() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + if (TexData && TexData->Format != TexDesiredFormat) + { + ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(this); + ImFontAtlasBuildDestroy(this); + ImFontAtlasBuildAddTexture(this, new_tex_size.x, new_tex_size.y); + } + if (Builder == NULL) ImFontAtlasBuildInit(this); @@ -3168,7 +3176,10 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon return; IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); + // Note that texture size estimate is likely incorrect in this situation, as Freetype backend doesn't use oversampling. + ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); ImFontAtlasBuildDestroy(atlas); + if (atlas->FontLoader && atlas->FontLoader->LoaderShutdown) { atlas->FontLoader->LoaderShutdown(atlas); @@ -3178,8 +3189,9 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon atlas->FontLoaderName = font_loader ? font_loader->Name : "NULL"; if (atlas->FontLoader && atlas->FontLoader->LoaderInit) atlas->FontLoader->LoaderInit(atlas); - if (atlas->Builder && font_loader != NULL) - atlas->ClearCache(); + + ImFontAtlasBuildAddTexture(atlas, new_tex_size.x, new_tex_size.y); + ImFontAtlasBuildInit(atlas); } // Preload all glyph ranges for legacy backends. @@ -3684,17 +3696,13 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); } -// You should not need to call this manually! -// If you think you do, let us know and we can advise about policies auto-compact. -void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) +// FIXME-NEWATLAS: Expose atlas->TexMinWidth etc. +ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) { + if (atlas->Builder == NULL || atlas->TexData == NULL || atlas->TexData->Status == ImTextureStatus_WantDestroy) + return IM_FONTATLAS_DEFAULT_TEXTURE_SIZE; + ImFontAtlasBuilder* builder = atlas->Builder; - - ImTextureData* old_tex = atlas->TexData; - int old_tex_w = old_tex->Width; - int old_tex_h = old_tex->Height; - - // FIXME-NEWATLAS: Expose atlas->TexMinWidth etc. const int min_w = ImMax(builder->MaxRectSize.x, 512); const int min_h = builder->MaxRectSize.y; const int surface_approx = atlas->_PackedSurface - builder->RectsDiscardedSurface; // Expected surface after repack @@ -3716,11 +3724,21 @@ void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) new_tex_h = ImUpperPowerOfTwo(new_tex_h); new_tex_w = ImMax(min_w, (int)((surface_approx + new_tex_h - 1) / new_tex_h)); } + return ImVec2i(new_tex_w, new_tex_h); +} - if (builder->RectsDiscardedCount == 0 && new_tex_w == old_tex_w && new_tex_h == old_tex_h) +// You should not need to call this manually! +// If you think you do, let us know and we can advise about policies auto-compact. +void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + ImTextureData* old_tex = atlas->TexData; + ImVec2i old_tex_size = ImVec2i(old_tex->Width, old_tex->Height); + ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); + if (builder->RectsDiscardedCount == 0 && new_tex_size.x == old_tex_size.x && new_tex_size.y == old_tex_size.y) return; - ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); + ImFontAtlasBuildRepackTexture(atlas, new_tex_size.x, new_tex_size.y); } // Start packing over current empty texture @@ -3745,7 +3763,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) } // Create initial texture size if (atlas->TexData == NULL) - ImFontAtlasBuildAddTexture(atlas, 512, 128); + ImFontAtlasBuildAddTexture(atlas, IM_FONTATLAS_DEFAULT_TEXTURE_SIZE.x, IM_FONTATLAS_DEFAULT_TEXTURE_SIZE.y); const bool builder_is_new = (builder == NULL); if (builder_is_new) diff --git a/imgui_internal.h b/imgui_internal.h index 2c97cae01..53f0dbd07 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3712,6 +3712,7 @@ IMGUI_API ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w IMGUI_API void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h); IMGUI_API void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); +IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas); IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src); From df8450d928822f8dacd9f07e0bc50dd095c8228d Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 20 Dec 2024 22:33:11 +0100 Subject: [PATCH 035/191] Fonts: marked ImFontAtlas::Build() as obsolete --- imgui.cpp | 4 ++-- imgui.h | 6 +++--- imgui_draw.cpp | 41 ++++++++++++++++++++++------------------- imgui_internal.h | 1 + 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index ce2b06462..249456c7e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5243,9 +5243,9 @@ void ImGui::NewFrame() // Check that font atlas was built or backend support texture reload in which case we can build now ImFontAtlas* atlas = g.IO.Fonts; if (!atlas->TexIsBuilt && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) - atlas->Build(); + ImFontAtlasBuildMain(atlas); else // Legacy backend - IM_ASSERT(atlas->TexIsBuilt && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); + IM_ASSERT(atlas->TexIsBuilt && "Backend does not support ImGuiBackendFlags_RendererHasTexUpdates, and font atlas is not built! Update backend OR make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()."); // Check and assert for various common IO and Configuration mistakes ErrorCheckNewFrameSanityChecks(); diff --git a/imgui.h b/imgui.h index 3ce13eb88..7d04ebcd2 100644 --- a/imgui.h +++ b/imgui.h @@ -3530,15 +3530,15 @@ struct ImFontAtlas IMGUI_API void ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). IMGUI_API void ClearTexData(); // [OBSOLETE] Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void BuildGrowTexture(); + IMGUI_API void BuildCompactTexture(); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Build atlas, retrieve pixel data. // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). // The pitch is always = Width * BytesPerPixels (1 or 4) // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. - IMGUI_API void BuildGrowTexture(); - IMGUI_API void BuildCompactTexture(); -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel void SetTexID(ImTextureID id) { TexRef._TexData = NULL; TexRef._TexID = id; } // Called by legacy backends. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 631cb22e6..13060843e 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2466,9 +2466,9 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasTextureBlockCopy() // - ImFontAtlasTextureBlockQueueUpload() //----------------------------------------------------------------------------- +// - ImFontAtlas::Build() [legacy] // - ImFontAtlas::GetTexDataAsAlpha8() [legacy] // - ImFontAtlas::GetTexDataAsRGBA32() [legacy] -// - ImFontAtlas::Build() //----------------------------------------------------------------------------- // - ImFontAtlas::AddFont() // - ImFontAtlas::AddFontDefault() @@ -2872,6 +2872,12 @@ void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, { GetTexDataAsFormat(this, ImTextureFormat_RGBA32, out_pixels, out_width, out_height, out_bytes_per_pixel); } + +bool ImFontAtlas::Build() +{ + ImFontAtlasBuildMain(this); + return true; +} #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) @@ -3136,31 +3142,28 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso return true; } -bool ImFontAtlas::Build() +void ImFontAtlasBuildMain(ImFontAtlas* atlas) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - - if (TexData && TexData->Format != TexDesiredFormat) + IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); + if (atlas->TexData && atlas->TexData->Format != atlas->TexDesiredFormat) { - ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(this); - ImFontAtlasBuildDestroy(this); - ImFontAtlasBuildAddTexture(this, new_tex_size.x, new_tex_size.y); + ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); + ImFontAtlasBuildDestroy(atlas); + ImFontAtlasBuildAddTexture(atlas, new_tex_size.x, new_tex_size.y); } - if (Builder == NULL) - ImFontAtlasBuildInit(this); + if (atlas->Builder == NULL) + ImFontAtlasBuildInit(atlas); // Default font is none are specified - if (Sources.Size == 0) - AddFontDefault(); + if (atlas->Sources.Size == 0) + atlas->AddFontDefault(); - // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs - ImFontAtlasBuildUpdateRendererHasTexturesFromContext(this); - if (DrawListSharedData && DrawListSharedData->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures - ImFontAtlasBuildPreloadAllGlyphRanges(this); - TexIsBuilt = true; - - return true; + // [LEGACY] For backends not supporting RendererHasTexUpdates: preload all glyphs + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); + if (atlas->DrawListSharedData && atlas->DrawListSharedData->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures + ImFontAtlasBuildPreloadAllGlyphRanges(atlas); + atlas->TexIsBuilt = true; } void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v) diff --git a/imgui_internal.h b/imgui_internal.h index 53f0dbd07..48090ee90 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3705,6 +3705,7 @@ IMGUI_API void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char); +IMGUI_API void ImFontAtlasBuildMain(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas); From cec3e945f02bb2fb2e12d1bbd984971ab4f2be37 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Dec 2024 16:21:07 +0100 Subject: [PATCH 036/191] Fonts: added ImFontAtlas::RemoveFont(), fixed various leaks. --- imgui.cpp | 6 ++++ imgui.h | 5 ++-- imgui_draw.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++++--- imgui_internal.h | 1 + 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 249456c7e..adb1e84f1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16484,6 +16484,12 @@ void ImGui::DebugNodeFont(ImFont* font) } if (SmallButton("Set as default")) GetIO().FontDefault = font; + if (font->ContainerAtlas->Fonts.Size > 1 && !font->ContainerAtlas->Locked) + { + SameLine(); + if (SmallButton("Remove")) + font->ContainerAtlas->RemoveFont(font); + } // Display details SetNextItemWidth(GetFontSize() * 8); diff --git a/imgui.h b/imgui.h index 7d04ebcd2..e237e90ae 100644 --- a/imgui.h +++ b/imgui.h @@ -3521,10 +3521,11 @@ struct ImFontAtlas IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, 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, 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, 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); // FIXME-NEWATLAS: Clarify meaning/purpose - IMGUI_API void Clear(); // Clear all input and output. - IMGUI_API void ClearCache(); // Clear cached glyphs + IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures) + IMGUI_API void ClearCache(); // Clear cached glyphs and textures. // As we are transitioning toward a new font system, we expect to obsolete those soon: 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 ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 13060843e..faabec1d5 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2476,6 +2476,8 @@ void ImTextureData::DestroyPixels() // - ImFontAtlas::AddFontFromMemoryTTF() // - ImFontAtlas::AddFontFromMemoryCompressedTTF() // - ImFontAtlas::AddFontFromMemoryCompressedBase85TTF() +// - ImFontAtlas::RemoveFont() +// - ImFontAtlasBuildNotifySetFont() //----------------------------------------------------------------------------- // - ImFontAtlas::AddCustomRectRegular() // - ImFontAtlas::AddCustomRectFontGlyph() @@ -2593,11 +2595,15 @@ void ImFontAtlas::ClearInputData() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); for (ImFontConfig& font_cfg : Sources) + { + if (FontLoader && FontLoader->FontSrcDestroy != NULL) + FontLoader->FontSrcDestroy(this, &font_cfg); if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas) { IM_FREE(font_cfg.FontData); font_cfg.FontData = NULL; } + } // When clearing this we lose access to the font name and other information used to build the font. for (ImFont* font : Fonts) @@ -2641,12 +2647,12 @@ void ImFontAtlas::ClearFonts() void ImFontAtlas::Clear() { //IM_DELETE(Builder); // FIXME-NEW-ATLAS: Clarify ClearXXX functions - const ImFontLoader* font_loader = FontLoader; - ImFontAtlasBuildSetupFontLoader(this, NULL); + //const ImFontLoader* font_loader = FontLoader; + //ImFontAtlasBuildSetupFontLoader(this, NULL); ClearInputData(); ClearTexData(); ClearFonts(); - ImFontAtlasBuildSetupFontLoader(this, font_loader); + //ImFontAtlasBuildSetupFontLoader(this, font_loader); } // FIXME-NEWATLAS: Too widespread purpose. Clarify each call site in current WIP demo. @@ -3041,6 +3047,59 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed return font; } +// We allow old_font == new_font which forces updating all values (e.g. sizes) +static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, ImFont* new_font) +{ + if (ImDrawListSharedData* shared_data = atlas->DrawListSharedData) + { + if (shared_data->Font == old_font) + shared_data->Font = new_font; + if (ImGuiContext* ctx = shared_data->Context) + { + if (ctx->IO.FontDefault == old_font) + ctx->IO.FontDefault = new_font; + if (ctx->Font == old_font) + { + ImGuiContext* curr_ctx = ImGui::GetCurrentContext(); + bool need_bind_ctx = ctx != curr_ctx; + if (need_bind_ctx) + ImGui::SetCurrentContext(ctx); + ImGui::SetCurrentFont(new_font); + if (need_bind_ctx) + ImGui::SetCurrentContext(curr_ctx); + } + } + } +} + +void ImFontAtlas::RemoveFont(ImFont* font) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + ImFontAtlasBuildDiscardFontGlyphs(this, font); + + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = (ImFontConfig*)(void*)&font->Sources[src_n]; + if (FontLoader && FontLoader->FontSrcDestroy != NULL) + FontLoader->FontSrcDestroy(this, src); + if (src->FontData != NULL && src->FontDataOwnedByAtlas) + IM_FREE(src->FontData); + } + + bool removed = Fonts.find_erase(font); + IM_ASSERT(removed); + + Sources.erase(font->Sources, font->Sources + font->SourcesCount); + ImFontAtlasBuildUpdatePointers(this); + + font->ContainerAtlas = NULL; + IM_DELETE(font); + + // Notify external systems + ImFont* new_current_font = Fonts.empty() ? NULL : Fonts[0]; + ImFontAtlasBuildNotifySetFont(this, font, new_current_font); +} + // FIXME-NEWATLAS-V1: Feature is broken for now. /* // Register custom rectangle glyphs @@ -3485,13 +3544,19 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* sr font->LockSingleSrcConfigIdx = -1; } -void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font) +void ImFontAtlasBuildDiscardFontGlyphs(ImFontAtlas* atlas, ImFont* font) { for (ImFontGlyph& glyph : font->Glyphs) if (glyph.PackId >= 0) ImFontAtlasPackDiscardRect(atlas, glyph.PackId); font->BuildClearGlyphs(); + font->FallbackChar = font->EllipsisChar = 0; +} +// Discard old glyphs and reload font. Use if changing font size. +void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font) +{ + ImFontAtlasBuildDiscardFontGlyphs(atlas, font); for (int src_n = 0; src_n < font->SourcesCount; src_n++) { ImFontConfig* src = (ImFontConfig*)(void*)&font->Sources[src_n]; @@ -3505,6 +3570,9 @@ void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font) ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, src); // Technically this is called for each source sub-font, tho 99.9% of the time the first one fills everything. } + + // Notify external systems + ImFontAtlasBuildNotifySetFont(atlas, font, font); } // Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* @@ -4023,11 +4091,13 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src->FontData, src->FontNo); if (font_offset < 0) { + IM_DELETE(bd_font_data); IM_ASSERT_USER_ERROR(0, "stbtt_GetFontOffsetForIndex(): FontData is incorrect, or FontNo cannot be found."); return false; } if (!stbtt_InitFont(&bd_font_data->FontInfo, (unsigned char*)src->FontData, font_offset)) { + IM_DELETE(bd_font_data); IM_ASSERT_USER_ERROR(0, "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); return false; } diff --git a/imgui_internal.h b/imgui_internal.h index 48090ee90..f340b8e5e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3717,6 +3717,7 @@ IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildDiscardFontGlyphs(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); From a2bc3d81c2f1866dc6abc454221adfb6b5c0ef43 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 27 Dec 2024 11:23:22 +0100 Subject: [PATCH 037/191] Fonts: Fixed support for multiple contexts. --- imgui.cpp | 7 +++-- imgui.h | 3 +- imgui_draw.cpp | 75 ++++++++++++++++++++++++------------------------ imgui_internal.h | 1 - 4 files changed, 44 insertions(+), 42 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index adb1e84f1..0cf8bce21 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5184,7 +5184,8 @@ static void ImGui::UpdateTexturesNewFrame() // FIXME-NEWATLAS: How to reach/target all atlas? ImGuiContext& g = *GImGui; ImFontAtlas* atlas = g.IO.Fonts; - ImFontAtlasUpdateNewFrame(atlas); + if (g.FontAtlasOwnedByContext) + ImFontAtlasUpdateNewFrame(atlas); } // Build a single texture list @@ -5245,7 +5246,7 @@ void ImGui::NewFrame() if (!atlas->TexIsBuilt && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) ImFontAtlasBuildMain(atlas); else // Legacy backend - IM_ASSERT(atlas->TexIsBuilt && "Backend does not support ImGuiBackendFlags_RendererHasTexUpdates, and font atlas is not built! Update backend OR make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()."); + IM_ASSERT(atlas->TexIsBuilt && "Backend does not support ImGuiBackendFlags_RendererHasTextures, and font atlas is not built! Update backend OR make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()."); // Check and assert for various common IO and Configuration mistakes ErrorCheckNewFrameSanityChecks(); @@ -15615,7 +15616,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) if (TreeNode("Loader", "Loader: \'%s\'", atlas->FontLoaderName ? atlas->FontLoaderName : "NULL")) { const ImFontLoader* loader_current = atlas->FontLoader; - BeginDisabled(!atlas->DrawListSharedData || !atlas->DrawListSharedData->RendererHasTextures); + BeginDisabled(!atlas->RendererHasTextures); #ifdef IMGUI_ENABLE_STB_TRUETYPE const ImFontLoader* loader_stbtruetype = ImFontAtlasGetFontLoaderForStbTruetype(); if (RadioButton("stb_truetype", loader_current == loader_stbtruetype)) diff --git a/imgui.h b/imgui.h index e237e90ae..2fb54a511 100644 --- a/imgui.h +++ b/imgui.h @@ -3603,6 +3603,7 @@ struct ImFontAtlas ImTextureData* TexData; // Current texture ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetDrawData().Textures[]/GetPlatformIO().Textures[] instead! bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. + bool RendererHasTextures;// Copy of (BackendFlags & ImGuiBackendFlags_RendererHasTextures) from supporting context. bool TexIsBuilt; // Set when texture was built matching current font input bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight) @@ -3612,7 +3613,7 @@ struct ImFontAtlas //ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines int TexNextUniqueID; // Next value to be stored in TexData->UniqueID - ImDrawListSharedData* DrawListSharedData; // In principle this could become an array (e.g. multiple contexts using same atlas) + ImVector DrawListSharedDatas; // [Internal] Font builder ImFontAtlasBuilder* Builder; // Opaque interface to our data that doesn't need to be public diff --git a/imgui_draw.cpp b/imgui_draw.cpp index faabec1d5..b6280afb6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -390,7 +390,6 @@ ImDrawListSharedData::ImDrawListSharedData() ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); } ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); - RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. } ImDrawListSharedData::~ImDrawListSharedData() @@ -2580,6 +2579,7 @@ ImFontAtlas::ImFontAtlas() memset(this, 0, sizeof(*this)); TexDesiredFormat = ImTextureFormat_RGBA32; TexGlyphPadding = 1; + RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. TexRef._TexData = NULL;// this; TexNextUniqueID = 1; Builder = NULL; @@ -2637,10 +2637,10 @@ void ImFontAtlas::ClearFonts() ClearInputData(); Fonts.clear_delete(); TexIsBuilt = false; - if (DrawListSharedData) + for (ImDrawListSharedData* shared_data : DrawListSharedDatas) { - DrawListSharedData->Font = NULL; - DrawListSharedData->FontScale = DrawListSharedData->FontSize = 0.0f; + shared_data->Font = NULL; + shared_data->FontScale = shared_data->FontSize = 0.0f; } } @@ -2681,18 +2681,21 @@ static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* at // time of an early call to Build(), it would be impossible for us to tell if the backend supports texture update. // - Without this hack, we would have quite a pitfall as many legacy codebases have an early call to Build(). // Whereas conversely, the portion of people using ImDrawList without ImGui is expected to be pathologically rare. - if (atlas->DrawListSharedData) - if (ImGuiContext* imgui_ctx = atlas->DrawListSharedData->Context) - atlas->DrawListSharedData->RendererHasTextures = (imgui_ctx->IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + if (ImGuiContext* imgui_ctx = shared_data->Context) + { + atlas->RendererHasTextures = (imgui_ctx->IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + break; + } } -// Called by NewFrame() +// Called by NewFrame(). When multiple context own the atlas, only the first one calls this. void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) { if (atlas->TexIsBuilt && atlas->Builder->PreloadedAllGlyphsRanges) { ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); - IM_ASSERT_USER_ERROR(atlas->DrawListSharedData->RendererHasTextures == false, + IM_ASSERT_USER_ERROR(atlas->RendererHasTextures == false, "Called ImFontAtlas::Build() before ImGuiBackendFlags_RendererHasTextures got set! With new backends: you don't need to call Build()."); } @@ -3050,7 +3053,7 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed // We allow old_font == new_font which forces updating all values (e.g. sizes) static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, ImFont* new_font) { - if (ImDrawListSharedData* shared_data = atlas->DrawListSharedData) + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) { if (shared_data->Font == old_font) shared_data->Font = new_font; @@ -3124,7 +3127,7 @@ int ImFontAtlas::AddCustomRectRegular(int width, int height) IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - if (DrawListSharedData && DrawListSharedData->RendererHasTextures) + if (RendererHasTextures) { ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); ImFontAtlasRect* r = ImFontAtlasPackGetRect(this, r_id); @@ -3220,7 +3223,7 @@ void ImFontAtlasBuildMain(ImFontAtlas* atlas) // [LEGACY] For backends not supporting RendererHasTexUpdates: preload all glyphs ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); - if (atlas->DrawListSharedData && atlas->DrawListSharedData->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures + if (atlas->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures ImFontAtlasBuildPreloadAllGlyphRanges(atlas); atlas->TexIsBuilt = true; } @@ -3578,48 +3581,46 @@ void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font) // Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) { - IM_ASSERT(atlas->DrawListSharedData == NULL && data->FontAtlas == NULL); - atlas->DrawListSharedData = data; + IM_ASSERT(!atlas->DrawListSharedDatas.contains(data) && data->FontAtlas == NULL); + atlas->DrawListSharedDatas.push_back(data); data->FontAtlas = atlas; } void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) { - IM_ASSERT(atlas->DrawListSharedData == data && data->FontAtlas == atlas); - atlas->DrawListSharedData = data; + IM_ASSERT(atlas->DrawListSharedDatas.contains(data) && data->FontAtlas == atlas); + atlas->DrawListSharedDatas.find_erase(data); data->FontAtlas = NULL; } // Update texture identifier in all active draw lists void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex) { - ImDrawListSharedData* shared_data = atlas->DrawListSharedData; - if (shared_data == NULL) - return; - for (ImDrawList* draw_list : shared_data->DrawLists) - { - // Replace in command-buffer - // (there is not need to replace in ImDrawListSplitter: current channel is in ImDrawList's CmdBuffer[], - // other channels will be on SetCurrentChannel() which already needs to compare CmdHeader anyhow) - if (draw_list->CmdBuffer.Size > 0 && draw_list->_CmdHeader.TexRef == old_tex) - draw_list->_SetTexture(new_tex); + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + for (ImDrawList* draw_list : shared_data->DrawLists) + { + // Replace in command-buffer + // (there is not need to replace in ImDrawListSplitter: current channel is in ImDrawList's CmdBuffer[], + // other channels will be on SetCurrentChannel() which already needs to compare CmdHeader anyhow) + if (draw_list->CmdBuffer.Size > 0 && draw_list->_CmdHeader.TexRef == old_tex) + draw_list->_SetTexture(new_tex); - // Replace in stack - for (ImTextureRef& stacked_tex : draw_list->_TextureStack) - if (stacked_tex == old_tex) - stacked_tex = new_tex; - } + // Replace in stack + for (ImTextureRef& stacked_tex : draw_list->_TextureStack) + if (stacked_tex == old_tex) + stacked_tex = new_tex; + } } // Update texture coordinates in all draw list shared context void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) { - ImDrawListSharedData* shared_data = atlas->DrawListSharedData; - if (shared_data == NULL) - return; - shared_data->FontAtlas = atlas; - shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; - shared_data->TexUvLines = atlas->TexUvLines; + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + { + shared_data->FontAtlas = atlas; + shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; + shared_data->TexUvLines = atlas->TexUvLines; + } } // Set current texture. This is mostly called from AddTexture() + to handle a failed resize. diff --git a/imgui_internal.h b/imgui_internal.h index f340b8e5e..830ca9c6e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -821,7 +821,6 @@ struct IMGUI_API ImDrawListSharedData ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) - bool RendererHasTextures; // Copy of (GetIO().BackendFlags & ImGuiBackendFlags_RendererHasTextures). ImDrawListSharedData(); ~ImDrawListSharedData(); From 4ff1631b316fe849da9a649cf30a79aad752d8bb Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 8 Jan 2025 19:07:40 +0100 Subject: [PATCH 038/191] Fonts: Rasterizing ellipsis character from dot as one glyph + avoid preloading if it not needed. # Conflicts: # imgui.cpp --- imgui.cpp | 5 ++- imgui.h | 7 ++-- imgui_draw.cpp | 85 +++++++++++++++++++++++++++++++++++++++--------- imgui_internal.h | 1 + 4 files changed, 75 insertions(+), 23 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 0cf8bce21..7ac49382e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3738,7 +3738,7 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con const float font_size = draw_list->_Data->FontSize; const float font_scale = draw_list->_Data->FontScale; const char* text_end_ellipsis = NULL; - const float ellipsis_width = font->EllipsisWidth * font_scale; + const float ellipsis_width = font->GetCharAdvance(font->EllipsisChar) * font_scale; // We can now claim the space between pos_max.x and ellipsis_max.x const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0f); @@ -3754,8 +3754,7 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f)); ImVec4 cpu_fine_clip_rect(pos_min.x, pos_min.y, pos_max.x, pos_max.y); ImVec2 ellipsis_pos = ImTrunc(ImVec2(pos_min.x + text_size_clipped_x, pos_min.y)); - for (int i = 0; i < font->EllipsisCharCount; i++, ellipsis_pos.x += font->EllipsisCharStep * font_scale) - font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar, &cpu_fine_clip_rect); + font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar, &cpu_fine_clip_rect); } else { diff --git a/imgui.h b/imgui.h index 2fb54a511..d5136e2a6 100644 --- a/imgui.h +++ b/imgui.h @@ -3648,13 +3648,10 @@ struct ImFont // [Internal] Members: Cold ~32/40/60 bytes // Conceptually Sources[] is the list of font sources merged to create this font. short SourcesCount; // 2 // in // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont. - short EllipsisCharCount; // 1 // out // 1 or 3 - ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). - ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') ImFontConfig* Sources; // 4-8 // in // Pointer within ContainerAtlas->Sources[], to SourcesCount instances ImFontAtlas* ContainerAtlas; // 4-8 // out // What we has been loaded into - float EllipsisWidth; // 4 // out // Total ellipsis Width - float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 + ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). + ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) int MetricsTotalSurface;// 4 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b6280afb6..83212712f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2462,6 +2462,7 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasTextureBlockConvert() // - ImFontAtlasTextureBlockPostProcess() // - ImFontAtlasTextureBlockPostProcessMultiply() +// - ImFontAtlasTextureBlockFill() // - ImFontAtlasTextureBlockCopy() // - ImFontAtlasTextureBlockQueueUpload() //----------------------------------------------------------------------------- @@ -2490,6 +2491,7 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildUpdateBasicTexData() // - ImFontAtlasBuildUpdateLinesTexData() // - ImFontAtlasBuildAddFont() +// - ImFontAtlasBuildSetupFontCreateEllipsisFromDot() // - ImFontAtlasBuildSetupFontSpecialGlyphs() // - ImFontAtlasBuildReloadFont() //----------------------------------------------------------------------------- @@ -2820,10 +2822,29 @@ void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data } } -// Convert block from one texture to another +// Fill with single color. We don't use this directly but it is convenient for anyone working on uploading custom rects. +void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col) +{ + if (dst_tex->Format == ImTextureFormat_Alpha8) + { + ImU8 col_a = (col >> IM_COL32_A_SHIFT) & 0xFF; + for (int y = 0; y < h; y++) + memset((ImU8*)dst_tex->GetPixelsAt(dst_x, dst_y + y), col_a, w); + } + else + { + for (int y = 0; y < h; y++) + { + ImU32* p = (ImU32*)(void*)dst_tex->GetPixelsAt(dst_x, dst_y + y); + for (int x = w; x > 0; x--, p++) + *p = col; + } + } +} + +// Copy block from one texture to another void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h) { - IM_ASSERT(src_tex != dst_tex); IM_ASSERT(src_tex->Pixels != NULL && dst_tex->Pixels != NULL); IM_ASSERT(src_tex->Format == dst_tex->Format); IM_ASSERT(src_x >= 0 && src_x + w <= src_tex->Width); @@ -3483,6 +3504,44 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) return true; } +// Rasterize our own ellipsis character from a dot. +// This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. +// FIXME-NEWATLAS: This borrows too much from FontBackend_FontAddGlyph() and suggest that we should add further helpers. +static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, ImFontConfig* cfg, const ImFontGlyph* dot_glyph) +{ + ImFont* font = cfg->DstFont; + + ImFontAtlasRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_glyph->PackId); + const int dot_spacing = 1; + const float dot_step = (dot_glyph->X1 - dot_glyph->X0) + dot_spacing; + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, (dot_r->w * 3 + dot_spacing * 2), dot_r->h); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + font->MetricsTotalSurface += r->w * r->h; + + ImFontGlyph glyph; + glyph.Codepoint = (ImWchar)0x0085; // FIXME: Using arbitrary codepoint. + glyph.AdvanceX = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + dot_step * 3.0f - dot_spacing); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. + glyph.X0 = dot_glyph->X0; + glyph.Y0 = dot_glyph->Y0; + glyph.X1 = dot_glyph->X0 + dot_step * 3 - dot_spacing; + glyph.Y1 = dot_glyph->Y1; + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + glyph.Visible = true; + glyph.PackId = pack_id; + font->BuildRegisterGlyph(cfg, &glyph); + font->EllipsisChar = (ImWchar)glyph.Codepoint; + + // Copy to texture, post-process and queue update for backend + // FIXME-NEWATLAS-V2: Dot glyph is already post-processed as this point, so this would damage it. + ImTextureData* tex = atlas->TexData; + for (int n = 0; n < 3; n++) + ImFontAtlasTextureBlockCopy(tex, dot_r->x, dot_r->y, tex, r->x + (dot_r->w + dot_spacing) * n, r->y, dot_r->w, dot_r->h); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); +} + // Load/identify special glyphs // (note that this is called again for fonts with MergeMode) void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src) @@ -3526,23 +3585,19 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* sr // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. const ImWchar ellipsis_chars[] = { src->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; if (font->EllipsisChar == 0) - if (const ImFontGlyph* glyph = LoadFirstExistingGlyph(font, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars))) - { - font->EllipsisChar = (ImWchar)glyph->Codepoint; - font->EllipsisCharCount = 1; - font->EllipsisWidth = font->EllipsisCharStep = glyph->X1; - } + for (ImWchar candidate_char : ellipsis_chars) + if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) + { + font->EllipsisChar = candidate_char; + break; + } if (font->EllipsisChar == 0) { - // FIXME-NEWATLAS-V2: We can now rasterize this into a regular character and register it! const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; if (const ImFontGlyph* dot_glyph = LoadFirstExistingGlyph(font, dots_chars, IM_ARRAYSIZE(dots_chars))) - { - font->EllipsisChar = (ImWchar)dot_glyph->Codepoint; - font->EllipsisCharCount = 3; - font->EllipsisCharStep = (float)(int)(dot_glyph->X1 - dot_glyph->X0) + 1.0f; - font->EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + font->EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. - } + ImFontAtlasBuildSetupFontCreateEllipsisFromDot(atlas, src, dot_glyph); + else + font->EllipsisChar = (ImWchar)' '; } font->LockSingleSrcConfigIdx = -1; } diff --git a/imgui_internal.h b/imgui_internal.h index 830ca9c6e..bc98e83d2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3736,6 +3736,7 @@ IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); IMGUI_API void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data); IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor); +IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col); IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); From b06f3c6d1d02816b646a02fa3b30a15325d105fe Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Jan 2025 20:48:50 +0100 Subject: [PATCH 039/191] Fonts: turn public facing BuildRegisterGlyph() into ImFontAtlasBuildAddFontGlyph() thats sets up UV. --- imgui.h | 1 - imgui_draw.cpp | 55 +++++++++++++++++--------------- imgui_internal.h | 2 ++ misc/freetype/imgui_freetype.cpp | 10 ++---- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/imgui.h b/imgui.h index d5136e2a6..c4e8818d1 100644 --- a/imgui.h +++ b/imgui.h @@ -3687,7 +3687,6 @@ struct ImFont IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst);// , bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); IMGUI_API ImFontGlyph* BuildLoadGlyph(ImWchar c); - IMGUI_API void BuildRegisterGlyph(ImFontConfig* src, const ImFontGlyph* glyph); IMGUI_API void BuildGrowIndex(int new_size); IMGUI_API void BuildClearGlyphs(); }; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 83212712f..f8797841b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3507,9 +3507,9 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) // Rasterize our own ellipsis character from a dot. // This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. // FIXME-NEWATLAS: This borrows too much from FontBackend_FontAddGlyph() and suggest that we should add further helpers. -static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, ImFontConfig* cfg, const ImFontGlyph* dot_glyph) +static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, ImFontConfig* src, const ImFontGlyph* dot_glyph) { - ImFont* font = cfg->DstFont; + ImFont* font = src->DstFont; ImFontAtlasRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_glyph->PackId); const int dot_spacing = 1; @@ -3525,13 +3525,9 @@ static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, I glyph.Y0 = dot_glyph->Y0; glyph.X1 = dot_glyph->X0 + dot_step * 3 - dot_spacing; glyph.Y1 = dot_glyph->Y1; - glyph.U0 = (r->x) * atlas->TexUvScale.x; - glyph.V0 = (r->y) * atlas->TexUvScale.y; - glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; - glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; glyph.Visible = true; glyph.PackId = pack_id; - font->BuildRegisterGlyph(cfg, &glyph); + ImFontAtlasBuildAddFontGlyph(atlas, font, NULL, &glyph); font->EllipsisChar = (ImWchar)glyph.Codepoint; // Copy to texture, post-process and queue update for backend @@ -3577,7 +3573,7 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* sr ImFontGlyph tab_glyph; tab_glyph.Codepoint = '\t'; tab_glyph.AdvanceX = space_glyph->AdvanceX * IM_TABSIZE; - font->BuildRegisterGlyph(font->Sources, &tab_glyph); + ImFontAtlasBuildAddFontGlyph(atlas, font, src, &tab_glyph); } // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). @@ -4282,13 +4278,9 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, glyph.Y0 = y0 * recip_v + font_off_y; glyph.X1 = (x0 + (int)r->w) * recip_h + font_off_x; glyph.Y1 = (y0 + (int)r->h) * recip_v + font_off_y; - glyph.U0 = (r->x) * atlas->TexUvScale.x; - glyph.V0 = (r->y) * atlas->TexUvScale.y; - glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; - glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; glyph.Visible = true; glyph.PackId = pack_id; - font->BuildRegisterGlyph(src, &glyph); + ImFontAtlasBuildAddFontGlyph(atlas, font, src, &glyph); // Copy to texture, post-process and queue update for backend ImTextureData* tex = atlas->TexData; @@ -4300,7 +4292,7 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, } else { - font->BuildRegisterGlyph(src, &glyph); + ImFontAtlasBuildAddFontGlyph(atlas, font, src, &glyph); } return true; @@ -4699,12 +4691,23 @@ void ImFont::BuildGrowIndex(int new_size) // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. // Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). // 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. -void ImFont::BuildRegisterGlyph(ImFontConfig* src, const ImFontGlyph* in_glyph) +ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, const ImFontGlyph* in_glyph) { - int glyph_idx = Glyphs.Size; - Glyphs.push_back(*in_glyph); - ImFontGlyph& glyph = Glyphs[glyph_idx]; - IM_ASSERT(Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. + int glyph_idx = font->Glyphs.Size; + font->Glyphs.push_back(*in_glyph); + ImFontGlyph& glyph = font->Glyphs[glyph_idx]; + IM_ASSERT(font->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. + + // Set UV from packed rectangle + if (in_glyph->PackId >= 0) + { + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, in_glyph->PackId); + IM_ASSERT(in_glyph->U0 == 0.0f && in_glyph->V0 == 0.0f && in_glyph->U1 == 0.0f && in_glyph->V1 == 0.0f); + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + } if (src != NULL) { @@ -4725,17 +4728,17 @@ void ImFont::BuildRegisterGlyph(ImFontConfig* src, const ImFontGlyph* in_glyph) glyph.AdvanceX = advance_x + src->GlyphExtraAdvanceX; } if (glyph.Colored) - ContainerAtlas->TexPixelsUseColors = ContainerAtlas->TexData->UseColors = true; + atlas->TexPixelsUseColors = atlas->TexData->UseColors = true; // Update lookup tables int codepoint = glyph.Codepoint; - BuildGrowIndex(codepoint + 1); - IndexAdvanceX[codepoint] = glyph.AdvanceX; - IndexLookup[codepoint] = (ImU16)glyph_idx; - - // Mark 4K page as used + font->BuildGrowIndex(codepoint + 1); + font->IndexAdvanceX[codepoint] = glyph.AdvanceX; + font->IndexLookup[codepoint] = (ImU16)glyph_idx; const int page_n = codepoint / 8192; - Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); + font->Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); + + return &glyph; } void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst) diff --git a/imgui_internal.h b/imgui_internal.h index bc98e83d2..212dafbaf 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3721,6 +3721,8 @@ IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFon IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); +IMGUI_API ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* cfg, const ImFontGlyph* in_glyph); + IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index efe8612ba..92238f7fd 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -529,14 +529,10 @@ bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontCon glyph.Y0 = glyph_off_y * recip_v + font_off_y; glyph.X1 = (glyph_off_x + w) * recip_h + font_off_x; glyph.Y1 = (glyph_off_y + h) * recip_v + font_off_y; - glyph.U0 = (r->x) * atlas->TexUvScale.x; - glyph.V0 = (r->y) * atlas->TexUvScale.y; - glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; - glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; - glyph.PackId = pack_id; glyph.Visible = true; glyph.Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); - font->BuildRegisterGlyph(src, &glyph); + glyph.PackId = pack_id; + ImFontAtlasBuildAddFontGlyph(atlas, font, src, &glyph); // Copy to texture, post-process and queue update for backend ImTextureData* tex = atlas->TexData; @@ -548,7 +544,7 @@ bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontCon } else { - font->BuildRegisterGlyph(src, &glyph); + ImFontAtlasBuildAddFontGlyph(atlas, font, src, &glyph); } return true; } From 14614f561b966913d595eb8904e04f66ba23ffb1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Jan 2025 20:57:20 +0100 Subject: [PATCH 040/191] Textures: Ensure UpdateBox is set on texture _WantCreate state too. --- imgui_draw.cpp | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f8797841b..d8d46e279 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2435,6 +2435,8 @@ void ImTextureData::Create(ImTextureFormat format, int w, int h) Pixels = (unsigned char*)IM_ALLOC(Width * Height * BytesPerPixel); IM_ASSERT(Pixels != NULL); memset(Pixels, 0, Width * Height * BytesPerPixel); + UpdateRect.x = UpdateRect.y = (unsigned short)~0; + UpdateRect.w = UpdateRect.h = 0; } void ImTextureData::DestroyPixels() @@ -2705,9 +2707,12 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) { ImTextureData* tex = atlas->TexList[tex_n]; bool remove_from_list = false; - tex->Updates.resize(0); - tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; - tex->UpdateRect.w = tex->UpdateRect.h = 0; + if (tex->Status == ImTextureStatus_OK) + { + tex->Updates.resize(0); + tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; + tex->UpdateRect.w = tex->UpdateRect.h = 0; + } if (tex->Status == ImTextureStatus_Destroyed) { @@ -2858,22 +2863,23 @@ void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, I // Queue texture block update for renderer backend void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h) { - // Queue texture update (no need to queue if status is _WantCreate) - IM_ASSERT(atlas); + IM_ASSERT(tex->Status != ImTextureStatus_WantDestroy && tex->Status != ImTextureStatus_Destroyed); + IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000); + IM_UNUSED(atlas); + + ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h }; + int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w); + int new_y1 = ImMax(tex->UpdateRect.h == 0 ? 0 : tex->UpdateRect.y + tex->UpdateRect.h, req.y + req.h); + tex->UpdateRect.x = ImMin(tex->UpdateRect.x, req.x); + tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y); + tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x); + tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y); + + // No need to queue if status is _WantCreate if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates) { - IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000); - ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h }; tex->Status = ImTextureStatus_WantUpdates; tex->Updates.push_back(req); - int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w); - int new_y1 = ImMax(tex->UpdateRect.h == 0 ? 0 : tex->UpdateRect.y + tex->UpdateRect.h, req.y + req.h); - IM_ASSERT(new_x1 < 0x8000); - IM_ASSERT(new_y1 < 0x8000); - tex->UpdateRect.x = ImMin(tex->UpdateRect.x, req.x); - tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y); - tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x); - tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y); } } @@ -3403,6 +3409,7 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_ ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r->y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); } } + ImFontAtlasTextureBlockQueueUpload(atlas, atlas->TexData, r->x, r->y, r->w, r->h); atlas->TexUvWhitePixel = ImVec2((r->x + 0.5f) * atlas->TexUvScale.x, (r->y + 0.5f) * atlas->TexUvScale.y); } @@ -3472,6 +3479,7 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } //----------------------------------------------------------------------------------------------------------------------------- From 2137b3448b61c389d706da3deed092e32128f5c8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Jan 2025 23:04:00 +0100 Subject: [PATCH 041/191] Textures: Added atlas's TexMinWidth/TexMinHeight/TexMaxWidth/TexMaxHeight. Fixed ImFontAtlasBuildGetTextureSizeEstimate(). Basic error handling on OOM. --- imgui.h | 4 ++++ imgui_draw.cpp | 39 +++++++++++++++++++++++++------- imgui_internal.h | 2 +- misc/freetype/imgui_freetype.cpp | 7 ++++++ 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/imgui.h b/imgui.h index c4e8818d1..56366ae1e 100644 --- a/imgui.h +++ b/imgui.h @@ -3596,6 +3596,10 @@ struct ImFontAtlas ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) ImTextureFormat TexDesiredFormat; // Desired texture format (default to ImTextureFormat_RGBA32 but may be changed to ImTextureFormat_Alpha8). int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). + int TexMinWidth; // Minimum desired texture width. Must be a power of two. Default to 512. + int TexMinHeight; // Minimum desired texture height. Must be a power of two. Default to 128. + int TexMaxWidth; // Maximum desired texture width. Must be a power of two. Default to 8192. + int TexMaxHeight; // Maximum desired texture height. Must be a power of two. Default to 8192. void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). // [Internal] diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d8d46e279..040157190 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2576,13 +2576,16 @@ static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3 #define IM_FONTGLYPH_INDEX_UNUSED ((ImU16)-1) // 0xFFFF #define IM_FONTGLYPH_INDEX_NOT_FOUND ((ImU16)-2) // 0xFFFE -#define IM_FONTATLAS_DEFAULT_TEXTURE_SIZE ImVec2i(512, 128) ImFontAtlas::ImFontAtlas() { memset(this, 0, sizeof(*this)); TexDesiredFormat = ImTextureFormat_RGBA32; TexGlyphPadding = 1; + TexMinWidth = 512; + TexMinHeight = 128; + TexMaxWidth = 8192; + TexMaxHeight = 8192; RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. TexRef._TexData = NULL;// this; TexNextUniqueID = 1; @@ -3248,7 +3251,7 @@ void ImFontAtlasBuildMain(ImFontAtlas* atlas) if (atlas->Sources.Size == 0) atlas->AddFontDefault(); - // [LEGACY] For backends not supporting RendererHasTexUpdates: preload all glyphs + // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); if (atlas->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures ImFontAtlasBuildPreloadAllGlyphRanges(atlas); @@ -3268,7 +3271,7 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon return; IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); - // Note that texture size estimate is likely incorrect in this situation, as Freetype backend doesn't use oversampling. + // Note that texture size estimate is likely incorrect in this situation, as FreeType backend doesn't use oversampling. ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); ImFontAtlasBuildDestroy(atlas); @@ -3390,6 +3393,8 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_ if (add_and_draw) builder->PackIdMouseCursors = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); + if (builder->PackIdMouseCursors < 0) + return; ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdMouseCursors); // Draw to texture @@ -3424,6 +3429,8 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ ImFontAtlasBuilder* builder = atlas->Builder; if (add_and_draw) builder->PackIdLinesTexData = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); + if (builder->PackIdLinesTexData < 0) + return; ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdLinesTexData); // Register texture region for thick lines @@ -3813,6 +3820,7 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ // FIXME-NEWATLAS-V2: What to do when reaching limits exposed by backend? // FIXME-NEWATLAS-V2: does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); + IM_ASSERT(ImIsPowerOfTwo(atlas->TexMinWidth) && ImIsPowerOfTwo(atlas->TexMaxWidth) && ImIsPowerOfTwo(atlas->TexMinHeight) && ImIsPowerOfTwo(atlas->TexMaxHeight)); // Grow texture so it follows roughly a square. // FIXME-NEWATLAS-V1: Take account of RectsDiscardedSurface: may not need to grow. @@ -3823,19 +3831,24 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ const int pack_padding = atlas->TexGlyphPadding; new_tex_w = ImMax(new_tex_w, ImUpperPowerOfTwo(builder->MaxRectSize.x + pack_padding)); new_tex_h = ImMax(new_tex_h, ImUpperPowerOfTwo(builder->MaxRectSize.y + pack_padding)); + new_tex_w = ImClamp(new_tex_w, atlas->TexMinWidth, atlas->TexMaxWidth); + new_tex_h = ImClamp(new_tex_h, atlas->TexMinHeight, atlas->TexMaxHeight); + if (new_tex_w == old_tex_w && new_tex_h == old_tex_h) + return; ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); } -// FIXME-NEWATLAS: Expose atlas->TexMinWidth etc. ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) { + int min_w = ImUpperPowerOfTwo(atlas->TexMinWidth); + int min_h = ImUpperPowerOfTwo(atlas->TexMinHeight); if (atlas->Builder == NULL || atlas->TexData == NULL || atlas->TexData->Status == ImTextureStatus_WantDestroy) - return IM_FONTATLAS_DEFAULT_TEXTURE_SIZE; + return ImVec2i(min_w, min_h); ImFontAtlasBuilder* builder = atlas->Builder; - const int min_w = ImMax(builder->MaxRectSize.x, 512); - const int min_h = builder->MaxRectSize.y; + min_w = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.x), min_w); + min_h = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.y), min_h); const int surface_approx = atlas->_PackedSurface - builder->RectsDiscardedSurface; // Expected surface after repack const int surface_sqrt = (int)sqrtf((float)surface_approx); @@ -3855,6 +3868,8 @@ ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) new_tex_h = ImUpperPowerOfTwo(new_tex_h); new_tex_w = ImMax(min_w, (int)((surface_approx + new_tex_h - 1) / new_tex_h)); } + + IM_ASSERT(ImIsPowerOfTwo(new_tex_w) && ImIsPowerOfTwo(new_tex_h)); return ImVec2i(new_tex_w, new_tex_h); } @@ -3894,7 +3909,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) } // Create initial texture size if (atlas->TexData == NULL) - ImFontAtlasBuildAddTexture(atlas, IM_FONTATLAS_DEFAULT_TEXTURE_SIZE.x, IM_FONTATLAS_DEFAULT_TEXTURE_SIZE.y); + ImFontAtlasBuildAddTexture(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); const bool builder_is_new = (builder == NULL); if (builder_is_new) @@ -4051,6 +4066,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { + IM_ASSERT(id >= 0); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; IM_ASSERT(index_entry->Used); @@ -4255,6 +4271,13 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, const int w = (x1 - x0 + oversample_h - 1); const int h = (y1 - y0 + oversample_v - 1); ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); + if (pack_id < 0) + { + // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) + IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); + return false; + } + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); font->MetricsTotalSurface += w * h; diff --git a/imgui_internal.h b/imgui_internal.h index 212dafbaf..2e84a5554 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3696,7 +3696,7 @@ struct ImFontAtlasBuilder ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. ImFontAtlasRectId PackIdLinesTexData; - ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); RectsIndexFreeListStart = -1; } + ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } }; // FIXME-NEWATLAS: Cleanup diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 92238f7fd..b388d1531 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -509,6 +509,13 @@ bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontCon if (is_visible) { ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); + if (pack_id < 0) + { + // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) + IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); + return false; + } + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); font->MetricsTotalSurface += w * h; From 8ed4e2dde7a2832c28d2c20a6a5f97220a0e3d9f Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 13 Jan 2025 17:56:36 +0100 Subject: [PATCH 042/191] Fonts: Basic heuristic to repack instead of growing. Moved rects count/surface to internals. --- imgui.cpp | 4 ++-- imgui.h | 2 -- imgui_draw.cpp | 33 ++++++++++++++++++++++----------- imgui_internal.h | 3 +++ 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7ac49382e..e617a84d7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15594,7 +15594,7 @@ static void MetricsHelpMarker(const char* desc) } #ifdef IMGUI_ENABLE_FREETYPE -namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetBackendIOForFreeType(); } +namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); } #endif // [DEBUG] List fonts in a font atlas and display its texture @@ -15628,7 +15628,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) #endif SameLine(); #ifdef IMGUI_ENABLE_FREETYPE - const ImFontLoader* loader_freetype = ImGuiFreeType::GetBackendIOForFreeType(); + const ImFontLoader* loader_freetype = ImGuiFreeType::GetFontLoader(); if (RadioButton("FreeType", loader_current == loader_freetype)) ImFontAtlasBuildSetupFontLoader(atlas, loader_freetype); #else diff --git a/imgui.h b/imgui.h index 56366ae1e..c04d1175a 100644 --- a/imgui.h +++ b/imgui.h @@ -3626,8 +3626,6 @@ struct ImFontAtlas void* FontLoaderData; // Font backend opaque storage unsigned int FontBuilderFlags; // [FIXME: Should be called FontLoaderFlags] Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. . Per-font override is also available in ImFontConfig. int RefCount; // Number of contexts using this atlas - int _PackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. - int _PackedRects; // Number of packed rectangles. // [Obsolete] //int TexDesiredWidth; // OBSOLETED in 1.91.5 (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 040157190..65e806c54 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2504,6 +2504,7 @@ void ImTextureData::DestroyPixels() //----------------------------------------------------------------------------- // - ImFontAtlasBuildSetTexture() // - ImFontAtlasBuildAddTexture() +// - ImFontAtlasBuildMakeSpace() // - ImFontAtlasBuildRepackTexture() // - ImFontAtlasBuildGrowTexture() // - ImFontAtlasBuildCompactTexture() @@ -3521,7 +3522,7 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) // Rasterize our own ellipsis character from a dot. // This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. -// FIXME-NEWATLAS: This borrows too much from FontBackend_FontAddGlyph() and suggest that we should add further helpers. +// FIXME-NEWATLAS: This borrows too much from FontLoader's FontAddGlyph() and suggest that we should add further helpers. static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, ImFontConfig* src, const ImFontGlyph* dot_glyph) { ImFont* font = src->DstFont; @@ -3823,7 +3824,7 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ IM_ASSERT(ImIsPowerOfTwo(atlas->TexMinWidth) && ImIsPowerOfTwo(atlas->TexMaxWidth) && ImIsPowerOfTwo(atlas->TexMinHeight) && ImIsPowerOfTwo(atlas->TexMaxHeight)); // Grow texture so it follows roughly a square. - // FIXME-NEWATLAS-V1: Take account of RectsDiscardedSurface: may not need to grow. + // Caller should be taking account of RectsDiscardedSurface and may not need to grow. int new_tex_w = (old_tex_h < old_tex_w) ? old_tex_w : old_tex_w * 2; int new_tex_h = (old_tex_h < old_tex_w) ? old_tex_h * 2 : old_tex_h; @@ -3839,6 +3840,16 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); } +void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas) +{ + // Currently using a heuristic for repack without growing. + ImFontAtlasBuilder* builder = atlas->Builder; + if (builder->RectsDiscardedSurface < builder->RectsPackedSurface * 0.20f) + ImFontAtlasBuildGrowTexture(atlas); + else + ImFontAtlasBuildRepackTexture(atlas, atlas->TexData->Width, atlas->TexData->Height); +} + ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) { int min_w = ImUpperPowerOfTwo(atlas->TexMinWidth); @@ -3849,7 +3860,7 @@ ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) ImFontAtlasBuilder* builder = atlas->Builder; min_w = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.x), min_w); min_h = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.y), min_h); - const int surface_approx = atlas->_PackedSurface - builder->RectsDiscardedSurface; // Expected surface after repack + const int surface_approx = builder->RectsPackedSurface - builder->RectsDiscardedSurface; // Expected surface after repack const int surface_sqrt = (int)sqrtf((float)surface_approx); int new_tex_w; @@ -3893,10 +3904,10 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) ImFontAtlasBuilder* builder = atlas->Builder; // Select Backend - // - Note that we do not reassign to atlas->FontBackendIO, since it is likely to point to static data which + // - Note that we do not reassign to atlas->FontLoader, since it is likely to point to static data which // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are - // using a hot-reloading scheme that messes up static data, store your own instance of ImFontBackendIO somewhere - // and point to it instead of pointing directly to return value of the GetBackendIOXXX functions. + // using a hot-reloading scheme that messes up static data, store your own instance of FontLoader somewhere + // and point to it instead of pointing directly to return value of the GetFontLoaderXXX functions. if (atlas->FontLoader == NULL) { #ifdef IMGUI_ENABLE_FREETYPE @@ -3958,7 +3969,7 @@ void ImFontAtlasPackInit(ImFontAtlas * atlas) builder->PackNodes.resize(pack_node_count); IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width, tex->Height, builder->PackNodes.Data, builder->PackNodes.Size); - atlas->_PackedSurface = atlas->_PackedRects = 0; + builder->RectsPackedSurface = builder->RectsPackedCount = 0; builder->MaxRectSize = ImVec2i(0, 0); builder->MaxRectBounds = ImVec2i(0, 0); } @@ -4041,14 +4052,14 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon return -1; } - // Resize atlas! (this should be a rare event) - ImFontAtlasBuildGrowTexture(atlas); + // Resize or repack atlas! (this should be a rare event) + ImFontAtlasBuildMakeSpace(atlas); } builder->MaxRectBounds.x = ImMax(builder->MaxRectBounds.x, r.x + r.w + pack_padding); builder->MaxRectBounds.y = ImMax(builder->MaxRectBounds.y, r.y + r.h + pack_padding); - atlas->_PackedSurface += (w + pack_padding) * (h + pack_padding); - atlas->_PackedRects++; + builder->RectsPackedCount++; + builder->RectsPackedSurface += (w + pack_padding) * (h + pack_padding); builder->Rects.push_back(r); if (overwrite_entry != NULL) diff --git a/imgui_internal.h b/imgui_internal.h index 2e84a5554..71019611a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3685,6 +3685,8 @@ struct ImFontAtlasBuilder ImVector RectsIndex; // ImFontAtlasRectId -> index into Rects[] ImVector TempBuffer; // Misc scratch buffer int RectsIndexFreeListStart;// First unused entry + int RectsPackedCount; // Number of packed rectangles. + int RectsPackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. int RectsDiscardedCount; int RectsDiscardedSurface; ImVec2i MaxRectSize; // Largest rectangle to pack (de-facto used as a "minimum texture size") @@ -3709,6 +3711,7 @@ IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas); IMGUI_API ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h); IMGUI_API void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); From 288055180e097cf9260bae7cc15339382a02771a Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 13 Jan 2025 20:03:09 +0100 Subject: [PATCH 043/191] Fonts: Comments, remove ImFontAtlas facing BuildGrowTexture(), BuildCompactTexture(). Make IsBuilt() obsolete. --- imgui.cpp | 4 +- imgui.h | 9 ++--- imgui_draw.cpp | 97 +++++++++++++++++++++--------------------------- imgui_internal.h | 24 +++++------- 4 files changed, 58 insertions(+), 76 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e617a84d7..a505d5d6c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5180,7 +5180,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos) static void ImGui::UpdateTexturesNewFrame() { - // FIXME-NEWATLAS: How to reach/target all atlas? + // FIXME-NEWATLAS-V2: If we aim to support multiple atlases used by same context: how to reach/target all atlases? ImGuiContext& g = *GImGui; ImFontAtlas* atlas = g.IO.Fonts; if (g.FontAtlasOwnedByContext) @@ -8607,7 +8607,7 @@ void ImGui::SetCurrentFont(ImFont* font) // - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call PushTextureID()/PopTextureID() // the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem // because we have a concrete need and a test bed for multiple atlas textures. -// FIXME-NEWATLAS: perhaps we can now leverage ImFontAtlasUpdateDrawListsTextures() ? +// FIXME-NEWATLAS-V2: perhaps we can now leverage ImFontAtlasUpdateDrawListsTextures() ? void ImGui::PushFont(ImFont* font) { ImGuiContext& g = *GImGui; diff --git a/imgui.h b/imgui.h index c04d1175a..19c116cab 100644 --- a/imgui.h +++ b/imgui.h @@ -3336,6 +3336,7 @@ struct ImDrawData //----------------------------------------------------------------------------- // We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. +// Most standard backends only support RGBA32 but we provide a single channel option for low-resource/embedded systems. enum ImTextureFormat { ImTextureFormat_RGBA32, // 4 components per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 @@ -3523,16 +3524,14 @@ struct ImFontAtlas IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, 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); - // FIXME-NEWATLAS: Clarify meaning/purpose IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures) IMGUI_API void ClearCache(); // Clear cached glyphs and textures. + // As we are transitioning toward a new font system, we expect to obsolete those soon: 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 ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). IMGUI_API void ClearTexData(); // [OBSOLETE] Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. - IMGUI_API void BuildGrowTexture(); - IMGUI_API void BuildCompactTexture(); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Build atlas, retrieve pixel data. // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). @@ -3544,8 +3543,8 @@ struct ImFontAtlas IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel void SetTexID(ImTextureID id) { TexRef._TexData = NULL; TexRef._TexID = id; } // Called by legacy backends. void SetTexID(ImTextureRef id) { TexRef = id; } // Called by legacy backends. + bool IsBuilt() const { return Fonts.Size > 0 && TexIsBuilt; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... #endif - bool IsBuilt() const { return Fonts.Size > 0 && TexIsBuilt; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... //------------------------------------------- // Glyph Ranges @@ -3693,7 +3692,7 @@ struct ImFont IMGUI_API void BuildClearGlyphs(); }; -// FIXME-NEWATLAS: Added indirection to avoid patching ImDrawCmd after texture updates. +// Added indirection to avoid patching ImDrawCmd after texture updates. inline ImTextureID ImDrawCmd::GetTexID() const { // If you are getting this assert: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 65e806c54..3369aabe9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2451,16 +2451,14 @@ void ImTextureData::DestroyPixels() // [SECTION] ImFontAtlas, ImFontAtlasBuilder //----------------------------------------------------------------------------- // - Default texture data encoded in ASCII -// - ImFontAtlasBuilder +// - ImFontAtlas() +// - ImFontAtlas::Clear() +// - ImFontAtlas::ClearCache() // - ImFontAtlas::ClearInputData() // - ImFontAtlas::ClearTexData() // - ImFontAtlas::ClearFonts() -// - ImFontAtlas::Clear() -// - ImFontAtlas::ClearCache() -// - ImFontAtlas::BuildGrowTexture() -// - ImFontAtlas::BuildCompactTexture() -// - ImFontAtlasUpdateTextures() //----------------------------------------------------------------------------- +// - ImFontAtlasUpdateNewFrame() // - ImFontAtlasTextureBlockConvert() // - ImFontAtlasTextureBlockPostProcess() // - ImFontAtlasTextureBlockPostProcessMultiply() @@ -2468,9 +2466,9 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasTextureBlockCopy() // - ImFontAtlasTextureBlockQueueUpload() //----------------------------------------------------------------------------- -// - ImFontAtlas::Build() [legacy] // - ImFontAtlas::GetTexDataAsAlpha8() [legacy] // - ImFontAtlas::GetTexDataAsRGBA32() [legacy] +// - ImFontAtlas::Build() [legacy] //----------------------------------------------------------------------------- // - ImFontAtlas::AddFont() // - ImFontAtlas::AddFontDefault() @@ -2483,6 +2481,7 @@ void ImTextureData::DestroyPixels() //----------------------------------------------------------------------------- // - ImFontAtlas::AddCustomRectRegular() // - ImFontAtlas::AddCustomRectFontGlyph() +// - ImFontAtlas::GetCustomRectByIndex() // - ImFontAtlas::CalcCustomRectUV() // - ImFontAtlasGetMouseCursorTexData() //----------------------------------------------------------------------------- @@ -2495,6 +2494,8 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildAddFont() // - ImFontAtlasBuildSetupFontCreateEllipsisFromDot() // - ImFontAtlasBuildSetupFontSpecialGlyphs() +// - ImFontAtlasBuildDiscardFontGlyph() +// - ImFontAtlasBuildDiscardFontGlyphs() // - ImFontAtlasBuildReloadFont() //----------------------------------------------------------------------------- // - ImFontAtlasAddDrawListSharedData() @@ -2507,11 +2508,15 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildMakeSpace() // - ImFontAtlasBuildRepackTexture() // - ImFontAtlasBuildGrowTexture() +// - ImFontAtlasBuildRepackOrGrowTexture() +// - ImFontAtlasBuildGetTextureSizeEstimate() // - ImFontAtlasBuildCompactTexture() // - ImFontAtlasBuildInit() // - ImFontAtlasBuildDestroy() //----------------------------------------------------------------------------- // - ImFontAtlasPackInit() +// - ImFontAtlasPackAllocRectEntry() +// - ImFontAtlasPackDiscardRect() // - ImFontAtlasPackAddRect() // - ImFontAtlasPackGetRect() //----------------------------------------------------------------------------- @@ -2599,6 +2604,21 @@ ImFontAtlas::~ImFontAtlas() Clear(); } +void ImFontAtlas::Clear() +{ + ClearInputData(); + ClearTexData(); + ClearFonts(); +} + +void ImFontAtlas::ClearCache() +{ + ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(this); + ImFontAtlasBuildDestroy(this); + ImFontAtlasBuildAddTexture(this, new_tex_size.x, new_tex_size.y); + ImFontAtlasBuildInit(this); +} + void ImFontAtlas::ClearInputData() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); @@ -2624,8 +2644,6 @@ void ImFontAtlas::ClearInputData() font->LockDisableLoading = true; } Sources.clear(); - //CustomRects.clear(); - // Important: we leave TexReady untouched } void ImFontAtlas::ClearTexData() @@ -2634,7 +2652,6 @@ void ImFontAtlas::ClearTexData() TexList.clear(); IM_DELETE(TexData); TexData = NULL; - // Important: we leave TexReady untouched } void ImFontAtlas::ClearFonts() @@ -2652,36 +2669,6 @@ void ImFontAtlas::ClearFonts() } } -void ImFontAtlas::Clear() -{ - //IM_DELETE(Builder); // FIXME-NEW-ATLAS: Clarify ClearXXX functions - //const ImFontLoader* font_loader = FontLoader; - //ImFontAtlasBuildSetupFontLoader(this, NULL); - ClearInputData(); - ClearTexData(); - ClearFonts(); - //ImFontAtlasBuildSetupFontLoader(this, font_loader); -} - -// FIXME-NEWATLAS: Too widespread purpose. Clarify each call site in current WIP demo. -void ImFontAtlas::ClearCache() -{ - ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(this); - ImFontAtlasBuildDestroy(this); - ImFontAtlasBuildAddTexture(this, new_tex_size.x, new_tex_size.y); - ImFontAtlasBuildInit(this); -} - -void ImFontAtlas::BuildGrowTexture() -{ - ImFontAtlasBuildGrowTexture(this, TexData->Width, TexData->Height); -} - -void ImFontAtlas::BuildCompactTexture() -{ - ImFontAtlasBuildCompactTexture(this); -} - static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* atlas) { // [LEGACY] Copy back the ImGuiBackendFlags_RendererHasTextures flag from ImGui context. @@ -2753,15 +2740,6 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) } } -// Source buffer may be written to (used for in-place mods). -// Post-process hooks may eventually be added here. -void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data) -{ - // Multiply operator (legacy) - if (data->FontSrc->RasterizerMultiply != 1.0f) - ImFontAtlasTextureBlockPostProcessMultiply(data, data->FontSrc->RasterizerMultiply); -} - void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) { IM_ASSERT(src_pixels != NULL && dst_pixels != NULL); @@ -2797,6 +2775,15 @@ void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFo } } +// Source buffer may be written to (used for in-place mods). +// Post-process hooks may eventually be added here. +void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data) +{ + // Multiply operator (legacy) + if (data->FontSrc->RasterizerMultiply != 1.0f) + ImFontAtlasTextureBlockPostProcessMultiply(data, data->FontSrc->RasterizerMultiply); +} + void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor) { unsigned char* pixels = data->Pixels; @@ -3753,10 +3740,9 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) new_tex->UseColors = old_tex->UseColors; IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize+repack %dx%d => Texture #%03d: %dx%d\n", old_tex->UniqueID, old_tex->Width, old_tex->Height, new_tex->UniqueID, new_tex->Width, new_tex->Height); - // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. - // Repack, lose discarded rectangle, copy pixels // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic. + // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. ImFontAtlasPackInit(atlas); ImVector old_rects; ImVector old_index = builder->RectsIndex; @@ -3819,7 +3805,7 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ old_tex_h = atlas->TexData->Height; // FIXME-NEWATLAS-V2: What to do when reaching limits exposed by backend? - // FIXME-NEWATLAS-V2: does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? + // FIXME-NEWATLAS-V2: Does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? Could we expose e.g. tex->UsedRect. IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); IM_ASSERT(ImIsPowerOfTwo(atlas->TexMinWidth) && ImIsPowerOfTwo(atlas->TexMaxWidth) && ImIsPowerOfTwo(atlas->TexMinHeight) && ImIsPowerOfTwo(atlas->TexMaxHeight)); @@ -4018,7 +4004,7 @@ void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) } // Important: Calling this may recreate a new texture and therefore change atlas->TexData -// FIXME-NEWATLAS-V2: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 +// FIXME-NEWFONTS: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry) { IM_ASSERT(w > 0 && w <= 0xFFFF); @@ -4190,9 +4176,10 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* } src->FontLoaderData = bd_font_data; - // FIXME-NEWATLAS-V2: reevaluate sizing metrics + // FIXME-NEWFONTS: reevaluate sizing metrics int oversample_h, oversample_v; ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); + if (src->SizePixels > 0.0f) { bd_font_data->ScaleForRasterX = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels * src->RasterizerDensity) * oversample_h; @@ -4206,7 +4193,7 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* bd_font_data->ScaleForLayout = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels); } - // FIXME-NEWATLAS-V2: make use of line gap value + // FIXME-NEWFONTS: make use of line gap value int unscaled_ascent, unscaled_descent, unscaled_line_gap; stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); diff --git a/imgui_internal.h b/imgui_internal.h index 71019611a..2f0b5b5d7 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -141,10 +141,10 @@ struct ImGuiTextIndex; // Maintain a line index for a text buffer. // ImDrawList/ImFontAtlas struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas +struct ImFontAtlasPostProcessData; // Data available to potential texture post-processing functions struct ImFontAtlasRect; // Packed rectangle (same as ImTextureRect) struct ImFontAtlasRectEntry; // Packed rectangle lookup entry -struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas -struct ImFontAtlasPostProcessData; // Data available to potential post-process functions // ImGui struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) @@ -3658,7 +3658,7 @@ struct ImFontAtlasRectEntry unsigned int Used : 1; }; -// Data available to potential post-process functions +// Data available to potential texture post-processing functions struct ImFontAtlasPostProcessData { ImFontAtlas* FontAtlas; @@ -3701,15 +3701,13 @@ struct ImFontAtlasBuilder ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } }; -// FIXME-NEWATLAS: Cleanup +IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMain(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader); IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char); -IMGUI_API void ImFontAtlasBuildMain(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas); - IMGUI_API ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h); IMGUI_API void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h); @@ -3718,26 +3716,24 @@ IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas); IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, const ImFontGlyph* in_glyph); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildDiscardFontGlyphs(ImFontAtlas* atlas, ImFont* font); -IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font); // <--- Your future new best friend! IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); -IMGUI_API ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* cfg, const ImFontGlyph* in_glyph); - IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); -IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); IMGUI_API void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); IMGUI_API void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex); IMGUI_API void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas); - IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); IMGUI_API void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data); IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor); From 953ce90d27d361d00b4e299aa4aa62fa49cfebf4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 13 Jan 2025 20:25:16 +0100 Subject: [PATCH 044/191] Fonts: ImFontAtlasBuildInit() uses the occasion to sync HasTexUpdates from imgui context, narrowing the scope where it isn't set. --- imgui_draw.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3369aabe9..b253b5010 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3912,6 +3912,8 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) if (builder_is_new) builder = atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); + ImFontAtlasBuildUpdateRendererHasTexUpdatesFromContext(atlas); + ImFontAtlasPackInit(atlas); // Add required texture data From a509790a1c83f8b5727c590887c402f9fb31a2be Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 13 Jan 2025 20:33:36 +0100 Subject: [PATCH 045/191] Fonts: Added back support for AddCustomRectFontGlyph() Legacy path naturally works. --- imgui.h | 2 +- imgui_draw.cpp | 130 +++++++++++++++++------------------------------ imgui_internal.h | 1 + 3 files changed, 48 insertions(+), 85 deletions(-) diff --git a/imgui.h b/imgui.h index 19c116cab..15e784b4b 100644 --- a/imgui.h +++ b/imgui.h @@ -3581,7 +3581,7 @@ struct ImFontAtlas // - Read docs/FONTS.md for more details about using colorful icons. // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. IMGUI_API int AddCustomRectRegular(int width, int height); - IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); IMGUI_API ImFontAtlasCustomRect* GetCustomRectByIndex(int index); // [Internal] diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b253b5010..af8913719 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3121,68 +3121,51 @@ void ImFontAtlas::RemoveFont(ImFont* font) ImFontAtlasBuildNotifySetFont(this, font, new_current_font); } -// FIXME-NEWATLAS-V1: Feature is broken for now. -/* - // Register custom rectangle glyphs - for (int i = 0; i < atlas->CustomRects.Size; i++) - { - const ImFontAtlasCustomRect* r = &atlas->CustomRects[i]; - if (r->Font == NULL || r->GlyphID == 0) - continue; - - // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, PixelSnapH - IM_ASSERT(r->Font->ContainerAtlas == atlas); - ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(r, &uv0, &uv1); - r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); - if (r->GlyphColored) - r->Font->Glyphs.back().Colored = 1; - } -*/ - int ImFontAtlas::AddCustomRectRegular(int width, int height) { IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - if (RendererHasTextures) - { - ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); - ImFontAtlasRect* r = ImFontAtlasPackGetRect(this, r_id); - ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); - return r_id; - } - else - { - // FIXME-NEWATLAS-V1: Unfinished - ImFontAtlasCustomRect r; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - //CustomRects.push_back(r); - //return CustomRects.Size - 1; // Return index + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); + if (r_id < 0) return -1; - } + ImFontAtlasRect* r = ImFontAtlasPackGetRect(this, r_id); + if (RendererHasTextures) + ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + return r_id; } -int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { #ifdef IMGUI_USE_WCHAR32 - IM_ASSERT(id <= IM_UNICODE_CODEPOINT_MAX); + IM_ASSERT(codepoint <= IM_UNICODE_CODEPOINT_MAX); #endif IM_ASSERT(font != NULL); IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - ImFontAtlasCustomRect r; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - r.GlyphID = id; - r.GlyphColored = 0; // Set to 1 manually to mark glyph as colored // FIXME: No official API for that (#8133) - r.GlyphAdvanceX = advance_x; - r.GlyphOffset = offset; - r.Font = font; - //CustomRects.push_back(r); - //return CustomRects.Size - 1; // Return index - return -1; + + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); + if (r_id < 0) + return -1; + ImFontAtlasRect* r = ImFontAtlasPackGetRect(this, r_id); + if (RendererHasTextures) + ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + + if (font->IsGlyphLoaded(codepoint)) + ImFontAtlasBuildDiscardFontGlyph(this, font, (ImFontGlyph*)(void*)font->FindGlyph(codepoint)); + + ImFontGlyph glyph; + glyph.Codepoint = codepoint; + glyph.AdvanceX = advance_x; + glyph.X0 = offset.x; + glyph.Y0 = offset.y; + glyph.X1 = offset.x + r->w; + glyph.Y1 = offset.y + r->h; + glyph.Visible = true; + glyph.Colored = true; // FIXME: Arbitrary + glyph.PackId = r_id; + ImFontAtlasBuildAddFontGlyph(this, font, &font->Sources[0], &glyph); + return r_id; } ImFontAtlasCustomRect* ImFontAtlas::GetCustomRectByIndex(int idx) @@ -3308,41 +3291,6 @@ void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) } } -// FIXME-NEWATLAS: Unused -#if 0 -void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque) -{ - ImTextureData* tex = atlas->TexData; - stbrp_context* pack_context = (stbrp_context*)stbrp_context_opaque; - IM_ASSERT(pack_context != NULL); - - ImVector& user_rects = atlas->CustomRects; - IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. -#ifdef __GNUC__ - if (user_rects.Size < 1) { __builtin_unreachable(); } // Workaround for GCC bug if IM_ASSERT() is defined to conditionally throw (see #5343) -#endif - - const int pack_padding = atlas->TexGlyphPadding; - ImVector pack_rects; - pack_rects.resize(user_rects.Size); - memset(pack_rects.Data, 0, (size_t)pack_rects.size_in_bytes()); - for (int i = 0; i < user_rects.Size; i++) - { - pack_rects[i].w = user_rects[i].Width + pack_padding; - pack_rects[i].h = user_rects[i].Height + pack_padding; - } - stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); - for (int i = 0; i < pack_rects.Size; i++) - if (pack_rects[i].was_packed) - { - user_rects[i].X = (unsigned short)pack_rects[i].x; - user_rects[i].Y = (unsigned short)pack_rects[i].y; - IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); - tex->Height = ImMax(tex->Height, pack_rects[i].y + pack_rects[i].h); - } -} -#endif - // Render a white-colored bitmap encoded in a string void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char) { @@ -3601,6 +3549,20 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* sr font->LockSingleSrcConfigIdx = -1; } +void ImFontAtlasBuildDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontGlyph* glyph) +{ + if (glyph->PackId >= 0) + { + ImFontAtlasPackDiscardRect(atlas, glyph->PackId); + glyph->PackId = -1; + } + ImWchar c = glyph->Codepoint; + IM_ASSERT(font->FallbackChar != c && font->EllipsisChar != c); // Unsupported for simplicity + IM_ASSERT(glyph >= font->Glyphs.Data && glyph < font->Glyphs.Data + font->Glyphs.Size); + font->IndexLookup[c] = (ImWchar)IM_FONTGLYPH_INDEX_UNUSED; + font->IndexAdvanceX[c] = font->FallbackAdvanceX; +} + void ImFontAtlasBuildDiscardFontGlyphs(ImFontAtlas* atlas, ImFont* font) { for (ImFontGlyph& glyph : font->Glyphs) @@ -3912,7 +3874,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) if (builder_is_new) builder = atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); - ImFontAtlasBuildUpdateRendererHasTexUpdatesFromContext(atlas); + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); ImFontAtlasPackInit(atlas); diff --git a/imgui_internal.h b/imgui_internal.h index 2f0b5b5d7..6c9c6de73 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3718,6 +3718,7 @@ IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, const ImFontGlyph* in_glyph); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontGlyph* glyph); IMGUI_API void ImFontAtlasBuildDiscardFontGlyphs(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font); // <--- Your future new best friend! IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy From ba62becb7d0cdf18fcf2caed145b0f8bcb9b13c4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jan 2025 11:53:24 +0100 Subject: [PATCH 046/191] (Breaking) Fonts: remove ImFontAtlasCustomRect which is now the same as ImTextureRect --- imgui.h | 62 ++++++++++++++++++++++++++------------------------ imgui_draw.cpp | 21 +++++++++-------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/imgui.h b/imgui.h index 15e784b4b..c521236bf 100644 --- a/imgui.h +++ b/imgui.h @@ -3468,22 +3468,6 @@ struct ImFontGlyphRangesBuilder IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges }; -// See ImFontAtlas::AddCustomRectXXX functions. -struct ImFontAtlasCustomRect -{ - unsigned short X, Y; // Output // Packed position in Atlas - - // [Internal] - unsigned short Width, Height; // Input // Desired rectangle dimension - unsigned int GlyphID : 31; // Input // For custom font glyphs only (ID < 0x110000) - unsigned int GlyphColored : 1; // Input // For custom font glyphs only: glyph is colored, removed tinting. - float GlyphAdvanceX; // Input // For custom font glyphs only: glyph xadvance - ImVec2 GlyphOffset; // Input // For custom font glyphs only: glyph display offset - ImFont* Font; // Input // For custom font glyphs only: target font - ImFontAtlasCustomRect() { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } - bool IsPacked() const { return X != 0xFFFF; } -}; - // Flags for ImFontAtlas build enum ImFontAtlasFlags_ { @@ -3568,24 +3552,21 @@ struct ImFontAtlas // [ALPHA] Custom Rectangles/Glyphs API //------------------------------------------- - // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. + // You can request arbitrary rectangles to be packed into the atlas, for your own purpose. // You can request your rectangles to be mapped as font glyph (given a font + Unicode point), // so you can render e.g. custom colorful icons and use them as regular glyphs. - // - If your backend supports ImGuiBackendFlags_RendererHasTextures (since 1.92.X): - // - Packing is done immediately. Returns >= on success. Return <0 on error. - // - You can render your pixels into the texture right after calling the AddCustomRectXXX functions. - // - Texture may be resized, so you cannot cache UV coordinates. // FIXME-NEWATLAS-V1: How to handle that smoothly? - // - If your backend does NOT supports ImGuiBackendFlags_RendererHasTextures (older than 1.92.X): - // - After calling Build(), you can query the rectangle position and render your pixels. - // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. + // - Since 1.92.X, packing is done immediately in the function call. Returns >= on success, <0 on error. + // - You can render your pixels into the texture right after calling the AddCustomRectXXX functions, without waiting for the Build() call. + // - If your backend supports ImGuiBackendFlags_RendererHasTextures: + // Texture may be resized, so you cannot cache UV coordinates: always use CalcCustomRectUV(). + // - If you render colored output into your AddCustomRectRegular() rectangle: set 'atlas->TexPixelsUseColors = true' + // as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. IMGUI_API int AddCustomRectRegular(int width, int height); IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); - IMGUI_API ImFontAtlasCustomRect* GetCustomRectByIndex(int index); - - // [Internal] - IMGUI_API void CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; + IMGUI_API ImTextureRect* GetCustomRectByIndex(int index); + IMGUI_API void CalcCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; //------------------------------------------- // Members @@ -3613,7 +3594,6 @@ struct ImFontAtlas ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector Sources; // Source/configuration data - //ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines int TexNextUniqueID; // Next value to be stored in TexData->UniqueID ImVector DrawListSharedDatas; @@ -3627,7 +3607,8 @@ struct ImFontAtlas int RefCount; // Number of contexts using this atlas // [Obsolete] - //int TexDesiredWidth; // OBSOLETED in 1.91.5 (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) + //int TexDesiredWidth; // OBSOLETED in 1.92.X (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) + //typedef ImTextureRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.X //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ }; @@ -3905,6 +3886,27 @@ namespace ImGui //static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42 } +//-- OBSOLETED in 1.91.7 (from January 2025): ImFontAtlasCustomRect becomes ImTextureRect +// - ImFontAtlasCustomRect::X --> ImTextureRect::x +// - ImFontAtlasCustomRect::Y --> ImTextureRect::y +// - ImFontAtlasCustomRect::Width --> ImTextureRect::w +// - ImFontAtlasCustomRect::Height --> ImTextureRect::h +// - ImFontAtlasCustomRect::GlyphColored --> if you need to write to this, instead you can write to 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph() +// We could make ImTextureRect an union to use old names, such 1) this would be confusing 2) the fix is easy 3) ImFontAtlasCustomRect was always a rather esoteric api. +typedef ImTextureRect ImFontAtlasCustomRect; +/*struct ImFontAtlasCustomRect +{ + unsigned short X, Y; // Output // Packed position in Atlas + unsigned short Width, Height; // Input // [Internal] Desired rectangle dimension + unsigned int GlyphID:31; // Input // [Internal] For custom font glyphs only (ID < 0x110000) + unsigned int GlyphColored:1; // Input // [Internal] For custom font glyphs only: glyph is colored, removed tinting. + float GlyphAdvanceX; // Input // [Internal] For custom font glyphs only: glyph xadvance + ImVec2 GlyphOffset; // Input // [Internal] For custom font glyphs only: glyph display offset + ImFont* Font; // Input // [Internal] For custom font glyphs only: target font + ImFontAtlasCustomRect() { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } +};*/ + //-- OBSOLETED in 1.82 (from Mars 2021): flags for AddRect(), AddRectFilled(), AddImageRounded(), PathRect() //typedef ImDrawFlags ImDrawCornerFlags; //enum ImDrawCornerFlags_ diff --git a/imgui_draw.cpp b/imgui_draw.cpp index af8913719..1f13ed0f6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3135,6 +3135,8 @@ int ImFontAtlas::AddCustomRectRegular(int width, int height) return r_id; } +// FIXME: we automatically set glyph.Colored=true by default. +// If you need to alter this, you can write 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph(). int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { #ifdef IMGUI_USE_WCHAR32 @@ -3168,21 +3170,20 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int wid return r_id; } -ImFontAtlasCustomRect* ImFontAtlas::GetCustomRectByIndex(int idx) +ImTextureRect* ImFontAtlas::GetCustomRectByIndex(int idx) { - IM_STATIC_ASSERT(offsetof(ImFontAtlasCustomRect, X) == offsetof(ImFontAtlasRect, x)); - IM_STATIC_ASSERT(offsetof(ImFontAtlasCustomRect, Y) == offsetof(ImFontAtlasRect, y)); - IM_STATIC_ASSERT(offsetof(ImFontAtlasCustomRect, Width) == offsetof(ImFontAtlasRect, w)); - IM_STATIC_ASSERT(offsetof(ImFontAtlasCustomRect, Height) == offsetof(ImFontAtlasRect, h)); - return (ImFontAtlasCustomRect*)(void*)ImFontAtlasPackGetRect(this, idx); + IM_STATIC_ASSERT(offsetof(ImTextureRect, x) == offsetof(ImFontAtlasRect, x)); + IM_STATIC_ASSERT(offsetof(ImTextureRect, y) == offsetof(ImFontAtlasRect, y)); + IM_STATIC_ASSERT(offsetof(ImTextureRect, w) == offsetof(ImFontAtlasRect, w)); + IM_STATIC_ASSERT(offsetof(ImTextureRect, h) == offsetof(ImFontAtlasRect, h)); + return (ImTextureRect*)(void*)ImFontAtlasPackGetRect(this, idx); } -void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const +void ImFontAtlas::CalcCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const { IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates - IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed - *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); - *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); + *out_uv_min = ImVec2((float)rect->x * TexUvScale.x, (float)rect->y * TexUvScale.y); + *out_uv_max = ImVec2((float)(rect->x + rect->w) * TexUvScale.x, (float)(rect->y + rect->w) * TexUvScale.y); } bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) From 2bf6552f2facc23c5aa84a610fb8c3983ae87d1d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 16 Jan 2025 11:35:55 +0100 Subject: [PATCH 047/191] Fonts: Fixed/improved support for legacy backend. SetTexID() writes into our ImTextureData to keep the indirection, clear TexIsBuilt. The idea is that a legacy backend can somehow add a if (!atlas->IsBuilt()) ImGui_ImplXXXXX_CreateFontsTexture() call _after_ Render() and some features are supported. --- imgui.cpp | 16 ++++------------ imgui.h | 29 ++++++++++++++++------------- imgui_draw.cpp | 26 ++++++++++++++++++++------ 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index a505d5d6c..f681e71b6 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5178,13 +5178,16 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos) io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } +// FIXME-NEWATLAS-V2: If we aim to support multiple atlases used by same context: how to reach/target all atlases? static void ImGui::UpdateTexturesNewFrame() { - // FIXME-NEWATLAS-V2: If we aim to support multiple atlases used by same context: how to reach/target all atlases? ImGuiContext& g = *GImGui; ImFontAtlas* atlas = g.IO.Fonts; if (g.FontAtlasOwnedByContext) + { + atlas->RendererHasTextures = (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; ImFontAtlasUpdateNewFrame(atlas); + } } // Build a single texture list @@ -5240,13 +5243,6 @@ void ImGui::NewFrame() CallContextHooks(&g, ImGuiContextHookType_NewFramePre); - // Check that font atlas was built or backend support texture reload in which case we can build now - ImFontAtlas* atlas = g.IO.Fonts; - if (!atlas->TexIsBuilt && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) - ImFontAtlasBuildMain(atlas); - else // Legacy backend - IM_ASSERT(atlas->TexIsBuilt && "Backend does not support ImGuiBackendFlags_RendererHasTextures, and font atlas is not built! Update backend OR make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()."); - // Check and assert for various common IO and Configuration mistakes ErrorCheckNewFrameSanityChecks(); @@ -8573,11 +8569,7 @@ void ImGui::UpdateFontsNewFrame() { ImGuiContext& g = *GImGui; if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) - { g.IO.Fonts->Locked = true; - for (ImFont* font : g.IO.Fonts->Fonts) - font->LockDisableLoading = true; - } SetCurrentFont(GetDefaultFont()); IM_ASSERT(g.Font->IsLoaded()); } diff --git a/imgui.h b/imgui.h index c521236bf..73e3f7edd 100644 --- a/imgui.h +++ b/imgui.h @@ -3332,7 +3332,7 @@ struct ImDrawData }; //----------------------------------------------------------------------------- -// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureDataUpdate, ImTextureData +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData //----------------------------------------------------------------------------- // We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. @@ -3517,17 +3517,20 @@ struct ImFontAtlas IMGUI_API void ClearTexData(); // [OBSOLETE] Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - // Build atlas, retrieve pixel data. - // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). - // The pitch is always = Width * BytesPerPixels (1 or 4) - // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into - // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. - IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. - IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - void SetTexID(ImTextureID id) { TexRef._TexData = NULL; TexRef._TexID = id; } // Called by legacy backends. - void SetTexID(ImTextureRef id) { TexRef = id; } // Called by legacy backends. - bool IsBuilt() const { return Fonts.Size > 0 && TexIsBuilt; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... + // Legacy path for build atlas + retrieving pixel data. + // - User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // - The pitch is always = Width * BytesPerPixels (1 or 4) + // - Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into + // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. + // - From 1.92 with backends supporting ImGuiBackendFlags_RendererHasTextures: + // - Calling Build(), GetTexDataAsAlpha8(), GetTexDataAsRGBA32() is not needed. + // - In backend: replace calls to ImFontAtlas::SetTexID() with calls to ImTextureData::SetTexID() after honoring texture creation. + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { IM_ASSERT(TexRef._TexID == ImTextureID_Invalid); TexRef._TexData->TexID = id; } // Called by legacy backends. May be called before texture creation. + void SetTexID(ImTextureRef id) { IM_ASSERT(TexRef._TexID == ImTextureID_Invalid && id._TexData == NULL); TexRef._TexData->TexID = id._TexID; } // Called by legacy backends. + bool IsBuilt() const { return Fonts.Size > 0 && TexIsBuilt; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent.. #endif //------------------------------------------- @@ -3588,7 +3591,7 @@ struct ImFontAtlas ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetDrawData().Textures[]/GetPlatformIO().Textures[] instead! bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. bool RendererHasTextures;// Copy of (BackendFlags & ImGuiBackendFlags_RendererHasTextures) from supporting context. - bool TexIsBuilt; // Set when texture was built matching current font input + bool TexIsBuilt; // Set when texture was built matching current font input. Mostly useful for legacy IsBuilt() call. bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight) ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 1f13ed0f6..5e9d1de91 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2485,6 +2485,7 @@ void ImTextureData::DestroyPixels() // - ImFontAtlas::CalcCustomRectUV() // - ImFontAtlasGetMouseCursorTexData() //----------------------------------------------------------------------------- +// - ImFontAtlasBuildMain() // - ImFontAtlasBuildSetupFontLoader() // - ImFontAtlasBuildPreloadAllGlyphRanges() // - ImFontAtlasBuildUpdatePointers() @@ -2685,15 +2686,24 @@ static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* at } // Called by NewFrame(). When multiple context own the atlas, only the first one calls this. +// If you are calling this yourself, ensure atlas->RendererHasTexUpdates is et. void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) { - if (atlas->TexIsBuilt && atlas->Builder->PreloadedAllGlyphsRanges) + // Check that font atlas was built or backend support texture reload in which case we can build now + if (atlas->RendererHasTextures) { - ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); - IM_ASSERT_USER_ERROR(atlas->RendererHasTextures == false, - "Called ImFontAtlas::Build() before ImGuiBackendFlags_RendererHasTextures got set! With new backends: you don't need to call Build()."); + atlas->TexIsBuilt = true; + if (atlas->Builder == NULL) // This will only happen if fonts were not already loaded. + ImFontAtlasBuildMain(atlas); } + else // Legacy backend + { + IM_ASSERT_USER_ERROR(atlas->TexIsBuilt, "Backend does not support ImGuiBackendFlags_RendererHasTextures, and font atlas is not built! Update backend OR make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()."); + } + if (atlas->TexIsBuilt && atlas->Builder->PreloadedAllGlyphsRanges) + IM_ASSERT_USER_ERROR(atlas->RendererHasTextures == false, "Called ImFontAtlas::Build() before ImGuiBackendFlags_RendererHasTextures got set! With new backends: you don't need to call Build()."); + // Update texture status for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { ImTextureData* tex = atlas->TexList[tex_n]; @@ -2865,6 +2875,7 @@ void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y); tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x); tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y); + atlas->TexIsBuilt = false; // No need to queue if status is _WantCreate if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates) @@ -3206,6 +3217,7 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso return true; } +// When atlas->RendererHasTexUpdates == true, this is only called if no font were loaded. void ImFontAtlasBuildMain(ImFontAtlas* atlas) { IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); @@ -3589,6 +3601,7 @@ void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font) atlas->FontLoader->FontSrcInit(atlas, src); ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, src); // Technically this is called for each source sub-font, tho 99.9% of the time the first one fills everything. + atlas->TexIsBuilt = false; } // Notify external systems @@ -3684,6 +3697,7 @@ ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h) new_tex->Create(atlas->TexDesiredFormat, w, h); new_tex->Status = ImTextureStatus_WantCreate; + atlas->TexIsBuilt = false; ImFontAtlasBuildSetTexture(atlas, new_tex); @@ -4037,12 +4051,12 @@ ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id ImFontGlyph* ImFont::BuildLoadGlyph(ImWchar codepoint) { - if (LockDisableLoading) + ImFontAtlas* atlas = ContainerAtlas; + if (LockDisableLoading || atlas->Locked) return NULL; //char utf8_buf[5]; //IMGUI_DEBUG_LOG("[font] BuildAddGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); - ImFontAtlas* atlas = ContainerAtlas; // Load from single source or all sources? int srcs_count = (LockSingleSrcConfigIdx != -1) ? 1 : SourcesCount; From bd19bc50858d80113080fd22f7a8538d953e3c7c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 16 Jan 2025 14:57:33 +0100 Subject: [PATCH 048/191] Fonts: Removed BuildClearGlyphs(), conflated with ClearOutputData() --- imgui.h | 1 - imgui_draw.cpp | 20 ++------------------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/imgui.h b/imgui.h index 73e3f7edd..1baa4067a 100644 --- a/imgui.h +++ b/imgui.h @@ -3673,7 +3673,6 @@ struct ImFont IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); IMGUI_API ImFontGlyph* BuildLoadGlyph(ImWchar c); IMGUI_API void BuildGrowIndex(int new_size); - IMGUI_API void BuildClearGlyphs(); }; // Added indirection to avoid patching ImDrawCmd after texture updates. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 5e9d1de91..2351b2f51 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2522,8 +2522,6 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasPackGetRect() //----------------------------------------------------------------------------- // - ImFont::BuildLoadGlyph() -// - ImFont::BuildClearGlyphs() -//----------------------------------------------------------------------------- // - ImFontAtlasDebugLogTextureRequests() //----------------------------------------------------------------------------- // - ImFontAtlasGetFontLoaderForStbTruetype() @@ -3581,7 +3579,7 @@ void ImFontAtlasBuildDiscardFontGlyphs(ImFontAtlas* atlas, ImFont* font) for (ImFontGlyph& glyph : font->Glyphs) if (glyph.PackId >= 0) ImFontAtlasPackDiscardRect(atlas, glyph.PackId); - font->BuildClearGlyphs(); + font->ClearOutputData(); font->FallbackChar = font->EllipsisChar = 0; } @@ -3915,7 +3913,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) { for (ImFont* font : atlas->Fonts) - font->BuildClearGlyphs(); + font->ClearOutputData(); if (atlas->FontLoader && atlas->FontLoader->FontSrcDestroy != NULL) for (ImFontConfig& font_cfg : atlas->Sources) atlas->FontLoader->FontSrcDestroy(atlas, &font_cfg); @@ -4650,30 +4648,16 @@ ImFont::~ImFont() void ImFont::ClearOutputData() { - FontSize = 0.0f; FallbackAdvanceX = 0.0f; Glyphs.clear(); IndexAdvanceX.clear(); IndexLookup.clear(); FallbackGlyphIndex = -1; - ContainerAtlas = NULL; Ascent = Descent = 0.0f; MetricsTotalSurface = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); } -void ImFont::BuildClearGlyphs() -{ - FallbackAdvanceX = 0.0f; - Glyphs.clear(); - IndexAdvanceX.clear(); - IndexLookup.clear(); - FallbackGlyphIndex = 0; - MetricsTotalSurface = 0; - memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); - // Don't clear BuilderData -} - // API is designed this way to avoid exposing the 8K page size // e.g. use with IsGlyphRangeUnused(0, 255) bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) From 722f6013ff15957833960ebe46e1ea2eab97b5eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 16 Jan 2025 15:41:46 +0100 Subject: [PATCH 049/191] Fonts: Added a bit of user facing tooling. --- imgui.cpp | 23 +++++++++++++++++++++++ imgui_demo.cpp | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index f681e71b6..24073f715 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15594,6 +15594,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) { ImGuiContext& g = *GImGui; + SeparatorText("Fonts"); Text("Read "); SameLine(0, 0); TextLinkOpenURL("https://www.dearimgui.com/faq/"); @@ -15641,6 +15642,28 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) PopID(); } + SeparatorText("Font Atlas"); + if (Button("Clear Cache")) + atlas->ClearCache(); + SameLine(); + if (Button("Grow")) + ImFontAtlasBuildGrowTexture(atlas); + SameLine(); + if (Button("Compact")) + ImFontAtlasBuildCompactTexture(atlas); + + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + ImTextureData* tex = atlas->TexList[tex_n]; + if (tex_n > 0) + SameLine(); + Text("Tex: %dx%d", tex->Width, tex->Height); + } + const int packed_surface_sqrt = (int)sqrtf((float)atlas->Builder->RectsPackedSurface); + const int discarded_surface_sqrt = (int)sqrtf((float)atlas->Builder->RectsDiscardedSurface); + Text("Packed rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsPackedCount, atlas->Builder->RectsPackedSurface, packed_surface_sqrt, packed_surface_sqrt); + Text("incl. Discarded rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsDiscardedCount, atlas->Builder->RectsDiscardedSurface, discarded_surface_sqrt, discarded_surface_sqrt); + // Texture list for (ImTextureData* tex : atlas->TexList) { diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 5d5075e81..55b09a2bf 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8446,11 +8446,11 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) { ImGuiIO& io = GetIO(); ImFontAtlas* atlas = io.Fonts; - HelpMarker("Read FAQ and docs/FONTS.md for details on font loading."); ShowFontAtlas(atlas); // Post-baking font scaling. Note that this is NOT the nice way of scaling fonts, read below. // (we enforce hard clamping manually as by default DragFloat/SliderFloat allows CTRL+Click text to get out of bounds). + SeparatorText("Legacy Scaling"); const float MIN_SCALE = 0.3f; const float MAX_SCALE = 2.0f; HelpMarker( From b203ac1e0dce0a770954ca8b1d486d2ea00ff83c Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 18 Jan 2025 15:01:05 +0100 Subject: [PATCH 050/191] Fonts: Reduced reliance on ImFontConfig::DstFont. --- imgui_draw.cpp | 41 ++++++++++++++++++----------------------- imgui_internal.h | 2 +- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2351b2f51..18e9501c3 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2966,9 +2966,6 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) return NULL; } - // Invalidate texture - //TexReady = false; - //ClearTexData(); return new_font_cfg.DstFont; } @@ -3276,15 +3273,16 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas) { atlas->Builder->PreloadedAllGlyphsRanges = true; - for (int src_n = 0; src_n < atlas->Sources.Size; src_n++) - { - ImFontConfig* src = &atlas->Sources[src_n]; - const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); - IM_ASSERT(ranges != NULL); - for (; ranges[0]; ranges += 2) - for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 - src->DstFont->FindGlyphNoFallback((ImWchar)c); - } + for (ImFont* font : atlas->Fonts) + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = &font->Sources[src_n]; + const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); + IM_ASSERT(ranges != NULL); + for (; ranges[0]; ranges += 2) + for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 + font->FindGlyphNoFallback((ImWchar)c); + } } void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) @@ -3462,17 +3460,15 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) if (!font_loader->FontSrcInit(atlas, src)) return false; - ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, src); + ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); return true; } // Rasterize our own ellipsis character from a dot. // This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. // FIXME-NEWATLAS: This borrows too much from FontLoader's FontAddGlyph() and suggest that we should add further helpers. -static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, ImFontConfig* src, const ImFontGlyph* dot_glyph) +static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, ImFont* font, const ImFontGlyph* dot_glyph) { - ImFont* font = src->DstFont; - ImFontAtlasRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_glyph->PackId); const int dot_spacing = 1; const float dot_step = (dot_glyph->X1 - dot_glyph->X0) + dot_spacing; @@ -3502,15 +3498,14 @@ static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, I // Load/identify special glyphs // (note that this is called again for fonts with MergeMode) -void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src) +void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src) { - ImFont* font = src->DstFont; - const int cfg_idx_in_font = (int)(src - font->Sources); - IM_ASSERT(cfg_idx_in_font >= 0 && cfg_idx_in_font < font->SourcesCount); + const int src_idx_in_font = (int)(src - font->Sources); + IM_ASSERT(src_idx_in_font >= 0 && src_idx_in_font < font->SourcesCount); IM_UNUSED(atlas); // While manipulating glyphs during init we want to restrict all searches for one source font. - font->LockSingleSrcConfigIdx = (short)cfg_idx_in_font; + font->LockSingleSrcConfigIdx = (short)src_idx_in_font; // Setup Fallback character // FIXME-NEWATLAS: could we use a scheme where this is lazily loaded? @@ -3553,7 +3548,7 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* sr { const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; if (const ImFontGlyph* dot_glyph = LoadFirstExistingGlyph(font, dots_chars, IM_ARRAYSIZE(dots_chars))) - ImFontAtlasBuildSetupFontCreateEllipsisFromDot(atlas, src, dot_glyph); + ImFontAtlasBuildSetupFontCreateEllipsisFromDot(atlas, font, dot_glyph); else font->EllipsisChar = (ImWchar)' '; } @@ -3598,7 +3593,7 @@ void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font) if (atlas->FontLoader && atlas->FontLoader->FontSrcInit != NULL) atlas->FontLoader->FontSrcInit(atlas, src); - ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, src); // Technically this is called for each source sub-font, tho 99.9% of the time the first one fills everything. + ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); // Technically this is called for each source sub-font, tho 99.9% of the time the first one fills everything. atlas->TexIsBuilt = false; } diff --git a/imgui_internal.h b/imgui_internal.h index 6c9c6de73..1126a91ba 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3717,7 +3717,7 @@ IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, const ImFontGlyph* in_glyph); -IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontGlyph* glyph); IMGUI_API void ImFontAtlasBuildDiscardFontGlyphs(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font); // <--- Your future new best friend! From c5653d5f34b50d9edb2d189790df3fbabdc72f82 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 20 Jan 2025 20:01:21 +0100 Subject: [PATCH 051/191] Fonts: stb_truetype loader: Reworked scale handling to suggest this is not required caching. --- imgui_draw.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 18e9501c3..4edddf5fa 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4151,19 +4151,14 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* // FIXME-NEWFONTS: reevaluate sizing metrics int oversample_h, oversample_v; ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); - + float scale; if (src->SizePixels > 0.0f) - { - bd_font_data->ScaleForRasterX = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels * src->RasterizerDensity) * oversample_h; - bd_font_data->ScaleForRasterY = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels * src->RasterizerDensity) * oversample_v; - bd_font_data->ScaleForLayout = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels); - } + scale = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); else - { - bd_font_data->ScaleForRasterX = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels * src->RasterizerDensity) * oversample_h; - bd_font_data->ScaleForRasterY = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels * src->RasterizerDensity) * oversample_v; - bd_font_data->ScaleForLayout = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels); - } + scale = -stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); + bd_font_data->ScaleForRasterX = scale * src->SizePixels * src->RasterizerDensity * oversample_h; + bd_font_data->ScaleForRasterY = scale * src->SizePixels * src->RasterizerDensity * oversample_v; + bd_font_data->ScaleForLayout = scale * src->SizePixels; // FIXME-NEWFONTS: make use of line gap value int unscaled_ascent, unscaled_descent, unscaled_line_gap; From fb69a09d6e324573371daee953232988afc24a6a Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 23 Jan 2025 12:12:07 +0100 Subject: [PATCH 052/191] Fonts: Fixed leak due to indirectly recursing ImFontAtlasPackInit(). --- imgui_draw.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 4edddf5fa..dfb7bb0d6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3857,8 +3857,6 @@ void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) // Start packing over current empty texture void ImFontAtlasBuildInit(ImFontAtlas* atlas) { - ImFontAtlasBuilder* builder = atlas->Builder; - // Select Backend // - Note that we do not reassign to atlas->FontLoader, since it is likely to point to static data which // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are @@ -3873,14 +3871,20 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) #else IM_ASSERT(0); // Invalid Build function #endif + return; // ImFontAtlasBuildSetupFontBackendIO() automatically call ImFontAtlasBuildInit() } + // Create initial texture size if (atlas->TexData == NULL) ImFontAtlasBuildAddTexture(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); + ImFontAtlasBuilder* builder = atlas->Builder; // Do not move above const bool builder_is_new = (builder == NULL); if (builder_is_new) + { + IM_ASSERT(atlas->Builder == NULL); builder = atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); + } ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); From a2371ef90bad9e176e7eaa0693efc4a7329cf66c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 22 Jan 2025 20:01:26 +0100 Subject: [PATCH 053/191] Internals: added ImStableVector<> helper. --- imgui_internal.h | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/imgui_internal.h b/imgui_internal.h index 1126a91ba..07cd84667 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -133,7 +133,7 @@ Index of this file: //----------------------------------------------------------------------------- // Utilities -// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImPool<>, ImChunkStream<>) +// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImStableVector<>, ImPool<>, ImChunkStream<>) struct ImBitVector; // Store 1-bit per value struct ImRect; // An axis-aligned rectangle (2 points) struct ImGuiTextIndex; // Maintain a line index for a text buffer. @@ -357,6 +357,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer // - Helper: ImBitArray // - Helper: ImBitVector // - Helper: ImSpan<>, ImSpanAllocator<> +// - Helper: ImStableVector<> // - Helper: ImPool<> // - Helper: ImChunkStream<> // - Helper: ImGuiTextIndex @@ -694,6 +695,39 @@ struct ImSpanAllocator inline void GetSpan(int n, ImSpan* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } }; +// Helper: ImStableVector<> +// Allocating chunks of BLOCK_SIZE items. Objects pointers are never invalidated when growing, only by clear(). +// Important: does not destruct anything! +// Implemented only the minimum set of functions we need for it. +template +struct ImStableVector +{ + int Size = 0; + int Capacity = 0; + ImVector Blocks; + + // Functions + inline ~ImStableVector() { for (T* block : Blocks) IM_FREE(block); } + + inline void clear() { Size = Capacity = 0; Blocks.clear_delete(); } + inline void resize(int new_size) { if (new_size > Capacity) reserve(new_size); Size = new_size; } + inline void reserve(int new_cap) + { + new_cap = IM_MEMALIGN(new_cap, BLOCK_SIZE); + int old_count = Capacity / BLOCK_SIZE; + int new_count = new_cap / BLOCK_SIZE; + if (new_count <= old_count) + return; + Blocks.resize(new_count); + for (int n = old_count; n < new_count; n++) + Blocks[n] = (T*)IM_ALLOC(sizeof(T) * BLOCK_SIZE); + Capacity = new_cap; + } + inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } + inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } + inline T* push_back(const T& v) { int i = Size; IM_ASSERT(i >= 0); if (Size == Capacity) reserve(Capacity + BLOCK_SIZE); void* ptr = &Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; memcpy(ptr, &v, sizeof(v)); Size++; return (T*)ptr; } +}; + // Helper: ImPool<> // Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer, // Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object. From 7aba8da5515e3003be071f5392cac3aaa663c8aa Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 24 Jan 2025 18:10:42 +0100 Subject: [PATCH 054/191] (Breaking) Fonts: CalcWordWrapPositionA() -> CalcWordWrapPosition(), takes size instead of scale as this will be needed. --- imgui_draw.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index dfb7bb0d6..c9c32e930 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4795,7 +4795,7 @@ bool ImFont::IsGlyphInFont(ImWchar c) return false; } -// This is manually inlined in CalcTextSizeA() and CalcWordWrapPositionA(), with a non-inline call to BuildLoadGlyphGetAdvanceOrFallback(). +// This is manually inlined in CalcTextSizeA() and CalcWordWrapPosition(), with a non-inline call to BuildLoadGlyphGetAdvanceOrFallback(). IM_MSVC_RUNTIME_CHECKS_OFF float ImFont::GetCharAdvance(ImWchar c) { From 093d01269a05d5f6fab9eee85d3e187b22364fe7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 23 Jan 2025 15:46:22 +0100 Subject: [PATCH 055/191] Fonts: Baked system, with auto-bind, v10. # Conflicts: # imgui_internal.h --- imgui.cpp | 94 +++--- imgui.h | 62 ++-- imgui_draw.cpp | 480 ++++++++++++++++++++----------- imgui_internal.h | 39 ++- imgui_tables.cpp | 2 +- imgui_widgets.cpp | 33 ++- misc/freetype/imgui_freetype.cpp | 131 ++++++--- 7 files changed, 545 insertions(+), 296 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 24073f715..abe3cab3e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3738,7 +3738,8 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con const float font_size = draw_list->_Data->FontSize; const float font_scale = draw_list->_Data->FontScale; const char* text_end_ellipsis = NULL; - const float ellipsis_width = font->GetCharAdvance(font->EllipsisChar) * font_scale; + ImFontBaked* baked = font->GetFontBaked(font_size); + const float ellipsis_width = baked->GetCharAdvance(font->EllipsisChar) * font_scale; // We can now claim the space between pos_max.x and ellipsis_max.x const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0f); @@ -3939,7 +3940,8 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) Initialized = false; FontAtlasOwnedByContext = shared_font_atlas ? false : true; Font = NULL; - FontSize = FontBaseSize = FontScale = CurrentDpiScale = 0.0f; + FontBaked = NULL; + FontSize = /*FontBaseSize = */FontScale = CurrentDpiScale = 0.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); IO.Fonts->RefCount++; Time = 0.0f; @@ -4275,6 +4277,7 @@ void ImGui::Shutdown() g.MenusIdSubmittedThisFrame.clear(); g.InputTextState.ClearFreeMemory(); g.InputTextDeactivatedState.ClearFreeMemory(); + g.InputTextPasswordFont.ContainerAtlas = NULL; g.SettingsWindows.clear(); g.SettingsHandlers.clear(); @@ -4371,8 +4374,9 @@ static void SetCurrentWindow(ImGuiWindow* window) g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking if (window) { - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); - g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; + // FIXME-BAKED + //g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); + //g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } } @@ -5186,7 +5190,7 @@ static void ImGui::UpdateTexturesNewFrame() if (g.FontAtlasOwnedByContext) { atlas->RendererHasTextures = (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; - ImFontAtlasUpdateNewFrame(atlas); + ImFontAtlasUpdateNewFrame(atlas, g.FrameCount); } } @@ -8399,11 +8403,14 @@ ImVec2 ImGui::GetFontTexUvWhitePixel() void ImGui::SetWindowFontScale(float scale) { IM_ASSERT(scale > 0.0f); + // FIXME-BAKED + /* ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); window->FontWindowScale = scale; g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; + */ } void ImGui::PushFocusScope(ImGuiID id) @@ -8570,20 +8577,21 @@ void ImGui::UpdateFontsNewFrame() ImGuiContext& g = *GImGui; if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) g.IO.Fonts->Locked = true; - SetCurrentFont(GetDefaultFont()); + SetCurrentFont(GetDefaultFont(), GetDefaultFont()->Sources[0].SizePixels); IM_ASSERT(g.Font->IsLoaded()); } // Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only. -void ImGui::SetCurrentFont(ImFont* font) +void ImGui::SetCurrentFont(ImFont* font, float font_size) { ImGuiContext& g = *GImGui; IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? IM_ASSERT(font->Scale > 0.0f); g.Font = font; - g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale); - g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; - g.FontScale = g.FontSize / g.Font->FontSize; + //g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.FontBaked->Size * g.Font->Scale); + g.FontSize = font_size;// g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + g.FontBaked = g.Font->GetFontBaked(g.FontSize); + g.FontScale = g.FontSize / g.FontBaked->Size; g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.FontSize = g.FontSize; g.DrawListSharedData.FontScale = g.FontScale; @@ -8606,7 +8614,7 @@ void ImGui::PushFont(ImFont* font) if (font == NULL) font = GetDefaultFont(); g.FontStack.push_back(font); - SetCurrentFont(font); + SetCurrentFont(font, g.FontSize); } void ImGui::PopFont() @@ -8619,7 +8627,14 @@ void ImGui::PopFont() } g.FontStack.pop_back(); ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back(); - SetCurrentFont(font); + SetCurrentFont(font, g.FontSize); // FIXME-BAKED: size in stack +} + +void ImGui::SetFontSize(float size) +{ + // FIXME-BAKED + ImGuiContext& g = *GImGui; + SetCurrentFont(g.Font, size); } //----------------------------------------------------------------------------- @@ -15512,7 +15527,7 @@ void ImGui::DebugTextEncoding(const char* str) } TableNextColumn(); TextUnformatted(p, p + c_utf8_len); - if (GetFont()->FindGlyphNoFallback((ImWchar)c) == NULL) + if (!GetFont()->IsGlyphInFont((ImWchar)c)) { SameLine(); TextUnformatted("[missing]"); @@ -16478,8 +16493,8 @@ void ImGui::DebugNodeFont(ImFont* font) { ImGuiContext& g = *GImGui; ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; - bool opened = TreeNode(font, "Font: \"%s\": %.2f px, %d glyphs, %d sources(s)", - font->Sources ? font->Sources[0].Name : "", font->FontSize, font->Glyphs.Size, font->SourcesCount); + ImFontAtlas* atlas = font->ContainerAtlas; + bool opened = TreeNode(font, "Font: \"%s\": %d sources(s)", font->Sources ? font->Sources[0].Name : "", font->SourcesCount); // Display preview text if (!opened) @@ -16499,11 +16514,11 @@ void ImGui::DebugNodeFont(ImFont* font) } if (SmallButton("Set as default")) GetIO().FontDefault = font; - if (font->ContainerAtlas->Fonts.Size > 1 && !font->ContainerAtlas->Locked) + if (atlas->Fonts.Size > 1 && !atlas->Locked) { SameLine(); if (SmallButton("Remove")) - font->ContainerAtlas->RemoveFont(font); + atlas->RemoveFont(font); } // Display details @@ -16515,33 +16530,43 @@ void ImGui::DebugNodeFont(ImFont* font) "You may oversample them to get some flexibility with scaling. " "You can also render at multiple sizes and select which one to use at runtime.\n\n" "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); - Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + char c_str[5]; Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar); Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar); - const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface); - Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); - for (int config_i = 0; config_i < font->SourcesCount; config_i++) - if (font->Sources) - { - ImFontConfig* src = &font->Sources[config_i]; - int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); - BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", - config_i, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); - } + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + if (ImFontConfig* src = &font->Sources[src_n]) + BulletText("Input %d: \'%s\', Oversample: %d,%d, PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->OversampleH, src->OversampleV, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); // Display all glyphs of the fonts in separate pages of 256 characters + for (int baked_n = 0; baked_n < atlas->Builder->BakedPool.Size; baked_n++) { - if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + ImFontBaked* baked = &atlas->Builder->BakedPool[baked_n]; + if (baked->ContainerFont != font) + continue; + PushID(baked_n); + if (TreeNode("Glyphs", "Baked at %.2fpx: %d glyphs%s", baked->Size, baked->Glyphs.Size, (baked->LastUsedFrame < atlas->Builder->FrameCount - 1) ? " *Unused*" : "")) { if (SmallButton("Load all")) for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base++) - font->FindGlyph((ImWchar)base); + baked->FindGlyph((ImWchar)base); + + const int surface_sqrt = (int)ImSqrt((float)baked->MetricsTotalSurface); + Text("Ascent: %f, Descent: %f, Ascent-Descent: %f", baked->Ascent, baked->Descent, baked->Ascent - baked->Descent); + Text("Texture Area: about %d px ~%dx%d px", baked->MetricsTotalSurface, surface_sqrt, surface_sqrt); + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = &font->Sources[src_n]; + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, baked->Size, &oversample_h, &oversample_v); + BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); + } ImDrawList* draw_list = GetWindowDrawList(); const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); - const float cell_size = font->FontSize * 1; + const float cell_size = baked->Size * 1; const float cell_spacing = GetStyle().ItemSpacing.y; for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) { @@ -16556,7 +16581,7 @@ void ImGui::DebugNodeFont(ImFont* font) int count = 0; for (unsigned int n = 0; n < 256; n++) - if (font->IsGlyphLoaded((ImWchar)(base + n))) + if (baked->IsGlyphLoaded((ImWchar)(base + n))) count++; if (count <= 0) continue; @@ -16571,7 +16596,7 @@ void ImGui::DebugNodeFont(ImFont* font) // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->IsGlyphLoaded((ImWchar)(base + n)) ? font->FindGlyph((ImWchar)(base + n)) : NULL; + const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL; draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); if (!glyph) continue; @@ -16587,6 +16612,7 @@ void ImGui::DebugNodeFont(ImFont* font) } TreePop(); } + PopID(); } TreePop(); Unindent(); diff --git a/imgui.h b/imgui.h index 1baa4067a..85fbb47d9 100644 --- a/imgui.h +++ b/imgui.h @@ -50,7 +50,7 @@ Index of this file: // [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) // [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) -// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) +// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFontBaked, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) // [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformImeData) // [SECTION] Obsolete functions and types @@ -172,6 +172,7 @@ struct ImDrawVert; // A single vertex (pos + uv + col = 20 byte struct ImFont; // Runtime data for a single font within a parent ImFontAtlas struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader struct ImFontAtlasBuilder; // Opaque storage for building a ImFontAtlas +struct ImFontBaked; // Baked data for a ImFont at a given size. struct ImFontConfig; // Configuration data when adding a font or merging fonts struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data @@ -471,6 +472,8 @@ namespace ImGui // Parameters stacks (shared) IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font IMGUI_API void PopFont(); + IMGUI_API void SetFontSize(float size); + //IMGUI_API void PopFontSize(); IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); // modify a style color. always use this if you modify the style after NewFrame(). IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); IMGUI_API void PopStyleColor(int count = 1); @@ -3419,10 +3422,10 @@ struct ImFontConfig int OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED IN 1.91.9: use GlyphExtraAdvanceX) - ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. + ImVec2 GlyphOffset; // 0, 0 // [FIXME-BAKED] Offset all glyphs from this font input. const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). - float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font - float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs + float GlyphMinAdvanceX; // 0 // [FIXME-BAKED] Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font + float GlyphMaxAdvanceX; // FLT_MAX // [FIXME-BAKED] Maximum AdvanceX for glyphs float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. @@ -3599,6 +3602,7 @@ struct ImFontAtlas ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines int TexNextUniqueID; // Next value to be stored in TexData->UniqueID + int FontNextUniqueID; // Next value to be stored in ImFont->SourceID ImVector DrawListSharedDatas; // [Internal] Font builder @@ -3616,30 +3620,57 @@ struct ImFontAtlas //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ }; -// Font runtime data and rendering -// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). -struct ImFont +// Font runtime data for a given size +// Important: pointers to ImFontBaked are only valid for the current frame. +struct ImFontBaked { // [Internal] Members: Hot ~20/24 bytes (for CalcTextSize) ImVector IndexAdvanceX; // 12-16 // out // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). float FallbackAdvanceX; // 4 // out // FindGlyph(FallbackChar)->AdvanceX - float FontSize; // 4 // in // Height of characters/line, set during loading (don't change after loading) + float Size; // 4 // in // Height of characters/line, set during loading (doesn't change after loading) // [Internal] Members: Hot ~28/36 bytes (for RenderText loop) ImVector IndexLookup; // 12-16 // out // Sparse. Index glyphs by Unicode code-point. ImVector Glyphs; // 12-16 // out // All glyphs. int FallbackGlyphIndex; // 4 // out // Index of FontFallbackChar - // [Internal] Members: Cold ~32/40/60 bytes + // [Internal] Members: Cold + float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) + unsigned int MetricsTotalSurface:26;// 3 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + unsigned int WantDestroy:1; // 1 // // Queued for destroy + int LastUsedFrame; // 4 // // Record of that time this was bounds + ImGuiID BakedId; // 4 // + ImFont* ContainerFont; // 4-8 // in // Parent font + void* FontBackendData; // 4-8 // // Font backend opaque storage (per baked font) + + // Functions + IMGUI_API ImFontBaked(); + IMGUI_API void ClearOutputData(); + IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); // Return U+FFFD glyph if requested glyph doesn't exists. + IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); // Return NULL if glyph doesn't exist + IMGUI_API float GetCharAdvance(ImWchar c); + IMGUI_API bool IsGlyphLoaded(ImWchar c); + IMGUI_API ImFontGlyph* BuildLoadGlyph(ImWchar c); + IMGUI_API void BuildGrowIndex(int new_size); +}; + +// Font runtime data and rendering +// - ImFontAtlas automatically loads a default embedded font for you if you didn't load one manually. +// - Since 1.92.X a font may be rendered as any size! Therefore a font doesn't have one specific size. +// - Use 'font->GetBakedForSize(size)' to retrieve the ImFontBaked* corresponding to a given size. +// - If you used g.Font + g.FontSize (which is frequent from the ImGui layer), you can use g.FontBaked as a shortcut, as g.FontBaked == g.Font->GetBakedForSize(g.FontSize). +struct ImFont +{ + // [Internal] Members: Cold ~32/40/80 bytes // Conceptually Sources[] is the list of font sources merged to create this font. + ImFontBaked* LastBaked; // Cache last bound baked. DO NOT USE. Use GetFontBaked(). + ImGuiID FontId; // Unique identifier for the font short SourcesCount; // 2 // in // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont. ImFontConfig* Sources; // 4-8 // in // Pointer within ContainerAtlas->Sources[], to SourcesCount instances ImFontAtlas* ContainerAtlas; // 4-8 // out // What we has been loaded into ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() - float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) - int MetricsTotalSurface;// 4 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. bool LockDisableLoading; short LockSingleSrcConfigIdx; @@ -3647,10 +3678,7 @@ struct ImFont // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); // Return fallback glyph if requested glyph doesn't exists. - IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); // Return NULL if glyph doesn't exist - IMGUI_API float GetCharAdvance(ImWchar c); - IMGUI_API bool IsGlyphLoaded(ImWchar c); + IMGUI_API ImFontBaked* GetFontBaked(float font_size); // Get or create baked data for given size IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } const char* GetDebugName() const { return Sources ? Sources->Name : ""; } @@ -3664,15 +3692,13 @@ struct ImFont IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(FontSize * scale, text, text_end, wrap_width); } + inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(Sources[0].SizePixels * scale, text, text_end, wrap_width); } #endif // [Internal] Don't use! IMGUI_API void ClearOutputData(); IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst);// , bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); - IMGUI_API ImFontGlyph* BuildLoadGlyph(ImWchar c); - IMGUI_API void BuildGrowIndex(int new_size); }; // Added indirection to avoid patching ImDrawCmd after texture updates. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c9c32e930..e3986229a 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2394,6 +2394,7 @@ void ImGui::ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, in // [SECTION] ImFontConfig //----------------------------------------------------------------------------- +// FIXME-NEWATLAS: Oversample specification could be more dynamic. For now, favoring automatic selection. ImFontConfig::ImFontConfig() { memset(this, 0, sizeof(*this)); @@ -2495,9 +2496,9 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildAddFont() // - ImFontAtlasBuildSetupFontCreateEllipsisFromDot() // - ImFontAtlasBuildSetupFontSpecialGlyphs() -// - ImFontAtlasBuildDiscardFontGlyph() -// - ImFontAtlasBuildDiscardFontGlyphs() -// - ImFontAtlasBuildReloadFont() +// - ImFontAtlasBuildDiscardUnusedBakes() +// - ImFontAtlasBuildDiscardFontBaked() +// - ImFontAtlasBuildDiscardFontBakedGlyph() //----------------------------------------------------------------------------- // - ImFontAtlasAddDrawListSharedData() // - ImFontAtlasRemoveDrawListSharedData() @@ -2594,6 +2595,7 @@ ImFontAtlas::ImFontAtlas() RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. TexRef._TexData = NULL;// this; TexNextUniqueID = 1; + FontNextUniqueID = 1; Builder = NULL; } @@ -2685,7 +2687,8 @@ static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* at // Called by NewFrame(). When multiple context own the atlas, only the first one calls this. // If you are calling this yourself, ensure atlas->RendererHasTexUpdates is et. -void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) +// 'frame_count' needs to be provided because we can gc/prioritize baked fonts based on their age. +void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count) { // Check that font atlas was built or backend support texture reload in which case we can build now if (atlas->RendererHasTextures) @@ -2701,6 +2704,33 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) if (atlas->TexIsBuilt && atlas->Builder->PreloadedAllGlyphsRanges) IM_ASSERT_USER_ERROR(atlas->RendererHasTextures == false, "Called ImFontAtlas::Build() before ImGuiBackendFlags_RendererHasTextures got set! With new backends: you don't need to call Build()."); + // Clear BakedCurrent cache, this is important because it ensure the uncached path gets taken once. + // We also rely on ImFontBaked* pointers never crossing frames. + ImFontAtlasBuilder* builder = atlas->Builder; + builder->FrameCount = frame_count; + for (ImFont* font : atlas->Fonts) + font->LastBaked = NULL; + + // Garbage collect BakedPool + if (builder->BakedDiscardedCount > 0) + { + int dst_n = 0, src_n = 0; + for (; src_n < builder->BakedPool.Size; src_n++) + { + ImFontBaked* p_src = &builder->BakedPool[src_n]; + if (p_src->WantDestroy) + continue; + ImFontBaked* p_dst = &builder->BakedPool[dst_n++]; + if (p_dst == p_src) + continue; + memcpy(p_dst, p_src, sizeof(ImFontBaked)); + builder->BakedMap.SetVoidPtr(p_dst->BakedId, p_dst); + } + IM_ASSERT(dst_n + builder->BakedDiscardedCount == src_n); + builder->BakedPool.Size -= builder->BakedDiscardedCount; + builder->BakedDiscardedCount = 0; + } + // Update texture status for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { @@ -2928,15 +2958,23 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) ImFontAtlasBuildInit(this); // Create new font + ImFont* font; if (!font_cfg->MergeMode) - Fonts.push_back(IM_NEW(ImFont)); + { + font = IM_NEW(ImFont)(); + font->FontId = FontNextUniqueID++; + Fonts.push_back(font); + } else + { IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + font = Fonts.back(); + } Sources.push_back(*font_cfg); ImFontConfig& new_font_cfg = Sources.back(); if (new_font_cfg.DstFont == NULL) - new_font_cfg.DstFont = Fonts.back(); + new_font_cfg.DstFont = font; if (!new_font_cfg.FontDataOwnedByAtlas) { new_font_cfg.FontData = IM_ALLOC(new_font_cfg.FontDataSize); @@ -2960,7 +2998,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) Sources.pop_back(); if (!font_cfg->MergeMode) { - IM_DELETE(Fonts.back()); + IM_DELETE(font); Fonts.pop_back(); } return NULL; @@ -3091,7 +3129,7 @@ static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, bool need_bind_ctx = ctx != curr_ctx; if (need_bind_ctx) ImGui::SetCurrentContext(ctx); - ImGui::SetCurrentFont(new_font); + ImGui::SetCurrentFont(new_font, ctx->FontSize); if (need_bind_ctx) ImGui::SetCurrentContext(curr_ctx); } @@ -3102,7 +3140,7 @@ static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, void ImFontAtlas::RemoveFont(ImFont* font) { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - ImFontAtlasBuildDiscardFontGlyphs(this, font); + font->ClearOutputData(); for (int src_n = 0; src_n < font->SourcesCount; src_n++) { @@ -3151,7 +3189,7 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int wid IM_ASSERT(font != NULL); IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - +#if 0 ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); if (r_id < 0) return -1; @@ -3174,6 +3212,12 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int wid glyph.PackId = r_id; ImFontAtlasBuildAddFontGlyph(this, font, &font->Sources[0], &glyph); return r_id; +#endif + // FIXME-BAKED: Need a design for AddCustomRectFontGlyph() + IM_UNUSED(codepoint); + IM_UNUSED(offset); + IM_UNUSED(advance_x); + return -1; } ImTextureRect* ImFontAtlas::GetCustomRectByIndex(int idx) @@ -3237,10 +3281,10 @@ void ImFontAtlasBuildMain(ImFontAtlas* atlas) atlas->TexIsBuilt = true; } -void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v) +void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v) { // Automatically disable horizontal oversampling over size 36 - *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (src->SizePixels * src->RasterizerDensity > 36.0f || src->PixelSnapH) ? 1 : 2; + *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : ((size * src->RasterizerDensity > 36.0f) || src->PixelSnapH) ? 1 : 2; *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1; } @@ -3277,11 +3321,12 @@ void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas) for (int src_n = 0; src_n < font->SourcesCount; src_n++) { ImFontConfig* src = &font->Sources[src_n]; + ImFontBaked* baked = font->GetFontBaked(src->SizePixels); const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); IM_ASSERT(ranges != NULL); for (; ranges[0]; ranges += 2) for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 - font->FindGlyphNoFallback((ImWchar)c); + baked->FindGlyphNoFallback((ImWchar)c); } } @@ -3436,22 +3481,23 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ //----------------------------------------------------------------------------------------------------------------------------- -static const ImFontGlyph* LoadFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) +ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size) { - for (int n = 0; n < candidate_chars_count; n++) - if (candidate_chars[n] != 0) - if (const ImFontGlyph* glyph = font->FindGlyphNoFallback(candidate_chars[n])) - return glyph; - return NULL; + struct { ImGuiID FontId; float BakedSize; } hashed_data; + hashed_data.FontId = font_id; + hashed_data.BakedSize = baked_size; + return ImHashData(&hashed_data, sizeof(hashed_data)); } +//----------------------------------------------------------------------------------------------------------------------------- + bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) { ImFont* font = src->DstFont; if (src->MergeMode == false) { font->ClearOutputData(); - font->FontSize = src->SizePixels; + //font->FontSize = src->SizePixels; font->ContainerAtlas = atlas; IM_ASSERT(font->Sources == src); } @@ -3466,15 +3512,15 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) // Rasterize our own ellipsis character from a dot. // This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. -// FIXME-NEWATLAS: This borrows too much from FontLoader's FontAddGlyph() and suggest that we should add further helpers. -static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, ImFont* font, const ImFontGlyph* dot_glyph) +// FIXME-NEWATLAS: This borrows too much from FontBackend_FontAddGlyph() and suggest that we should add further helpers. +// FIXME-BAKED: prebaked ellipsis +/*static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, ImFont* font, const ImFontGlyph* dot_glyph) { ImFontAtlasRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_glyph->PackId); const int dot_spacing = 1; const float dot_step = (dot_glyph->X1 - dot_glyph->X0) + dot_spacing; ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, (dot_r->w * 3 + dot_spacing * 2), dot_r->h); ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); - font->MetricsTotalSurface += r->w * r->h; ImFontGlyph glyph; glyph.Codepoint = (ImWchar)0x0085; // FIXME: Using arbitrary codepoint. @@ -3494,6 +3540,33 @@ static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, I for (int n = 0; n < 3; n++) ImFontAtlasTextureBlockCopy(tex, dot_r->x, dot_r->y, tex, r->x + (dot_r->w + dot_spacing) * n, r->y, dot_r->w, dot_r->h); ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); +}*/ + +static void ImFontAtlasBuildSetupFontBakedSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) +{ + // FIXME-NEWATLAS: could we use a scheme where this is lazily loaded? + IM_ASSERT(baked->FallbackGlyphIndex == -1); + if (font->FallbackChar != 0) + if (const ImFontGlyph* glyph = baked->FindGlyphNoFallback(font->FallbackChar)) + { + baked->FallbackGlyphIndex = baked->Glyphs.index_from_ptr(glyph); // Storing index avoid need to update pointer on growth. + baked->FallbackAdvanceX = glyph->AdvanceX; + } + + // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) + ImFontGlyph* space_glyph = (ImFontGlyph*)(void*)baked->FindGlyphNoFallback((ImWchar)' '); + if (space_glyph != NULL) + space_glyph->Visible = false; + + // Setup Tab character. + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (baked->FindGlyphNoFallback('\t') == NULL && space_glyph != NULL) + { + ImFontGlyph tab_glyph; + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX = space_glyph->AdvanceX * IM_TABSIZE; + ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, &tab_glyph); + } } // Load/identify special glyphs @@ -3507,31 +3580,15 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, Im // While manipulating glyphs during init we want to restrict all searches for one source font. font->LockSingleSrcConfigIdx = (short)src_idx_in_font; - // Setup Fallback character - // FIXME-NEWATLAS: could we use a scheme where this is lazily loaded? + // Find Fallback character. Actual glyph loaded in GetFontBaked(). const ImWchar fallback_chars[] = { font->FallbackChar, (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; - if (font->FallbackGlyphIndex == -1) - if (const ImFontGlyph* glyph = LoadFirstExistingGlyph(font, fallback_chars, IM_ARRAYSIZE(fallback_chars))) - { - font->FallbackChar = (ImWchar)glyph->Codepoint; - font->FallbackGlyphIndex = font->Glyphs.index_from_ptr(glyph); // Storing index avoid need to update pointer on growth. - font->FallbackAdvanceX = glyph->AdvanceX; - } - - // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) - ImFontGlyph* space_glyph = (ImFontGlyph*)(void*)font->FindGlyph((ImWchar)' '); - if (space_glyph != NULL) - space_glyph->Visible = false; - - // Setup Tab character. - // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) - if (font->FindGlyphNoFallback('\t') == NULL && space_glyph != NULL) - { - ImFontGlyph tab_glyph; - tab_glyph.Codepoint = '\t'; - tab_glyph.AdvanceX = space_glyph->AdvanceX * IM_TABSIZE; - ImFontAtlasBuildAddFontGlyph(atlas, font, src, &tab_glyph); - } + if (font->FallbackChar == 0) + for (ImWchar candidate_char : fallback_chars) + if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) // FIXME: does not respect LockSingleSrcConfigIdx() + { + font->FallbackChar = (ImWchar)candidate_char; + break; + } // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. @@ -3546,16 +3603,16 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, Im } if (font->EllipsisChar == 0) { - const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; + /*const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; if (const ImFontGlyph* dot_glyph = LoadFirstExistingGlyph(font, dots_chars, IM_ARRAYSIZE(dots_chars))) ImFontAtlasBuildSetupFontCreateEllipsisFromDot(atlas, font, dot_glyph); - else + else*/ font->EllipsisChar = (ImWchar)' '; } font->LockSingleSrcConfigIdx = -1; } -void ImFontAtlasBuildDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontGlyph* glyph) +void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph) { if (glyph->PackId >= 0) { @@ -3564,41 +3621,45 @@ void ImFontAtlasBuildDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontGl } ImWchar c = glyph->Codepoint; IM_ASSERT(font->FallbackChar != c && font->EllipsisChar != c); // Unsupported for simplicity - IM_ASSERT(glyph >= font->Glyphs.Data && glyph < font->Glyphs.Data + font->Glyphs.Size); - font->IndexLookup[c] = (ImWchar)IM_FONTGLYPH_INDEX_UNUSED; - font->IndexAdvanceX[c] = font->FallbackAdvanceX; + IM_ASSERT(glyph >= baked->Glyphs.Data && glyph < baked->Glyphs.Data + baked->Glyphs.Size); + baked->IndexLookup[c] = IM_FONTGLYPH_INDEX_UNUSED; + baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; } -void ImFontAtlasBuildDiscardFontGlyphs(ImFontAtlas* atlas, ImFont* font) +void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFontBaked* baked) { - for (ImFontGlyph& glyph : font->Glyphs) + ImGuiContext& g = *GImGui; + IM_UNUSED(g); // for IMGUI_DEBUG_LOG_FONT() + + ImFontAtlasBuilder* builder = atlas->Builder; + ImFont* font = baked->ContainerFont; + IMGUI_DEBUG_LOG_FONT("[font] Discard baked %.2f for \"%s\"\n", baked->Size, font->GetDebugName()); + + for (ImFontGlyph& glyph : baked->Glyphs) if (glyph.PackId >= 0) ImFontAtlasPackDiscardRect(atlas, glyph.PackId); - font->ClearOutputData(); - font->FallbackChar = font->EllipsisChar = 0; + + if (atlas->FontLoader->FontBakedDestroy) + atlas->FontLoader->FontBakedDestroy(atlas, baked); + + builder->BakedMap.SetVoidPtr(baked->BakedId, NULL); + builder->BakedDiscardedCount++; + baked->ClearOutputData(); + baked->WantDestroy = true; + font->LastBaked = NULL; } -// Discard old glyphs and reload font. Use if changing font size. -void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font) +void ImFontAtlasBuildDiscardUnusedBakes(ImFontAtlas* atlas, ImFont* font_filter) { - ImFontAtlasBuildDiscardFontGlyphs(atlas, font); - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + ImFontAtlasBuilder* builder = atlas->Builder; + const int GC_FRAMES = 2; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) { - ImFontConfig* src = (ImFontConfig*)(void*)&font->Sources[src_n]; - IM_ASSERT(src->SizePixels > 0.0f); - - // Reload font in backend - if (atlas->FontLoader && atlas->FontLoader->FontSrcDestroy != NULL) - atlas->FontLoader->FontSrcDestroy(atlas, src); - if (atlas->FontLoader && atlas->FontLoader->FontSrcInit != NULL) - atlas->FontLoader->FontSrcInit(atlas, src); - - ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); // Technically this is called for each source sub-font, tho 99.9% of the time the first one fills everything. - atlas->TexIsBuilt = false; + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (font_filter == NULL || baked->ContainerFont == font_filter) + if (baked->LastUsedFrame + GC_FRAMES < atlas->Builder->FrameCount && !baked->WantDestroy) + ImFontAtlasBuildDiscardFontBaked(atlas, baked); } - - // Notify external systems - ImFontAtlasBuildNotifySetFont(atlas, font, font); } // Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* @@ -3747,8 +3808,8 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) builder->RectsDiscardedSurface = 0; // Patch glyphs UV - for (ImFont* font : atlas->Fonts) - for (ImFontGlyph& glyph : font->Glyphs) + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + for (ImFontGlyph& glyph : builder->BakedPool[baked_n].Glyphs) if (glyph.PackId != -1) { ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); @@ -3798,8 +3859,11 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas) { - // Currently using a heuristic for repack without growing. + // Can some baked contents be ditched? ImFontAtlasBuilder* builder = atlas->Builder; + ImFontAtlasBuildDiscardUnusedBakes(atlas, NULL); + + // Currently using a heuristic for repack without growing. if (builder->RectsDiscardedSurface < builder->RectsPackedSurface * 0.20f) ImFontAtlasBuildGrowTexture(atlas); else @@ -3845,6 +3909,8 @@ ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) { ImFontAtlasBuilder* builder = atlas->Builder; + ImFontAtlasBuildDiscardUnusedBakes(atlas, NULL); + ImTextureData* old_tex = atlas->TexData; ImVec2i old_tex_size = ImVec2i(old_tex->Width, old_tex->Height); ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); @@ -3927,7 +3993,7 @@ void ImFontAtlasPackInit(ImFontAtlas * atlas) ImFontAtlasBuilder* builder = atlas->Builder; // In theory we could decide to reduce the number of nodes, e.g. halve them, and waste a little texture space, but it doesn't seem worth it. - int pack_node_count = tex->Width; + const int pack_node_count = tex->Width; builder->PackNodes.resize(pack_node_count); IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width, tex->Height, builder->PackNodes.Data, builder->PackNodes.Size); @@ -4046,38 +4112,40 @@ ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id return &builder->Rects[index_entry->TargetIndex]; } -ImFontGlyph* ImFont::BuildLoadGlyph(ImWchar codepoint) +ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) { - ImFontAtlas* atlas = ContainerAtlas; - if (LockDisableLoading || atlas->Locked) + ImFont* font = ContainerFont; + ImFontBaked* baked = this; + ImFontAtlas* atlas = font->ContainerAtlas; + if (font->LockDisableLoading || atlas->Locked) return NULL; //char utf8_buf[5]; //IMGUI_DEBUG_LOG("[font] BuildAddGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); // Load from single source or all sources? - int srcs_count = (LockSingleSrcConfigIdx != -1) ? 1 : SourcesCount; - ImFontConfig* srcs = (LockSingleSrcConfigIdx != -1) ? &Sources[LockSingleSrcConfigIdx] : Sources; + int srcs_count = (font->LockSingleSrcConfigIdx != -1) ? 1 : font->SourcesCount; + ImFontConfig* srcs = (font->LockSingleSrcConfigIdx != -1) ? &font->Sources[font->LockSingleSrcConfigIdx] : font->Sources; // Call backend const ImFontLoader* font_loader = atlas->FontLoader; - if (!font_loader->FontAddGlyph(atlas, this, srcs, srcs_count, codepoint)) + if (!font_loader->FontBakedAddGlyph(atlas, baked, srcs, srcs_count, codepoint)) { // Mark index as not found, so we don't attempt the search twice - BuildGrowIndex(codepoint + 1); - IndexAdvanceX[codepoint] = FallbackAdvanceX; - IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; + baked->BuildGrowIndex(codepoint + 1); + baked->IndexAdvanceX[codepoint] = baked->FallbackAdvanceX; + baked->IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; return NULL; } // FIXME: Add hooks for e.g. #7962 - ImFontGlyph* glyph = &Glyphs.back(); + ImFontGlyph* glyph = &baked->Glyphs.back(); return glyph; } // The point of this indirection is to not be inlined in debug mode in order to not bloat inner loop.b IM_MSVC_RUNTIME_CHECKS_OFF -static float BuildLoadGlyphGetAdvanceOrFallback(ImFont* font, unsigned int codepoint) +static float BuildLoadGlyphGetAdvanceOrFallback(ImFontBaked* font, unsigned int codepoint) { ImFontGlyph* glyph = font->BuildLoadGlyph((ImWchar)codepoint); return glyph ? glyph->AdvanceX : font->FallbackAdvanceX; @@ -4124,9 +4192,7 @@ void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) struct ImGui_ImplStbTrueType_FontSrcData { stbtt_fontinfo FontInfo; - float ScaleForRasterX; // Factor in RasterizationDensity * OversampleH - float ScaleForRasterY; // Factor in RasterizationDensity * OversampleV - float ScaleForLayout; + float ScaleFactor; }; static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) @@ -4152,28 +4218,10 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* } src->FontLoaderData = bd_font_data; - // FIXME-NEWFONTS: reevaluate sizing metrics - int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); - float scale; if (src->SizePixels > 0.0f) - scale = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); + bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); else - scale = -stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); - bd_font_data->ScaleForRasterX = scale * src->SizePixels * src->RasterizerDensity * oversample_h; - bd_font_data->ScaleForRasterY = scale * src->SizePixels * src->RasterizerDensity * oversample_v; - bd_font_data->ScaleForLayout = scale * src->SizePixels; - - // FIXME-NEWFONTS: make use of line gap value - int unscaled_ascent, unscaled_descent, unscaled_line_gap; - stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); - - if (src->MergeMode == false) - { - ImFont* font = src->DstFont; - font->Ascent = ImCeil(unscaled_ascent * bd_font_data->ScaleForLayout); - font->Descent = ImFloor(unscaled_descent * bd_font_data->ScaleForLayout); - } + bd_font_data->ScaleFactor = -stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); return true; } @@ -4197,7 +4245,27 @@ static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFon return glyph_index != 0; } -static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* srcs, int srcs_count, ImWchar codepoint) +static void ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontBaked* baked) +{ + IM_UNUSED(atlas); + ImFont* font = baked->ContainerFont; + + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = &font->Sources[src_n]; + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + + // FIXME-NEWFONTS: reevaluate how to use sizing metrics + // FIXME-NEWFONTS: make use of line gap value + float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + baked->Ascent = ImCeil(unscaled_ascent * scale_for_layout); + baked->Descent = ImFloor(unscaled_descent * scale_for_layout); + } +} + +static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* srcs, int srcs_count, ImWchar codepoint) { // Search for first font which has the glyph ImGui_ImplStbTrueType_FontSrcData* bd_font_data = NULL; @@ -4215,9 +4283,12 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, if (glyph_index == 0) return false; // Not found - const float scale_for_layout = bd_font_data->ScaleForLayout; // ~ (font units to pixels) - const float scale_for_raster_x = bd_font_data->ScaleForRasterX; // ~ (font units to pixels) * RasterizationDensity * OversampleH - const float scale_for_raster_y = bd_font_data->ScaleForRasterY; // ~ (font units to pixels) * RasterizationDensity * OversampleV + // Fonts unit to pixels + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, baked->Size, &oversample_h, &oversample_v); + const float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; + const float scale_for_raster_x = bd_font_data->ScaleFactor * baked->Size * src->RasterizerDensity * oversample_h; + const float scale_for_raster_y = bd_font_data->ScaleFactor * baked->Size * src->RasterizerDensity * oversample_v; // Obtain size and advance int x0, y0, x1, y1; @@ -4235,8 +4306,6 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, // (generally based on stbtt_PackFontRangesRenderIntoRects) if (is_visible) { - int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); const int w = (x1 - x0 + oversample_h - 1); const int h = (y1 - y0 + oversample_v - 1); ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); @@ -4246,9 +4315,7 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); return false; } - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); - font->MetricsTotalSurface += w * h; // Render stbtt_GetGlyphBitmapBox(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, &x0, &y0, &x1, &y1); @@ -4267,7 +4334,7 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, stbtt__v_prefilter(bitmap_pixels, r->w, r->h, r->w, oversample_v); float font_off_x = src->GlyphOffset.x + stbtt__oversample_shift(oversample_h); - float font_off_y = src->GlyphOffset.y + stbtt__oversample_shift(oversample_v) + IM_ROUND(font->Ascent); + float font_off_y = src->GlyphOffset.y + stbtt__oversample_shift(oversample_v) + IM_ROUND(baked->Ascent); float recip_h = 1.0f / (oversample_h * src->RasterizerDensity); float recip_v = 1.0f / (oversample_v * src->RasterizerDensity); @@ -4280,19 +4347,19 @@ static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, glyph.Y1 = (y0 + (int)r->h) * recip_v + font_off_y; glyph.Visible = true; glyph.PackId = pack_id; - ImFontAtlasBuildAddFontGlyph(atlas, font, src, &glyph); + ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph); // Copy to texture, post-process and queue update for backend ImTextureData* tex = atlas->TexData; IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); ImFontAtlasTextureBlockConvert(bitmap_pixels, ImTextureFormat_Alpha8, w, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); - ImFontAtlasPostProcessData pp_data = { atlas, font, src, &font->Glyphs.back(), tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h }; + ImFontAtlasPostProcessData pp_data = { atlas, baked->ContainerFont, src, baked, &baked->Glyphs.back(), tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h }; ImFontAtlasTextureBlockPostProcess(&pp_data); ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } else { - ImFontAtlasBuildAddFontGlyph(atlas, font, src, &glyph); + ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph); } return true; @@ -4305,7 +4372,9 @@ const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() loader.FontSrcInit = ImGui_ImplStbTrueType_FontSrcInit; loader.FontSrcDestroy = ImGui_ImplStbTrueType_FontSrcDestroy; loader.FontSrcContainsGlyph = ImGui_ImplStbTrueType_FontSrcContainsGlyph; - loader.FontAddGlyph = ImGui_ImplStbTrueType_FontAddGlyph; + loader.FontBakedInit = ImGui_ImplStbTrueType_FontBakedInit; + loader.FontBakedDestroy = NULL; + loader.FontBakedAddGlyph = ImGui_ImplStbTrueType_FontBakedAddGlyph; return &loader; } @@ -4628,6 +4697,34 @@ void ImFontGlyphRangesBuilder::BuildRanges(ImVector* out_ranges) // [SECTION] ImFont //----------------------------------------------------------------------------- +ImFontBaked::ImFontBaked() +{ + memset(this, 0, sizeof(*this)); + FallbackGlyphIndex = -1; +} + +void ImFontBaked::ClearOutputData() +{ + FallbackAdvanceX = 0.0f; + Glyphs.clear(); + IndexAdvanceX.clear(); + IndexLookup.clear(); + FallbackGlyphIndex = -1; + Ascent = Descent = 0.0f; + MetricsTotalSurface = 0; +} + +void ImFontBaked::BuildGrowIndex(int new_size) +{ + IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); + if (new_size <= IndexLookup.Size) + return; + //ImGuiContext& g = *GImGui; + //IMGUI_DEBUG_LOG_FONT("[font] BuildGrowIndex(%d)\n", new_size); + IndexAdvanceX.resize(new_size, -1.0f); + IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); +} + ImFont::ImFont() { memset(this, 0, sizeof(*this)); @@ -4642,14 +4739,17 @@ ImFont::~ImFont() void ImFont::ClearOutputData() { - FallbackAdvanceX = 0.0f; - Glyphs.clear(); - IndexAdvanceX.clear(); - IndexLookup.clear(); - FallbackGlyphIndex = -1; - Ascent = Descent = 0.0f; - MetricsTotalSurface = 0; + if (ImFontAtlas* atlas = ContainerAtlas) + if (ImFontAtlasBuilder* builder = atlas->Builder) + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->ContainerFont == this) + ImFontAtlasBuildDiscardFontBaked(atlas, baked); + } + FallbackChar = EllipsisChar = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); + LastBaked = NULL; } // API is designed this way to avoid exposing the 8K page size @@ -4665,24 +4765,15 @@ bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) return true; } -void ImFont::BuildGrowIndex(int new_size) -{ - IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); - if (new_size <= IndexLookup.Size) - return; - IndexAdvanceX.resize(new_size, -1.0f); - IndexLookup.resize(new_size, (ImU16)-1); -} - // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. // Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). // 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. -ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, const ImFontGlyph* in_glyph) +ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph) { - int glyph_idx = font->Glyphs.Size; - font->Glyphs.push_back(*in_glyph); - ImFontGlyph& glyph = font->Glyphs[glyph_idx]; - IM_ASSERT(font->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. + int glyph_idx = baked->Glyphs.Size; + baked->Glyphs.push_back(*in_glyph); + ImFontGlyph& glyph = baked->Glyphs[glyph_idx]; + IM_ASSERT(baked->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. // Set UV from packed rectangle if (in_glyph->PackId >= 0) @@ -4693,6 +4784,7 @@ ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFo glyph.V0 = (r->y) * atlas->TexUvScale.y; glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + baked->MetricsTotalSurface += r->w * r->h; } if (src != NULL) @@ -4718,17 +4810,22 @@ ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFo // Update lookup tables int codepoint = glyph.Codepoint; - font->BuildGrowIndex(codepoint + 1); - font->IndexAdvanceX[codepoint] = glyph.AdvanceX; - font->IndexLookup[codepoint] = (ImU16)glyph_idx; + baked->BuildGrowIndex(codepoint + 1); + baked->IndexAdvanceX[codepoint] = glyph.AdvanceX; + baked->IndexLookup[codepoint] = (ImU16)glyph_idx; const int page_n = codepoint / 8192; - font->Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); + baked->ContainerFont->Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); return &glyph; } void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst) { + // FIXME-BAKED: Implement AddRemapChar() + IM_UNUSED(from_codepoint); + IM_UNUSED(to_codepoint); + IM_UNUSED(overwrite_dst); + /* IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. unsigned int index_size = (unsigned int)IndexLookup.Size; @@ -4740,10 +4837,11 @@ void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool ove BuildGrowIndex(from_codepoint + 1); IndexLookup[from_codepoint] = (to_codepoint < index_size) ? IndexLookup.Data[to_codepoint] : (ImU16)-1; IndexAdvanceX[from_codepoint] = (to_codepoint < index_size) ? IndexAdvanceX.Data[to_codepoint] : 1.0f; + */ } // Find glyph, load if necessary, return fallback if missing -ImFontGlyph* ImFont::FindGlyph(ImWchar c) +ImFontGlyph* ImFontBaked::FindGlyph(ImWchar c) { if (c < (size_t)IndexLookup.Size) IM_LIKELY { @@ -4758,7 +4856,7 @@ ImFontGlyph* ImFont::FindGlyph(ImWchar c) } // Attempt to load but when missing, return NULL instead of FallbackGlyph -ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) +ImFontGlyph* ImFontBaked::FindGlyphNoFallback(ImWchar c) { if (c < (size_t)IndexLookup.Size) IM_LIKELY { @@ -4768,11 +4866,10 @@ ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) if (i != IM_FONTGLYPH_INDEX_UNUSED) return &Glyphs.Data[i]; } - ImFontGlyph* glyph = BuildLoadGlyph(c); - return glyph; + return BuildLoadGlyph(c); } -bool ImFont::IsGlyphLoaded(ImWchar c) +bool ImFontBaked::IsGlyphLoaded(ImWchar c) { if (c < (size_t)IndexLookup.Size) IM_LIKELY { @@ -4797,7 +4894,7 @@ bool ImFont::IsGlyphInFont(ImWchar c) // This is manually inlined in CalcTextSizeA() and CalcWordWrapPosition(), with a non-inline call to BuildLoadGlyphGetAdvanceOrFallback(). IM_MSVC_RUNTIME_CHECKS_OFF -float ImFont::GetCharAdvance(ImWchar c) +float ImFontBaked::GetCharAdvance(ImWchar c) { if ((int)c < IndexAdvanceX.Size) { @@ -4813,6 +4910,46 @@ float ImFont::GetCharAdvance(ImWchar c) } IM_MSVC_RUNTIME_CHECKS_RESTORE +// ImFontBaked pointers are valid for the entire frame but shall never be kept between frames. +ImFontBaked* ImFont::GetFontBaked(float size) +{ + ImFontBaked* baked = LastBaked; + if (baked && baked->Size == size) + return baked; + + ImFontAtlas* atlas = ContainerAtlas; + ImFontAtlasBuilder* builder = atlas->Builder; + + ImGuiID baked_id = ImFontAtlasBakedGetId(FontId, size); + ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); + baked = *p_baked_in_map; + if (baked != NULL) + { + // FIXME-BAKED: Design for picking a nearest size? + IM_ASSERT(baked->Size == size && baked->ContainerFont == this && baked->BakedId == baked_id); + baked->LastUsedFrame = builder->FrameCount; + LastBaked = baked; + return baked; + } + + // Create new + ImGuiContext& g = *GImGui; + IM_UNUSED(g); // for IMGUI_DEBUG_LOG_FONT() + IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", size); + baked = builder->BakedPool.push_back(ImFontBaked()); + baked->Size = size; + baked->BakedId = baked_id; + baked->ContainerFont = this; + baked->LastUsedFrame = ContainerAtlas->Builder->FrameCount; + LastBaked = baked; + *p_baked_in_map = baked; // To avoid 'builder->BakedMap.SetVoidPtr(baked_id, baked);' while we can. + if (atlas->FontLoader->FontBakedInit) + atlas->FontLoader->FontBakedInit(atlas, baked); + ImFontAtlasBuildSetupFontBakedSpecialGlyphs(atlas, baked->ContainerFont, baked); + + return baked; +} + // Trim trailing space and find beginning of next line static inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end) { @@ -4839,10 +4976,13 @@ const char* ImFont::CalcWordWrapPosition(float size, const char* text, const cha // Cut words that cannot possibly fit within one line. // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + ImFontBaked* baked = GetFontBaked(size); + const float scale = size / baked->Size; + float line_width = 0.0f; float word_width = 0.0f; float blank_width = 0.0f; - const float scale = size / FontSize; wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters const char* word_end = text; @@ -4877,9 +5017,9 @@ const char* ImFont::CalcWordWrapPosition(float size, const char* text, const cha } // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' - float char_width = (c < (unsigned int)IndexAdvanceX.Size) ? IndexAdvanceX.Data[c] : -1.0f; + float char_width = (c < (unsigned int)baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f; if (char_width < 0.0f) - char_width = BuildLoadGlyphGetAdvanceOrFallback(this, c); + char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); if (ImCharIsBlankW(c)) { @@ -4935,7 +5075,8 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons text_end = text_begin + ImStrlen(text_begin); // FIXME-OPT: Need to avoid this. const float line_height = size; - const float scale = size / FontSize; + ImFontBaked* baked = GetFontBaked(size); + const float scale = size / baked->Size; ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; @@ -4986,9 +5127,9 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons } // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' - float char_width = (c < (unsigned int)IndexAdvanceX.Size) ? IndexAdvanceX.Data[c] : -1.0f; + float char_width = (c < (unsigned int)baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f; if (char_width < 0.0f) - char_width = BuildLoadGlyphGetAdvanceOrFallback(this, c); + char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); char_width *= scale; if (line_width + char_width >= max_width) @@ -5015,12 +5156,13 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip) { - const ImFontGlyph* glyph = FindGlyph(c); + ImFontBaked* baked = GetFontBaked(size); + const ImFontGlyph* glyph = baked->FindGlyph(c); if (!glyph || !glyph->Visible) return; if (glyph->Colored) col |= ~IM_COL32_A_MASK; - float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + float scale = (size >= 0.0f) ? (size / baked->Size) : 1.0f; float x = IM_TRUNC(pos.x); float y = IM_TRUNC(pos.y); @@ -5062,8 +5204,10 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im if (!text_end) text_end = text_begin + ImStrlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. - const float scale = size / FontSize; - const float line_height = FontSize * scale; + const float line_height = size; + ImFontBaked* baked = GetFontBaked(size); + + const float scale = size / baked->Size; const float origin_x = x; const bool word_wrap_enabled = (wrap_width > 0.0f); @@ -5158,7 +5302,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im continue; } - const ImFontGlyph* glyph = FindGlyph((ImWchar)c); + const ImFontGlyph* glyph = baked->FindGlyph((ImWchar)c); if (glyph == NULL) continue; diff --git a/imgui_internal.h b/imgui_internal.h index 07cd84667..5075028ca 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2130,11 +2130,12 @@ struct ImGuiContext ImGuiIO IO; ImGuiPlatformIO PlatformIO; ImGuiStyle Style; - ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() - float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. - float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. - float FontScale; // == FontSize / Font->FontSize - float CurrentDpiScale; // Current window/viewport DpiScale + ImFont* Font; // == FontStack.back().Font + ImFontBaked* FontBaked; // == Font->GetFontBaked(FontSize) + float FontSize; // == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. + //float FontBaseSize; // == io.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. + float FontScale; // == FontBaked->Size / Font->FontSize. Scale factor over baked size. + float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; ImVectorTextures; double Time; @@ -2673,7 +2674,7 @@ public: // We don't use g.FontSize because the window may be != g.CurrentWindow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } - float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontWindowScaleParents; } + //float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontWindowScaleParents; } ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); } ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } }; @@ -3098,7 +3099,7 @@ namespace ImGui IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); // Fonts, drawing - IMGUI_API void SetCurrentFont(ImFont* font); + IMGUI_API void SetCurrentFont(ImFont* font, float font_size); inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } IMGUI_API void PushPasswordFont(); inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. @@ -3661,7 +3662,9 @@ struct ImFontLoader bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); - bool (*FontAddGlyph)(ImFontAtlas* atlas, ImFont* font, ImFontConfig* srcs, int srcs_count, ImWchar codepoint); + void (*FontBakedInit)(ImFontAtlas* atlas, ImFontBaked* baked); + void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontBaked* baked); + bool (*FontBakedAddGlyph)(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* srcs, int srcs_count, ImWchar codepoint); ImFontLoader() { memset(this, 0, sizeof(*this)); } }; @@ -3698,6 +3701,7 @@ struct ImFontAtlasPostProcessData ImFontAtlas* FontAtlas; ImFont* Font; ImFontConfig* FontSrc; + ImFontBaked* FontBaked; ImFontGlyph* Glyph; // Pixel data @@ -3723,11 +3727,17 @@ struct ImFontAtlasBuilder int RectsPackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. int RectsDiscardedCount; int RectsDiscardedSurface; + int FrameCount; // Current frame count ImVec2i MaxRectSize; // Largest rectangle to pack (de-facto used as a "minimum texture size") ImVec2i MaxRectBounds; // Bottom-right most used pixels bool LockDisableResize; // Disable resizing texture bool PreloadedAllGlyphsRanges; // Set when missing ImGuiBackendFlags_RendererHasTextures features forces atlas to preload everything. + // Cache of all ImFontBaked + ImStableVector BakedPool; + ImGuiStorage BakedMap; + int BakedDiscardedCount; + // Custom rectangle identifiers ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. ImFontAtlasRectId PackIdLinesTexData; @@ -3750,20 +3760,23 @@ IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas); IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); -IMGUI_API ImFontGlyph* ImFontAtlasBuildAddFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, const ImFontGlyph* in_glyph); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); -IMGUI_API void ImFontAtlasBuildDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontGlyph* glyph); -IMGUI_API void ImFontAtlasBuildDiscardFontGlyphs(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasBuildDiscardUnusedBakes(ImFontAtlas* atlas, ImFont* font_filter); +IMGUI_API void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); +IMGUI_API void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font); // <--- Your future new best friend! IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy -IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); +IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); + +IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); +IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size); IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); IMGUI_API ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); -IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count); IMGUI_API void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); IMGUI_API void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); IMGUI_API void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e1813110d..e41041ce1 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3375,7 +3375,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ButtonBehavior(row_r, row_id, NULL, NULL); KeepAliveID(row_id); - const float ascent_scaled = g.Font->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better + const float ascent_scaled = g.FontBaked->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f); const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component const ImVec2 align = g.Style.TableAngledHeadersTextAlign; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c632b45a4..2304aaf37 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1518,7 +1518,7 @@ bool ImGui::TextLink(const char* label) ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); } - float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f); + float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontScale * 0.20f); window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode // FIXME-DPI PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); @@ -3956,9 +3956,10 @@ static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line) { ImGuiContext& g = *ctx; - ImFont* font = g.Font; + //ImFont* font = g.Font; + ImFontBaked* baked = g.FontBaked; const float line_height = g.FontSize; - const float scale = line_height / font->FontSize; + const float scale = line_height / baked->Size; ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; @@ -3984,7 +3985,7 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c if (c == '\r') continue; - line_width += font->GetCharAdvance((ImWchar)c) * scale; + line_width += baked->GetCharAdvance((ImWchar)c) * scale; } if (text_size.x < line_width) @@ -4011,7 +4012,7 @@ namespace ImStb { static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; } static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; } -static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.FontBaked->GetCharAdvance((ImWchar)c) * g.FontScale; } static char STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) { @@ -4314,19 +4315,21 @@ void ImGui::PushPasswordFont() { ImGuiContext& g = *GImGui; ImFont* in_font = g.Font; + ImFontBaked* in_baked = g.FontBaked; + ImFontGlyph glyph = *in_baked->FindGlyph('*'); + glyph.PackId = -1; ImFont* out_font = &g.InputTextPasswordFont; - ImFontGlyph* glyph = in_font->FindGlyph('*'); - out_font->FontSize = in_font->FontSize; out_font->Scale = in_font->Scale; - out_font->Ascent = in_font->Ascent; - out_font->Descent = in_font->Descent; out_font->ContainerAtlas = in_font->ContainerAtlas; - out_font->Glyphs.resize(0); - out_font->Glyphs.push_back(*glyph); - out_font->FallbackGlyphIndex = 0; - out_font->FallbackAdvanceX = glyph->AdvanceX; out_font->LockDisableLoading = true; - IM_ASSERT(out_font->Glyphs.Size == 1 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0); + ImFontBaked* out_baked = out_font->GetFontBaked(in_baked->Size); + IM_ASSERT(out_baked->Glyphs.Size <= 1 && out_baked->IndexAdvanceX.Size == 0 && out_baked->IndexLookup.Size == 0); + out_baked->Ascent = in_baked->Ascent; + out_baked->Descent = in_baked->Descent; + out_baked->Glyphs.resize(0); + out_baked->Glyphs.push_back(glyph); + out_baked->FallbackGlyphIndex = 0; + out_baked->FallbackAdvanceX = glyph.AdvanceX; PushFont(out_font); } @@ -5354,7 +5357,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else { ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true); - if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines + if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); rect.ClipWith(clip_rect); if (rect.Overlaps(clip_rect)) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index b388d1531..022ec2b2c 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -46,6 +46,7 @@ #include FT_FREETYPE_H // #include FT_MODULE_H // #include FT_GLYPH_H // +#include FT_SIZES_H // #include FT_SYNTHESIS_H // // Handle LunaSVG and PlutoSVG @@ -150,15 +151,19 @@ namespace ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); } }; - // Font parameters and metrics. - struct ImGui_ImplFreeType_FontInfo + // Stored in ImFontBaked::FontBackendData: pointer to SourcesCount instances of this. + struct ImGui_ImplFreeType_FontSrcBakedData { - float PixelHeight; // Size this font was generated with. + FT_Size FtSize; // This represent a FT_Face with a given size. + + // Metrics float Ascender; // The pixel extents above the baseline in pixels (typically positive). float Descender; // The extents below the baseline in pixels (typically negative). float LineSpacing; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate. float LineGap; // The spacing in pixels between one row's descent and the next row's ascent. float MaxAdvanceWidth; // This field gives the maximum horizontal cursor advance for all glyphs in the font. + + ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); } }; // Stored in ImFontConfig::FontLoaderData @@ -166,20 +171,19 @@ namespace { bool InitFont(FT_Library ft_library, ImFontConfig* src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. void CloseFont(); - void SetPixelHeight(float pixel_height); // Change font pixel size. const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint); void BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch); ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); } ~ImGui_ImplFreeType_FontSrcData() { CloseFont(); } // Members - ImGui_ImplFreeType_FontInfo Info; // Font descriptor of the current font. FT_Face FtFace; unsigned int UserFlags; // = ImFontConfig::RasterizerFlags FT_Int32 LoadFlags; FT_Render_Mode RenderMode; float RasterizationDensity; float InvRasterizationDensity; + ImFontBaked* BakedLastActivated; }; bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, ImFontConfig* src, unsigned int extra_font_builder_flags) @@ -222,9 +226,6 @@ namespace RasterizationDensity = src->RasterizerDensity; InvRasterizationDensity = 1.0f / RasterizationDensity; - memset(&Info, 0, sizeof(Info)); - SetPixelHeight(src->SizePixels); - return true; } @@ -237,31 +238,6 @@ namespace } } - void ImGui_ImplFreeType_FontSrcData::SetPixelHeight(float pixel_height) - { - // Vuhdo (2017): "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height' - // is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me. - // FT_Set_Pixel_Sizes() doesn't seem to get us the same result." - // (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL) - FT_Size_RequestRec req; - req.type = (UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM; - req.width = 0; - req.height = (uint32_t)(pixel_height * 64 * RasterizationDensity); - req.horiResolution = 0; - req.vertResolution = 0; - FT_Request_Size(FtFace, &req); - // Note: To handle multiple sizes later, we may need to use FT_New_Size(), FT_Activate_Size() - - // Update font info - FT_Size_Metrics metrics = FtFace->size->metrics; - Info.PixelHeight = pixel_height * InvRasterizationDensity; - Info.Ascender = (float)FT_CEIL(metrics.ascender) * InvRasterizationDensity; - Info.Descender = (float)FT_CEIL(metrics.descender) * InvRasterizationDensity; - Info.LineSpacing = (float)FT_CEIL(metrics.height) * InvRasterizationDensity; - Info.LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * InvRasterizationDensity; - Info.MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * InvRasterizationDensity; - } - const FT_Glyph_Metrics* ImGui_ImplFreeType_FontSrcData::LoadGlyph(uint32_t codepoint) { uint32_t glyph_index = FT_Get_Char_Index(FtFace, codepoint); @@ -447,12 +423,6 @@ bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) if (!bd_font_data->InitFont(bd->Library, src, atlas->FontBuilderFlags)) return false; - if (src->MergeMode == false) - { - ImFont* font = src->DstFont; - font->Ascent = bd_font_data->Info.Ascender; - font->Descent = bd_font_data->Info.Descender; - } return true; } @@ -464,7 +434,65 @@ void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) src->FontLoaderData = NULL; } -bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontConfig* srcs, int srcs_count, ImWchar codepoint) +void ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontBaked* baked) +{ + IM_UNUSED(atlas); + ImFont* font = baked->ContainerFont; + const float size = baked->Size; + + IM_ASSERT(baked->FontBackendData == NULL); + ImGui_ImplFreeType_FontSrcBakedData* bd_baked_datas = (ImGui_ImplFreeType_FontSrcBakedData*)IM_ALLOC(sizeof(ImGui_ImplFreeType_FontSrcBakedData) * font->SourcesCount); + baked->FontBackendData = bd_baked_datas; + + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = &font->Sources[src_n]; + ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontBackendData; + bd_font_data->BakedLastActivated = baked; + + // We need one FT_Size per source, so create one ImGui_ImplFreeType_FontBakedData for each source. + ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = &bd_baked_datas[src_n]; + FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize); + FT_Activate_Size(bd_baked_data->FtSize); + + // Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height' + // is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me. + // FT_Set_Pixel_Sizes() doesn't seem to get us the same result." + // (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL) + FT_Size_RequestRec req; + req.type = (bd_font_data->UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM; + req.width = 0; + req.height = (uint32_t)(size * 64 * bd_font_data->RasterizationDensity); + req.horiResolution = 0; + req.vertResolution = 0; + FT_Request_Size(bd_font_data->FtFace, &req); + + // Read metrics + FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics; + bd_baked_data->Ascender = (float)FT_CEIL(metrics.ascender) * bd_font_data->InvRasterizationDensity; + bd_baked_data->Descender = (float)FT_CEIL(metrics.descender) * bd_font_data->InvRasterizationDensity; + bd_baked_data->LineSpacing = (float)FT_CEIL(metrics.height) * bd_font_data->InvRasterizationDensity; + bd_baked_data->LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * bd_font_data->InvRasterizationDensity; + bd_baked_data->MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * bd_font_data->InvRasterizationDensity; + + // Output + baked->Ascent = bd_baked_data->Ascender; + baked->Descent = bd_baked_data->Descender; + } +} + +void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontBaked* baked) +{ + IM_UNUSED(atlas); + ImFont* font = baked->ContainerFont; + ImGui_ImplFreeType_FontSrcBakedData* bd_baked_datas = (ImGui_ImplFreeType_FontSrcBakedData*)baked->FontBackendData; + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + FT_Done_Size(bd_baked_datas[src_n].FtSize); + IM_FREE(bd_baked_datas); + baked->FontBackendData = NULL; +} + +bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* srcs, int srcs_count, ImWchar codepoint) { // Search for first font which has the glyph ImGui_ImplFreeType_FontSrcData* bd_font_data = NULL; @@ -481,6 +509,15 @@ bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontCon if (glyph_index == 0) return false; // Not found + if (bd_font_data->BakedLastActivated != baked) + { + // Activate current size + int src_n = (int)(font_cfg - srcs); + ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = &((ImGui_ImplFreeType_FontSrcBakedData*)baked->FontBackendData)[src_n]; + FT_Activate_Size(bd_baked_data->FtSize); + bd_font_data->BakedLastActivated = baked; + } + const FT_Glyph_Metrics* metrics = bd_font_data->LoadGlyph(codepoint); if (metrics == NULL) return false; @@ -515,9 +552,7 @@ bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontCon IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); return false; } - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); - font->MetricsTotalSurface += w * h; // Render pixels to our temporary buffer atlas->Builder->TempBuffer.resize(w * h * 4); @@ -525,7 +560,7 @@ bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontCon bd_font_data->BlitGlyph(ft_bitmap, temp_buffer, w); float font_off_x = src->GlyphOffset.x; - float font_off_y = src->GlyphOffset.y + IM_ROUND(font->Ascent); + float font_off_y = src->GlyphOffset.y + IM_ROUND(baked->Ascent); float recip_h = 1.0f / src->RasterizerDensity; float recip_v = 1.0f / src->RasterizerDensity; @@ -539,19 +574,19 @@ bool ImGui_ImplFreeType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImFontCon glyph.Visible = true; glyph.Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); glyph.PackId = pack_id; - ImFontAtlasBuildAddFontGlyph(atlas, font, src, &glyph); + ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph); // Copy to texture, post-process and queue update for backend ImTextureData* tex = atlas->TexData; IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); ImFontAtlasTextureBlockConvert(temp_buffer, ImTextureFormat_RGBA32, w * 4, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); - ImFontAtlasPostProcessData pp_data = { atlas, font, src, &font->Glyphs.back(), tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h }; + ImFontAtlasPostProcessData pp_data = { atlas, baked->ContainerFont, src, baked, &baked->Glyphs.back(), tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h }; ImFontAtlasTextureBlockPostProcess(&pp_data); ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } else { - ImFontAtlasBuildAddFontGlyph(atlas, font, src, &glyph); + ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph); } return true; } @@ -573,7 +608,9 @@ const ImFontLoader* ImGuiFreeType::GetFontLoader() loader.FontSrcInit = ImGui_ImplFreeType_FontSrcInit; loader.FontSrcDestroy = ImGui_ImplFreeType_FontSrcDestroy; loader.FontSrcContainsGlyph = ImGui_ImplFreetype_FontSrcContainsGlyph; - loader.FontAddGlyph = ImGui_ImplFreeType_FontAddGlyph; + loader.FontBakedInit = ImGui_ImplFreeType_FontBakedInit; + loader.FontBakedDestroy = ImGui_ImplFreeType_FontBakedDestroy; + loader.FontBakedAddGlyph = ImGui_ImplFreeType_FontBakedAddGlyph; return &loader; } From 80404fae3048eb27ae04d06388475ad4b7475deb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jan 2025 16:32:25 +0100 Subject: [PATCH 056/191] Fonts: clarify ClearTexData() as not supported with dynamic atlases. --- imgui.cpp | 6 ++++-- imgui.h | 4 ++-- imgui_draw.cpp | 42 ++++++++++++++++++++++++------------------ 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index abe3cab3e..0b1433857 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5785,7 +5785,8 @@ void ImGui::EndFrame() UpdateTexturesEndFrame(); // Unlock font atlas - g.IO.Fonts->Locked = false; + ImFontAtlas* atlas = g.IO.Fonts; + atlas->Locked = false; // Clear Input data for next frame g.IO.MousePosPrev = g.IO.MousePos; @@ -8575,8 +8576,9 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) void ImGui::UpdateFontsNewFrame() { ImGuiContext& g = *GImGui; + ImFontAtlas* atlas = g.IO.Fonts; if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) - g.IO.Fonts->Locked = true; + atlas->Locked = true; SetCurrentFont(GetDefaultFont(), GetDefaultFont()->Sources[0].SizePixels); IM_ASSERT(g.Font->IsLoaded()); } diff --git a/imgui.h b/imgui.h index 85fbb47d9..717c9f0e4 100644 --- a/imgui.h +++ b/imgui.h @@ -3517,7 +3517,7 @@ struct ImFontAtlas // As we are transitioning toward a new font system, we expect to obsolete those soon: 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 ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). - IMGUI_API void ClearTexData(); // [OBSOLETE] Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void ClearTexData(); // [OBSOLETE] Clear CPU-side copy of the texture data. Saves RAM once the texture has been copied to graphics memory. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy path for build atlas + retrieving pixel data. @@ -3592,7 +3592,7 @@ struct ImFontAtlas ImTextureRef TexRef; // User data to refer to the latest texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. ImTextureData* TexData; // Current texture ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetDrawData().Textures[]/GetPlatformIO().Textures[] instead! - bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. + bool Locked; // Marked as locked during ImGui::NewFrame()..EndFrame() scope if TexUpdates are not supported. Any attempt to modify the atlas will assert. bool RendererHasTextures;// Copy of (BackendFlags & ImGuiBackendFlags_RendererHasTextures) from supporting context. bool TexIsBuilt; // Set when texture was built matching current font input. Mostly useful for legacy IsBuilt() call. bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e3986229a..7c7a7916a 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2607,9 +2607,11 @@ ImFontAtlas::~ImFontAtlas() void ImFontAtlas::Clear() { - ClearInputData(); - ClearTexData(); ClearFonts(); + bool backup_renderer_has_textures = RendererHasTextures; + RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. + ClearTexData(); + RendererHasTextures = backup_renderer_has_textures; } void ImFontAtlas::ClearCache() @@ -2647,12 +2649,14 @@ void ImFontAtlas::ClearInputData() Sources.clear(); } +// Clear CPU-side copy of the texture data. void ImFontAtlas::ClearTexData() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - TexList.clear(); - IM_DELETE(TexData); - TexData = NULL; + IM_ASSERT(RendererHasTextures == false && "Not supported for dynamic atlases, but you may call Clear()."); + for (ImTextureData* tex : TexList) + tex->DestroyPixels(); + //Locked = true; // Hoped to be able to lock this down but some reload patterns may not be happy with it. } void ImFontAtlas::ClearFonts() @@ -2686,7 +2690,7 @@ static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* at } // Called by NewFrame(). When multiple context own the atlas, only the first one calls this. -// If you are calling this yourself, ensure atlas->RendererHasTexUpdates is et. +// If you are calling this yourself, ensure atlas->RendererHasTextures is set. // 'frame_count' needs to be provided because we can gc/prioritize baked fonts based on their age. void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count) { @@ -3256,7 +3260,7 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso return true; } -// When atlas->RendererHasTexUpdates == true, this is only called if no font were loaded. +// When atlas->RendererHasTextures = true, this is only called if no font were loaded. void ImFontAtlasBuildMain(ImFontAtlas* atlas) { IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); @@ -3481,16 +3485,6 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ //----------------------------------------------------------------------------------------------------------------------------- -ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size) -{ - struct { ImGuiID FontId; float BakedSize; } hashed_data; - hashed_data.FontId = font_id; - hashed_data.BakedSize = baked_size; - return ImHashData(&hashed_data, sizeof(hashed_data)); -} - -//----------------------------------------------------------------------------------------------------------------------------- - bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) { ImFont* font = src->DstFont; @@ -4910,6 +4904,14 @@ float ImFontBaked::GetCharAdvance(ImWchar c) } IM_MSVC_RUNTIME_CHECKS_RESTORE +ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size) +{ + struct { ImGuiID FontId; float BakedSize; } hashed_data; + hashed_data.FontId = font_id; + hashed_data.BakedSize = baked_size; + return ImHashData(&hashed_data, sizeof(hashed_data)); +} + // ImFontBaked pointers are valid for the entire frame but shall never be kept between frames. ImFontBaked* ImFont::GetFontBaked(float size) { @@ -4920,18 +4922,22 @@ ImFontBaked* ImFont::GetFontBaked(float size) ImFontAtlas* atlas = ContainerAtlas; ImFontAtlasBuilder* builder = atlas->Builder; + // FIXME-BAKED: Design for picking a nearest size? ImGuiID baked_id = ImFontAtlasBakedGetId(FontId, size); ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); baked = *p_baked_in_map; if (baked != NULL) { - // FIXME-BAKED: Design for picking a nearest size? IM_ASSERT(baked->Size == size && baked->ContainerFont == this && baked->BakedId == baked_id); baked->LastUsedFrame = builder->FrameCount; LastBaked = baked; return baked; } + // FIXME-BAKED: If atlas is locked, find closest match + if (atlas->Locked) + IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! + // Create new ImGuiContext& g = *GImGui; IM_UNUSED(g); // for IMGUI_DEBUG_LOG_FONT() From daaf0e4ef30678540f3ad862008de218e8fbb0f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 27 Jan 2025 20:24:05 +0100 Subject: [PATCH 057/191] Fonts: Added PushFontSize(), PopFontSize() api. Added font_size param to PushFont() as well. Fonts: Fixed PopFont() recovery. (To squash into "Added PushFontSize(), PopFontSize() api." --- imgui.cpp | 80 ++++++++++++++++++++++++++++++------------------ imgui.h | 14 +++++---- imgui_draw.cpp | 11 ++++--- imgui_internal.h | 8 ++++- 4 files changed, 73 insertions(+), 40 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 0b1433857..fb26c967b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1270,6 +1270,7 @@ static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt); // Misc static void UpdateFontsNewFrame(); +static void UpdateFontsEndFrame(); static void UpdateTexturesNewFrame(); static void UpdateTexturesEndFrame(); static void UpdateSettings(); @@ -5762,6 +5763,7 @@ void ImGui::EndFrame() // End frame g.WithinFrameScope = false; g.FrameCountEnded = g.FrameCount; + UpdateFontsEndFrame(); // Initiate moving window + handle left-click and right-click focus UpdateMouseMovingWindowEndFrame(); @@ -8579,64 +8581,84 @@ void ImGui::UpdateFontsNewFrame() ImFontAtlas* atlas = g.IO.Fonts; if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) atlas->Locked = true; - SetCurrentFont(GetDefaultFont(), GetDefaultFont()->Sources[0].SizePixels); + PushFont(GetDefaultFont(), GetDefaultFont()->Sources[0].SizePixels); IM_ASSERT(g.Font->IsLoaded()); } -// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only. -void ImGui::SetCurrentFont(ImFont* font, float font_size) +void ImGui::UpdateFontsEndFrame() { - ImGuiContext& g = *GImGui; - IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? - IM_ASSERT(font->Scale > 0.0f); - g.Font = font; - //g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.FontBaked->Size * g.Font->Scale); - g.FontSize = font_size;// g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; - g.FontBaked = g.Font->GetFontBaked(g.FontSize); - g.FontScale = g.FontSize / g.FontBaked->Size; - g.DrawListSharedData.Font = g.Font; - g.DrawListSharedData.FontSize = g.FontSize; - g.DrawListSharedData.FontScale = g.FontScale; - ImFontAtlasUpdateDrawListsSharedData(g.Font->ContainerAtlas); - if (g.CurrentWindow) - g.CurrentWindow->DrawList->_SetTexture(font->ContainerAtlas->TexRef); + PopFont(); } -// Use ImDrawList::_SetTextureID(), making our shared g.FontStack[] authoritative against window-local ImDrawList. -// - Whereas ImDrawList::PushTextureID()/PopTextureID() is not to be used across Begin() calls. +// Use ImDrawList::_SetTexture(), making our shared g.FontStack[] authoritative against window-local ImDrawList. +// - Whereas ImDrawList::PushTexture()/PopTexture() is not to be used across Begin() calls. // - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did... // - Some code paths never really fully worked with multiple atlas textures. -// - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call PushTextureID()/PopTextureID() +// - The right-ish solution may be to remove _SetTexture() and make AddText/RenderText lazily call PushTexture()/PopTexture() // the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem // because we have a concrete need and a test bed for multiple atlas textures. // FIXME-NEWATLAS-V2: perhaps we can now leverage ImFontAtlasUpdateDrawListsTextures() ? -void ImGui::PushFont(ImFont* font) +void ImGui::SetCurrentFont(ImFont* font, float font_size) +{ + ImGuiContext& g = *GImGui; + g.Font = font; + //g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.FontBaked->FontSize * g.Font->Scale); + g.FontSize = font_size;// g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + if (font != NULL) + { + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(font->Scale > 0.0f); + g.FontBaked = g.Font->GetFontBaked(g.FontSize); + g.FontScale = g.FontSize / g.FontBaked->Size; + g.DrawListSharedData.Font = g.Font; + g.DrawListSharedData.FontSize = g.FontSize; + g.DrawListSharedData.FontScale = g.FontScale; + ImFontAtlasUpdateDrawListsSharedData(g.Font->ContainerAtlas); + if (g.CurrentWindow != NULL) + g.CurrentWindow->DrawList->_SetTexture(font->ContainerAtlas->TexRef); + } + else + { + g.FontBaked = NULL; + g.FontScale = 0.0f; + } +} + +void ImGui::PushFont(ImFont* font, float font_size) { ImGuiContext& g = *GImGui; if (font == NULL) font = GetDefaultFont(); - g.FontStack.push_back(font); - SetCurrentFont(font, g.FontSize); + if (font_size < 0.0f) + font_size = g.FontSize; + g.FontStack.push_back({ font, font_size }); + SetCurrentFont(font, font_size); } void ImGui::PopFont() { ImGuiContext& g = *GImGui; - if (g.FontStack.Size <= 0) + if (g.FontStack.Size <= 1 && g.WithinFrameScope) { IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); return; } g.FontStack.pop_back(); - ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back(); - SetCurrentFont(font, g.FontSize); // FIXME-BAKED: size in stack + if (ImFontStackData* font_stack_data = (g.FontStack.Size > 0) ? &g.FontStack.back() : NULL) + SetCurrentFont(font_stack_data->Font, font_stack_data->FontSize); + else + SetCurrentFont(NULL, 0.0f); } -void ImGui::SetFontSize(float size) +void ImGui::PushFontSize(float font_size) { - // FIXME-BAKED ImGuiContext& g = *GImGui; - SetCurrentFont(g.Font, size); + PushFont(g.Font, font_size); +} + +void ImGui::PopFontSize() +{ + PopFont(); } //----------------------------------------------------------------------------- diff --git a/imgui.h b/imgui.h index 717c9f0e4..d60deceb9 100644 --- a/imgui.h +++ b/imgui.h @@ -469,11 +469,13 @@ namespace ImGui IMGUI_API void SetScrollFromPosX(float local_x, float center_x_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. IMGUI_API void SetScrollFromPosY(float local_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. - // Parameters stacks (shared) - IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + // Parameters stacks (font) + IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. IMGUI_API void PopFont(); - IMGUI_API void SetFontSize(float size); - //IMGUI_API void PopFontSize(); + IMGUI_API void PushFontSize(float size); + IMGUI_API void PopFontSize(); + + // Parameters stacks (shared) IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); // modify a style color. always use this if you modify the style after NewFrame(). IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); IMGUI_API void PopStyleColor(int count = 1); @@ -3657,8 +3659,8 @@ struct ImFontBaked // Font runtime data and rendering // - ImFontAtlas automatically loads a default embedded font for you if you didn't load one manually. // - Since 1.92.X a font may be rendered as any size! Therefore a font doesn't have one specific size. -// - Use 'font->GetBakedForSize(size)' to retrieve the ImFontBaked* corresponding to a given size. -// - If you used g.Font + g.FontSize (which is frequent from the ImGui layer), you can use g.FontBaked as a shortcut, as g.FontBaked == g.Font->GetBakedForSize(g.FontSize). +// - Use 'font->GetFontBaked(size)' to retrieve the ImFontBaked* corresponding to a given size. +// - If you used g.Font + g.FontSize (which is frequent from the ImGui layer), you can use g.FontBaked as a shortcut, as g.FontBaked == g.Font->GetFontBaked(g.FontSize). struct ImFont { // [Internal] Members: Cold ~32/40/80 bytes diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7c7a7916a..362697609 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -434,7 +434,7 @@ void ImDrawList::_SetDrawListSharedData(ImDrawListSharedData* data) } // Initialize before use in a new frame. We always have a command ready in the buffer. -// In the majority of cases, you would want to call PushClipRect() and PushTextureID() after this. +// In the majority of cases, you would want to call PushClipRect() and PushTexture() after this. void ImDrawList::_ResetForNewFrame() { // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. @@ -683,7 +683,7 @@ void ImDrawList::PopTexture() _OnChangedTexture(); } -// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTextureID()/PopTextureID(). +// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTexture()/PopTexture(). void ImDrawList::_SetTexture(ImTextureRef tex_ref) { if (_CmdHeader.TexRef == tex_ref) @@ -3042,7 +3042,7 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f * 1.0f; if (font_cfg.Name[0] == '\0') - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf, %dpx", (int)font_cfg.SizePixels); + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf"); font_cfg.EllipsisChar = (ImWchar)0x0085; font_cfg.GlyphOffset.y = 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units @@ -3074,7 +3074,7 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, // Store a short copy of filename into into the font name for convenience const char* p; for (p = filename + ImStrlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s", p); } return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges); } @@ -3137,6 +3137,9 @@ static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, if (need_bind_ctx) ImGui::SetCurrentContext(curr_ctx); } + for (ImFontStackData& font_stack_data : ctx->FontStack) + if (font_stack_data.Font == old_font) + font_stack_data.Font = new_font; } } } diff --git a/imgui_internal.h b/imgui_internal.h index 5075028ca..f0812d71f 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -869,6 +869,12 @@ struct ImDrawDataBuilder ImDrawDataBuilder() { memset(this, 0, sizeof(*this)); } }; +struct ImFontStackData +{ + ImFont* Font; + float FontSize; +}; + //----------------------------------------------------------------------------- // [SECTION] Style support //----------------------------------------------------------------------------- @@ -2237,7 +2243,7 @@ struct ImGuiContext ImGuiCol DebugFlashStyleColorIdx; // (Keep close to ColorStack to share cache line) ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() - ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() + ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin() ImVector ItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() ImVector GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() From be151977ca24c503cc7176d2a1eaf9aa4207ac6f Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jan 2025 14:31:13 +0100 Subject: [PATCH 058/191] Fonts: Texture resizing favor growing height, halve pack nodes. --- imgui_draw.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 362697609..ff861d8d1 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3838,9 +3838,10 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ IM_ASSERT(ImIsPowerOfTwo(atlas->TexMinWidth) && ImIsPowerOfTwo(atlas->TexMaxWidth) && ImIsPowerOfTwo(atlas->TexMinHeight) && ImIsPowerOfTwo(atlas->TexMaxHeight)); // Grow texture so it follows roughly a square. - // Caller should be taking account of RectsDiscardedSurface and may not need to grow. - int new_tex_w = (old_tex_h < old_tex_w) ? old_tex_w : old_tex_w * 2; - int new_tex_h = (old_tex_h < old_tex_w) ? old_tex_h * 2 : old_tex_h; + // - Grow height before width, as width imply more packing nodes. + // - Caller should be taking account of RectsDiscardedSurface and may not need to grow. + int new_tex_w = (old_tex_h <= old_tex_w) ? old_tex_w : old_tex_w * 2; + int new_tex_h = (old_tex_h <= old_tex_w) ? old_tex_h * 2 : old_tex_h; // Handle minimum size first (for pathologically large packed rects) const int pack_padding = atlas->TexGlyphPadding; @@ -3990,7 +3991,7 @@ void ImFontAtlasPackInit(ImFontAtlas * atlas) ImFontAtlasBuilder* builder = atlas->Builder; // In theory we could decide to reduce the number of nodes, e.g. halve them, and waste a little texture space, but it doesn't seem worth it. - const int pack_node_count = tex->Width; + const int pack_node_count = tex->Width / 2; builder->PackNodes.resize(pack_node_count); IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width, tex->Height, builder->PackNodes.Data, builder->PackNodes.Size); From 3ce753c489307835eee04a448db18264fe02fa4c Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jan 2025 14:36:06 +0100 Subject: [PATCH 059/191] Fonts: Debug dump to disk, debug log. --- imgui_draw.cpp | 29 ++++++++++++++++++----------- imgui_internal.h | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ff861d8d1..ef7ff1549 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3625,9 +3625,6 @@ void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImF void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFontBaked* baked) { - ImGuiContext& g = *GImGui; - IM_UNUSED(g); // for IMGUI_DEBUG_LOG_FONT() - ImFontAtlasBuilder* builder = atlas->Builder; ImFont* font = baked->ContainerFont; IMGUI_DEBUG_LOG_FONT("[font] Discard baked %.2f for \"%s\"\n", baked->Size, font->GetDebugName()); @@ -3755,11 +3752,20 @@ ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h) return new_tex; } -void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) +#if 0 +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "../stb/stb_image_write.h" +static void ImFontAtlasDebugWriteTexToDisk(ImTextureData* tex, const char* description) { ImGuiContext& g = *GImGui; - IM_UNUSED(g); // for IMGUI_DEBUG_LOG_FONT() + char buf[128]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "[%05d] Texture #%03d - %s.png", g.FrameCount, tex->UniqueID, description); + stbi_write_png(buf, tex->Width, tex->Height, tex->BytesPerPixel, tex->Pixels, tex->GetPitch()); // tex->BytesPerPixel is technically not component, but ok for the formats we support. +} +#endif +void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) +{ ImFontAtlasBuilder* builder = atlas->Builder; builder->LockDisableResize = true; @@ -3767,6 +3773,10 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) ImTextureData* new_tex = ImFontAtlasBuildAddTexture(atlas, w, h); new_tex->UseColors = old_tex->UseColors; IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize+repack %dx%d => Texture #%03d: %dx%d\n", old_tex->UniqueID, old_tex->Width, old_tex->Height, new_tex->UniqueID, new_tex->Width, new_tex->Height); + //for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + // IMGUI_DEBUG_LOG_FONT("[font] - Baked %.2fpx, %d glyphs, want_destroy=%d\n", builder->BakedPool[baked_n].FontSize, builder->BakedPool[baked_n].Glyphs.Size, builder->BakedPool[baked_n].WantDestroy); + //IMGUI_DEBUG_LOG_FONT("[font] - Old packed rects: %d, area %d px\n", builder->RectsPackedCount, builder->RectsPackedSurface); + //ImFontAtlasDebugWriteTexToDisk(old_tex, "Before Pack"); // Repack, lose discarded rectangle, copy pixels // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic. @@ -3822,10 +3832,12 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) builder->LockDisableResize = false; ImFontAtlasUpdateDrawListsSharedData(atlas); + //ImFontAtlasDebugWriteTexToDisk(new_tex, "After Pack"); } void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_h) { + //ImFontAtlasDebugWriteTexToDisk(atlas->TexData, "Before Grow"); ImFontAtlasBuilder* builder = atlas->Builder; if (old_tex_w == -1) old_tex_w = atlas->TexData->Width; @@ -3858,6 +3870,7 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas) { // Can some baked contents be ditched? + //IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlasBuildMakeSpace()\n"); ImFontAtlasBuilder* builder = atlas->Builder; ImFontAtlasBuildDiscardUnusedBakes(atlas, NULL); @@ -4072,8 +4085,6 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon // If we ran out of attempts, return fallback if (attempts_remaining == 0 || builder->LockDisableResize) { - ImGuiContext& g = *GImGui; - IM_UNUSED(g); IMGUI_DEBUG_LOG_FONT("[font] Failed packing %dx%d rectangle. Returning fallback.\n", w, h); return -1; } @@ -4717,8 +4728,6 @@ void ImFontBaked::BuildGrowIndex(int new_size) IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); if (new_size <= IndexLookup.Size) return; - //ImGuiContext& g = *GImGui; - //IMGUI_DEBUG_LOG_FONT("[font] BuildGrowIndex(%d)\n", new_size); IndexAdvanceX.resize(new_size, -1.0f); IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); } @@ -4943,8 +4952,6 @@ ImFontBaked* ImFont::GetFontBaked(float size) IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! // Create new - ImGuiContext& g = *GImGui; - IM_UNUSED(g); // for IMGUI_DEBUG_LOG_FONT() IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", size); baked = builder->BakedPool.push_back(ImFontBaked()); baked->Size = size; diff --git a/imgui_internal.h b/imgui_internal.h index f0812d71f..3216f2181 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -247,7 +247,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) -#define IMGUI_DEBUG_LOG_FONT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_FONT(...) do { ImGuiContext& g2 = *GImGui; if (g2.DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Static Asserts From 57d345ff80415edbefb0024bdcb7997621292b9b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jan 2025 17:49:52 +0100 Subject: [PATCH 060/191] Textures: Comments around ImTextureID type. --- imgui.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/imgui.h b/imgui.h index d60deceb9..935f2b3b7 100644 --- a/imgui.h +++ b/imgui.h @@ -311,12 +311,17 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // ImTextureID: user data for renderer backend to identify a texture [Compile-time configurable type] -// - To use something else than an opaque void* pointer: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file. -// - This can be whatever to you want it to be! read the FAQ about ImTextureID for details. -// - You can make this a structure with various constructors if you need. You will have to implement ==/!= operators. -// - (note: before v1.91.4 (2024/10/08) the default type for ImTextureID was void*. Use intermediary intptr_t cast and read FAQ if you have casting warnings) +// Overview: +// - Backend and user/app code provides ImTextureID values that gets stored inside draw commands (ImDrawCmd) during the ImGui frame. +// - Backend uses ImDrawCmd::GetTexID() to retrieve the ImTextureID value during rendering. Then, they can bind the textures of each draw command. +// Configuring the type: +// - To use something else than a 64-bit value: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file. +// - This can be whatever to you want it to be! read the FAQ entry about textures for details. +// - You may decide to store a higher-level structure containing texture, sampler, shader etc. with various constructors if you like. You will need to implement ==/!= operators. +// History: +// - In v1.91.4 (2024/10/08): the default type for ImTextureID was changed from 'void*' to 'ImU64'. This allowed backends requirig 64-bit worth of data to build on 32-bit architectures. Use intermediary intptr_t cast and read FAQ if you have casting warnings. #ifndef ImTextureID -typedef ImU64 ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) +typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or integer). A majority of backends are ok with that. #endif // Define this to another value if you need value of 0 to be valid. From df694c89b00ee69f4a18203630a6a1500e92b665 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Feb 2025 17:16:34 +0100 Subject: [PATCH 061/191] Fonts: Baked system, v11. --- imgui_draw.cpp | 38 +++++++++++++++++++++----------------- imgui_internal.h | 4 ++-- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ef7ff1549..b537132fb 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2497,8 +2497,9 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildSetupFontCreateEllipsisFromDot() // - ImFontAtlasBuildSetupFontSpecialGlyphs() // - ImFontAtlasBuildDiscardUnusedBakes() -// - ImFontAtlasBuildDiscardFontBaked() // - ImFontAtlasBuildDiscardFontBakedGlyph() +// - ImFontAtlasBuildDiscardFontBaked() +// - ImFontAtlasBuildDiscardFont() //----------------------------------------------------------------------------- // - ImFontAtlasAddDrawListSharedData() // - ImFontAtlasRemoveDrawListSharedData() @@ -3623,10 +3624,9 @@ void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImF baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; } -void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFontBaked* baked) +void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) { ImFontAtlasBuilder* builder = atlas->Builder; - ImFont* font = baked->ContainerFont; IMGUI_DEBUG_LOG_FONT("[font] Discard baked %.2f for \"%s\"\n", baked->Size, font->GetDebugName()); for (ImFontGlyph& glyph : baked->Glyphs) @@ -3643,16 +3643,25 @@ void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFontBaked* baked) font->LastBaked = NULL; } -void ImFontAtlasBuildDiscardUnusedBakes(ImFontAtlas* atlas, ImFont* font_filter) +void ImFontAtlasBuildDiscardFont(ImFontAtlas* atlas, ImFont* font) +{ + if (ImFontAtlasBuilder* builder = atlas->Builder) // This can be called from font destructor + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->ContainerFont == font && !baked->WantDestroy) + ImFontAtlasBuildDiscardFontBaked(atlas, font, baked); + } +} + +void ImFontAtlasBuildDiscardUnusedBakes(ImFontAtlas* atlas, int gc_frames) { ImFontAtlasBuilder* builder = atlas->Builder; - const int GC_FRAMES = 2; for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) { ImFontBaked* baked = &builder->BakedPool[baked_n]; - if (font_filter == NULL || baked->ContainerFont == font_filter) - if (baked->LastUsedFrame + GC_FRAMES < atlas->Builder->FrameCount && !baked->WantDestroy) - ImFontAtlasBuildDiscardFontBaked(atlas, baked); + if (baked->LastUsedFrame + gc_frames < atlas->Builder->FrameCount && !baked->WantDestroy) + ImFontAtlasBuildDiscardFontBaked(atlas, baked->ContainerFont, baked); } } @@ -3872,7 +3881,7 @@ void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas) // Can some baked contents be ditched? //IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlasBuildMakeSpace()\n"); ImFontAtlasBuilder* builder = atlas->Builder; - ImFontAtlasBuildDiscardUnusedBakes(atlas, NULL); + ImFontAtlasBuildDiscardUnusedBakes(atlas, 2); // Currently using a heuristic for repack without growing. if (builder->RectsDiscardedSurface < builder->RectsPackedSurface * 0.20f) @@ -3920,7 +3929,7 @@ ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) { ImFontAtlasBuilder* builder = atlas->Builder; - ImFontAtlasBuildDiscardUnusedBakes(atlas, NULL); + ImFontAtlasBuildDiscardUnusedBakes(atlas, 1); ImTextureData* old_tex = atlas->TexData; ImVec2i old_tex_size = ImVec2i(old_tex->Width, old_tex->Height); @@ -4747,13 +4756,7 @@ ImFont::~ImFont() void ImFont::ClearOutputData() { if (ImFontAtlas* atlas = ContainerAtlas) - if (ImFontAtlasBuilder* builder = atlas->Builder) - for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) - { - ImFontBaked* baked = &builder->BakedPool[baked_n]; - if (baked->ContainerFont == this) - ImFontAtlasBuildDiscardFontBaked(atlas, baked); - } + ImFontAtlasBuildDiscardFont(atlas, this); FallbackChar = EllipsisChar = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); LastBaked = NULL; @@ -4936,6 +4939,7 @@ ImFontBaked* ImFont::GetFontBaked(float size) ImFontAtlasBuilder* builder = atlas->Builder; // FIXME-BAKED: Design for picking a nearest size? + // FIXME-BAKED: Altering font density won't work right away. ImGuiID baked_id = ImFontAtlasBakedGetId(FontId, size); ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); baked = *p_baked_in_map; diff --git a/imgui_internal.h b/imgui_internal.h index 3216f2181..a54e3a350 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3767,10 +3767,10 @@ IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); -IMGUI_API void ImFontAtlasBuildDiscardUnusedBakes(ImFontAtlas* atlas, ImFont* font_filter); +IMGUI_API void ImFontAtlasBuildDiscardUnusedBakes(ImFontAtlas* atlas, int gc_frames); +IMGUI_API void ImFontAtlasBuildDiscardFont(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); IMGUI_API void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); -IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFont* font); // <--- Your future new best friend! IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); From 99f6b305c1e7d6b08c70f18971b8865af2a1b725 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Feb 2025 17:46:59 +0100 Subject: [PATCH 062/191] Fonts: Baked system, v12: support GlyphOffset / GlyphMinAdvanceX / GlyphMaxAdvanceX by scaling from ref value. Overwriting cfg->PixelSnapH = true; in imgui_freetype is weird. --- imgui.cpp | 2 +- imgui.h | 9 +++++---- imgui_draw.cpp | 14 +++++++++++--- misc/freetype/imgui_freetype.cpp | 12 ++++++++++-- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index fb26c967b..a7cc6a81a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8602,7 +8602,7 @@ void ImGui::SetCurrentFont(ImFont* font, float font_size) { ImGuiContext& g = *GImGui; g.Font = font; - //g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.FontBaked->FontSize * g.Font->Scale); + //g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.FontBaked->Size * g.Font->Scale); g.FontSize = font_size;// g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; if (font != NULL) { diff --git a/imgui.h b/imgui.h index 935f2b3b7..24706e062 100644 --- a/imgui.h +++ b/imgui.h @@ -3424,15 +3424,16 @@ struct ImFontConfig bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. + bool PixelSnapV; // true // Align Scaled GlyphOffset.y to pixel boundaries. int FontNo; // 0 // Index of font within TTF/OTF file int OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. int OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). - //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED IN 1.91.9: use GlyphExtraAdvanceX) - ImVec2 GlyphOffset; // 0, 0 // [FIXME-BAKED] Offset all glyphs from this font input. + //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset (in pixels) all glyphs from this font input. Absolute value for default size, other sizes will scale this value. const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). - float GlyphMinAdvanceX; // 0 // [FIXME-BAKED] Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font - float GlyphMaxAdvanceX; // FLT_MAX // [FIXME-BAKED] Maximum AdvanceX for glyphs + float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. + float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b537132fb..921241185 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4351,8 +4351,15 @@ static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBa if (oversample_v > 1) stbtt__v_prefilter(bitmap_pixels, r->w, r->h, r->w, oversample_v); - float font_off_x = src->GlyphOffset.x + stbtt__oversample_shift(oversample_h); - float font_off_y = src->GlyphOffset.y + stbtt__oversample_shift(oversample_v) + IM_ROUND(baked->Ascent); + const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0].SizePixels; + float font_off_x = (src->GlyphOffset.x * offsets_scale); + float font_off_y = (src->GlyphOffset.y * offsets_scale); + if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. + font_off_x = IM_ROUND(font_off_x); + if (src->PixelSnapV) + font_off_y = IM_ROUND(font_off_y); + font_off_x += stbtt__oversample_shift(oversample_h); + font_off_y += stbtt__oversample_shift(oversample_v) + IM_ROUND(baked->Ascent); float recip_h = 1.0f / (oversample_h * src->RasterizerDensity); float recip_v = 1.0f / (oversample_v * src->RasterizerDensity); @@ -4800,7 +4807,8 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked if (src != NULL) { // Clamp & recenter if needed - float advance_x = ImClamp(glyph.AdvanceX, src->GlyphMinAdvanceX, src->GlyphMaxAdvanceX); + const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0].SizePixels; + float advance_x = ImClamp(glyph.AdvanceX, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); if (advance_x != glyph.AdvanceX) { float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - glyph.AdvanceX) * 0.5f) : (advance_x - glyph.AdvanceX) * 0.5f; diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 022ec2b2c..326132b41 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -204,6 +204,9 @@ namespace if (UserFlags & ImGuiFreeTypeBuilderFlags_NoHinting) LoadFlags |= FT_LOAD_NO_HINTING; + else + src->PixelSnapH = true; // FIXME: A bit weird to do this this way. + if (UserFlags & ImGuiFreeTypeBuilderFlags_NoAutoHint) LoadFlags |= FT_LOAD_NO_AUTOHINT; if (UserFlags & ImGuiFreeTypeBuilderFlags_ForceAutoHint) @@ -559,8 +562,13 @@ bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBaked* baked uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data; bd_font_data->BlitGlyph(ft_bitmap, temp_buffer, w); - float font_off_x = src->GlyphOffset.x; - float font_off_y = src->GlyphOffset.y + IM_ROUND(baked->Ascent); + const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0].SizePixels; + float font_off_x = (src->GlyphOffset.x * offsets_scale); + float font_off_y = (src->GlyphOffset.y * offsets_scale) + baked->Ascent; + if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. + font_off_x = IM_ROUND(font_off_x); + if (src->PixelSnapV) + font_off_y = IM_ROUND(font_off_y); float recip_h = 1.0f / src->RasterizerDensity; float recip_v = 1.0f / src->RasterizerDensity; From 842c313db2d2b25673383cd0830b367fc51104af Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 7 Feb 2025 19:47:41 +0100 Subject: [PATCH 063/191] Fonts: Reordered ImFont fields. --- imgui.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index 24706e062..e9b31d367 100644 --- a/imgui.h +++ b/imgui.h @@ -3669,13 +3669,15 @@ struct ImFontBaked // - If you used g.Font + g.FontSize (which is frequent from the ImGui layer), you can use g.FontBaked as a shortcut, as g.FontBaked == g.Font->GetFontBaked(g.FontSize). struct ImFont { - // [Internal] Members: Cold ~32/40/80 bytes + // [Internal] Members: Hot ~8-16 bytes + ImFontBaked* LastBaked; // 4-8 // Cache last bound baked. DO NOT USE. Use GetFontBaked(). + ImFontAtlas* ContainerAtlas; // 4-8 // What we has been loaded into + + // [Internal] Members: Cold ~24-52 bytes // Conceptually Sources[] is the list of font sources merged to create this font. - ImFontBaked* LastBaked; // Cache last bound baked. DO NOT USE. Use GetFontBaked(). ImGuiID FontId; // Unique identifier for the font short SourcesCount; // 2 // in // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont. ImFontConfig* Sources; // 4-8 // in // Pointer within ContainerAtlas->Sources[], to SourcesCount instances - ImFontAtlas* ContainerAtlas; // 4-8 // out // What we has been loaded into ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() From 658059022629077455c5dad7028c667944ddeec8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Feb 2025 19:15:26 +0100 Subject: [PATCH 064/191] Fonts: Allow PushFont/NewFrame/PopFont idioms to function. --- imgui.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index a7cc6a81a..dd4dbf15a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8581,7 +8581,13 @@ void ImGui::UpdateFontsNewFrame() ImFontAtlas* atlas = g.IO.Fonts; if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) atlas->Locked = true; - PushFont(GetDefaultFont(), GetDefaultFont()->Sources[0].SizePixels); + + // We do this really unusual thing of calling *push_front()*, the reason behind that we want to support the PushFont()/NewFrame()/PopFont() idiom. + ImFontStackData font_stack_data = { ImGui::GetDefaultFont(), ImGui::GetDefaultFont()->Sources[0].SizePixels }; + g.FontStack.push_front(font_stack_data); + if (g.FontStack.Size == 1) + ImGui::SetCurrentFont(font_stack_data.Font, font_stack_data.FontSize); + IM_ASSERT(g.Font->IsLoaded()); } From 82b81fce685e303db2355a04e1c257eb2eb0d914 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Feb 2025 19:36:11 +0100 Subject: [PATCH 065/191] Fonts: PushFontSize() with -1 uses sources[0]'s size for now (backward compat design) --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index dd4dbf15a..f416223cd 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8636,7 +8636,7 @@ void ImGui::PushFont(ImFont* font, float font_size) if (font == NULL) font = GetDefaultFont(); if (font_size < 0.0f) - font_size = g.FontSize; + font_size = font->Sources[0].SizePixels; // g.FontSize; g.FontStack.push_back({ font, font_size }); SetCurrentFont(font, font_size); } From 066b24d7416be6b1ad640adb0c3c0e66c39193a9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 4 Feb 2025 20:02:10 +0100 Subject: [PATCH 066/191] Fonts: Fixed _OnChangedTextureID() asserting when calling on e.g. finalized drawlists. --- imgui_draw.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 921241185..0f3cf4dc8 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -599,7 +599,10 @@ void ImDrawList::_OnChangedTexture() AddDrawCmd(); return; } - IM_ASSERT(curr_cmd->UserCallback == NULL); + + // Unlike other _OnChangedXXX functions this may be called by ImFontAtlasUpdateDrawListsTextures() in more locations so we need to handle this case. + if (curr_cmd->UserCallback != NULL) + return; // Try to merge with previous command if it matches, else use current command ImDrawCmd* prev_cmd = curr_cmd - 1; From 96786a183bf78c12e49ece064af316604c4b5fb2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Feb 2025 14:04:05 +0100 Subject: [PATCH 067/191] Fonts: Create a fallback glyph if none is available (fix crash on fonts with no fallback) --- imgui_draw.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0f3cf4dc8..89ceda78f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3545,20 +3545,27 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) static void ImFontAtlasBuildSetupFontBakedSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) { - // FIXME-NEWATLAS: could we use a scheme where this is lazily loaded? - IM_ASSERT(baked->FallbackGlyphIndex == -1); - if (font->FallbackChar != 0) - if (const ImFontGlyph* glyph = baked->FindGlyphNoFallback(font->FallbackChar)) - { - baked->FallbackGlyphIndex = baked->Glyphs.index_from_ptr(glyph); // Storing index avoid need to update pointer on growth. - baked->FallbackAdvanceX = glyph->AdvanceX; - } - // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) ImFontGlyph* space_glyph = (ImFontGlyph*)(void*)baked->FindGlyphNoFallback((ImWchar)' '); if (space_glyph != NULL) space_glyph->Visible = false; + // Load fallback in order to obtain its index + // FIXME-NEWATLAS: could we use a scheme where this is lazily loaded? + IM_ASSERT(baked->FallbackGlyphIndex == -1); + ImFontGlyph* fallback_glyph = NULL; + if (font->FallbackChar != 0) + fallback_glyph = baked->FindGlyphNoFallback(font->FallbackChar); + if (fallback_glyph == NULL) + { + ImFontGlyph glyph; + glyph.Codepoint = 0; + glyph.AdvanceX = space_glyph ? space_glyph->AdvanceX : IM_ROUND(baked->Size * 0.40f); + fallback_glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, &glyph); + } + baked->FallbackGlyphIndex = baked->Glyphs.index_from_ptr(fallback_glyph); // Storing index avoid need to update pointer on growth and simplify inner loop code + baked->FallbackAdvanceX = fallback_glyph->AdvanceX; + // Setup Tab character. // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) if (baked->FindGlyphNoFallback('\t') == NULL && space_glyph != NULL) From 815553c4b462c4284bd1690fe25e62d1a926ed96 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Feb 2025 14:41:59 +0100 Subject: [PATCH 068/191] Fonts: ImFontConfig: added GlyphExcludeRanges[]. --- imgui.h | 1 + imgui_draw.cpp | 23 ++++++++++++++++++++++- imgui_internal.h | 1 + misc/freetype/imgui_freetype.cpp | 2 ++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index e9b31d367..e0bea0f51 100644 --- a/imgui.h +++ b/imgui.h @@ -3432,6 +3432,7 @@ struct ImFontConfig //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. ImVec2 GlyphOffset; // 0, 0 // Offset (in pixels) all glyphs from this font input. Absolute value for default size, other sizes will scale this value. const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). + const ImWchar* GlyphExcludeRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a VERY SHORT user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 89ceda78f..11f78e8c5 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2990,6 +2990,15 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); } + // Sanity check + if (font_cfg->GlyphExcludeRanges != NULL) + { + int size = 0; + for (const ImWchar* p = font_cfg->GlyphExcludeRanges; p[0] != 0; p++, size++) {} + IM_ASSERT((size & 1) == 0 && "GlyphExcludeRanges[] size must be multiple of two!"); + IM_ASSERT((size <= 64) && "GlyphExcludeRanges[] size must be small!"); + } + // Round font size // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes. @@ -4149,7 +4158,7 @@ ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) return NULL; //char utf8_buf[5]; - //IMGUI_DEBUG_LOG("[font] BuildAddGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); + //IMGUI_DEBUG_LOG("[font] BuildLoadGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); // Load from single source or all sources? int srcs_count = (font->LockSingleSrcConfigIdx != -1) ? 1 : font->SourcesCount; @@ -4293,6 +4302,16 @@ static void ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontBaked* } } +// Important! This assume by ImFontConfig::GlyphFilter is a SMALL ARRAY (e.g. <10 entries) +bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint) +{ + if (const ImWchar* exclude_list = src->GlyphExcludeRanges) + for (; exclude_list[0] != 0; exclude_list += 2) + if (codepoint >= exclude_list[0] && codepoint <= exclude_list[1]) + return false; + return true; +} + static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* srcs, int srcs_count, ImWchar codepoint) { // Search for first font which has the glyph @@ -4302,6 +4321,8 @@ static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBa for (int src_n = 0; src_n < srcs_count; src_n++) { src = &srcs[src_n]; + if (src->GlyphExcludeRanges && !ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) + continue; bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; IM_ASSERT(bd_font_data); glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); diff --git a/imgui_internal.h b/imgui_internal.h index a54e3a350..7e7a2c697 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3773,6 +3773,7 @@ IMGUI_API void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, IMGUI_API void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); +IMGUI_API bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint); IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 326132b41..e71534c52 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -504,6 +504,8 @@ bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBaked* baked for (int src_n = 0; src_n < srcs_count; src_n++) { src = &srcs[src_n]; + if (src->GlyphExcludeRanges && !ImFontAtlasBuildFilterCodepointForSource(src, codepoint)) + continue; bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint); if (glyph_index != 0) From eb79e3ab3d7a329f33a96f3ed1e3f85024b94418 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Feb 2025 17:51:46 +0100 Subject: [PATCH 069/191] Fonts: Restore a functional AddCustomRectFontGlyph(). --- imgui.h | 17 +++++++++-------- imgui_draw.cpp | 31 ++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/imgui.h b/imgui.h index e0bea0f51..d62f56fa8 100644 --- a/imgui.h +++ b/imgui.h @@ -3521,7 +3521,7 @@ struct ImFontAtlas IMGUI_API void RemoveFont(ImFont* font); IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures) - IMGUI_API void ClearCache(); // Clear cached glyphs and textures. + IMGUI_API void ClearCache(); // Clear cached glyphs and textures. Invalidates all AddCustomRectXXX return values. // As we are transitioning toward a new font system, we expect to obsolete those soon: 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. @@ -3571,7 +3571,7 @@ struct ImFontAtlas // You can request your rectangles to be mapped as font glyph (given a font + Unicode point), // so you can render e.g. custom colorful icons and use them as regular glyphs. // - Since 1.92.X, packing is done immediately in the function call. Returns >= on success, <0 on error. - // - You can render your pixels into the texture right after calling the AddCustomRectXXX functions, without waiting for the Build() call. + // - You can render your pixels into the texture right after calling the AddCustomRectXXX() functions. // - If your backend supports ImGuiBackendFlags_RendererHasTextures: // Texture may be resized, so you cannot cache UV coordinates: always use CalcCustomRectUV(). // - If you render colored output into your AddCustomRectRegular() rectangle: set 'atlas->TexPixelsUseColors = true' @@ -3579,7 +3579,10 @@ struct ImFontAtlas // - Read docs/FONTS.md for more details about using colorful icons. // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. IMGUI_API int AddCustomRectRegular(int width, int height); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); + IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); +#endif IMGUI_API ImTextureRect* GetCustomRectByIndex(int index); IMGUI_API void CalcCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; @@ -3925,13 +3928,11 @@ namespace ImGui //static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42 } -//-- OBSOLETED in 1.91.7 (from January 2025): ImFontAtlasCustomRect becomes ImTextureRect -// - ImFontAtlasCustomRect::X --> ImTextureRect::x -// - ImFontAtlasCustomRect::Y --> ImTextureRect::y -// - ImFontAtlasCustomRect::Width --> ImTextureRect::w -// - ImFontAtlasCustomRect::Height --> ImTextureRect::h +//-- OBSOLETED in 1.92.x: ImFontAtlasCustomRect becomes ImTextureRect +// - ImFontAtlasCustomRect::X,Y --> ImTextureRect::x,y +// - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h // - ImFontAtlasCustomRect::GlyphColored --> if you need to write to this, instead you can write to 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph() -// We could make ImTextureRect an union to use old names, such 1) this would be confusing 2) the fix is easy 3) ImFontAtlasCustomRect was always a rather esoteric api. +// We could make ImTextureRect an union to use old names, but 1) this would be confusing 2) the fix is easy 3) ImFontAtlasCustomRect was always a rather esoteric api. typedef ImTextureRect ImFontAtlasCustomRect; /*struct ImFontAtlasCustomRect { diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 11f78e8c5..2a0a300a7 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3199,9 +3199,21 @@ int ImFontAtlas::AddCustomRectRegular(int width, int height) return r_id; } +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// This API does not make sense anymore with scalable fonts. +// - Prefer adding a font source (ImFontConfig) using a custom/procedural loader. +// - You may use ImFontFlags_LockBakedSizes to limit an existing font to known baked sizes: +// ImFont* myfont = io.Fonts->AddFontFromFileTTF(....); +// myfont->GetFontBaked(16.0f); +// myfont->Flags |= ImFontFlags_LockBakedSizes; +int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) +{ + float font_size = font->Sources[0].SizePixels; + return AddCustomRectFontGlyphForSize(font, font_size, codepoint, width, height, advance_x, offset); +} // FIXME: we automatically set glyph.Colored=true by default. // If you need to alter this, you can write 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph(). -int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) +int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { #ifdef IMGUI_USE_WCHAR32 IM_ASSERT(codepoint <= IM_UNICODE_CODEPOINT_MAX); @@ -3209,7 +3221,9 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int wid IM_ASSERT(font != NULL); IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); -#if 0 + + ImFontBaked* baked = font->GetFontBaked(font_size); + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); if (r_id < 0) return -1; @@ -3217,8 +3231,8 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int wid if (RendererHasTextures) ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); - if (font->IsGlyphLoaded(codepoint)) - ImFontAtlasBuildDiscardFontGlyph(this, font, (ImFontGlyph*)(void*)font->FindGlyph(codepoint)); + if (baked->IsGlyphLoaded(codepoint)) + ImFontAtlasBuildDiscardFontBakedGlyph(this, font, baked, (ImFontGlyph*)(void*)baked->FindGlyph(codepoint)); ImFontGlyph glyph; glyph.Codepoint = codepoint; @@ -3230,15 +3244,10 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int wid glyph.Visible = true; glyph.Colored = true; // FIXME: Arbitrary glyph.PackId = r_id; - ImFontAtlasBuildAddFontGlyph(this, font, &font->Sources[0], &glyph); + ImFontAtlasBakedAddFontGlyph(this, baked, &font->Sources[0], &glyph); return r_id; -#endif - // FIXME-BAKED: Need a design for AddCustomRectFontGlyph() - IM_UNUSED(codepoint); - IM_UNUSED(offset); - IM_UNUSED(advance_x); - return -1; } +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImTextureRect* ImFontAtlas::GetCustomRectByIndex(int idx) { From 8a8d8a7b38f0cb13e711bb3e1be74083f5a968ff Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Feb 2025 18:25:52 +0100 Subject: [PATCH 070/191] Fonts: Exposed CompactCache(). Hide ClearCache(). --- imgui.cpp | 8 ++++---- imgui.h | 2 +- imgui_draw.cpp | 31 ++++++++++++++++++++----------- imgui_internal.h | 3 ++- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f416223cd..3f6c01ded 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15688,14 +15688,14 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) } SeparatorText("Font Atlas"); - if (Button("Clear Cache")) - atlas->ClearCache(); + if (Button("Compact")) + atlas->CompactCache(); SameLine(); if (Button("Grow")) ImFontAtlasBuildGrowTexture(atlas); SameLine(); - if (Button("Compact")) - ImFontAtlasBuildCompactTexture(atlas); + if (Button("Clear Output")) + ImFontAtlasBuildClearTexture(atlas); for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { diff --git a/imgui.h b/imgui.h index d62f56fa8..1b60ddc9c 100644 --- a/imgui.h +++ b/imgui.h @@ -3521,7 +3521,7 @@ struct ImFontAtlas IMGUI_API void RemoveFont(ImFont* font); IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures) - IMGUI_API void ClearCache(); // Clear cached glyphs and textures. Invalidates all AddCustomRectXXX return values. + IMGUI_API void CompactCache(); // Compact cached glyphs and texture. // As we are transitioning toward a new font system, we expect to obsolete those soon: 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. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2a0a300a7..01a02367b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2457,7 +2457,7 @@ void ImTextureData::DestroyPixels() // - Default texture data encoded in ASCII // - ImFontAtlas() // - ImFontAtlas::Clear() -// - ImFontAtlas::ClearCache() +// - ImFontAtlas::CompactCache() // - ImFontAtlas::ClearInputData() // - ImFontAtlas::ClearTexData() // - ImFontAtlas::ClearFonts() @@ -2499,7 +2499,7 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildAddFont() // - ImFontAtlasBuildSetupFontCreateEllipsisFromDot() // - ImFontAtlasBuildSetupFontSpecialGlyphs() -// - ImFontAtlasBuildDiscardUnusedBakes() +// - ImFontAtlasBuildDiscardBakes() // - ImFontAtlasBuildDiscardFontBakedGlyph() // - ImFontAtlasBuildDiscardFontBaked() // - ImFontAtlasBuildDiscardFont() @@ -2615,15 +2615,14 @@ void ImFontAtlas::Clear() bool backup_renderer_has_textures = RendererHasTextures; RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. ClearTexData(); + if (Builder != NULL) + ImFontAtlasBuildClearTexture(this); RendererHasTextures = backup_renderer_has_textures; } -void ImFontAtlas::ClearCache() +void ImFontAtlas::CompactCache() { - ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(this); - ImFontAtlasBuildDestroy(this); - ImFontAtlasBuildAddTexture(this, new_tex_size.x, new_tex_size.y); - ImFontAtlasBuildInit(this); + ImFontAtlasBuildCompactTexture(this); } void ImFontAtlas::ClearInputData() @@ -3682,13 +3681,14 @@ void ImFontAtlasBuildDiscardFont(ImFontAtlas* atlas, ImFont* font) } } -void ImFontAtlasBuildDiscardUnusedBakes(ImFontAtlas* atlas, int gc_frames) +// use unused_frames==0 to discard everything. +void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames) { ImFontAtlasBuilder* builder = atlas->Builder; for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) { ImFontBaked* baked = &builder->BakedPool[baked_n]; - if (baked->LastUsedFrame + gc_frames < atlas->Builder->FrameCount && !baked->WantDestroy) + if (baked->LastUsedFrame + unused_frames <= atlas->Builder->FrameCount && !baked->WantDestroy) ImFontAtlasBuildDiscardFontBaked(atlas, baked->ContainerFont, baked); } } @@ -3909,7 +3909,7 @@ void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas) // Can some baked contents be ditched? //IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlasBuildMakeSpace()\n"); ImFontAtlasBuilder* builder = atlas->Builder; - ImFontAtlasBuildDiscardUnusedBakes(atlas, 2); + ImFontAtlasBuildDiscardBakes(atlas, 2); // Currently using a heuristic for repack without growing. if (builder->RectsDiscardedSurface < builder->RectsPackedSurface * 0.20f) @@ -3952,12 +3952,21 @@ ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) return ImVec2i(new_tex_w, new_tex_h); } +// Clear all output. Invalidates all AddCustomRectXXX return values. +void ImFontAtlasBuildClearTexture(ImFontAtlas* atlas) +{ + ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); + ImFontAtlasBuildDestroy(atlas); + ImFontAtlasBuildAddTexture(atlas, new_tex_size.x, new_tex_size.y); + ImFontAtlasBuildInit(atlas); +} + // You should not need to call this manually! // If you think you do, let us know and we can advise about policies auto-compact. void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) { ImFontAtlasBuilder* builder = atlas->Builder; - ImFontAtlasBuildDiscardUnusedBakes(atlas, 1); + ImFontAtlasBuildDiscardBakes(atlas, 0); ImTextureData* old_tex = atlas->TexData; ImVec2i old_tex_size = ImVec2i(old_tex->Width, old_tex->Height); diff --git a/imgui_internal.h b/imgui_internal.h index 7e7a2c697..2f5ba1514 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3761,13 +3761,14 @@ IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* IMGUI_API ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h); IMGUI_API void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasBuildClearTexture(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas); IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); -IMGUI_API void ImFontAtlasBuildDiscardUnusedBakes(ImFontAtlas* atlas, int gc_frames); +IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); IMGUI_API void ImFontAtlasBuildDiscardFont(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); IMGUI_API void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); From dc1320df64d4b93e20c8f95b5ad709e6d46cc49e Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Feb 2025 11:57:30 +0100 Subject: [PATCH 071/191] Fonts: ImFontFlags: ImFontFlags_NoLoadGlyphs + add ImFontFlags_LockBakedSizes --- imgui.h | 16 +++++++++++++--- imgui_draw.cpp | 21 ++++++++++++++++----- imgui_widgets.cpp | 2 +- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/imgui.h b/imgui.h index 1b60ddc9c..fa345e7fe 100644 --- a/imgui.h +++ b/imgui.h @@ -230,7 +230,8 @@ typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A // - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance -typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build +typedef int ImFontFlags; // -> enum ImFontFlags_ // Flags: for ImFont +typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for InvisibleButton() typedef int ImGuiChildFlags; // -> enum ImGuiChildFlags_ // Flags: for BeginChild() @@ -3666,6 +3667,15 @@ struct ImFontBaked IMGUI_API void BuildGrowIndex(int new_size); }; +// Font flags +// (in future versions as we redesign font loading API, this will become more important and better documented. for now please consider this as internal/advanced use) +enum ImFontFlags_ +{ + ImFontFlags_None = 0, + ImFontFlags_LockBakedSizes = 1 << 0, // Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. + ImFontFlags_NoLoadGlyphs = 1 << 1, // Disable loading new glyphs. +}; + // Font runtime data and rendering // - ImFontAtlas automatically loads a default embedded font for you if you didn't load one manually. // - Since 1.92.X a font may be rendered as any size! Therefore a font doesn't have one specific size. @@ -3673,9 +3683,10 @@ struct ImFontBaked // - If you used g.Font + g.FontSize (which is frequent from the ImGui layer), you can use g.FontBaked as a shortcut, as g.FontBaked == g.Font->GetFontBaked(g.FontSize). struct ImFont { - // [Internal] Members: Hot ~8-16 bytes + // [Internal] Members: Hot ~12-20 bytes ImFontBaked* LastBaked; // 4-8 // Cache last bound baked. DO NOT USE. Use GetFontBaked(). ImFontAtlas* ContainerAtlas; // 4-8 // What we has been loaded into + ImFontFlags Flags; // 4 // Font flags // [Internal] Members: Cold ~24-52 bytes // Conceptually Sources[] is the list of font sources merged to create this font. @@ -3686,7 +3697,6 @@ struct ImFont ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. - bool LockDisableLoading; short LockSingleSrcConfigIdx; // Methods diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 01a02367b..6f6f2e4ac 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2647,7 +2647,7 @@ void ImFontAtlas::ClearInputData() font->Sources = NULL; font->SourcesCount = 0; } - font->LockDisableLoading = true; + font->Flags |= ImFontFlags_NoLoadGlyphs; } Sources.clear(); } @@ -3688,8 +3688,11 @@ void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames) for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) { ImFontBaked* baked = &builder->BakedPool[baked_n]; - if (baked->LastUsedFrame + unused_frames <= atlas->Builder->FrameCount && !baked->WantDestroy) - ImFontAtlasBuildDiscardFontBaked(atlas, baked->ContainerFont, baked); + if (baked->LastUsedFrame + unused_frames > atlas->Builder->FrameCount) + continue; + if (baked->WantDestroy || (baked->ContainerFont->Flags & ImFontFlags_LockBakedSizes)) + continue; + ImFontAtlasBuildDiscardFontBaked(atlas, baked->ContainerFont, baked); } } @@ -3816,7 +3819,8 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) //ImFontAtlasDebugWriteTexToDisk(old_tex, "Before Pack"); // Repack, lose discarded rectangle, copy pixels - // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic. + // FIXME-NEWATLAS: This is unstable because packing order is based on RectsIndex + // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic, and fix stability. // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. ImFontAtlasPackInit(atlas); ImVector old_rects; @@ -4172,7 +4176,7 @@ ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) ImFont* font = ContainerFont; ImFontBaked* baked = this; ImFontAtlas* atlas = font->ContainerAtlas; - if (font->LockDisableLoading || atlas->Locked) + if (atlas->Locked || (font->Flags & ImFontFlags_NoLoadGlyphs)) return NULL; //char utf8_buf[5]; @@ -5008,6 +5012,13 @@ ImFontBaked* ImFont::GetFontBaked(float size) return baked; } + // FIXME-BAKED: If loading is locked, find closest match + if (Flags & ImFontFlags_LockBakedSizes) + { + IM_ASSERT(LastBaked); + return LastBaked; + } + // FIXME-BAKED: If atlas is locked, find closest match if (atlas->Locked) IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2304aaf37..6a86c7956 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4321,7 +4321,7 @@ void ImGui::PushPasswordFont() ImFont* out_font = &g.InputTextPasswordFont; out_font->Scale = in_font->Scale; out_font->ContainerAtlas = in_font->ContainerAtlas; - out_font->LockDisableLoading = true; + out_font->Flags |= ImFontFlags_NoLoadGlyphs; ImFontBaked* out_baked = out_font->GetFontBaked(in_baked->Size); IM_ASSERT(out_baked->Glyphs.Size <= 1 && out_baked->IndexAdvanceX.Size == 0 && out_baked->IndexLookup.Size == 0); out_baked->Ascent = in_baked->Ascent; From 92993e68c8a401623385ca04724914ae0575afa7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Feb 2025 13:06:05 +0100 Subject: [PATCH 072/191] Fonts: Baked system, fix subsequent sources overriding shared font metrics. --- imgui_draw.cpp | 3 ++- misc/freetype/imgui_freetype.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6f6f2e4ac..48ecc6b1b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4313,7 +4313,8 @@ static void ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontBaked* { ImFontConfig* src = &font->Sources[src_n]; ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; - + if (src_n != 0) + continue; // FIXME-NEWFONTS: reevaluate how to use sizing metrics // FIXME-NEWFONTS: make use of line gap value float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index e71534c52..5de45a75f 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -479,8 +479,11 @@ void ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontBaked* baked) bd_baked_data->MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * bd_font_data->InvRasterizationDensity; // Output - baked->Ascent = bd_baked_data->Ascender; - baked->Descent = bd_baked_data->Descender; + if (src_n == 0) + { + baked->Ascent = bd_baked_data->Ascender; + baked->Descent = bd_baked_data->Descender; + } } } From 76b252f80a3322a12f39952658b82b17630c018f Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 7 Feb 2025 14:51:37 +0100 Subject: [PATCH 073/191] Fonts: Added ImFontAtlasBakedSetFontGlyphBitmap(). --- imgui_draw.cpp | 43 ++++++++++++++++++-------------- imgui_internal.h | 1 + misc/freetype/imgui_freetype.cpp | 34 +++++++++++-------------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 48ecc6b1b..2a9e9a45f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4370,9 +4370,10 @@ static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBa const bool is_visible = (x0 != x1 && y0 != y1); // Prepare glyph - ImFontGlyph glyph; - glyph.Codepoint = codepoint; - glyph.AdvanceX = advance * scale_for_layout; + ImFontGlyph glyph_in = {}; + ImFontGlyph* glyph = &glyph_in; + glyph->Codepoint = codepoint; + glyph->AdvanceX = advance * scale_for_layout; // Pack and retrieve position inside texture atlas // (generally based on stbtt_PackFontRangesRenderIntoRects) @@ -4420,25 +4421,18 @@ static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBa // Register glyph // r->x r->y are coordinates inside texture (in pixels) // glyph.X0, glyph.Y0 are drawing coordinates from base text position, and accounting for oversampling. - glyph.X0 = x0 * recip_h + font_off_x; - glyph.Y0 = y0 * recip_v + font_off_y; - glyph.X1 = (x0 + (int)r->w) * recip_h + font_off_x; - glyph.Y1 = (y0 + (int)r->h) * recip_v + font_off_y; - glyph.Visible = true; - glyph.PackId = pack_id; - ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph); - - // Copy to texture, post-process and queue update for backend - ImTextureData* tex = atlas->TexData; - IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); - ImFontAtlasTextureBlockConvert(bitmap_pixels, ImTextureFormat_Alpha8, w, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); - ImFontAtlasPostProcessData pp_data = { atlas, baked->ContainerFont, src, baked, &baked->Glyphs.back(), tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h }; - ImFontAtlasTextureBlockPostProcess(&pp_data); - ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); + glyph->X0 = x0 * recip_h + font_off_x; + glyph->Y0 = y0 * recip_v + font_off_y; + glyph->X1 = (x0 + (int)r->w) * recip_h + font_off_x; + glyph->Y1 = (y0 + (int)r->h) * recip_v + font_off_y; + glyph->Visible = true; + glyph->PackId = pack_id; + glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); + ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, glyph, r, bitmap_pixels, ImTextureFormat_Alpha8, w); } else { - ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph); + glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); } return true; @@ -4891,6 +4885,17 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked return &glyph; } +// Copy to texture, post-process and queue update for backend +void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImFontAtlasRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch) +{ + ImTextureData* tex = atlas->TexData; + IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); + ImFontAtlasTextureBlockConvert(src_pixels, src_fmt, src_pitch, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), r->w, r->h); + ImFontAtlasPostProcessData pp_data = { atlas, baked->ContainerFont, src, baked, glyph, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), r->w, r->h }; + ImFontAtlasTextureBlockPostProcess(&pp_data); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); +} + void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst) { // FIXME-BAKED: Implement AddRemapChar() diff --git a/imgui_internal.h b/imgui_internal.h index 2f5ba1514..9bde7ed43 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3777,6 +3777,7 @@ IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* s IMGUI_API bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint); IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); +IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImFontAtlasRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size); IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 5de45a75f..7be3fc1e5 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -546,9 +546,10 @@ bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBaked* baked const bool is_visible = (w != 0 && h != 0); // Prepare glyph - ImFontGlyph glyph = {}; - glyph.Codepoint = codepoint; - glyph.AdvanceX = (slot->advance.x / FT_SCALEFACTOR) * bd_font_data->InvRasterizationDensity; + ImFontGlyph glyph_in = {}; + ImFontGlyph* glyph = &glyph_in; + glyph->Codepoint = codepoint; + glyph->AdvanceX = (slot->advance.x / FT_SCALEFACTOR) * bd_font_data->InvRasterizationDensity; // Pack and retrieve position inside texture atlas if (is_visible) @@ -580,26 +581,19 @@ bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBaked* baked // Register glyph float glyph_off_x = (float)face->glyph->bitmap_left; float glyph_off_y = (float)-face->glyph->bitmap_top; - glyph.X0 = glyph_off_x * recip_h + font_off_x; - glyph.Y0 = glyph_off_y * recip_v + font_off_y; - glyph.X1 = (glyph_off_x + w) * recip_h + font_off_x; - glyph.Y1 = (glyph_off_y + h) * recip_v + font_off_y; - glyph.Visible = true; - glyph.Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); - glyph.PackId = pack_id; - ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph); - - // Copy to texture, post-process and queue update for backend - ImTextureData* tex = atlas->TexData; - IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); - ImFontAtlasTextureBlockConvert(temp_buffer, ImTextureFormat_RGBA32, w * 4, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); - ImFontAtlasPostProcessData pp_data = { atlas, baked->ContainerFont, src, baked, &baked->Glyphs.back(), tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h }; - ImFontAtlasTextureBlockPostProcess(&pp_data); - ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); + glyph->X0 = glyph_off_x * recip_h + font_off_x; + glyph->Y0 = glyph_off_y * recip_v + font_off_y; + glyph->X1 = (glyph_off_x + w) * recip_h + font_off_x; + glyph->Y1 = (glyph_off_y + h) * recip_v + font_off_y; + glyph->Visible = true; + glyph->Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); + glyph->PackId = pack_id; + glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); + ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, glyph, r, (const unsigned char*)temp_buffer, ImTextureFormat_RGBA32, w * 4); } else { - ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph); + glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); } return true; } From d59f10d7f50402963c3e80521d2e4caf22278e60 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 7 Feb 2025 15:58:16 +0100 Subject: [PATCH 074/191] Fonts: reinstated ImFontAtlasBuildSetupFontCreateEllipsisFromDot() compatible with baked system, lazily baked. --- imgui.h | 1 + imgui_draw.cpp | 72 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/imgui.h b/imgui.h index fa345e7fe..46a0b8749 100644 --- a/imgui.h +++ b/imgui.h @@ -3697,6 +3697,7 @@ struct ImFont ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated. short LockSingleSrcConfigIdx; // Methods diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2a9e9a45f..bc55b7273 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2497,7 +2497,8 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildUpdateBasicTexData() // - ImFontAtlasBuildUpdateLinesTexData() // - ImFontAtlasBuildAddFont() -// - ImFontAtlasBuildSetupFontCreateEllipsisFromDot() +// - ImFontAtlasBuildSetupFontBakedEllipsis() +// - ImFontAtlasBuildSetupFontBakedBlanks() // - ImFontAtlasBuildSetupFontSpecialGlyphs() // - ImFontAtlasBuildDiscardBakes() // - ImFontAtlasBuildDiscardFontBakedGlyph() @@ -3528,37 +3529,50 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) return true; } -// Rasterize our own ellipsis character from a dot. +// Create a compact, baked "..." if it doesn't exist, by using the ".". // This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. -// FIXME-NEWATLAS: This borrows too much from FontBackend_FontAddGlyph() and suggest that we should add further helpers. -// FIXME-BAKED: prebaked ellipsis -/*static void ImFontAtlasBuildSetupFontCreateEllipsisFromDot(ImFontAtlas* atlas, ImFont* font, const ImFontGlyph* dot_glyph) +// FIXME-NEWATLAS: This borrows too much from FontLoader's FontLoaderGlyph() handlers and suggest that we should add further helpers. +static ImFontGlyph* ImFontAtlasBuildSetupFontBakedEllipsis(ImFontAtlas* atlas, ImFontBaked* baked) { - ImFontAtlasRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_glyph->PackId); + ImFont* font = baked->ContainerFont; + IM_ASSERT(font->EllipsisChar != 0); + + const ImFontGlyph* dot_glyph = baked->FindGlyphNoFallback((ImWchar)'.'); + if (dot_glyph == NULL) + dot_glyph = baked->FindGlyphNoFallback((ImWchar)0xFF0E); + if (dot_glyph == NULL) + return NULL; + ImFontAtlasRectId dot_r_id = dot_glyph->PackId; // Deep copy to avoid invalidation of glyphs and rect pointers + ImFontAtlasRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); const int dot_spacing = 1; const float dot_step = (dot_glyph->X1 - dot_glyph->X0) + dot_spacing; + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, (dot_r->w * 3 + dot_spacing * 2), dot_r->h); ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); - ImFontGlyph glyph; - glyph.Codepoint = (ImWchar)0x0085; // FIXME: Using arbitrary codepoint. - glyph.AdvanceX = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + dot_step * 3.0f - dot_spacing); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. - glyph.X0 = dot_glyph->X0; - glyph.Y0 = dot_glyph->Y0; - glyph.X1 = dot_glyph->X0 + dot_step * 3 - dot_spacing; - glyph.Y1 = dot_glyph->Y1; - glyph.Visible = true; - glyph.PackId = pack_id; - ImFontAtlasBuildAddFontGlyph(atlas, font, NULL, &glyph); - font->EllipsisChar = (ImWchar)glyph.Codepoint; + ImFontGlyph glyph_in = {}; + ImFontGlyph* glyph = &glyph_in; + glyph->Codepoint = font->EllipsisChar; + glyph->AdvanceX = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + dot_step * 3.0f - dot_spacing); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. + glyph->X0 = dot_glyph->X0; + glyph->Y0 = dot_glyph->Y0; + glyph->X1 = dot_glyph->X0 + dot_step * 3 - dot_spacing; + glyph->Y1 = dot_glyph->Y1; + glyph->Visible = true; + glyph->PackId = pack_id; + glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, glyph); + dot_glyph = NULL; // Invalidated // Copy to texture, post-process and queue update for backend // FIXME-NEWATLAS-V2: Dot glyph is already post-processed as this point, so this would damage it. + dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); ImTextureData* tex = atlas->TexData; for (int n = 0; n < 3; n++) ImFontAtlasTextureBlockCopy(tex, dot_r->x, dot_r->y, tex, r->x + (dot_r->w + dot_spacing) * n, r->y, dot_r->w, dot_r->h); ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); -}*/ + + return glyph; +} static void ImFontAtlasBuildSetupFontBakedSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) { @@ -3582,6 +3596,14 @@ static void ImFontAtlasBuildSetupFontBakedSpecialGlyphs(ImFontAtlas* atlas, ImFo } baked->FallbackGlyphIndex = baked->Glyphs.index_from_ptr(fallback_glyph); // Storing index avoid need to update pointer on growth and simplify inner loop code baked->FallbackAdvanceX = fallback_glyph->AdvanceX; +} + +static void ImFontAtlasBuildSetupFontBakedBlanks(ImFontAtlas* atlas, ImFontBaked* baked) +{ + // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) + ImFontGlyph* space_glyph = baked->FindGlyphNoFallback((ImWchar)' '); + if (space_glyph != NULL) + space_glyph->Visible = false; // Setup Tab character. // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) @@ -3628,11 +3650,8 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, Im } if (font->EllipsisChar == 0) { - /*const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; - if (const ImFontGlyph* dot_glyph = LoadFirstExistingGlyph(font, dots_chars, IM_ARRAYSIZE(dots_chars))) - ImFontAtlasBuildSetupFontCreateEllipsisFromDot(atlas, font, dot_glyph); - else*/ - font->EllipsisChar = (ImWchar)' '; + font->EllipsisChar = 0x0085; + font->EllipsisAutoBake = true; } font->LockSingleSrcConfigIdx = -1; } @@ -4162,6 +4181,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon } } +// Important: don'return pointer valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { IM_ASSERT(id >= 0); @@ -4182,6 +4202,12 @@ ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) //char utf8_buf[5]; //IMGUI_DEBUG_LOG("[font] BuildLoadGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); + // Special hook + // FIXME-NEWATLAS: it would be nicer if this used a more standardized way of hooking + if (codepoint == font->EllipsisChar && font->EllipsisAutoBake) + if (ImFontGlyph* glyph = ImFontAtlasBuildSetupFontBakedEllipsis(atlas, baked)) + return glyph; + // Load from single source or all sources? int srcs_count = (font->LockSingleSrcConfigIdx != -1) ? 1 : font->SourcesCount; ImFontConfig* srcs = (font->LockSingleSrcConfigIdx != -1) ? &font->Sources[font->LockSingleSrcConfigIdx] : font->Sources; From 1cfc0de31df9b76a01a4c19f958d86dd4633202e Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Feb 2025 15:19:23 +0100 Subject: [PATCH 075/191] Fonts: Core allocates per-baked-per-src backend buffers, to allow having custom backend per font source. Backend BakedInit/Destroy/AddGlyph process a single source. --- imgui.h | 2 +- imgui_draw.cpp | 129 ++++++++++++++++++------------- imgui_internal.h | 12 ++- misc/freetype/imgui_freetype.cpp | 118 ++++++++++++---------------- 4 files changed, 133 insertions(+), 128 deletions(-) diff --git a/imgui.h b/imgui.h index 46a0b8749..e1d83d378 100644 --- a/imgui.h +++ b/imgui.h @@ -3654,7 +3654,7 @@ struct ImFontBaked int LastUsedFrame; // 4 // // Record of that time this was bounds ImGuiID BakedId; // 4 // ImFont* ContainerFont; // 4-8 // in // Parent font - void* FontBackendData; // 4-8 // // Font backend opaque storage (per baked font) + void* FontLoaderDatas; // 4-8 // // Font loader opaque storage (per baked font * sources): single contiguous buffer allocated by imgui, passed to loader. // Functions IMGUI_API ImFontBaked(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index bc55b7273..ee88d5f8d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3531,7 +3531,7 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) // Create a compact, baked "..." if it doesn't exist, by using the ".". // This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. -// FIXME-NEWATLAS: This borrows too much from FontLoader's FontLoaderGlyph() handlers and suggest that we should add further helpers. +// FIXME-NEWATLAS: This borrows too much from FontLoader's FontLoadGlyph() handlers and suggest that we should add further helpers. static ImFontGlyph* ImFontAtlasBuildSetupFontBakedEllipsis(ImFontAtlas* atlas, ImFontBaked* baked) { ImFont* font = baked->ContainerFont; @@ -3670,6 +3670,31 @@ void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImF baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; } +ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float size, ImGuiID baked_id) +{ + IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", size); + ImFontBaked* baked = atlas->Builder->BakedPool.push_back(ImFontBaked()); + baked->Size = size; + baked->BakedId = baked_id; + baked->ContainerFont = font; + baked->LastUsedFrame = atlas->Builder->FrameCount; + + // Initialize backend data + size_t loader_data_size = font->SourcesCount * atlas->FontLoader->FontBakedSrcLoaderDataSize; + baked->FontLoaderDatas = (loader_data_size > 0) ? IM_ALLOC(loader_data_size) : NULL; + char* backend_user_data_p = (char*)baked->FontLoaderDatas; + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = &font->Sources[src_n]; + if (atlas->FontLoader->FontBakedInit) + atlas->FontLoader->FontBakedInit(atlas, src, baked, backend_user_data_p); + backend_user_data_p += atlas->FontLoader->FontBakedSrcLoaderDataSize; + } + + ImFontAtlasBuildSetupFontBakedSpecialGlyphs(atlas, baked->ContainerFont, baked); + return baked; +} + void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) { ImFontAtlasBuilder* builder = atlas->Builder; @@ -3679,9 +3704,19 @@ void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBa if (glyph.PackId >= 0) ImFontAtlasPackDiscardRect(atlas, glyph.PackId); - if (atlas->FontLoader->FontBakedDestroy) - atlas->FontLoader->FontBakedDestroy(atlas, baked); - + char* backend_user_data_p = (char*)baked->FontLoaderDatas; + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = &font->Sources[src_n]; + if (atlas->FontLoader->FontBakedDestroy) + atlas->FontLoader->FontBakedDestroy(atlas, src, baked, backend_user_data_p); + backend_user_data_p += atlas->FontLoader->FontBakedSrcLoaderDataSize; + } + if (baked->FontLoaderDatas) + { + IM_FREE(baked->FontLoaderDatas); + baked->FontLoaderDatas = NULL; + } builder->BakedMap.SetVoidPtr(baked->BakedId, NULL); builder->BakedDiscardedCount++; baked->ClearOutputData(); @@ -4017,7 +4052,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) #else IM_ASSERT(0); // Invalid Build function #endif - return; // ImFontAtlasBuildSetupFontBackendIO() automatically call ImFontAtlasBuildInit() + return; // ImFontAtlasBuildSetupFontLoader() automatically call ImFontAtlasBuildInit() } // Create initial texture size @@ -4191,6 +4226,16 @@ ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id return &builder->Rects[index_entry->TargetIndex]; } +// Important! This assume by ImFontConfig::GlyphFilter is a SMALL ARRAY (e.g. <10 entries) +static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint) +{ + if (const ImWchar* exclude_list = src->GlyphExcludeRanges) + for (; exclude_list[0] != 0; exclude_list += 2) + if (codepoint >= exclude_list[0] && codepoint <= exclude_list[1]) + return false; + return true; +} + ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) { ImFont* font = ContainerFont; @@ -4214,18 +4259,25 @@ ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) // Call backend const ImFontLoader* font_loader = atlas->FontLoader; - if (!font_loader->FontBakedAddGlyph(atlas, baked, srcs, srcs_count, codepoint)) + char* backend_user_data_p = (char*)baked->FontLoaderDatas; + for (int src_n = 0; src_n < srcs_count; src_n++) { - // Mark index as not found, so we don't attempt the search twice - baked->BuildGrowIndex(codepoint + 1); - baked->IndexAdvanceX[codepoint] = baked->FallbackAdvanceX; - baked->IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; - return NULL; + ImFontConfig* src = &srcs[src_n]; + if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) + if (font_loader->FontBakedAddGlyph(atlas, src, baked, backend_user_data_p, codepoint)) + { + // FIXME: Add hooks for e.g. #7962 + ImFontGlyph* glyph = &baked->Glyphs.back(); + return glyph; + } + backend_user_data_p += font_loader->FontBakedSrcLoaderDataSize; } - // FIXME: Add hooks for e.g. #7962 - ImFontGlyph* glyph = &baked->Glyphs.back(); - return glyph; + // Mark index as not found, so we don't attempt the search twice + baked->BuildGrowIndex(codepoint + 1); + baked->IndexAdvanceX[codepoint] = baked->FallbackAdvanceX; + baked->IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; + return NULL; } // The point of this indirection is to not be inlined in debug mode in order to not bloat inner loop.b @@ -4330,17 +4382,13 @@ static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFon return glyph_index != 0; } -static void ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontBaked* baked) +static void ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*) { IM_UNUSED(atlas); - ImFont* font = baked->ContainerFont; - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + if (src->MergeMode == false) { - ImFontConfig* src = &font->Sources[src_n]; - ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; - if (src_n != 0) - continue; // FIXME-NEWFONTS: reevaluate how to use sizing metrics // FIXME-NEWFONTS: make use of line gap value float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; @@ -4351,33 +4399,12 @@ static void ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontBaked* } } -// Important! This assume by ImFontConfig::GlyphFilter is a SMALL ARRAY (e.g. <10 entries) -bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint) -{ - if (const ImWchar* exclude_list = src->GlyphExcludeRanges) - for (; exclude_list[0] != 0; exclude_list += 2) - if (codepoint >= exclude_list[0] && codepoint <= exclude_list[1]) - return false; - return true; -} - -static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* srcs, int srcs_count, ImWchar codepoint) +static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint) { // Search for first font which has the glyph - ImGui_ImplStbTrueType_FontSrcData* bd_font_data = NULL; - ImFontConfig* src = NULL; - int glyph_index = 0; - for (int src_n = 0; src_n < srcs_count; src_n++) - { - src = &srcs[src_n]; - if (src->GlyphExcludeRanges && !ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) - continue; - bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; - IM_ASSERT(bd_font_data); - glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); - if (glyph_index != 0) - break; - } + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_ASSERT(bd_font_data); + int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); if (glyph_index == 0) return false; // Not found @@ -5056,17 +5083,9 @@ ImFontBaked* ImFont::GetFontBaked(float size) IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! // Create new - IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", size); - baked = builder->BakedPool.push_back(ImFontBaked()); - baked->Size = size; - baked->BakedId = baked_id; - baked->ContainerFont = this; - baked->LastUsedFrame = ContainerAtlas->Builder->FrameCount; + baked = ImFontAtlasBuildAddFontBaked(atlas, this, size, baked_id); LastBaked = baked; *p_baked_in_map = baked; // To avoid 'builder->BakedMap.SetVoidPtr(baked_id, baked);' while we can. - if (atlas->FontLoader->FontBakedInit) - atlas->FontLoader->FontBakedInit(atlas, baked); - ImFontAtlasBuildSetupFontBakedSpecialGlyphs(atlas, baked->ContainerFont, baked); return baked; } diff --git a/imgui_internal.h b/imgui_internal.h index 9bde7ed43..caca87463 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3668,9 +3668,13 @@ struct ImFontLoader bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); - void (*FontBakedInit)(ImFontAtlas* atlas, ImFontBaked* baked); - void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontBaked* baked); - bool (*FontBakedAddGlyph)(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* srcs, int srcs_count, ImWchar codepoint); + void (*FontBakedInit)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); + void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); + bool (*FontBakedAddGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint); + + // Size of backend data, Per Baked * Per Source. Buffers are managed by core to avoid excessive allocations. + // FIXME: At this point the two other types of buffers may be managed by core to be consistent? + size_t FontBakedSrcLoaderDataSize; ImFontLoader() { memset(this, 0, sizeof(*this)); } }; @@ -3770,11 +3774,11 @@ IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontCo IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); IMGUI_API void ImFontAtlasBuildDiscardFont(ImFontAtlas* atlas, ImFont* font); +IMGUI_API ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); IMGUI_API void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); IMGUI_API void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); -IMGUI_API bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint); IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImFontAtlasRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 7be3fc1e5..17cd2caaa 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -143,7 +143,7 @@ namespace // | | // |------------- advanceX ----------->| - // Stored in ImFontAtlas::FontLoaderData + // Stored in ImFontAtlas::FontLoaderData. ALLOCATED BY US. struct ImGui_ImplFreeType_Data { FT_Library Library; @@ -151,7 +151,7 @@ namespace ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); } }; - // Stored in ImFontBaked::FontBackendData: pointer to SourcesCount instances of this. + // Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE. struct ImGui_ImplFreeType_FontSrcBakedData { FT_Size FtSize; // This represent a FT_Face with a given size. @@ -166,7 +166,7 @@ namespace ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); } }; - // Stored in ImFontConfig::FontLoaderData + // Stored in ImFontConfig::FontLoaderData. ALLOCATED BY US. struct ImGui_ImplFreeType_FontSrcData { bool InitFont(FT_Library ft_library, ImFontConfig* src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. @@ -437,91 +437,72 @@ void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) src->FontLoaderData = NULL; } -void ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontBaked* baked) +void ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) { IM_UNUSED(atlas); - ImFont* font = baked->ContainerFont; const float size = baked->Size; - IM_ASSERT(baked->FontBackendData == NULL); - ImGui_ImplFreeType_FontSrcBakedData* bd_baked_datas = (ImGui_ImplFreeType_FontSrcBakedData*)IM_ALLOC(sizeof(ImGui_ImplFreeType_FontSrcBakedData) * font->SourcesCount); - baked->FontBackendData = bd_baked_datas; + ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; + bd_font_data->BakedLastActivated = baked; - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + // We use one FT_Size per (source + baked) combination. + ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src; + IM_ASSERT(bd_baked_data != NULL); + IM_PLACEMENT_NEW(bd_baked_data) ImGui_ImplFreeType_FontSrcBakedData(); + + FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize); + FT_Activate_Size(bd_baked_data->FtSize); + + // Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height' + // is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me. + // FT_Set_Pixel_Sizes() doesn't seem to get us the same result." + // (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL) + FT_Size_RequestRec req; + req.type = (bd_font_data->UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM; + req.width = 0; + req.height = (uint32_t)(size * 64 * bd_font_data->RasterizationDensity); + req.horiResolution = 0; + req.vertResolution = 0; + FT_Request_Size(bd_font_data->FtFace, &req); + + // Read metrics + FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics; + bd_baked_data->Ascender = (float)FT_CEIL(metrics.ascender) * bd_font_data->InvRasterizationDensity; + bd_baked_data->Descender = (float)FT_CEIL(metrics.descender) * bd_font_data->InvRasterizationDensity; + bd_baked_data->LineSpacing = (float)FT_CEIL(metrics.height) * bd_font_data->InvRasterizationDensity; + bd_baked_data->LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * bd_font_data->InvRasterizationDensity; + bd_baked_data->MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * bd_font_data->InvRasterizationDensity; + + // Output + if (src->MergeMode == false) { - ImFontConfig* src = &font->Sources[src_n]; - ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontBackendData; - bd_font_data->BakedLastActivated = baked; - - // We need one FT_Size per source, so create one ImGui_ImplFreeType_FontBakedData for each source. - ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = &bd_baked_datas[src_n]; - FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize); - FT_Activate_Size(bd_baked_data->FtSize); - - // Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height' - // is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me. - // FT_Set_Pixel_Sizes() doesn't seem to get us the same result." - // (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL) - FT_Size_RequestRec req; - req.type = (bd_font_data->UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM; - req.width = 0; - req.height = (uint32_t)(size * 64 * bd_font_data->RasterizationDensity); - req.horiResolution = 0; - req.vertResolution = 0; - FT_Request_Size(bd_font_data->FtFace, &req); - - // Read metrics - FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics; - bd_baked_data->Ascender = (float)FT_CEIL(metrics.ascender) * bd_font_data->InvRasterizationDensity; - bd_baked_data->Descender = (float)FT_CEIL(metrics.descender) * bd_font_data->InvRasterizationDensity; - bd_baked_data->LineSpacing = (float)FT_CEIL(metrics.height) * bd_font_data->InvRasterizationDensity; - bd_baked_data->LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * bd_font_data->InvRasterizationDensity; - bd_baked_data->MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * bd_font_data->InvRasterizationDensity; - - // Output - if (src_n == 0) - { - baked->Ascent = bd_baked_data->Ascender; - baked->Descent = bd_baked_data->Descender; - } + baked->Ascent = bd_baked_data->Ascender; + baked->Descent = bd_baked_data->Descender; } } -void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontBaked* baked) +void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) { IM_UNUSED(atlas); - ImFont* font = baked->ContainerFont; - ImGui_ImplFreeType_FontSrcBakedData* bd_baked_datas = (ImGui_ImplFreeType_FontSrcBakedData*)baked->FontBackendData; - for (int src_n = 0; src_n < font->SourcesCount; src_n++) - FT_Done_Size(bd_baked_datas[src_n].FtSize); - IM_FREE(bd_baked_datas); - baked->FontBackendData = NULL; + IM_UNUSED(baked); + IM_UNUSED(src); + ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src; + IM_ASSERT(bd_baked_data != NULL); + FT_Done_Size(bd_baked_data->FtSize); + bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE() } -bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* srcs, int srcs_count, ImWchar codepoint) +bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint) { - // Search for first font which has the glyph - ImGui_ImplFreeType_FontSrcData* bd_font_data = NULL; - ImFontConfig* src = NULL; - uint32_t glyph_index = 0; - for (int src_n = 0; src_n < srcs_count; src_n++) - { - src = &srcs[src_n]; - if (src->GlyphExcludeRanges && !ImFontAtlasBuildFilterCodepointForSource(src, codepoint)) - continue; - bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; - glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint); - if (glyph_index != 0) - break; - } + ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; + uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint); if (glyph_index == 0) return false; // Not found if (bd_font_data->BakedLastActivated != baked) { // Activate current size - int src_n = (int)(font_cfg - srcs); - ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = &((ImGui_ImplFreeType_FontSrcBakedData*)baked->FontBackendData)[src_n]; + ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src; FT_Activate_Size(bd_baked_data->FtSize); bd_font_data->BakedLastActivated = baked; } @@ -618,6 +599,7 @@ const ImFontLoader* ImGuiFreeType::GetFontLoader() loader.FontBakedInit = ImGui_ImplFreeType_FontBakedInit; loader.FontBakedDestroy = ImGui_ImplFreeType_FontBakedDestroy; loader.FontBakedAddGlyph = ImGui_ImplFreeType_FontBakedAddGlyph; + loader.FontBakedSrcLoaderDataSize = sizeof(ImGui_ImplFreeType_FontSrcBakedData); return &loader; } From c06a7585a3bfea23f69d791e1f011b3f2c3440fb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Feb 2025 19:31:13 +0100 Subject: [PATCH 076/191] Fonts: A font source can specify its own loader/backend. --- imgui.h | 2 ++ imgui_draw.cpp | 82 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/imgui.h b/imgui.h index e1d83d378..28e84e2a5 100644 --- a/imgui.h +++ b/imgui.h @@ -3444,7 +3444,9 @@ struct ImFontConfig // [Internal] char Name[40]; // Name (strictly to ease debugging) + ImFontFlags Flags; // Font flags (don't use just yet) ImFont* DstFont; // Target font (as we merging fonts, multiple ImFontConfig may target the same font) + const ImFontLoader* FontLoader; // Custom font backend for this source (other use one stored in ImFontAtlas) void* FontLoaderData; // Font loader opaque storage (per font config) IMGUI_API ImFontConfig(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ee88d5f8d..3106f2fe7 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2607,14 +2607,18 @@ ImFontAtlas::ImFontAtlas() ImFontAtlas::~ImFontAtlas() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - Clear(); + RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. + ClearFonts(); + ClearTexData(); + TexList.clear_delete(); + TexData = NULL; } void ImFontAtlas::Clear() { - ClearFonts(); bool backup_renderer_has_textures = RendererHasTextures; RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. + ClearFonts(); ClearTexData(); if (Builder != NULL) ImFontAtlasBuildClearTexture(this); @@ -2631,8 +2635,9 @@ void ImFontAtlas::ClearInputData() IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); for (ImFontConfig& font_cfg : Sources) { - if (FontLoader && FontLoader->FontSrcDestroy != NULL) - FontLoader->FontSrcDestroy(this, &font_cfg); + const ImFontLoader* loader = font_cfg.FontLoader ? font_cfg.FontLoader : FontLoader; + if (loader && loader->FontSrcDestroy != NULL) + loader->FontSrcDestroy(this, &font_cfg); if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas) { IM_FREE(font_cfg.FontData); @@ -2957,7 +2962,7 @@ bool ImFontAtlas::Build() ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); + IM_ASSERT((font_cfg->FontData != NULL && font_cfg->FontDataSize > 0) || (font_cfg->FontLoader != NULL)); IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); IM_ASSERT(font_cfg->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); @@ -2971,6 +2976,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) { font = IM_NEW(ImFont)(); font->FontId = FontNextUniqueID++; + font->Flags = font_cfg->Flags; Fonts.push_back(font); } else @@ -2998,6 +3004,9 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) IM_ASSERT((size & 1) == 0 && "GlyphExcludeRanges[] size must be multiple of two!"); IM_ASSERT((size <= 64) && "GlyphExcludeRanges[] size must be small!"); } + if (font_cfg->FontLoader != NULL) + IM_ASSERT(font_cfg->FontLoader->FontBakedAddGlyph != NULL); + IM_ASSERT(font_cfg->FontLoaderData == NULL); // Round font size // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. @@ -3165,8 +3174,9 @@ void ImFontAtlas::RemoveFont(ImFont* font) for (int src_n = 0; src_n < font->SourcesCount; src_n++) { ImFontConfig* src = (ImFontConfig*)(void*)&font->Sources[src_n]; - if (FontLoader && FontLoader->FontSrcDestroy != NULL) - FontLoader->FontSrcDestroy(this, src); + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : FontLoader; + if (loader && loader->FontSrcDestroy != NULL) + loader->FontSrcDestroy(this, src); if (src->FontData != NULL && src->FontDataOwnedByAtlas) IM_FREE(src->FontData); } @@ -3317,6 +3327,8 @@ void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* ou *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1; } +// Setup main font loader for the atlas +// Every font source (ImFontConfig) will use this unless ImFontConfig::FontLoader specify a custom loader. void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader) { if (atlas->FontLoader == font_loader) @@ -3521,9 +3533,10 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) IM_ASSERT(font->Sources == src); } - const ImFontLoader* font_loader = atlas->FontLoader; - if (!font_loader->FontSrcInit(atlas, src)) - return false; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcInit != NULL) + if (!loader->FontSrcInit(atlas, src)) + return false; ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); return true; @@ -3680,15 +3693,22 @@ ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, floa baked->LastUsedFrame = atlas->Builder->FrameCount; // Initialize backend data - size_t loader_data_size = font->SourcesCount * atlas->FontLoader->FontBakedSrcLoaderDataSize; + size_t loader_data_size = 0; + for (int src_n = 0; src_n < font->SourcesCount; src_n++) // Cannot easily be cached as we allow changing backend + { + ImFontConfig* src = &font->Sources[src_n]; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + loader_data_size += loader->FontBakedSrcLoaderDataSize; + } baked->FontLoaderDatas = (loader_data_size > 0) ? IM_ALLOC(loader_data_size) : NULL; - char* backend_user_data_p = (char*)baked->FontLoaderDatas; + char* loader_data_p = (char*)baked->FontLoaderDatas; for (int src_n = 0; src_n < font->SourcesCount; src_n++) { ImFontConfig* src = &font->Sources[src_n]; - if (atlas->FontLoader->FontBakedInit) - atlas->FontLoader->FontBakedInit(atlas, src, baked, backend_user_data_p); - backend_user_data_p += atlas->FontLoader->FontBakedSrcLoaderDataSize; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontBakedInit) + loader->FontBakedInit(atlas, src, baked, loader_data_p); + loader_data_p += loader->FontBakedSrcLoaderDataSize; } ImFontAtlasBuildSetupFontBakedSpecialGlyphs(atlas, baked->ContainerFont, baked); @@ -3704,13 +3724,14 @@ void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBa if (glyph.PackId >= 0) ImFontAtlasPackDiscardRect(atlas, glyph.PackId); - char* backend_user_data_p = (char*)baked->FontLoaderDatas; + char* loader_data_p = (char*)baked->FontLoaderDatas; for (int src_n = 0; src_n < font->SourcesCount; src_n++) { ImFontConfig* src = &font->Sources[src_n]; - if (atlas->FontLoader->FontBakedDestroy) - atlas->FontLoader->FontBakedDestroy(atlas, src, baked, backend_user_data_p); - backend_user_data_p += atlas->FontLoader->FontBakedSrcLoaderDataSize; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontBakedDestroy) + loader->FontBakedDestroy(atlas, src, baked, loader_data_p); + loader_data_p += loader->FontBakedSrcLoaderDataSize; } if (baked->FontLoaderDatas) { @@ -4094,9 +4115,12 @@ void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) { for (ImFont* font : atlas->Fonts) font->ClearOutputData(); - if (atlas->FontLoader && atlas->FontLoader->FontSrcDestroy != NULL) - for (ImFontConfig& font_cfg : atlas->Sources) - atlas->FontLoader->FontSrcDestroy(atlas, &font_cfg); + for (ImFontConfig& font_cfg : atlas->Sources) + { + const ImFontLoader* loader = font_cfg.FontLoader ? font_cfg.FontLoader : atlas->FontLoader; + if (loader && loader->FontSrcDestroy != NULL) + loader->FontSrcDestroy(atlas, &font_cfg); + } IM_DELETE(atlas->Builder); atlas->Builder = NULL; @@ -4258,19 +4282,19 @@ ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) ImFontConfig* srcs = (font->LockSingleSrcConfigIdx != -1) ? &font->Sources[font->LockSingleSrcConfigIdx] : font->Sources; // Call backend - const ImFontLoader* font_loader = atlas->FontLoader; - char* backend_user_data_p = (char*)baked->FontLoaderDatas; + char* loader_user_data_p = (char*)baked->FontLoaderDatas; for (int src_n = 0; src_n < srcs_count; src_n++) { ImFontConfig* src = &srcs[src_n]; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) - if (font_loader->FontBakedAddGlyph(atlas, src, baked, backend_user_data_p, codepoint)) + if (loader->FontBakedAddGlyph(atlas, src, baked, loader_user_data_p, codepoint)) { // FIXME: Add hooks for e.g. #7962 ImFontGlyph* glyph = &baked->Glyphs.back(); return glyph; } - backend_user_data_p += font_loader->FontBakedSrcLoaderDataSize; + loader_user_data_p += loader->FontBakedSrcLoaderDataSize; } // Mark index as not found, so we don't attempt the search twice @@ -5017,8 +5041,12 @@ bool ImFont::IsGlyphInFont(ImWchar c) { ImFontAtlas* atlas = ContainerAtlas; for (int src_n = 0; src_n < SourcesCount; src_n++) - if (atlas->FontLoader->FontSrcContainsGlyph(atlas, &Sources[src_n], c)) + { + ImFontConfig* src = &Sources[src_n]; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcContainsGlyph != NULL && loader->FontSrcContainsGlyph(atlas, src, c)) return true; + } return false; } From 18c8a93cca8133bf74039b23c2ac30bb185a5cf0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 7 Feb 2025 18:48:01 +0100 Subject: [PATCH 077/191] Fonts: Rework ImFontLoader signatures. InitBaked may return false to signify this size is not supported. --- imgui_draw.cpp | 23 ++++++++++------------- imgui_internal.h | 4 ++-- misc/freetype/imgui_freetype.cpp | 22 ++++++++++------------ 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3106f2fe7..0a6726366 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3005,7 +3005,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) IM_ASSERT((size <= 64) && "GlyphExcludeRanges[] size must be small!"); } if (font_cfg->FontLoader != NULL) - IM_ASSERT(font_cfg->FontLoader->FontBakedAddGlyph != NULL); + IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); IM_ASSERT(font_cfg->FontLoaderData == NULL); // Round font size @@ -4288,12 +4288,8 @@ ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) ImFontConfig* src = &srcs[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) - if (loader->FontBakedAddGlyph(atlas, src, baked, loader_user_data_p, codepoint)) - { - // FIXME: Add hooks for e.g. #7962 - ImFontGlyph* glyph = &baked->Glyphs.back(); - return glyph; - } + if (ImFontGlyph* glyph = loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint)) + return glyph; // FIXME: Add hooks for e.g. #7962 loader_user_data_p += loader->FontBakedSrcLoaderDataSize; } @@ -4406,7 +4402,7 @@ static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFon return glyph_index != 0; } -static void ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*) +static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*) { IM_UNUSED(atlas); @@ -4421,16 +4417,17 @@ static void ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig baked->Ascent = ImCeil(unscaled_ascent * scale_for_layout); baked->Descent = ImFloor(unscaled_descent * scale_for_layout); } + return true; } -static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint) +static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint) { // Search for first font which has the glyph ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; IM_ASSERT(bd_font_data); int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); if (glyph_index == 0) - return false; // Not found + return NULL; // Fonts unit to pixels int oversample_h, oversample_v; @@ -4463,7 +4460,7 @@ static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontCo { // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); - return false; + return NULL; } ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); @@ -4512,7 +4509,7 @@ static bool ImGui_ImplStbTrueType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontCo glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); } - return true; + return glyph; } const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() @@ -4524,7 +4521,7 @@ const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() loader.FontSrcContainsGlyph = ImGui_ImplStbTrueType_FontSrcContainsGlyph; loader.FontBakedInit = ImGui_ImplStbTrueType_FontBakedInit; loader.FontBakedDestroy = NULL; - loader.FontBakedAddGlyph = ImGui_ImplStbTrueType_FontBakedAddGlyph; + loader.FontBakedLoadGlyph = ImGui_ImplStbTrueType_FontBakedLoadGlyph; return &loader; } diff --git a/imgui_internal.h b/imgui_internal.h index caca87463..40975abd7 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3668,9 +3668,9 @@ struct ImFontLoader bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); - void (*FontBakedInit)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); + bool (*FontBakedInit)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); - bool (*FontBakedAddGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint); + ImFontGlyph* (*FontBakedLoadGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint); // Size of backend data, Per Baked * Per Source. Buffers are managed by core to avoid excessive allocations. // FIXME: At this point the two other types of buffers may be managed by core to be consistent? diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 17cd2caaa..39919c8e4 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -437,7 +437,7 @@ void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) src->FontLoaderData = NULL; } -void ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) +bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) { IM_UNUSED(atlas); const float size = baked->Size; @@ -479,6 +479,7 @@ void ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImF baked->Ascent = bd_baked_data->Ascender; baked->Descent = bd_baked_data->Descender; } + return true; } void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) @@ -492,12 +493,12 @@ void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE() } -bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint) +ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint) { ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint); if (glyph_index == 0) - return false; // Not found + return NULL; if (bd_font_data->BakedLastActivated != baked) { @@ -509,18 +510,15 @@ bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontConfig* src, const FT_Glyph_Metrics* metrics = bd_font_data->LoadGlyph(codepoint); if (metrics == NULL) - return false; + return NULL; // Render glyph into a bitmap (currently held by FreeType) FT_Face face = bd_font_data->FtFace; FT_GlyphSlot slot = face->glyph; FT_Error error = FT_Render_Glyph(slot, bd_font_data->RenderMode); - if (error != 0) - return false; - const FT_Bitmap* ft_bitmap = &slot->bitmap; - if (ft_bitmap == nullptr) - return false; + if (error != 0 || ft_bitmap == nullptr) + return NULL; const int w = (int)ft_bitmap->width; const int h = (int)ft_bitmap->rows; @@ -540,7 +538,7 @@ bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontConfig* src, { // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); - return false; + return NULL; } ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); @@ -576,7 +574,7 @@ bool ImGui_ImplFreeType_FontBakedAddGlyph(ImFontAtlas* atlas, ImFontConfig* src, { glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); } - return true; + return glyph; } bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) @@ -598,7 +596,7 @@ const ImFontLoader* ImGuiFreeType::GetFontLoader() loader.FontSrcContainsGlyph = ImGui_ImplFreetype_FontSrcContainsGlyph; loader.FontBakedInit = ImGui_ImplFreeType_FontBakedInit; loader.FontBakedDestroy = ImGui_ImplFreeType_FontBakedDestroy; - loader.FontBakedAddGlyph = ImGui_ImplFreeType_FontBakedAddGlyph; + loader.FontBakedLoadGlyph = ImGui_ImplFreeType_FontBakedLoadGlyph; loader.FontBakedSrcLoaderDataSize = sizeof(ImGui_ImplFreeType_FontSrcBakedData); return &loader; } From 78a17038c2437aeedb29e4654588713896dd4abc Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 10 Feb 2025 15:09:46 +0100 Subject: [PATCH 078/191] imgui_freetype: no need to store metrics locally. --- misc/freetype/imgui_freetype.cpp | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 39919c8e4..fd6920e0c 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -155,14 +155,6 @@ namespace struct ImGui_ImplFreeType_FontSrcBakedData { FT_Size FtSize; // This represent a FT_Face with a given size. - - // Metrics - float Ascender; // The pixel extents above the baseline in pixels (typically positive). - float Descender; // The extents below the baseline in pixels (typically negative). - float LineSpacing; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate. - float LineGap; // The spacing in pixels between one row's descent and the next row's ascent. - float MaxAdvanceWidth; // This field gives the maximum horizontal cursor advance for all glyphs in the font. - ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); } }; @@ -465,19 +457,17 @@ bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImF req.vertResolution = 0; FT_Request_Size(bd_font_data->FtFace, &req); - // Read metrics - FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics; - bd_baked_data->Ascender = (float)FT_CEIL(metrics.ascender) * bd_font_data->InvRasterizationDensity; - bd_baked_data->Descender = (float)FT_CEIL(metrics.descender) * bd_font_data->InvRasterizationDensity; - bd_baked_data->LineSpacing = (float)FT_CEIL(metrics.height) * bd_font_data->InvRasterizationDensity; - bd_baked_data->LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * bd_font_data->InvRasterizationDensity; - bd_baked_data->MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * bd_font_data->InvRasterizationDensity; - // Output if (src->MergeMode == false) { - baked->Ascent = bd_baked_data->Ascender; - baked->Descent = bd_baked_data->Descender; + // Read metrics + FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics; + const float scale = bd_font_data->InvRasterizationDensity; + baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive). + baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative). + //LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate. + //LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * scale; // The spacing in pixels between one row's descent and the next row's ascent. + //MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * scale; // This field gives the maximum horizontal cursor advance for all glyphs in the font. } return true; } @@ -500,7 +490,7 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon if (glyph_index == 0) return NULL; - if (bd_font_data->BakedLastActivated != baked) + if (bd_font_data->BakedLastActivated != baked) // <-- could use id { // Activate current size ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src; From d8a612f73b34fb10a763b50cf1bc48a81447b1d7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 10 Feb 2025 22:32:09 +0100 Subject: [PATCH 079/191] Fonts: Fallback glyph is now lazily loaded on demand (yay!). Moving ImFontBaked:: functions outside of class. --- imgui.h | 5 ++-- imgui_draw.cpp | 79 +++++++++++++++++++++++++++++--------------------- 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/imgui.h b/imgui.h index 28e84e2a5..811125c97 100644 --- a/imgui.h +++ b/imgui.h @@ -3652,7 +3652,8 @@ struct ImFontBaked // [Internal] Members: Cold float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) unsigned int MetricsTotalSurface:26;// 3 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) - unsigned int WantDestroy:1; // 1 // // Queued for destroy + unsigned int WantDestroy:1; // 0 // // Queued for destroy + unsigned int LockLoadingFallback:1; // 0 // // int LastUsedFrame; // 4 // // Record of that time this was bounds ImGuiID BakedId; // 4 // ImFont* ContainerFont; // 4-8 // in // Parent font @@ -3665,8 +3666,6 @@ struct ImFontBaked IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); // Return NULL if glyph doesn't exist IMGUI_API float GetCharAdvance(ImWchar c); IMGUI_API bool IsGlyphLoaded(ImWchar c); - IMGUI_API ImFontGlyph* BuildLoadGlyph(ImWchar c); - IMGUI_API void BuildGrowIndex(int new_size); }; // Font flags diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0a6726366..54a7acab2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2499,6 +2499,7 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildAddFont() // - ImFontAtlasBuildSetupFontBakedEllipsis() // - ImFontAtlasBuildSetupFontBakedBlanks() +// - ImFontAtlasBuildSetupFontBakedFallback() // - ImFontAtlasBuildSetupFontSpecialGlyphs() // - ImFontAtlasBuildDiscardBakes() // - ImFontAtlasBuildDiscardFontBakedGlyph() @@ -2527,7 +2528,8 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasPackAddRect() // - ImFontAtlasPackGetRect() //----------------------------------------------------------------------------- -// - ImFont::BuildLoadGlyph() +// - ImFontBaked_BuildGrowIndex() +// - ImFontBaked_BuildLoadGlyph() // - ImFontAtlasDebugLogTextureRequests() //----------------------------------------------------------------------------- // - ImFontAtlasGetFontLoaderForStbTruetype() @@ -3242,7 +3244,7 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); if (baked->IsGlyphLoaded(codepoint)) - ImFontAtlasBuildDiscardFontBakedGlyph(this, font, baked, (ImFontGlyph*)(void*)baked->FindGlyph(codepoint)); + ImFontAtlasBuildDiscardFontBakedGlyph(this, font, baked, baked->FindGlyph(codepoint)); ImFontGlyph glyph; glyph.Codepoint = codepoint; @@ -3367,7 +3369,7 @@ void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas) IM_ASSERT(ranges != NULL); for (; ranges[0]; ranges += 2) for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 - baked->FindGlyphNoFallback((ImWchar)c); + baked->FindGlyph((ImWchar)c); } } @@ -3587,25 +3589,23 @@ static ImFontGlyph* ImFontAtlasBuildSetupFontBakedEllipsis(ImFontAtlas* atlas, I return glyph; } -static void ImFontAtlasBuildSetupFontBakedSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) +// Load fallback in order to obtain its index +// (this is called from in hot-path so we avoid extraneous parameters to minimize impact on code size) +static void ImFontAtlasBuildSetupFontBakedFallback(ImFontBaked* baked) { - // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) - ImFontGlyph* space_glyph = (ImFontGlyph*)(void*)baked->FindGlyphNoFallback((ImWchar)' '); - if (space_glyph != NULL) - space_glyph->Visible = false; - - // Load fallback in order to obtain its index - // FIXME-NEWATLAS: could we use a scheme where this is lazily loaded? IM_ASSERT(baked->FallbackGlyphIndex == -1); + IM_ASSERT(baked->FallbackAdvanceX == 0.0f); + ImFont* font = baked->ContainerFont; ImFontGlyph* fallback_glyph = NULL; if (font->FallbackChar != 0) fallback_glyph = baked->FindGlyphNoFallback(font->FallbackChar); if (fallback_glyph == NULL) { + ImFontGlyph* space_glyph = baked->FindGlyphNoFallback((ImWchar)' '); ImFontGlyph glyph; glyph.Codepoint = 0; glyph.AdvanceX = space_glyph ? space_glyph->AdvanceX : IM_ROUND(baked->Size * 0.40f); - fallback_glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, &glyph); + fallback_glyph = ImFontAtlasBakedAddFontGlyph(font->ContainerAtlas, baked, NULL, &glyph); } baked->FallbackGlyphIndex = baked->Glyphs.index_from_ptr(fallback_glyph); // Storing index avoid need to update pointer on growth and simplify inner loop code baked->FallbackAdvanceX = fallback_glyph->AdvanceX; @@ -3711,7 +3711,7 @@ ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, floa loader_data_p += loader->FontBakedSrcLoaderDataSize; } - ImFontAtlasBuildSetupFontBakedSpecialGlyphs(atlas, baked->ContainerFont, baked); + ImFontAtlasBuildSetupFontBakedBlanks(atlas, baked); return baked; } @@ -4260,13 +4260,26 @@ static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar return true; } -ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) +static void ImFontBaked_BuildGrowIndex(ImFontBaked* baked, int new_size) { - ImFont* font = ContainerFont; - ImFontBaked* baked = this; + IM_ASSERT(baked->IndexAdvanceX.Size == baked->IndexLookup.Size); + if (new_size <= baked->IndexLookup.Size) + return; + baked->IndexAdvanceX.resize(new_size, -1.0f); + baked->IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); +} + +static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codepoint) +{ + ImFont* font = baked->ContainerFont; ImFontAtlas* atlas = font->ContainerAtlas; if (atlas->Locked || (font->Flags & ImFontFlags_NoLoadGlyphs)) + { + // Lazily load fallback glyph + if (baked->FallbackGlyphIndex == -1 && baked->LockLoadingFallback == 0) + ImFontAtlasBuildSetupFontBakedFallback(baked); return NULL; + } //char utf8_buf[5]; //IMGUI_DEBUG_LOG("[font] BuildLoadGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); @@ -4293,8 +4306,14 @@ ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) loader_user_data_p += loader->FontBakedSrcLoaderDataSize; } + // Lazily load fallback glyph + if (baked->LockLoadingFallback) + return NULL; + if (baked->FallbackGlyphIndex == -1) + ImFontAtlasBuildSetupFontBakedFallback(baked); + // Mark index as not found, so we don't attempt the search twice - baked->BuildGrowIndex(codepoint + 1); + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); baked->IndexAdvanceX[codepoint] = baked->FallbackAdvanceX; baked->IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; return NULL; @@ -4302,10 +4321,10 @@ ImFontGlyph* ImFontBaked::BuildLoadGlyph(ImWchar codepoint) // The point of this indirection is to not be inlined in debug mode in order to not bloat inner loop.b IM_MSVC_RUNTIME_CHECKS_OFF -static float BuildLoadGlyphGetAdvanceOrFallback(ImFontBaked* font, unsigned int codepoint) +static float BuildLoadGlyphGetAdvanceOrFallback(ImFontBaked* baked, unsigned int codepoint) { - ImFontGlyph* glyph = font->BuildLoadGlyph((ImWchar)codepoint); - return glyph ? glyph->AdvanceX : font->FallbackAdvanceX; + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(baked, (ImWchar)codepoint); + return glyph ? glyph->AdvanceX : baked->FallbackAdvanceX; } IM_MSVC_RUNTIME_CHECKS_RESTORE @@ -4861,15 +4880,6 @@ void ImFontBaked::ClearOutputData() MetricsTotalSurface = 0; } -void ImFontBaked::BuildGrowIndex(int new_size) -{ - IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); - if (new_size <= IndexLookup.Size) - return; - IndexAdvanceX.resize(new_size, -1.0f); - IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); -} - ImFont::ImFont() { memset(this, 0, sizeof(*this)); @@ -4950,7 +4960,7 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked // Update lookup tables int codepoint = glyph.Codepoint; - baked->BuildGrowIndex(codepoint + 1); + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); baked->IndexAdvanceX[codepoint] = glyph.AdvanceX; baked->IndexLookup[codepoint] = (ImU16)glyph_idx; const int page_n = codepoint / 8192; @@ -5002,7 +5012,7 @@ ImFontGlyph* ImFontBaked::FindGlyph(ImWchar c) if (i != IM_FONTGLYPH_INDEX_UNUSED) return &Glyphs.Data[i]; } - ImFontGlyph* glyph = BuildLoadGlyph(c); + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(this, c); return glyph ? glyph : &Glyphs.Data[FallbackGlyphIndex]; } @@ -5017,7 +5027,10 @@ ImFontGlyph* ImFontBaked::FindGlyphNoFallback(ImWchar c) if (i != IM_FONTGLYPH_INDEX_UNUSED) return &Glyphs.Data[i]; } - return BuildLoadGlyph(c); + LockLoadingFallback = true; // This is actually a rare call, not done in hot-loop, so we prioritize not adding extra cruft to ImFontBaked_BuildLoadGlyph() call sites. + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(this, c); + LockLoadingFallback = false; + return glyph; } bool ImFontBaked::IsGlyphLoaded(ImWchar c) @@ -5060,7 +5073,7 @@ float ImFontBaked::GetCharAdvance(ImWchar c) } // Same as BuildLoadGlyphGetAdvanceOrFallback(): - const ImFontGlyph* glyph = BuildLoadGlyph(c); + const ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(this, c); return glyph ? glyph->AdvanceX : FallbackAdvanceX; } IM_MSVC_RUNTIME_CHECKS_RESTORE From ef6beaeff68951a8c0f736843dd6f8a0b638796c Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 10 Feb 2025 22:53:33 +0100 Subject: [PATCH 080/191] Fonts: removed LockSingleSrcConfigIdx which isn't needed anymore since we don't load glyphs in ImFontAtlasBuildAddFont(). --- imgui.h | 1 - imgui_draw.cpp | 15 +++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/imgui.h b/imgui.h index 811125c97..05bda5b54 100644 --- a/imgui.h +++ b/imgui.h @@ -3699,7 +3699,6 @@ struct ImFont float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated. - short LockSingleSrcConfigIdx; // Methods IMGUI_API ImFont(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 54a7acab2..ebf42bcb4 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3637,14 +3637,11 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, Im IM_ASSERT(src_idx_in_font >= 0 && src_idx_in_font < font->SourcesCount); IM_UNUSED(atlas); - // While manipulating glyphs during init we want to restrict all searches for one source font. - font->LockSingleSrcConfigIdx = (short)src_idx_in_font; - // Find Fallback character. Actual glyph loaded in GetFontBaked(). const ImWchar fallback_chars[] = { font->FallbackChar, (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; if (font->FallbackChar == 0) for (ImWchar candidate_char : fallback_chars) - if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) // FIXME: does not respect LockSingleSrcConfigIdx() + if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) { font->FallbackChar = (ImWchar)candidate_char; break; @@ -3666,7 +3663,6 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, Im font->EllipsisChar = 0x0085; font->EllipsisAutoBake = true; } - font->LockSingleSrcConfigIdx = -1; } void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph) @@ -4290,15 +4286,11 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep if (ImFontGlyph* glyph = ImFontAtlasBuildSetupFontBakedEllipsis(atlas, baked)) return glyph; - // Load from single source or all sources? - int srcs_count = (font->LockSingleSrcConfigIdx != -1) ? 1 : font->SourcesCount; - ImFontConfig* srcs = (font->LockSingleSrcConfigIdx != -1) ? &font->Sources[font->LockSingleSrcConfigIdx] : font->Sources; - // Call backend char* loader_user_data_p = (char*)baked->FontLoaderDatas; - for (int src_n = 0; src_n < srcs_count; src_n++) + for (int src_n = 0; src_n < font->SourcesCount; src_n++) { - ImFontConfig* src = &srcs[src_n]; + ImFontConfig* src = &font->Sources[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) if (ImFontGlyph* glyph = loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint)) @@ -4884,7 +4876,6 @@ ImFont::ImFont() { memset(this, 0, sizeof(*this)); Scale = 1.0f; - LockSingleSrcConfigIdx = -1; } ImFont::~ImFont() From 2bf6879daee8b4f1eae10ee03af953389baae061 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 19 Feb 2025 15:16:19 +0100 Subject: [PATCH 081/191] Fonts: tidying up font scale logic. # Conflicts: # imgui_internal.h --- imgui.cpp | 43 ++++++++++++++++++++++--------------------- imgui_internal.h | 9 ++++++--- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 3f6c01ded..2d5c33753 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3942,7 +3942,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) FontAtlasOwnedByContext = shared_font_atlas ? false : true; Font = NULL; FontBaked = NULL; - FontSize = /*FontBaseSize = */FontScale = CurrentDpiScale = 0.0f; + FontSize = FontSizeBeforeScaling = FontScale = CurrentDpiScale = 0.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); IO.Fonts->RefCount++; Time = 0.0f; @@ -4375,9 +4375,7 @@ static void SetCurrentWindow(ImGuiWindow* window) g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking if (window) { - // FIXME-BAKED - //g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); - //g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; + ImGui::UpdateCurrentFontSize(); ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } } @@ -8406,14 +8404,9 @@ ImVec2 ImGui::GetFontTexUvWhitePixel() void ImGui::SetWindowFontScale(float scale) { IM_ASSERT(scale > 0.0f); - // FIXME-BAKED - /* - ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); window->FontWindowScale = scale; - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); - g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; - */ + UpdateCurrentFontSize(); } void ImGui::PushFocusScope(ImGuiID id) @@ -8608,26 +8601,34 @@ void ImGui::SetCurrentFont(ImFont* font, float font_size) { ImGuiContext& g = *GImGui; g.Font = font; - //g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.FontBaked->Size * g.Font->Scale); - g.FontSize = font_size;// g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + g.FontSizeBeforeScaling = font_size; + UpdateCurrentFontSize(); + if (font != NULL) { IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? IM_ASSERT(font->Scale > 0.0f); - g.FontBaked = g.Font->GetFontBaked(g.FontSize); - g.FontScale = g.FontSize / g.FontBaked->Size; g.DrawListSharedData.Font = g.Font; - g.DrawListSharedData.FontSize = g.FontSize; - g.DrawListSharedData.FontScale = g.FontScale; ImFontAtlasUpdateDrawListsSharedData(g.Font->ContainerAtlas); if (g.CurrentWindow != NULL) g.CurrentWindow->DrawList->_SetTexture(font->ContainerAtlas->TexRef); } - else - { - g.FontBaked = NULL; - g.FontScale = 0.0f; - } +} + +void ImGui::UpdateCurrentFontSize() +{ + ImGuiContext& g = *GImGui; + float final_size = g.FontSizeBeforeScaling * g.IO.FontGlobalScale; + final_size *= g.Font->Scale; + if (ImGuiWindow* window = g.CurrentWindow) + final_size *= window->FontWindowScale; + final_size = ImMax(1.0f, IM_ROUND(final_size)); + + g.FontSize = final_size; + g.FontBaked = (g.Font != NULL) ? g.Font->GetFontBaked(g.FontSize) : NULL; + g.FontScale = (g.Font != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; + g.DrawListSharedData.FontSize = g.FontSize; + g.DrawListSharedData.FontScale = g.FontScale; } void ImGui::PushFont(ImFont* font, float font_size) diff --git a/imgui_internal.h b/imgui_internal.h index 40975abd7..c0c8002fd 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2138,8 +2138,8 @@ struct ImGuiContext ImGuiStyle Style; ImFont* Font; // == FontStack.back().Font ImFontBaked* FontBaked; // == Font->GetFontBaked(FontSize) - float FontSize; // == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. - //float FontBaseSize; // == io.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. + float FontSize; // == FontSizeBeforeScaling * io.FontGlobalScale * font->Scale * g.CurrentWindow->FontWindowScale. Current text height. + float FontSizeBeforeScaling; // == value passed to PushFontSize() float FontScale; // == FontBaked->Size / Font->FontSize. Scale factor over baked size. float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; @@ -2680,9 +2680,11 @@ public: // We don't use g.FontSize because the window may be != g.CurrentWindow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } - //float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontWindowScaleParents; } ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); } ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } + + // [Obsolete] ImGuiWindow::CalcFontSize() was removed in 1.92.x because error-prone/misleading. You can use window->FontRefSize for a copy of g.FontSize at the time of the last Begin() call for this window. + //float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontWindowScaleParents; } }; //----------------------------------------------------------------------------- @@ -3106,6 +3108,7 @@ namespace ImGui // Fonts, drawing IMGUI_API void SetCurrentFont(ImFont* font, float font_size); + IMGUI_API void UpdateCurrentFontSize(); inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } IMGUI_API void PushPasswordFont(); inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. From e98a314e0620346b9d9da14fb88fe9fbd88a74af Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 19 Feb 2025 15:41:26 +0100 Subject: [PATCH 082/191] Textures: Added ImTextureData::UsedRect. # Conflicts: # imgui_internal.h --- imgui.cpp | 9 ++++++--- imgui.h | 3 ++- imgui_draw.cpp | 7 ++++++- imgui_internal.h | 1 + 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2d5c33753..a8a04080c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15724,10 +15724,13 @@ void ImGui::DebugNodeTexture(ImTextureData* tex) ImGuiContext& g = *GImGui; if (TreeNode(tex, "Texture #%03d (%dx%d pixels)", tex->UniqueID, tex->Width, tex->Height)) { + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + Checkbox("Show used rect", &cfg->ShowTextureUsedRect); PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); - ImTextureRef tex_id; - tex_id._TexData = tex; // Don't use tex->TexID directly so first frame works. - ImageWithBg(tex_id, ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImVec2 p = GetCursorScreenPos(); + ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + if (cfg->ShowTextureUsedRect) + GetWindowDrawList()->AddRect(ImVec2(p.x + tex->UsedRect.x, p.y + tex->UsedRect.y), ImVec2(p.x + tex->UsedRect.x + tex->UsedRect.w, p.y + tex->UsedRect.y + tex->UsedRect.h), IM_COL32(255, 0, 255, 255)); PopStyleVar(); char texid_desc[20]; diff --git a/imgui.h b/imgui.h index 05bda5b54..1d7cfbc92 100644 --- a/imgui.h +++ b/imgui.h @@ -3389,7 +3389,8 @@ struct IMGUI_API ImTextureData unsigned char* Pixels; // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. ImTextureID TexID; // Always use SetTexID() to modify! Identifier stored in ImDrawCmd::GetTexID() and passed to backend RenderDrawData loop. void* BackendUserData; // Convenience storage for backend. Some backends may have enough with TexID. - ImTextureRect UpdateRect; // Bounding box encompassing all individual updates. + ImTextureRect UsedRect; // Bounding box encompassing all past and queued Updates[]. + ImTextureRect UpdateRect; // Bounding box encompassing all queued Updates[]. ImVector Updates; // Array of individual updates. int UnusedFrames; // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. Always >0 when Status==WantDestroy. unsigned short RefCount; // Number of contexts using this texture. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ebf42bcb4..558dd28e4 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2439,6 +2439,7 @@ void ImTextureData::Create(ImTextureFormat format, int w, int h) Pixels = (unsigned char*)IM_ALLOC(Width * Height * BytesPerPixel); IM_ASSERT(Pixels != NULL); memset(Pixels, 0, Width * Height * BytesPerPixel); + UsedRect.x = UsedRect.y = UsedRect.w = UsedRect.h = 0; UpdateRect.x = UpdateRect.y = (unsigned short)~0; UpdateRect.w = UpdateRect.h = 0; } @@ -2918,6 +2919,10 @@ void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y); tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x); tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y); + tex->UsedRect.x = ImMin(tex->UsedRect.x, req.x); + tex->UsedRect.y = ImMin(tex->UsedRect.y, req.y); + tex->UsedRect.w = (unsigned short)(ImMax(tex->UsedRect.x + tex->UsedRect.w, req.x + req.w) - tex->UsedRect.x); + tex->UsedRect.h = (unsigned short)(ImMax(tex->UsedRect.y + tex->UsedRect.h, req.y + req.h) - tex->UsedRect.y); atlas->TexIsBuilt = false; // No need to queue if status is _WantCreate @@ -3957,7 +3962,7 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ old_tex_h = atlas->TexData->Height; // FIXME-NEWATLAS-V2: What to do when reaching limits exposed by backend? - // FIXME-NEWATLAS-V2: Does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? Could we expose e.g. tex->UsedRect. + // FIXME-NEWATLAS-V2: Does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); IM_ASSERT(ImIsPowerOfTwo(atlas->TexMinWidth) && ImIsPowerOfTwo(atlas->TexMaxWidth) && ImIsPowerOfTwo(atlas->TexMinHeight) && ImIsPowerOfTwo(atlas->TexMaxHeight)); diff --git a/imgui_internal.h b/imgui_internal.h index c0c8002fd..12f188b68 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2075,6 +2075,7 @@ struct ImGuiMetricsConfig bool ShowDrawCmdMesh = true; bool ShowDrawCmdBoundingBoxes = true; bool ShowTextEncodingViewer = false; + bool ShowTextureUsedRect = false; int ShowWindowsRectsType = -1; int ShowTablesRectsType = -1; int HighlightMonitorIdx = -1; From 161e2223220b2fa1002c963f1c801ebbb8897316 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Mar 2025 16:14:41 +0100 Subject: [PATCH 083/191] Fonts: GetFontBaked() default to searching for closest size font. --- imgui_draw.cpp | 53 ++++++++++++++++++++++++++++++++++++++---------- imgui_internal.h | 1 + 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 558dd28e4..c3275550a 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3716,6 +3716,30 @@ ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, floa return baked; } +// FIXME-OPT: This is not a fast query. Adding a BakedCount field in Font might allow to take a shortcut for the most common case. +ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont* font, float font_size) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontBaked* closest_larger_match = NULL; + ImFontBaked* closest_smaller_match = NULL; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->ContainerFont != font || baked->WantDestroy) + continue; + if (baked->Size > font_size && (closest_larger_match == NULL || baked->Size < closest_larger_match->Size)) + closest_larger_match = baked; + if (baked->Size < font_size && (closest_smaller_match == NULL || baked->Size > closest_smaller_match->Size)) + closest_smaller_match = baked; + } + if (closest_larger_match) + if (closest_smaller_match == NULL || (closest_larger_match->Size >= font_size * 2.0f && closest_smaller_match->Size > font_size * 0.5f)) + return closest_larger_match; + if (closest_smaller_match) + return closest_smaller_match; + return NULL; +} + void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) { ImFontAtlasBuilder* builder = atlas->Builder; @@ -4976,9 +5000,9 @@ void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } +// FIXME-NEWATLAS: Implement AddRemapChar() which was removed since transitioning to baked logic. void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst) { - // FIXME-BAKED: Implement AddRemapChar() IM_UNUSED(from_codepoint); IM_UNUSED(to_codepoint); IM_UNUSED(overwrite_dst); @@ -5092,8 +5116,8 @@ ImFontBaked* ImFont::GetFontBaked(float size) ImFontAtlas* atlas = ContainerAtlas; ImFontAtlasBuilder* builder = atlas->Builder; - // FIXME-BAKED: Design for picking a nearest size? - // FIXME-BAKED: Altering font density won't work right away. + // FIXME-NEWATLAS: Design for picking a nearest size based on some criterias? + // FIXME-NEWATLAS: Altering font density won't work right away. ImGuiID baked_id = ImFontAtlasBakedGetId(FontId, size); ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); baked = *p_baked_in_map; @@ -5105,17 +5129,24 @@ ImFontBaked* ImFont::GetFontBaked(float size) return baked; } - // FIXME-BAKED: If loading is locked, find closest match - if (Flags & ImFontFlags_LockBakedSizes) + // If atlas is locked, find closest match + // FIXME-OPT: This is not an optimal query. + if ((Flags & ImFontFlags_LockBakedSizes) || atlas->Locked) { - IM_ASSERT(LastBaked); - return LastBaked; + baked = ImFontAtlasBuildGetClosestFontBakedMatch(atlas, this, size); + if (baked != NULL) + { + baked->LastUsedFrame = builder->FrameCount; + LastBaked = baked; + return baked;; + } + if (atlas->Locked) + { + IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! + return NULL; + } } - // FIXME-BAKED: If atlas is locked, find closest match - if (atlas->Locked) - IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! - // Create new baked = ImFontAtlasBuildAddFontBaked(atlas, this, size, baked_id); LastBaked = baked; diff --git a/imgui_internal.h b/imgui_internal.h index 12f188b68..a7e481629 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3779,6 +3779,7 @@ IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); IMGUI_API void ImFontAtlasBuildDiscardFont(ImFontAtlas* atlas, ImFont* font); IMGUI_API ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); +IMGUI_API ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont* font, float font_size); IMGUI_API void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); IMGUI_API void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy From 0b71339122fabecf925a0a9fb8ca90dacaac71f9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Mar 2025 17:05:24 +0100 Subject: [PATCH 084/191] Demo: Add a "Fonts" section for visibility. --- imgui.cpp | 16 ++++++++++------ imgui_demo.cpp | 4 +--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index a8a04080c..ef311491c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15640,6 +15640,11 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) { ImGuiContext& g = *GImGui; + SeparatorText("Backend Support for Dynamic Fonts"); + BeginDisabled(); + CheckboxFlags("io.BackendFlags: RendererHasTextures", &GetIO().BackendFlags, ImGuiBackendFlags_RendererHasTextures); + EndDisabled(); + SeparatorText("Fonts"); Text("Read "); SameLine(0, 0); @@ -16548,12 +16553,11 @@ void ImGui::DebugNodeFont(ImFont* font) } if (SmallButton("Set as default")) GetIO().FontDefault = font; - if (atlas->Fonts.Size > 1 && !atlas->Locked) - { - SameLine(); - if (SmallButton("Remove")) - atlas->RemoveFont(font); - } + SameLine(); + BeginDisabled(atlas->Fonts.Size <= 1 || atlas->Locked); + if (SmallButton("Remove")) + atlas->RemoveFont(font); + EndDisabled(); // Display details SetNextItemWidth(GetFontSize() * 8); diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 55b09a2bf..894012903 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1745,6 +1745,7 @@ static void DemoWindowWidgetsFonts() { ImFontAtlas* atlas = ImGui::GetIO().Fonts; ImGui::ShowFontAtlas(atlas); + // FIXME-NEWATLAS: Provide a demo to add/create a procedural font? ImGui::TreePop(); } } @@ -8171,9 +8172,6 @@ void ImGui::ShowAboutWindow(bool* p_open) // - ShowStyleEditor() //----------------------------------------------------------------------------- -// Forward declare ShowFontAtlas() which isn't worth putting in public API yet -namespace ImGui { IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); } - // Demo helper function to select among loaded fonts. // Here we use the regular BeginCombo()/EndCombo() api which is the more flexible one. void ImGui::ShowFontSelector(const char* label) From 131f5c57ab1836cdbf0c6962d9d9cfa7ebd34d49 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Mar 2025 17:18:54 +0100 Subject: [PATCH 085/191] Textures: Detect when using a texture that's about to be destroyed. --- imgui.cpp | 5 ++++- imgui_draw.cpp | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index ef311491c..b8dca131c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15733,7 +15733,10 @@ void ImGui::DebugNodeTexture(ImTextureData* tex) Checkbox("Show used rect", &cfg->ShowTextureUsedRect); PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); ImVec2 p = GetCursorScreenPos(); - ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + if (tex->WantDestroyNextFrame) + Dummy(ImVec2((float)tex->Width, (float)tex->Height)); + else + ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); if (cfg->ShowTextureUsedRect) GetWindowDrawList()->AddRect(ImVec2(p.x + tex->UsedRect.x, p.y + tex->UsedRect.y), ImVec2(p.x + tex->UsedRect.x + tex->UsedRect.w, p.y + tex->UsedRect.y + tex->UsedRect.h), IM_COL32(255, 0, 255, 255)); PopStyleVar(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c3275550a..4c5a47143 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -676,6 +676,8 @@ void ImDrawList::PushTexture(ImTextureRef tex_ref) { _TextureStack.push_back(tex_ref); _CmdHeader.TexRef = tex_ref; + if (tex_ref._TexData != NULL) + IM_ASSERT(tex_ref._TexData->WantDestroyNextFrame == false); _OnChangedTexture(); } From dec8d3863abdd126fdce044bb0a0f808f2ae3ba6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Mar 2025 20:08:00 +0100 Subject: [PATCH 086/191] Fonts: Added a ImFontFlags_NoLoadError flag to let user code try file paths. (3611) --- imgui.h | 1 + imgui_draw.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index 1d7cfbc92..6703c128b 100644 --- a/imgui.h +++ b/imgui.h @@ -3676,6 +3676,7 @@ enum ImFontFlags_ ImFontFlags_None = 0, ImFontFlags_LockBakedSizes = 1 << 0, // Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. ImFontFlags_NoLoadGlyphs = 1 << 1, // Disable loading new glyphs. + ImFontFlags_NoLoadError = 1 << 2, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. }; // Font runtime data and rendering diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 4c5a47143..ec379ffa1 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3096,7 +3096,8 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); if (!data) { - IM_ASSERT_USER_ERROR(0, "Could not load font file!"); + if (font_cfg_template == NULL || (font_cfg_template->Flags & ImFontFlags_NoLoadError) == 0) + IM_ASSERT_USER_ERROR(0, "Could not load font file!"); return NULL; } ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); From 93410c47e11c2f9d719a1c87111a1d5b08af6e96 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Mar 2025 18:42:41 +0100 Subject: [PATCH 087/191] Fonts: Fixed various small warnings / build issues. --- imgui.cpp | 2 +- imgui.h | 4 ++-- imgui_draw.cpp | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b8dca131c..644275201 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16670,7 +16670,7 @@ void ImGui::DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) if (glyph->PackId >= 0) { ImFontAtlasRect* r = ImFontAtlasPackGetRect(font->ContainerAtlas, glyph->PackId); - Text("PackId: %d (%dx%d rect at %d,%d)", glyph->PackId, r->w, r->h, r->x, r->y);; + Text("PackId: %d (%dx%d rect at %d,%d)", glyph->PackId, r->w, r->h, r->x, r->y); } } diff --git a/imgui.h b/imgui.h index 6703c128b..f03a20306 100644 --- a/imgui.h +++ b/imgui.h @@ -339,7 +339,7 @@ struct ImTextureRef { ImTextureRef() { memset(this, 0, sizeof(*this)); } ImTextureRef(ImTextureID tex_id) { memset(this, 0, sizeof(*this)); _TexID = tex_id; } -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(ImTextureUserID) ImTextureRef(void* tex_id) { memset(this, 0, sizeof(*this)); _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID //inline operator intptr_t() const { return (intptr_t)_TexID; } // For legacy backends casting to ImTextureID #endif @@ -3406,7 +3406,7 @@ struct IMGUI_API ImTextureData unsigned char* GetPixelsAt(int x, int y) { IM_ASSERT(Pixels != NULL); return Pixels + (x + y * Width) * BytesPerPixel; } int GetSizeInBytes() const { return Width * Height * BytesPerPixel; } int GetPitch() const { return Width * BytesPerPixel; } - ImTextureRef GetTexRef() const { ImTextureRef tex_ref; tex_ref._TexData = (ImTextureData*)(void*)this; tex_ref._TexID = TexID; return tex_ref; } + ImTextureRef GetTexRef() { ImTextureRef tex_ref; tex_ref._TexData = this; tex_ref._TexID = TexID; return tex_ref; } ImTextureID GetTexID() const { return TexID; } // Called by Renderer backend diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ec379ffa1..d51ae9e50 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -40,6 +40,7 @@ Index of this file: #endif #include // vsnprintf, sscanf, printf +#include // intptr_t // Visual Studio warnings #ifdef _MSC_VER @@ -3193,6 +3194,7 @@ void ImFontAtlas::RemoveFont(ImFont* font) bool removed = Fonts.find_erase(font); IM_ASSERT(removed); + IM_UNUSED(removed); Sources.erase(font->Sources, font->Sources + font->SourcesCount); ImFontAtlasBuildUpdatePointers(this); @@ -3644,6 +3646,7 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, Im const int src_idx_in_font = (int)(src - font->Sources); IM_ASSERT(src_idx_in_font >= 0 && src_idx_in_font < font->SourcesCount); IM_UNUSED(atlas); + IM_UNUSED(src_idx_in_font); // Find Fallback character. Actual glyph loaded in GetFontBaked(). const ImWchar fallback_chars[] = { font->FallbackChar, (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; @@ -3680,9 +3683,10 @@ void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImF ImFontAtlasPackDiscardRect(atlas, glyph->PackId); glyph->PackId = -1; } - ImWchar c = glyph->Codepoint; + ImWchar c = (ImWchar)glyph->Codepoint; IM_ASSERT(font->FallbackChar != c && font->EllipsisChar != c); // Unsupported for simplicity IM_ASSERT(glyph >= baked->Glyphs.Data && glyph < baked->Glyphs.Data + baked->Glyphs.Size); + IM_UNUSED(font); baked->IndexLookup[c] = IM_FONTGLYPH_INDEX_UNUSED; baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; } @@ -4371,6 +4375,7 @@ void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update %d regions, texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, tex->Updates.Size, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); for (const ImTextureRect& r : tex->Updates) { + IM_UNUSED(r); IM_ASSERT(r.x >= 0 && r.y >= 0); IM_ASSERT(r.x + r.w <= tex->Width && r.y + r.h <= tex->Height); // In theory should subtract PackPadding but it's currently part of atlas and mid-frame change would wreck assert. //IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update (% 4d..%-4d)->(% 4d..%-4d), texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, r.x, r.y, r.x + r.w, r.y + r.h, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); @@ -5141,7 +5146,7 @@ ImFontBaked* ImFont::GetFontBaked(float size) { baked->LastUsedFrame = builder->FrameCount; LastBaked = baked; - return baked;; + return baked; } if (atlas->Locked) { From da51485e17e08ef156aada958bf05b4e27b0d739 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Mar 2025 18:15:25 +0100 Subject: [PATCH 088/191] Fonts: Obsolete GetGlyphRangesXXX() functions. Update font documentation. --- docs/FONTS.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ imgui.h | 5 ++++- imgui_draw.cpp | 3 +++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/FONTS.md b/docs/FONTS.md index 514af0799..9b783dcdc 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -12,6 +12,7 @@ In the [misc/fonts/](https://github.com/ocornut/imgui/tree/master/misc/fonts) fo ## Index - [Troubleshooting](#troubleshooting) +- [New! Dynamic Fonts system in 1.92 (March 2025)](#new-dynamic-fonts-system-in-192-march-2025) - [How should I handle DPI in my application?](#how-should-i-handle-dpi-in-my-application) - [Fonts Loading Instructions](#fonts-loading-instructions) - [Loading Font Data from Memory](#loading-font-data-from-memory) @@ -43,6 +44,8 @@ See [About UTF-8 Encoding](#about-utf-8-encoding). Use the encoding viewer to co ### (3) Missing glyph ranges. +🆕 **Since 1.92, with an up to date backend: specifying glyph ranges is necessary.** + You need to load a font with explicit glyph ranges if you want to use non-ASCII characters. See [Fonts Loading Instructions](#fonts-loading-instructions). Use [Debug Tools](#debug-tools) confirm loaded fonts and loaded glyph ranges. This is a current constraint of Dear ImGui (which we will lift in the future): when loading a font you need to specify which characters glyphs to load. @@ -50,6 +53,8 @@ All loaded fonts glyphs are rendered into a single texture atlas ahead of time. ### (4) Font atlas texture fails to upload to GPU. +🆕 **Since 1.92, with an up to date backend: atlas is built incrementally and dynamically resized, this is less likely to happen** + This is often of byproduct of point 3. If you have large number of glyphs or multiple fonts, the texture may become too big for your graphics API. **The typical result of failing to upload a texture is if every glyph or everything appears as empty white rectangles.** Mind the fact that some graphics drivers have texture size limitation. If you are building a PC application, mind the fact that your users may use hardware with lower limitations than yours. ![empty squares](https://github.com/user-attachments/assets/68b50fb5-8b9d-4c38-baec-6ac384f06d26) @@ -63,6 +68,22 @@ Some solutions: Future versions of Dear ImGui should solve this problem. +##### [Return to Index](#index) + +--------------------------------------- + +## New! Dynamic Fonts system in 1.92 (March 2025+) + +v1.92 will introduce a newer, dynamic font system. It requires backend to support the `ImGuiBackendFlags_HasTextures` feature: +- Users of icons, Asian and non-English languages do not need to pre-build all glyphs ahead of time. Saving on loading time, memory, and also reducing issues with missing glyphs. Specifying glyph ranges is not needed anymore. +- PushFontSize() may be used anytime to change font size. +- Packing custom rectangles is more convenient as pixels may be written to immediately. +- Any update to fonts previously required backend specific calls to re-upload the texture, and said calls were not portable across backends. It is now possible to scale fonts etc. in a way that doesn't require you to make backend-specific calls. +- It is possible to plug a custom loader/backend to any font source. + +See [#8465](https://github.com/ocornut/imgui/issues/8465) for more details. + + ##### [Return to Index](#index) --------------------------------------- @@ -131,6 +152,8 @@ io.Fonts->Build(); **Add a fourth parameter to bake specific font ranges only:** +🆕 **Since 1.92, with an up to date backend: specifying glyph ranges is necessary. All the GetGlyphRangesXXX() functions are marked obsolete.** + ```cpp // Basic Latin, Extended Latin io.Fonts->AddFontFromFileTTF("font.ttf", size_pixels, nullptr, io.Fonts->GetGlyphRangesDefault()); @@ -145,10 +168,20 @@ See [Using Custom Glyph Ranges](#using-custom-glyph-ranges) section to create yo **Example loading and using a Japanese font:** +🆕 **Since 1.92, with an up to date backend:** + +```cpp +ImGuiIO& io = ImGui::GetIO(); +io.Fonts->AddFontFromFileTTF("NotoSansCJKjp-Medium.otf", 20.0f); +``` + +**Before 1.92, or without an up to date backend:** + ```cpp ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromFileTTF("NotoSansCJKjp-Medium.otf", 20.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); ``` + ```cpp ImGui::Text(u8"こんにちは!テスト %d", 123); if (ImGui::Button(u8"ロード")) @@ -215,6 +248,8 @@ To refer to the icon UTF-8 codepoints from your C++ code, you may use those head So you can use `ICON_FA_SEARCH` as a string that will render as a "Search" icon. +🆕 **Since 1.92, with an up to date backend: specifying glyph ranges is necessary. You can omit this parameter.** + Example Setup: ```cpp // Merge icons into default tool font @@ -289,6 +324,8 @@ io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\seguiemj.ttf", 16.0f, &cfg, ra ## Using Custom Glyph Ranges +🆕 **Since 1.92, with an up to date backend: specifying glyph ranges is necessary, so this is not needed.** + You can use the `ImFontGlyphRangesBuilder` helper to create glyph ranges based on text input. For example: for a game where your script is known, if you can feed your entire script to it and only build the characters the game needs. ```cpp ImVector ranges; @@ -308,6 +345,15 @@ io.Fonts->Build(); // Build the atlas while ## Using Custom Colorful Icons +🆕 **Since 1.92, with an up to date backend: this system has been revamped.** + +TL;DR; With the new system, it is recommended that you create a custom `ImFontLoader` and register your fonts with it. +`AddCustomRectFontGlyph()` has been obsolete because its API does not make much sense with resizable fonts. + +You can ask questions in [#8466](https://github.com/ocornut/imgui/issues/8466). + +🆕 **Before 1.92:** + As an alternative to rendering colorful glyphs using imgui_freetype with `ImGuiFreeTypeBuilderFlags_LoadColor`, you may allocate your own space in the texture atlas and write yourself into it. **(This is a BETA api, use if you are familiar with dear imgui and with your rendering backend)** - You can use the `ImFontAtlas::AddCustomRect()` and `ImFontAtlas::AddCustomRectFontGlyph()` api to register rectangles that will be packed into the font atlas texture. Register them before building the atlas, then call Build()`. diff --git a/imgui.h b/imgui.h index f03a20306..8f01ff7ef 100644 --- a/imgui.h +++ b/imgui.h @@ -3553,11 +3553,13 @@ struct ImFontAtlas // Glyph Ranges //------------------------------------------- + // Since 1.92: specifying glyph ranges is only useful/necessary if your backend doesn't support ImGuiBackendFlags_HasTextures! + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) // NB: Make sure that your string are UTF-8 and NOT in your local code page. // Read https://github.com/ocornut/imgui/blob/master/docs/FONTS.md/#about-utf-8-encoding for details. // NB: Consider using ImFontGlyphRangesBuilder to build glyph ranges from textual data. - IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin IMGUI_API const ImWchar* GetGlyphRangesGreek(); // Default + Greek and Coptic IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs @@ -3566,6 +3568,7 @@ struct ImFontAtlas IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters IMGUI_API const ImWchar* GetGlyphRangesVietnamese(); // Default + Vietnamese characters +#endif //------------------------------------------- // [ALPHA] Custom Rectangles/Glyphs API diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d51ae9e50..c99dbb5a8 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4579,6 +4579,7 @@ const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() // [SECTION] ImFontAtlas: glyph ranges helpers //------------------------------------------------------------------------- // - GetGlyphRangesDefault() +// Obsolete functions since 1.92: // - GetGlyphRangesGreek() // - GetGlyphRangesKorean() // - GetGlyphRangesChineseFull() @@ -4600,6 +4601,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesDefault() return &ranges[0]; } +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS const ImWchar* ImFontAtlas::GetGlyphRangesGreek() { static const ImWchar ranges[] = @@ -4849,6 +4851,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesVietnamese() }; return &ranges[0]; } +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //----------------------------------------------------------------------------- // [SECTION] ImFontGlyphRangesBuilder From c98e3c0effe9887d29c426560da8803be005c069 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Mar 2025 18:39:32 +0100 Subject: [PATCH 089/191] Fonts: ImFontConfig::GlyphExcludeRanges is owner and copied. --- imgui.cpp | 6 ++++++ imgui.h | 2 +- imgui_draw.cpp | 24 +++++++++++++++--------- imgui_internal.h | 2 ++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 644275201..cb546e4ba 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2011,6 +2011,12 @@ char* ImStrdup(const char* str) return (char*)memcpy(buf, (const void*)str, len + 1); } +void* ImMemdup(const void* src, size_t size) +{ + void* dst = IM_ALLOC(size); + return memcpy(dst, src, size); +} + char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src) { size_t dst_buf_size = p_dst_size ? *p_dst_size : ImStrlen(dst) + 1; diff --git a/imgui.h b/imgui.h index 8f01ff7ef..550af74ba 100644 --- a/imgui.h +++ b/imgui.h @@ -3434,7 +3434,7 @@ struct ImFontConfig //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. ImVec2 GlyphOffset; // 0, 0 // Offset (in pixels) all glyphs from this font input. Absolute value for default size, other sizes will scale this value. const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). - const ImWchar* GlyphExcludeRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a VERY SHORT user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. + const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a VERY SHORT user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c99dbb5a8..f154e5628 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2644,11 +2644,7 @@ void ImFontAtlas::ClearInputData() const ImFontLoader* loader = font_cfg.FontLoader ? font_cfg.FontLoader : FontLoader; if (loader && loader->FontSrcDestroy != NULL) loader->FontSrcDestroy(this, &font_cfg); - if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas) - { - IM_FREE(font_cfg.FontData); - font_cfg.FontData = NULL; - } + ImFontAtlasBuildDiscardFontSource(this, &font_cfg); } // When clearing this we lose access to the font name and other information used to build the font. @@ -2969,6 +2965,17 @@ bool ImFontAtlas::Build() } #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +void ImFontAtlasBuildDiscardFontSource(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + if (src->FontDataOwnedByAtlas) + IM_FREE(src->FontData); + if (src->GlyphExcludeRanges) + IM_FREE((void*)src->GlyphExcludeRanges); + src->FontData = NULL; + src->GlyphExcludeRanges = NULL; +} + ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); @@ -3001,9 +3008,8 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) new_font_cfg.DstFont = font; if (!new_font_cfg.FontDataOwnedByAtlas) { - new_font_cfg.FontData = IM_ALLOC(new_font_cfg.FontDataSize); new_font_cfg.FontDataOwnedByAtlas = true; - memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + new_font_cfg.FontData = ImMemdup(font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); } // Sanity check @@ -3013,6 +3019,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) for (const ImWchar* p = font_cfg->GlyphExcludeRanges; p[0] != 0; p++, size++) {} IM_ASSERT((size & 1) == 0 && "GlyphExcludeRanges[] size must be multiple of two!"); IM_ASSERT((size <= 64) && "GlyphExcludeRanges[] size must be small!"); + new_font_cfg.GlyphExcludeRanges = (ImWchar*)ImMemdup(font_cfg->GlyphExcludeRanges, sizeof(font_cfg->GlyphExcludeRanges[0]) * (size + 1)); } if (font_cfg->FontLoader != NULL) IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); @@ -3029,8 +3036,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) if (!ImFontAtlasBuildAddFont(this, &new_font_cfg)) { // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) - if (new_font_cfg.FontDataOwnedByAtlas) - IM_FREE(new_font_cfg.FontData); + ImFontAtlasBuildDiscardFontSource(this, &new_font_cfg); Sources.pop_back(); if (!font_cfg->MergeMode) { diff --git a/imgui_internal.h b/imgui_internal.h index a7e481629..6252ee818 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -389,6 +389,7 @@ IMGUI_API int ImStricmp(const char* str1, const char* str2); IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); // Case insensitive compare to a certain count. IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); // Copy to a certain count and always zero terminate (strncpy doesn't). IMGUI_API char* ImStrdup(const char* str); // Duplicate a string. +IMGUI_API void* ImMemdup(const void* src, size_t size); // Duplicate a chunk of memory. IMGUI_API char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* str); // Copy in provided buffer, recreate buffer if needed. IMGUI_API const char* ImStrchrRange(const char* str_begin, const char* str_end, char c); // Find first occurrence of 'c' in string range. IMGUI_API const char* ImStreolRange(const char* str, const char* str_end); // End end-of-line @@ -3778,6 +3779,7 @@ IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontCo IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); IMGUI_API void ImFontAtlasBuildDiscardFont(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasBuildDiscardFontSource(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); IMGUI_API ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont* font, float font_size); IMGUI_API void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); From 40f988ce2a23e64be2831abe94f28345ce60e7a2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Mar 2025 19:57:49 +0100 Subject: [PATCH 090/191] Fonts: in ShowFontAtlas() preserve open-state for latest texture. Improve debug display. --- imgui.cpp | 20 ++++++++++++-------- imgui_draw.cpp | 17 ++++++++++++++--- imgui_internal.h | 5 ++++- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index cb546e4ba..466ab28c7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15420,7 +15420,8 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImG // - RenderViewportsThumbnails() [Internal] // - DebugTextEncoding() // - MetricsHelpMarker() [Internal] -// - ShowFontAtlas() [Internal] +// - ShowFontAtlas() [Internal but called by Demo!] +// - DebugNodeTexture() [Internal] // - ShowMetricsWindow() // - DebugNodeColumns() [Internal] // - DebugNodeDrawList() [Internal] @@ -15722,18 +15723,20 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) Text("incl. Discarded rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsDiscardedCount, atlas->Builder->RectsDiscardedSurface, discarded_surface_sqrt, discarded_surface_sqrt); // Texture list - for (ImTextureData* tex : atlas->TexList) + // (ensure the last texture always use the same ID, so we can keep it open neatly) + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { - PushID(tex); - DebugNodeTexture(tex); - PopID(); + if (tex_n == atlas->TexList.Size - 1) + SetNextItemOpen(true, ImGuiCond_Once); + DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n); } } -void ImGui::DebugNodeTexture(ImTextureData* tex) +void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id) { ImGuiContext& g = *GImGui; - if (TreeNode(tex, "Texture #%03d (%dx%d pixels)", tex->UniqueID, tex->Width, tex->Height)) + PushID(int_id); + if (TreeNode("", "Texture #%03d (%dx%d pixels)", tex->UniqueID, tex->Width, tex->Height)) { ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; Checkbox("Show used rect", &cfg->ShowTextureUsedRect); @@ -15748,12 +15751,13 @@ void ImGui::DebugNodeTexture(ImTextureData* tex) PopStyleVar(); char texid_desc[20]; - Text("Format = %d", tex->Format); + Text("Format = %s (%d)", ImTextureDataGetFormatName(tex->Format), tex->Format); Text("TexID = %s", FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->TexID)); Text("BackendUserData = %p", tex->BackendUserData); Text("UseColors = %d", tex->UseColors); TreePop(); } + PopID(); } void ImGui::ShowMetricsWindow(bool* p_open) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f154e5628..04a00c422 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2420,7 +2420,7 @@ ImFontConfig::ImFontConfig() // - ImTextureData::DestroyPixels() //----------------------------------------------------------------------------- -static int GetTextureFormatBytesPerPixel(ImTextureFormat format) +int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format) { switch (format) { @@ -2431,13 +2431,24 @@ static int GetTextureFormatBytesPerPixel(ImTextureFormat format) return 0; } +const char* ImTextureDataGetFormatName(ImTextureFormat format) +{ + switch (format) + { + case ImTextureFormat_Alpha8: return "Alpha8"; + case ImTextureFormat_RGBA32: return "RGBA32"; + } + return "N/A"; +} + + void ImTextureData::Create(ImTextureFormat format, int w, int h) { DestroyPixels(); Format = format; Width = w; Height = h; - BytesPerPixel = GetTextureFormatBytesPerPixel(format); + BytesPerPixel = ImTextureDataGetFormatBytesPerPixel(format); UseColors = false; Pixels = (unsigned char*)IM_ALLOC(Width * Height * BytesPerPixel); IM_ASSERT(Pixels != NULL); @@ -2798,7 +2809,7 @@ void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFo IM_ASSERT(src_pixels != NULL && dst_pixels != NULL); if (src_fmt == dst_fmt) { - int line_sz = w * GetTextureFormatBytesPerPixel(src_fmt); + int line_sz = w * ImTextureDataGetFormatBytesPerPixel(src_fmt); for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) memcpy(dst_pixels, src_pixels, line_sz); } diff --git a/imgui_internal.h b/imgui_internal.h index 6252ee818..c1857aafb 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3625,7 +3625,7 @@ namespace ImGui IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); - IMGUI_API void DebugNodeTexture(ImTextureData* tex); + IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id); // ID used to facilitate persisting the "current" texture. IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); @@ -3809,6 +3809,9 @@ IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); +IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format); +IMGUI_API const char* ImTextureDataGetFormatName(ImTextureFormat format); + #ifndef IMGUI_DISABLE_DEBUG_TOOLS IMGUI_API void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas); #endif From 41a0e991f0ea621b93cfe3672a274147a5457a72 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Mar 2025 20:34:18 +0100 Subject: [PATCH 091/191] Fonts: Added UI to edit FreeType loader flags. Added ImFontAtlasBuildReloadAll() / ImFontAtlasBuildReloadFont() --- imgui.cpp | 26 +++++++++++++++++++++++--- imgui_draw.cpp | 22 +++++++++++++++++++--- imgui_internal.h | 5 ++++- misc/freetype/imgui_freetype.cpp | 18 +++++++++++++++++- misc/freetype/imgui_freetype.h | 3 +++ 5 files changed, 66 insertions(+), 8 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 466ab28c7..1d1ad8650 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15639,7 +15639,7 @@ static void MetricsHelpMarker(const char* desc) } #ifdef IMGUI_ENABLE_FREETYPE -namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); } +namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); IMGUI_API bool DebugEditFontBuilderFlags(unsigned int* p_font_builder_flags); } #endif // [DEBUG] List fonts in a font atlas and display its texture @@ -15682,6 +15682,12 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) const ImFontLoader* loader_freetype = ImGuiFreeType::GetFontLoader(); if (RadioButton("FreeType", loader_current == loader_freetype)) ImFontAtlasBuildSetupFontLoader(atlas, loader_freetype); + if (loader_current == loader_freetype) + { + Text("Shared FreeType Loader Flags:"); + if (ImGuiFreeType::DebugEditFontBuilderFlags(&atlas->FontBuilderFlags)) + ImFontAtlasBuildReloadAll(atlas); + } #else BeginDisabled(); RadioButton("FreeType", false); @@ -16585,10 +16591,24 @@ void ImGui::DebugNodeFont(ImFont* font) char c_str[5]; Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar); Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar); + for (int src_n = 0; src_n < font->SourcesCount; src_n++) if (ImFontConfig* src = &font->Sources[src_n]) - BulletText("Input %d: \'%s\', Oversample: %d,%d, PixelSnapH: %d, Offset: (%.1f,%.1f)", - src_n, src->Name, src->OversampleH, src->OversampleV, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); + if (TreeNode(src, "Input %d: \'%s\', Oversample: %d,%d, PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->OversampleH, src->OversampleV, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y)) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + Text("Loader: '%s'", loader->Name ? loader->Name : "N/A"); +#ifdef IMGUI_ENABLE_FREETYPE + if (loader->Name != NULL && strcmp(loader->Name, "FreeType") == 0) + { + Text("FreeType Loader Flags: 0x%08X", src->FontBuilderFlags); + if (ImGuiFreeType::DebugEditFontBuilderFlags(&src->FontBuilderFlags)) + ImFontAtlasBuildReloadFont(atlas, src); + } +#endif + TreePop(); + } // Display all glyphs of the fonts in separate pages of 256 characters for (int baked_n = 0; baked_n < atlas->Builder->BakedPool.Size; baked_n++) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 04a00c422..c7418cfca 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2519,7 +2519,7 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildDiscardBakes() // - ImFontAtlasBuildDiscardFontBakedGlyph() // - ImFontAtlasBuildDiscardFontBaked() -// - ImFontAtlasBuildDiscardFont() +// - ImFontAtlasBuildDiscardFontBakes() //----------------------------------------------------------------------------- // - ImFontAtlasAddDrawListSharedData() // - ImFontAtlasRemoveDrawListSharedData() @@ -3551,6 +3551,22 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ //----------------------------------------------------------------------------------------------------------------------------- +void ImFontAtlasBuildReloadAll(ImFontAtlas* atlas) +{ + const ImFontLoader* main_loader = atlas->FontLoader; + ImFontAtlasBuildSetupFontLoader(atlas, NULL); + ImFontAtlasBuildSetupFontLoader(atlas, main_loader); +} + +void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFontConfig* src) +{ + // FIXME-NEWATLAS: rebuild single font not supported yet. + IM_UNUSED(src); + ImFontAtlasBuildReloadAll(atlas); +} + +//----------------------------------------------------------------------------------------------------------------------------- + bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) { ImFont* font = src->DstFont; @@ -3794,7 +3810,7 @@ void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBa font->LastBaked = NULL; } -void ImFontAtlasBuildDiscardFont(ImFontAtlas* atlas, ImFont* font) +void ImFontAtlasBuildDiscardFontBakes(ImFontAtlas* atlas, ImFont* font) { if (ImFontAtlasBuilder* builder = atlas->Builder) // This can be called from font destructor for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) @@ -4943,7 +4959,7 @@ ImFont::~ImFont() void ImFont::ClearOutputData() { if (ImFontAtlas* atlas = ContainerAtlas) - ImFontAtlasBuildDiscardFont(atlas, this); + ImFontAtlasBuildDiscardFontBakes(atlas, this); FallbackChar = EllipsisChar = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); LastBaked = NULL; diff --git a/imgui_internal.h b/imgui_internal.h index c1857aafb..c92aea666 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3775,10 +3775,13 @@ IMGUI_API void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildReloadAll(ImFontAtlas* atlas); // Reinit/rebuild, notably if font loader params have changed. +IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFontConfig* src); // Reinit/rebuild, notably if font loader params have changed. + IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); -IMGUI_API void ImFontAtlasBuildDiscardFont(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasBuildDiscardFontBakes(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildDiscardFontSource(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); IMGUI_API ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont* font, float font_size); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index fd6920e0c..edd6bdd14 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -578,7 +578,7 @@ bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* s const ImFontLoader* ImGuiFreeType::GetFontLoader() { static ImFontLoader loader; - loader.Name = "freetype"; + loader.Name = "FreeType"; loader.LoaderInit = ImGui_ImplFreeType_LoaderInit; loader.LoaderShutdown = ImGui_ImplFreeType_LoaderShutdown; loader.FontSrcInit = ImGui_ImplFreeType_FontSrcInit; @@ -598,6 +598,22 @@ void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* u GImGuiFreeTypeAllocatorUserData = user_data; } +bool ImGuiFreeType::DebugEditFontBuilderFlags(unsigned int* p_font_loader_flags) +{ + bool edited = false; + edited |= ImGui::CheckboxFlags("NoHinting", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_NoHinting); + edited |= ImGui::CheckboxFlags("NoAutoHint", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_NoAutoHint); + edited |= ImGui::CheckboxFlags("ForceAutoHint",p_font_loader_flags, ImGuiFreeTypeBuilderFlags_ForceAutoHint); + edited |= ImGui::CheckboxFlags("LightHinting", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_LightHinting); + edited |= ImGui::CheckboxFlags("MonoHinting", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_MonoHinting); + edited |= ImGui::CheckboxFlags("Bold", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_Bold); + edited |= ImGui::CheckboxFlags("Oblique", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_Oblique); + edited |= ImGui::CheckboxFlags("Monochrome", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_Monochrome); + edited |= ImGui::CheckboxFlags("LoadColor", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_LoadColor); + edited |= ImGui::CheckboxFlags("Bitmap", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_Bitmap); + return edited; +} + #ifdef IMGUI_ENABLE_FREETYPE_LUNASVG // For more details, see https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c // The original code from the demo is licensed under CeCILL-C Free Software License Agreement (https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/LICENSE.TXT) diff --git a/misc/freetype/imgui_freetype.h b/misc/freetype/imgui_freetype.h index 2d7b3a34f..b4e9ba1fd 100644 --- a/misc/freetype/imgui_freetype.h +++ b/misc/freetype/imgui_freetype.h @@ -49,6 +49,9 @@ namespace ImGuiFreeType // However, as FreeType does lots of allocations we provide a way for the user to redirect it to a separate memory heap if desired. IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data = nullptr); + // Display UI to edit FontBuilderFlags in ImFontAtlas (shared) or ImFontConfig (single source) + IMGUI_API bool DebugEditFontBuilderFlags(unsigned int* p_font_loader_flags); + // Obsolete names (will be removed soon) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontBuilderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE' From 735d31e54a481e4ca6c90630ee6eb338999364d5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Mar 2025 20:50:00 +0100 Subject: [PATCH 092/191] Demo: Exposed some basic UI in demo for sanity. --- imgui_demo.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 894012903..43c3cf9db 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -430,6 +430,17 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Text("dear imgui says hello! (%s) (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); ImGui::Spacing(); + ImGui::SeparatorText("dynamic_fonts branch"); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5); + ImGui::DragFloat("io.FontGlobalScale", &ImGui::GetIO().FontGlobalScale, 0.05f, 0.5f, 5.0f); + ImGui::BulletText("This is scaling font only. General scaling will come later."); + ImGui::BulletText("Load an actual font that's not the default for best result!"); + ImGui::BulletText("See 'Widgets->Fonts' below for more.."); + ImGui::BulletText("Current font loader: '%s'", ImGui::GetIO().Fonts->FontLoaderName); + ImGui::BulletText("Please submit feedback:"); ImGui::SameLine(); + ImGui::TextLinkOpenURL("https://github.com/ocornut/imgui/issues/8465"); + ImGui::Spacing(); + IMGUI_DEMO_MARKER("Help"); if (ImGui::CollapsingHeader("Help")) { From 8ea0ae454f1fe427c4968b9b562d66c0f40a6ca4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 9 Mar 2025 21:36:44 +0100 Subject: [PATCH 093/191] Fonts: fixed a bug using size specified by secondary font sources. --- imgui_draw.cpp | 2 ++ misc/freetype/imgui_freetype.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c7418cfca..52aeeee9d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4460,6 +4460,8 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); else bd_font_data->ScaleFactor = -stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); + if (src > src->DstFont->Sources) + bd_font_data->ScaleFactor *= src->SizePixels / src->DstFont->Sources[0].SizePixels; // FIXME-NEWATLAS: Should tidy up that a bit return true; } diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index edd6bdd14..7264fc263 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -432,7 +432,7 @@ void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) { IM_UNUSED(atlas); - const float size = baked->Size; + const float size = baked->Size * (src->SizePixels / baked->ContainerFont->Sources[0].SizePixels); // FIXME-NEWATLAS: Should tidy up that a bit ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; bd_font_data->BakedLastActivated = baked; From 52a6863771df71a0a1031512eea8f1b37b9311f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 10 Mar 2025 10:55:29 +0100 Subject: [PATCH 094/191] Textures: ImTextureData pixels are not immediately destroyed on setting ImTextureStatus_WantDestroy. --- imgui_draw.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 52aeeee9d..df4429d9f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2780,9 +2780,9 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count) else if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_WantDestroy) { // Request destroy. Keep bool as it allows us to keep track of things. + // We don't destroy pixels right away, as backend may have an in-flight copy from RAM. IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); tex->Status = ImTextureStatus_WantDestroy; - tex->DestroyPixels(); } // The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering. @@ -2794,9 +2794,10 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count) if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) remove_from_list = true; - // Remove + // Destroy and remove if (remove_from_list) { + tex->DestroyPixels(); IM_DELETE(tex); atlas->TexList.erase(atlas->TexList.begin() + tex_n); tex_n--; From 144f444217576121f1b16dba18a20e715ad01cf0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 11 Mar 2025 11:46:18 +0100 Subject: [PATCH 095/191] Fonts: fixed memory leaks, shutting down font loader, and on AddFont() failure in FreeType backend. --- imgui.h | 3 +-- imgui_draw.cpp | 14 ++++++++------ misc/freetype/imgui_freetype.cpp | 4 ++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/imgui.h b/imgui.h index 550af74ba..cdb27e7e9 100644 --- a/imgui.h +++ b/imgui.h @@ -3711,7 +3711,7 @@ struct ImFont IMGUI_API ImFontBaked* GetFontBaked(float font_size); // Get or create baked data for given size IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } - const char* GetDebugName() const { return Sources ? Sources->Name : ""; } + const char* GetDebugName() const { return Sources ? Sources[0].Name : ""; } // [Internal] Don't use! // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. @@ -3720,7 +3720,6 @@ struct ImFont IMGUI_API const char* CalcWordWrapPosition(float size, const char* text, const char* text_end, float wrap_width); IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip = NULL); IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false); - #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(Sources[0].SizePixels * scale, text, text_end, wrap_width); } #endif diff --git a/imgui_draw.cpp b/imgui_draw.cpp index df4429d9f..88f623c8b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3034,7 +3034,10 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) new_font_cfg.GlyphExcludeRanges = (ImWchar*)ImMemdup(font_cfg->GlyphExcludeRanges, sizeof(font_cfg->GlyphExcludeRanges[0]) * (size + 1)); } if (font_cfg->FontLoader != NULL) + { IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); + IM_ASSERT(font_cfg->FontLoader->LoaderInit == NULL && font_cfg->FontLoader->LoaderShutdown == NULL); // FIXME-NEWATLAS: Unsupported yet. + } IM_ASSERT(font_cfg->FontLoaderData == NULL); // Round font size @@ -3369,11 +3372,6 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); ImFontAtlasBuildDestroy(atlas); - if (atlas->FontLoader && atlas->FontLoader->LoaderShutdown) - { - atlas->FontLoader->LoaderShutdown(atlas); - IM_ASSERT(atlas->FontLoaderData == NULL); - } atlas->FontLoader = font_loader; atlas->FontLoaderName = font_loader ? font_loader->Name : "NULL"; if (atlas->FontLoader && atlas->FontLoader->LoaderInit) @@ -4187,7 +4185,11 @@ void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) if (loader && loader->FontSrcDestroy != NULL) loader->FontSrcDestroy(atlas, &font_cfg); } - + if (atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + { + atlas->FontLoader->LoaderShutdown(atlas); + IM_ASSERT(atlas->FontLoaderData == NULL); + } IM_DELETE(atlas->Builder); atlas->Builder = NULL; } diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 7264fc263..72140f952 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -416,7 +416,11 @@ bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) src->FontLoaderData = bd_font_data; if (!bd_font_data->InitFont(bd->Library, src, atlas->FontBuilderFlags)) + { + IM_DELETE(bd_font_data); + src->FontLoaderData = NULL; return false; + } return true; } From 7ac1bff482b6c982df4cc4a9a2e1b1503c6cfa88 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 11 Mar 2025 19:54:33 +0100 Subject: [PATCH 096/191] Fonts: fixed an issue calling legacy ImFontAtlas::Clear(). --- imgui_draw.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 88f623c8b..f6cf1f4a6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2637,8 +2637,6 @@ void ImFontAtlas::Clear() RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. ClearFonts(); ClearTexData(); - if (Builder != NULL) - ImFontAtlasBuildClearTexture(this); RendererHasTextures = backup_renderer_has_textures; } @@ -3374,8 +3372,6 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon atlas->FontLoader = font_loader; atlas->FontLoaderName = font_loader ? font_loader->Name : "NULL"; - if (atlas->FontLoader && atlas->FontLoader->LoaderInit) - atlas->FontLoader->LoaderInit(atlas); ImFontAtlasBuildAddTexture(atlas, new_tex_size.x, new_tex_size.y); ImFontAtlasBuildInit(atlas); @@ -4140,8 +4136,12 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) return; // ImFontAtlasBuildSetupFontLoader() automatically call ImFontAtlasBuildInit() } + IM_ASSERT(atlas->FontLoaderData == NULL); + if (atlas->FontLoader && atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); + // Create initial texture size - if (atlas->TexData == NULL) + if (atlas->TexData == NULL || atlas->TexData->Pixels == NULL) ImFontAtlasBuildAddTexture(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); ImFontAtlasBuilder* builder = atlas->Builder; // Do not move above From e76cfe5aad93e52b00302f3a9182c64336ab7169 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 13 Mar 2025 16:30:20 +0100 Subject: [PATCH 097/191] Fonts: fixed implicit init when calling AddCustomRectRegular(). LoaderShutdown match BuildDestroy. --- imgui_draw.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f6cf1f4a6..873e77508 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3231,6 +3231,9 @@ int ImFontAtlas::AddCustomRectRegular(int width, int height) IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); + if (Builder == NULL) + ImFontAtlasBuildInit(this); + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); if (r_id < 0) return -1; @@ -4185,7 +4188,7 @@ void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) if (loader && loader->FontSrcDestroy != NULL) loader->FontSrcDestroy(atlas, &font_cfg); } - if (atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) { atlas->FontLoader->LoaderShutdown(atlas); IM_ASSERT(atlas->FontLoaderData == NULL); From b12c42e75db8deeb56dff989f90bd7413f5d39b9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 13 Mar 2025 16:50:19 +0100 Subject: [PATCH 098/191] Fonts: change uses of ImFontAtlasRect to ImTextureRect for simplicity. --- imgui.cpp | 2 +- imgui_draw.cpp | 40 ++++++++++++++------------------ imgui_internal.h | 12 +++------- misc/freetype/imgui_freetype.cpp | 2 +- 4 files changed, 23 insertions(+), 33 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 1d1ad8650..ef3abcb46 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16699,7 +16699,7 @@ void ImGui::DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); if (glyph->PackId >= 0) { - ImFontAtlasRect* r = ImFontAtlasPackGetRect(font->ContainerAtlas, glyph->PackId); + ImTextureRect* r = ImFontAtlasPackGetRect(font->ContainerAtlas, glyph->PackId); Text("PackId: %d (%dx%d rect at %d,%d)", glyph->PackId, r->w, r->h, r->x, r->y); } } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 873e77508..cea3bc07a 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3237,7 +3237,7 @@ int ImFontAtlas::AddCustomRectRegular(int width, int height) ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); if (r_id < 0) return -1; - ImFontAtlasRect* r = ImFontAtlasPackGetRect(this, r_id); + ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); if (RendererHasTextures) ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); return r_id; @@ -3271,7 +3271,7 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); if (r_id < 0) return -1; - ImFontAtlasRect* r = ImFontAtlasPackGetRect(this, r_id); + ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); if (RendererHasTextures) ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); @@ -3295,11 +3295,7 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im ImTextureRect* ImFontAtlas::GetCustomRectByIndex(int idx) { - IM_STATIC_ASSERT(offsetof(ImTextureRect, x) == offsetof(ImFontAtlasRect, x)); - IM_STATIC_ASSERT(offsetof(ImTextureRect, y) == offsetof(ImFontAtlasRect, y)); - IM_STATIC_ASSERT(offsetof(ImTextureRect, w) == offsetof(ImFontAtlasRect, w)); - IM_STATIC_ASSERT(offsetof(ImTextureRect, h) == offsetof(ImFontAtlasRect, h)); - return (ImTextureRect*)(void*)ImFontAtlasPackGetRect(this, idx); + return ImFontAtlasPackGetRect(this, idx); } void ImFontAtlas::CalcCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const @@ -3316,7 +3312,7 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) return false; - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, atlas->Builder->PackIdMouseCursors); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, atlas->Builder->PackIdMouseCursors); ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->x, (float)r->y); ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; *out_size = size; @@ -3453,7 +3449,7 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_ builder->PackIdMouseCursors = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); if (builder->PackIdMouseCursors < 0) return; - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdMouseCursors); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdMouseCursors); // Draw to texture if (add_and_draw) @@ -3489,7 +3485,7 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ builder->PackIdLinesTexData = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); if (builder->PackIdLinesTexData < 0) return; - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdLinesTexData); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdLinesTexData); // Register texture region for thick lines // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row @@ -3599,12 +3595,12 @@ static ImFontGlyph* ImFontAtlasBuildSetupFontBakedEllipsis(ImFontAtlas* atlas, I if (dot_glyph == NULL) return NULL; ImFontAtlasRectId dot_r_id = dot_glyph->PackId; // Deep copy to avoid invalidation of glyphs and rect pointers - ImFontAtlasRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); + ImTextureRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); const int dot_spacing = 1; const float dot_step = (dot_glyph->X1 - dot_glyph->X0) + dot_spacing; ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, (dot_r->w * 3 + dot_spacing * 2), dot_r->h); - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); ImFontGlyph glyph_in = {}; ImFontGlyph* glyph = &glyph_in; @@ -3961,7 +3957,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic, and fix stability. // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. ImFontAtlasPackInit(atlas); - ImVector old_rects; + ImVector old_rects; ImVector old_index = builder->RectsIndex; old_rects.swap(builder->Rects); @@ -3969,7 +3965,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) { if (index_entry.Used == false) continue; - ImFontAtlasRect& old_r = old_rects[index_entry.TargetIndex]; + ImTextureRect& old_r = old_rects[index_entry.TargetIndex]; if (old_r.w == 0 && old_r.h == 0) continue; ImFontAtlasRectId new_r_id = ImFontAtlasPackAddRect(atlas, old_r.w, old_r.h, &index_entry); @@ -3986,7 +3982,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) return; } IM_ASSERT(new_r_id == builder->RectsIndex.index_from_ptr(&index_entry)); - ImFontAtlasRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); + ImTextureRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h); } IM_ASSERT(old_rects.Size == builder->Rects.Size + builder->RectsDiscardedCount); @@ -3998,7 +3994,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) for (ImFontGlyph& glyph : builder->BakedPool[baked_n].Glyphs) if (glyph.PackId != -1) { - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); glyph.U0 = (r->x) * atlas->TexUvScale.x; glyph.V0 = (r->y) * atlas->TexUvScale.y; glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; @@ -4244,7 +4240,7 @@ void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; IM_ASSERT(index_entry->Used && index_entry->TargetIndex >= 0); - ImFontAtlasRect* rect = ImFontAtlasPackGetRect(atlas, id); + ImTextureRect* rect = ImFontAtlasPackGetRect(atlas, id); index_entry->Used = false; index_entry->TargetIndex = builder->RectsIndexFreeListStart; @@ -4268,7 +4264,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon builder->MaxRectSize.y = ImMax(builder->MaxRectSize.y, h); // Pack - ImFontAtlasRect r = { 0, 0, (unsigned short)w, (unsigned short)h }; + ImTextureRect r = { 0, 0, (unsigned short)w, (unsigned short)h }; for (int attempts_remaining = 3; attempts_remaining >= 0; attempts_remaining--) { // Try packing @@ -4312,7 +4308,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon } // Important: don'return pointer valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. -ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { IM_ASSERT(id >= 0); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; @@ -4551,7 +4547,7 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); return NULL; } - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); // Render stbtt_GetGlyphBitmapBox(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, &x0, &y0, &x1, &y1); @@ -4999,7 +4995,7 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked // Set UV from packed rectangle if (in_glyph->PackId >= 0) { - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, in_glyph->PackId); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, in_glyph->PackId); IM_ASSERT(in_glyph->U0 == 0.0f && in_glyph->V0 == 0.0f && in_glyph->U1 == 0.0f && in_glyph->V1 == 0.0f); glyph.U0 = (r->x) * atlas->TexUvScale.x; glyph.V0 = (r->y) * atlas->TexUvScale.y; @@ -5042,7 +5038,7 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked } // Copy to texture, post-process and queue update for backend -void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImFontAtlasRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch) +void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch) { ImTextureData* tex = atlas->TexData; IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); diff --git a/imgui_internal.h b/imgui_internal.h index c92aea666..940473abe 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -143,7 +143,6 @@ struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas struct ImFontAtlasPostProcessData; // Data available to potential texture post-processing functions -struct ImFontAtlasRect; // Packed rectangle (same as ImTextureRect) struct ImFontAtlasRectEntry; // Packed rectangle lookup entry // ImGui @@ -3692,11 +3691,6 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- -struct ImFontAtlasRect -{ - unsigned short x, y; - unsigned short w, h; -}; typedef int ImFontAtlasRectId; // <0 when invalid // Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) @@ -3734,7 +3728,7 @@ struct ImFontAtlasBuilder { stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file. ImVector PackNodes; - ImVector Rects; + ImVector Rects; ImVector RectsIndex; // ImFontAtlasRectId -> index into Rects[] ImVector TempBuffer; // Misc scratch buffer int RectsIndexFreeListStart;// First unused entry @@ -3791,12 +3785,12 @@ IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* a IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); -IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImFontAtlasRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); +IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size); IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); -IMGUI_API ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 72140f952..5151a90d5 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -534,7 +534,7 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); return NULL; } - ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); // Render pixels to our temporary buffer atlas->Builder->TempBuffer.resize(w * h * 4); From 85d050758024d04bf8f3911bafce4bee433cf0c0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 13 Mar 2025 16:54:39 +0100 Subject: [PATCH 099/191] Fonts: narrowed invalid value for ImFontAtlasRectId to -1 a we will change implementation. --- imgui.h | 2 +- imgui_draw.cpp | 22 +++++++++++----------- imgui_internal.h | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/imgui.h b/imgui.h index cdb27e7e9..c3e568cfe 100644 --- a/imgui.h +++ b/imgui.h @@ -3577,7 +3577,7 @@ struct ImFontAtlas // You can request arbitrary rectangles to be packed into the atlas, for your own purpose. // You can request your rectangles to be mapped as font glyph (given a font + Unicode point), // so you can render e.g. custom colorful icons and use them as regular glyphs. - // - Since 1.92.X, packing is done immediately in the function call. Returns >= on success, <0 on error. + // - Since 1.92.X, packing is done immediately in the function call. Returns -1 on error. // - You can render your pixels into the texture right after calling the AddCustomRectXXX() functions. // - If your backend supports ImGuiBackendFlags_RendererHasTextures: // Texture may be resized, so you cannot cache UV coordinates: always use CalcCustomRectUV(). diff --git a/imgui_draw.cpp b/imgui_draw.cpp index cea3bc07a..92c6b0655 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3235,7 +3235,7 @@ int ImFontAtlas::AddCustomRectRegular(int width, int height) ImFontAtlasBuildInit(this); ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); - if (r_id < 0) + if (r_id == -1) return -1; ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); if (RendererHasTextures) @@ -3269,7 +3269,7 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im ImFontBaked* baked = font->GetFontBaked(font_size); ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); - if (r_id < 0) + if (r_id == -1) return -1; ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); if (RendererHasTextures) @@ -3447,7 +3447,7 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_ if (add_and_draw) builder->PackIdMouseCursors = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); - if (builder->PackIdMouseCursors < 0) + if (builder->PackIdMouseCursors == -1) return; ImTextureRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdMouseCursors); @@ -3483,7 +3483,7 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ ImFontAtlasBuilder* builder = atlas->Builder; if (add_and_draw) builder->PackIdLinesTexData = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); - if (builder->PackIdLinesTexData < 0) + if (builder->PackIdLinesTexData == -1) return; ImTextureRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdLinesTexData); @@ -3705,7 +3705,7 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, Im void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph) { - if (glyph->PackId >= 0) + if (glyph->PackId != -1) { ImFontAtlasPackDiscardRect(atlas, glyph->PackId); glyph->PackId = -1; @@ -3780,7 +3780,7 @@ void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBa IMGUI_DEBUG_LOG_FONT("[font] Discard baked %.2f for \"%s\"\n", baked->Size, font->GetDebugName()); for (ImFontGlyph& glyph : baked->Glyphs) - if (glyph.PackId >= 0) + if (glyph.PackId != -1) ImFontAtlasPackDiscardRect(atlas, glyph.PackId); char* loader_data_p = (char*)baked->FontLoaderDatas; @@ -4235,7 +4235,7 @@ static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int r // This is expected to be called in batches and followed by a repack void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { - IM_ASSERT(id >= 0); + IM_ASSERT(id != -1); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; IM_ASSERT(index_entry->Used && index_entry->TargetIndex >= 0); @@ -4310,7 +4310,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon // Important: don'return pointer valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { - IM_ASSERT(id >= 0); + IM_ASSERT(id != -1); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; IM_ASSERT(index_entry->Used); @@ -4541,10 +4541,10 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, const int w = (x1 - x0 + oversample_h - 1); const int h = (y1 - y0 + oversample_v - 1); ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); - if (pack_id < 0) + if (pack_id == -1) { // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) - IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); + IM_ASSERT_USER_ERROR(pack_id != -1, "Out of texture memory."); return NULL; } ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); @@ -4993,7 +4993,7 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked IM_ASSERT(baked->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. // Set UV from packed rectangle - if (in_glyph->PackId >= 0) + if (in_glyph->PackId != -1) { ImTextureRect* r = ImFontAtlasPackGetRect(atlas, in_glyph->PackId); IM_ASSERT(in_glyph->U0 == 0.0f && in_glyph->V0 == 0.0f && in_glyph->U1 == 0.0f && in_glyph->V1 == 0.0f); diff --git a/imgui_internal.h b/imgui_internal.h index 940473abe..6d13087f4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3691,7 +3691,7 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- -typedef int ImFontAtlasRectId; // <0 when invalid +typedef int ImFontAtlasRectId; // -1 when invalid // Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) // User are returned ImFontAtlasRectId values which are meant to be persistent. From f816b861fc506d2d438868d4bc0667cd65ab8ae8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 13 Mar 2025 17:10:52 +0100 Subject: [PATCH 100/191] (Breaking) Fonts: rename GetCustomRectByIndex() to GetCustomRect(). Made return struct const. --- docs/FONTS.md | 7 ++----- imgui.h | 28 ++++++++++++++-------------- imgui_draw.cpp | 6 +++--- imgui_internal.h | 4 +++- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/docs/FONTS.md b/docs/FONTS.md index 9b783dcdc..452499059 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -357,7 +357,7 @@ You can ask questions in [#8466](https://github.com/ocornut/imgui/issues/8466). As an alternative to rendering colorful glyphs using imgui_freetype with `ImGuiFreeTypeBuilderFlags_LoadColor`, you may allocate your own space in the texture atlas and write yourself into it. **(This is a BETA api, use if you are familiar with dear imgui and with your rendering backend)** - You can use the `ImFontAtlas::AddCustomRect()` and `ImFontAtlas::AddCustomRectFontGlyph()` api to register rectangles that will be packed into the font atlas texture. Register them before building the atlas, then call Build()`. -- You can then use `ImFontAtlas::GetCustomRectByIndex(int)` to query the position/size of your rectangle within the texture, and blit/copy any graphics data of your choice into those rectangles. +- You can then use `ImFontAtlas::GetCustomRect(int)` to query the position/size of your rectangle within the texture, and blit/copy any graphics data of your choice into those rectangles. - This API is beta because it is likely to change in order to support multi-dpi (multiple viewports on multiple monitors with varying DPI scale). #### Pseudo-code: @@ -377,9 +377,7 @@ int tex_width, tex_height; io.Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_width, &tex_height); for (int rect_n = 0; rect_n < IM_ARRAYSIZE(rect_ids); rect_n++) -{ - int rect_id = rect_ids[rect_n]; - if (const ImFontAtlasCustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id)) + if (const ImTextureRect* rect = io.Fonts->GetCustomRect(rect_ids[rect_n])) { // Fill the custom rectangle with red pixels (in reality you would draw/copy your bitmap data here!) for (int y = 0; y < rect->Height; y++) @@ -389,7 +387,6 @@ for (int rect_n = 0; rect_n < IM_ARRAYSIZE(rect_ids); rect_n++) *p++ = IM_COL32(255, 0, 0, 255); } } -} ``` ##### [Return to Index](#index) diff --git a/imgui.h b/imgui.h index c3e568cfe..2a781b765 100644 --- a/imgui.h +++ b/imgui.h @@ -3574,24 +3574,24 @@ struct ImFontAtlas // [ALPHA] Custom Rectangles/Glyphs API //------------------------------------------- - // You can request arbitrary rectangles to be packed into the atlas, for your own purpose. - // You can request your rectangles to be mapped as font glyph (given a font + Unicode point), - // so you can render e.g. custom colorful icons and use them as regular glyphs. - // - Since 1.92.X, packing is done immediately in the function call. Returns -1 on error. + // Register and retrieve custom rectangles + // - You can request arbitrary rectangles to be packed into the atlas, for your own purpose. + // - You can request your rectangles to be mapped as font glyph (given a font + Unicode point), + // so you can render e.g. custom colorful icons and use them as regular glyphs. + // - Since 1.92.X, packing is done immediately in the function call. // - You can render your pixels into the texture right after calling the AddCustomRectXXX() functions. - // - If your backend supports ImGuiBackendFlags_RendererHasTextures: - // Texture may be resized, so you cannot cache UV coordinates: always use CalcCustomRectUV(). - // - If you render colored output into your AddCustomRectRegular() rectangle: set 'atlas->TexPixelsUseColors = true' - // as this may help some backends decide of preferred texture format. + // - Texture may be resized, so you cannot cache UV coordinates: always use CalcCustomRectUV()! + // - If you render colored output into your AddCustomRectRegular() rectangle: set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. - // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. - IMGUI_API int AddCustomRectRegular(int width, int height); + // - Note: this API may be reworked further in order to facilitate supporting e.g. multi-monitor, varying DPI settings. + IMGUI_API int AddCustomRectRegular(int width, int height); // Register a rectangle. Return -1 on error. + IMGUI_API const ImTextureRect* GetCustomRect(int id); // Get rectangle coordinate in current texture. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); - IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); + IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); + inline const ImTextureRect* GetCustomRectByIndex(int id) { return GetCustomRect(id); } #endif - IMGUI_API ImTextureRect* GetCustomRectByIndex(int index); - IMGUI_API void CalcCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; + IMGUI_API void CalcCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; //------------------------------------------- // Members diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 92c6b0655..c83d5b94a 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2500,7 +2500,7 @@ void ImTextureData::DestroyPixels() //----------------------------------------------------------------------------- // - ImFontAtlas::AddCustomRectRegular() // - ImFontAtlas::AddCustomRectFontGlyph() -// - ImFontAtlas::GetCustomRectByIndex() +// - ImFontAtlas::GetCustomRect() // - ImFontAtlas::CalcCustomRectUV() // - ImFontAtlasGetMouseCursorTexData() //----------------------------------------------------------------------------- @@ -3293,9 +3293,9 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im } #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -ImTextureRect* ImFontAtlas::GetCustomRectByIndex(int idx) +const ImTextureRect* ImFontAtlas::GetCustomRect(int id) { - return ImFontAtlasPackGetRect(this, idx); + return ImFontAtlasPackGetRect(this, (ImFontAtlasRectId)id); } void ImFontAtlas::CalcCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const diff --git a/imgui_internal.h b/imgui_internal.h index 6d13087f4..045064363 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3691,7 +3691,9 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- -typedef int ImFontAtlasRectId; // -1 when invalid +// An identifier to a rectangle in the atlas. -1 when invalid. +// The rectangle may move, use GetCustomRect() to retrieve it. +typedef int ImFontAtlasRectId; // Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) // User are returned ImFontAtlasRectId values which are meant to be persistent. From 4048494aa1dfb61858ca96247542b1e4b8b2e958 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 13 Mar 2025 17:28:13 +0100 Subject: [PATCH 101/191] Fonts: rename ImFontAtlasBuildClearTexture() to ImFontAtlasBuildClear(). --- imgui.cpp | 2 +- imgui_draw.cpp | 2 +- imgui_internal.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index ef3abcb46..5e51dfa55 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15714,7 +15714,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) ImFontAtlasBuildGrowTexture(atlas); SameLine(); if (Button("Clear Output")) - ImFontAtlasBuildClearTexture(atlas); + ImFontAtlasBuildClear(atlas); for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c83d5b94a..20151496e 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4091,7 +4091,7 @@ ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) } // Clear all output. Invalidates all AddCustomRectXXX return values. -void ImFontAtlasBuildClearTexture(ImFontAtlas* atlas) +void ImFontAtlasBuildClear(ImFontAtlas* atlas) { ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); ImFontAtlasBuildDestroy(atlas); diff --git a/imgui_internal.h b/imgui_internal.h index 045064363..dfd11f6dc 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3762,11 +3762,11 @@ IMGUI_API void ImFontAtlasBuildMain(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader); IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char); +IMGUI_API void ImFontAtlasBuildClear(ImFontAtlas* atlas); // Clear output and custom rects IMGUI_API ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h); IMGUI_API void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h); -IMGUI_API void ImFontAtlasBuildClearTexture(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas); From 41517bca0c42e7fa5ab3207c528a988f0bc556b1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 13 Mar 2025 19:14:10 +0100 Subject: [PATCH 102/191] (Breaking) Fonts: renamed CalcCustomRectUV() to GetCustomRectUV() for simplicity. --- imgui.h | 27 ++++++++++++++++----------- imgui_draw.cpp | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/imgui.h b/imgui.h index 2a781b765..44f48c263 100644 --- a/imgui.h +++ b/imgui.h @@ -3580,18 +3580,17 @@ struct ImFontAtlas // so you can render e.g. custom colorful icons and use them as regular glyphs. // - Since 1.92.X, packing is done immediately in the function call. // - You can render your pixels into the texture right after calling the AddCustomRectXXX() functions. - // - Texture may be resized, so you cannot cache UV coordinates: always use CalcCustomRectUV()! + // - Texture may be resized, so you cannot cache UV coordinates: always use GetCustomRectUV()! // - If you render colored output into your AddCustomRectRegular() rectangle: set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. // - Note: this API may be reworked further in order to facilitate supporting e.g. multi-monitor, varying DPI settings. + // - Pre-1.92 names: + // - AddCustomRectFontGlyph() --> Use custom ImFontLoader inside ImFontConfig + // - GetCustomRectByIndex() --> Use GetCustomRect() + // - CalcCustomRectUV() --> Use GetCustomRectUV() IMGUI_API int AddCustomRectRegular(int width, int height); // Register a rectangle. Return -1 on error. IMGUI_API const ImTextureRect* GetCustomRect(int id); // Get rectangle coordinate in current texture. -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); - IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); - inline const ImTextureRect* GetCustomRectByIndex(int id) { return GetCustomRect(id); } -#endif - IMGUI_API void CalcCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; + IMGUI_API void GetCustomRectUV(const ImTextureRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const; // Get UV coordinates for a given rectangle //------------------------------------------- // Members @@ -3633,10 +3632,16 @@ struct ImFontAtlas int RefCount; // Number of contexts using this atlas // [Obsolete] - //int TexDesiredWidth; // OBSOLETED in 1.92.X (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) - //typedef ImTextureRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.X - //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ - //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig + IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X + inline const ImTextureRect* GetCustomRectByIndex(int id) { return GetCustomRect(id); } // OBSOLETED in 1.92.X + inline void CalcCustomRectUV(const ImTextureRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const { return GetCustomRectUV(r, out_uv_min, out_uv_max); } // OBSOLETED in 1.92.X +#endif + //int TexDesiredWidth; // OBSOLETED in 1.92.X (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) + //typedef ImTextureRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.X + //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ + //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ }; // Font runtime data for a given size diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 20151496e..3207c92c7 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3298,7 +3298,7 @@ const ImTextureRect* ImFontAtlas::GetCustomRect(int id) return ImFontAtlasPackGetRect(this, (ImFontAtlasRectId)id); } -void ImFontAtlas::CalcCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const +void ImFontAtlas::GetCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const { IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates *out_uv_min = ImVec2((float)rect->x * TexUvScale.x, (float)rect->y * TexUvScale.y); From cc65015e4e1ecd7fffb4e8838bb2d9da2b9deecd Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 17 Mar 2025 17:18:05 +0100 Subject: [PATCH 103/191] Fonts: fixed crashing password fields. # Conflicts: # imgui_internal.h --- imgui.cpp | 2 +- imgui_draw.cpp | 2 +- imgui_internal.h | 4 +++- imgui_widgets.cpp | 44 +++++++++++++++++++++++++------------------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5e51dfa55..f65cd777c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4086,6 +4086,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) MouseCursor = ImGuiMouseCursor_Arrow; MouseStationaryTimer = 0.0f; + InputTextPasswordFontBackupFlags = ImFontFlags_None; TempInputId = 0; memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue)); BeginMenuDepth = BeginComboDepth = 0; @@ -4284,7 +4285,6 @@ void ImGui::Shutdown() g.MenusIdSubmittedThisFrame.clear(); g.InputTextState.ClearFreeMemory(); g.InputTextDeactivatedState.ClearFreeMemory(); - g.InputTextPasswordFont.ContainerAtlas = NULL; g.SettingsWindows.clear(); g.SettingsHandlers.clear(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3207c92c7..fa15be25e 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4136,7 +4136,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) } IM_ASSERT(atlas->FontLoaderData == NULL); - if (atlas->FontLoader && atlas->FontLoader->LoaderInit) + if (atlas->FontLoader->LoaderInit) atlas->FontLoader->LoaderInit(atlas); // Create initial texture size diff --git a/imgui_internal.h b/imgui_internal.h index dfd11f6dc..83a16aa9d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2391,7 +2391,8 @@ struct ImGuiContext // Widget state ImGuiInputTextState InputTextState; ImGuiInputTextDeactivatedState InputTextDeactivatedState; - ImFont InputTextPasswordFont; + ImFontBaked InputTextPasswordFontBackupBaked; + ImFontFlags InputTextPasswordFontBackupFlags; ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. ImGuiDataTypeStorage DataTypeZeroValue; // 0 for all data types int BeginMenuDepth; @@ -3112,6 +3113,7 @@ namespace ImGui IMGUI_API void UpdateCurrentFontSize(); inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } IMGUI_API void PushPasswordFont(); + IMGUI_API void PopPasswordFont(); inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport); // get background draw list for the given viewport. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport); // get foreground draw list for the given viewport. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6a86c7956..91d50933f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4314,23 +4314,29 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons void ImGui::PushPasswordFont() { ImGuiContext& g = *GImGui; - ImFont* in_font = g.Font; - ImFontBaked* in_baked = g.FontBaked; - ImFontGlyph glyph = *in_baked->FindGlyph('*'); - glyph.PackId = -1; - ImFont* out_font = &g.InputTextPasswordFont; - out_font->Scale = in_font->Scale; - out_font->ContainerAtlas = in_font->ContainerAtlas; - out_font->Flags |= ImFontFlags_NoLoadGlyphs; - ImFontBaked* out_baked = out_font->GetFontBaked(in_baked->Size); - IM_ASSERT(out_baked->Glyphs.Size <= 1 && out_baked->IndexAdvanceX.Size == 0 && out_baked->IndexLookup.Size == 0); - out_baked->Ascent = in_baked->Ascent; - out_baked->Descent = in_baked->Descent; - out_baked->Glyphs.resize(0); - out_baked->Glyphs.push_back(glyph); - out_baked->FallbackGlyphIndex = 0; - out_baked->FallbackAdvanceX = glyph.AdvanceX; - PushFont(out_font); + ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked; + IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0); + ImFontGlyph* glyph = g.FontBaked->FindGlyph('*'); + g.InputTextPasswordFontBackupFlags = g.Font->Flags; + backup->FallbackGlyphIndex = g.FontBaked->FallbackGlyphIndex; + backup->FallbackAdvanceX = g.FontBaked->FallbackAdvanceX; + backup->IndexLookup.swap(g.FontBaked->IndexLookup); + backup->IndexAdvanceX.swap(g.FontBaked->IndexAdvanceX); + g.Font->Flags |= ImFontFlags_NoLoadGlyphs; + g.FontBaked->FallbackGlyphIndex = g.FontBaked->Glyphs.index_from_ptr(glyph); + g.FontBaked->FallbackAdvanceX = glyph->AdvanceX; +} + +void ImGui::PopPasswordFont() +{ + ImGuiContext& g = *GImGui; + ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked; + g.Font->Flags = g.InputTextPasswordFontBackupFlags; + g.FontBaked->FallbackGlyphIndex = backup->FallbackGlyphIndex; + g.FontBaked->FallbackAdvanceX = backup->FallbackAdvanceX; + g.FontBaked->IndexLookup.swap(backup->IndexLookup); + g.FontBaked->IndexAdvanceX.swap(backup->IndexAdvanceX); + IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0); } // Return false to discard a character. @@ -5232,7 +5238,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (new_is_displaying_hint != is_displaying_hint) { if (is_password && !is_displaying_hint) - PopFont(); + PopPasswordFont(); is_displaying_hint = new_is_displaying_hint; if (is_password && !is_displaying_hint) PushPasswordFont(); @@ -5423,7 +5429,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (is_password && !is_displaying_hint) - PopFont(); + PopPasswordFont(); if (is_multiline) { From 8bd1fc4f04b8e2ae62ebdd76de128b2929b16fa0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 17 Mar 2025 22:08:58 +0100 Subject: [PATCH 104/191] Textures: Added ImTextureRef::GetTexID() mostly for consistency. Without it the logic may seem more confusing to grok for end-user. --- imgui.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index 44f48c263..14396377e 100644 --- a/imgui.h +++ b/imgui.h @@ -343,6 +343,7 @@ struct ImTextureRef ImTextureRef(void* tex_id) { memset(this, 0, sizeof(*this)); _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID //inline operator intptr_t() const { return (intptr_t)_TexID; } // For legacy backends casting to ImTextureID #endif + inline ImTextureID GetTexID() const; // == (_TexData ? _TexData->TexID : _TexID) // Implemented below in the file. // Members ImTextureData* _TexData; // Texture, generally owned by a ImFontAtlas @@ -3072,7 +3073,7 @@ struct ImDrawCmd // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! // If for some reason you non C++ tech stack makes it difficult to call it, we may decide to separate the fields in ImDrawCmd. - inline ImTextureID GetTexID() const; + inline ImTextureID GetTexID() const; // == (TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID }; // Vertex layout @@ -3735,7 +3736,12 @@ struct ImFont IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); }; -// Added indirection to avoid patching ImDrawCmd after texture updates. +// We added an indirection to avoid patching ImDrawCmd after texture updates but this could be a solution too. +inline ImTextureID ImTextureRef::GetTexID() const +{ + return _TexData ? _TexData->TexID : _TexID; +} + inline ImTextureID ImDrawCmd::GetTexID() const { // If you are getting this assert: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) From e41bf16ff155f0626c32d7b2ea158a1880847fda Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 18 Mar 2025 18:03:40 +0100 Subject: [PATCH 105/191] Fonts: fixed ImTextureID() being zero-cleared instead of using ImTextureUserID_Invalid. . --- imgui.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui.h b/imgui.h index 14396377e..ba9c16b22 100644 --- a/imgui.h +++ b/imgui.h @@ -337,11 +337,11 @@ typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or IM_MSVC_RUNTIME_CHECKS_OFF struct ImTextureRef { - ImTextureRef() { memset(this, 0, sizeof(*this)); } - ImTextureRef(ImTextureID tex_id) { memset(this, 0, sizeof(*this)); _TexID = tex_id; } -#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(ImTextureUserID) - ImTextureRef(void* tex_id) { memset(this, 0, sizeof(*this)); _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID - //inline operator intptr_t() const { return (intptr_t)_TexID; } // For legacy backends casting to ImTextureID + ImTextureRef() { _TexData = NULL; _TexID = ImTextureID_Invalid; } + ImTextureRef(ImTextureID tex_id) { _TexData = NULL; _TexID = tex_id; } +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(ImTextureID) + ImTextureRef(void* tex_id) { _TexData = NULL; _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID + //inline operator intptr_t() const { return (intptr_t)_TexID; } // For legacy backends casting to ImTextureID #endif inline ImTextureID GetTexID() const; // == (_TexData ? _TexData->TexID : _TexID) // Implemented below in the file. From 0fff7ceda450994b221df7538ea9d934b17a5611 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 19 Mar 2025 14:06:08 +0100 Subject: [PATCH 106/191] Fonts: comments, tweaks, minor amends. Comments, tweaks --- imgui.cpp | 2 +- imgui.h | 88 +++++++++++++++++++++++++++++++------------------- imgui_demo.cpp | 18 +++++++---- 3 files changed, 67 insertions(+), 41 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f65cd777c..0e5a4c775 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16552,7 +16552,7 @@ void ImGui::DebugNodeFont(ImFont* font) ImGuiContext& g = *GImGui; ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; ImFontAtlas* atlas = font->ContainerAtlas; - bool opened = TreeNode(font, "Font: \"%s\": %d sources(s)", font->Sources ? font->Sources[0].Name : "", font->SourcesCount); + bool opened = TreeNode(font, "Font: \"%s\": %d sources(s)", font->GetDebugName(), font->SourcesCount); // Display preview text if (!opened) diff --git a/imgui.h b/imgui.h index ba9c16b22..f8e5b6c55 100644 --- a/imgui.h +++ b/imgui.h @@ -311,29 +311,44 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // [SECTION] Texture identifiers (ImTextureID, ImTextureRef) //----------------------------------------------------------------------------- -// ImTextureID: user data for renderer backend to identify a texture [Compile-time configurable type] +// ImTextureID = backend specific, low-level identifier for a texture uploaded in GPU/graphics system. +// [Compile-time configurable type] // Overview: -// - Backend and user/app code provides ImTextureID values that gets stored inside draw commands (ImDrawCmd) during the ImGui frame. -// - Backend uses ImDrawCmd::GetTexID() to retrieve the ImTextureID value during rendering. Then, they can bind the textures of each draw command. +// - When a Rendered Backend creates a texture, it store its native identifier into a ImTextureID value. +// (e.g. Used by DX11 backend to a `ID3D11ShaderResourceView*`; Used by OpenGL backends to store `GLuint'; +// Used by SDLGPU backend to store a `SDL_GPUTextureSamplerBinding*`, etc.). +// - User may submit their own textures to e.g. ImGui::Image() function by passing the same type. +// - During the rendering loop, the Renderer Backend retrieve the ImTextureID, which stored inside +// ImTextureRef, which is stored inside ImDrawCmd. // Configuring the type: -// - To use something else than a 64-bit value: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file. +// - To use something other than a 64-bit value: add '#define ImTextureID MyTextureType*' in your imconfig.h file. // - This can be whatever to you want it to be! read the FAQ entry about textures for details. -// - You may decide to store a higher-level structure containing texture, sampler, shader etc. with various constructors if you like. You will need to implement ==/!= operators. +// - You may decide to store a higher-level structure containing texture, sampler, shader etc. with various +// constructors if you like. You will need to implement ==/!= operators. // History: // - In v1.91.4 (2024/10/08): the default type for ImTextureID was changed from 'void*' to 'ImU64'. This allowed backends requirig 64-bit worth of data to build on 32-bit architectures. Use intermediary intptr_t cast and read FAQ if you have casting warnings. +// - In v1.92.0 (2025/XX/XX): added ImTextureRef which carry either a ImTextureID either a pointer to internal texture atlas. All user facing functions taking ImTextureID changed to ImTextureRef #ifndef ImTextureID typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or integer). A majority of backends are ok with that. #endif -// Define this to another value if you need value of 0 to be valid. +// Define this if you need 0 to be a valid ImTextureID for your backend. #ifndef ImTextureID_Invalid #define ImTextureID_Invalid ((ImTextureID)0) #endif -// ImTextureRef contains: -// - a texture/atlas pointer, typically when created by Dear ImGui itself. -// - OR a raw ImTextureID value (user/backend identifier), typically when created by user code to load images. -// There is no constructor to create a ImTextureID from a ImTextureData* as we don't expect this to be useful to the end-user. +// ImTextureRef = higher-level identifier for a texture. +// The identifier is valid even before the texture has been uploaded to the GPU/graphics system. +// This is what gets passed to functions such as ImGui::Image(), ImDrawList::AddImage(). +// This is what gets stored in draw commands (ImDrawCmd) to identify a texture during rendering. +// - When a texture is created by user code (e.g. custom images), we directly stores the low-level ImTextureID. +// - When a texture is created by the backend, we stores a ImTextureData* which becomes an indirection +// to extract the ImTextureID value during rendering, after texture upload has happened. +// - There is no constructor to create a ImTextureID from a ImTextureData* as we don't expect this +// to be useful to the end-user, and it would be erroneously called by many legacy code. +// - If you want to bind the current atlas when using custom rectangle, you can use io.Fonts->TexRef. +// - Binding generators for languages such as C (which don't have constructors), should provide a helper: +// inline ImTextureRef ImTextureRefFromID(ImTextureID tex_id) { ImTextureRef tex_ref = { ._TexData = NULL, .TexID = tex_id }; return tex_ref; } IM_MSVC_RUNTIME_CHECKS_OFF struct ImTextureRef { @@ -341,13 +356,12 @@ struct ImTextureRef ImTextureRef(ImTextureID tex_id) { _TexData = NULL; _TexID = tex_id; } #if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(ImTextureID) ImTextureRef(void* tex_id) { _TexData = NULL; _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID - //inline operator intptr_t() const { return (intptr_t)_TexID; } // For legacy backends casting to ImTextureID #endif inline ImTextureID GetTexID() const; // == (_TexData ? _TexData->TexID : _TexID) // Implemented below in the file. - // Members - ImTextureData* _TexData; // Texture, generally owned by a ImFontAtlas - ImTextureID _TexID; // _OR_ Underlying texture identifier for backend, if already uploaded (otherwise pulled from _TexData) + // Members (either are set, never both!) + ImTextureData* _TexData; // A texture, generally owned by a ImFontAtlas. Will convert to ImTextureID during render loop, after texture has been uploaded. + ImTextureID _TexID; // _OR_ Low-level backend texture identifier, if already uploaded or created by user/app. Generally provided to e.g. ImGui::Image() calls. }; IM_MSVC_RUNTIME_CHECKS_RESTORE @@ -604,7 +618,7 @@ namespace ImGui IMGUI_API bool TextLinkOpenURL(const char* label, const char* url = NULL); // hyperlink text button, automatically open file/url when clicked // Widgets: Images - // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples + // - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. // - Image() pads adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side. // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. @@ -2817,7 +2831,8 @@ static inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return IM_MSVC_RUNTIME_CHECKS_RESTORE #endif -// Helpers: ImTexture ==/!= operators provided as convenience (not strictly necessary) +// Helpers: ImTextureRef ==/!= operators provided as convenience +// (note that _TexID and _TexData are never set simultaneously) static inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._TexData == rhs._TexData; } static inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._TexData != rhs._TexData; } //#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // For legacy backends @@ -3072,7 +3087,6 @@ struct ImDrawCmd // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! - // If for some reason you non C++ tech stack makes it difficult to call it, we may decide to separate the fields in ImDrawCmd. inline ImTextureID GetTexID() const; // == (TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID }; @@ -3295,8 +3309,8 @@ struct ImDrawList // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - //IMGUI_API void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.x - //IMGUI_API void PopTextureID() { PopTexture(); } // RENAMED in 1.92.x + IMGUI_API void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.x + IMGUI_API void PopTextureID() { PopTexture(); } // RENAMED in 1.92.x #endif //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) @@ -3344,7 +3358,11 @@ struct ImDrawData }; //----------------------------------------------------------------------------- -// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) +//----------------------------------------------------------------------------- +// In principle, the only data types that user/application code should care about are 'ImTextureRef' and 'ImTextureID'. +// They are defined above in this header file. Read their description to the difference between ImTextureRef and ImTextureID. +// FOR ALL OTHER ImTextureXXXX TYPES: ONLY CORE LIBRARY AND RENDERER BACKENDS NEED TO KNOW AND CARE ABOUT THEM. //----------------------------------------------------------------------------- // We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. @@ -3355,7 +3373,7 @@ enum ImTextureFormat ImTextureFormat_Alpha8, // 1 component per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight }; -// Status of a texture +// Status of a texture to communicate with Renderer Backend. enum ImTextureStatus { ImTextureStatus_OK, @@ -3366,7 +3384,7 @@ enum ImTextureStatus }; // Coordinates of a rectangle within a texture. -// When a texture is in ImTextureStatus_WantUpdates state, we provide a list of individual rectangles to copy to GPU texture. +// When a texture is in ImTextureStatus_WantUpdates state, we provide a list of individual rectangles to copy to the graphics system. // You may use ImTextureData::Updates[] for the list, or ImTextureData::UpdateBox for a single bounding box. struct ImTextureRect { @@ -3375,7 +3393,8 @@ struct ImTextureRect }; // Specs and pixel storage for a texture used by Dear ImGui. -// The renderer backend will generally create a GPU-side version of this. +// This is only useful for (1) core library and (2) backends. End-user/applications do not need to care about this. +// Renderer Backends will create a GPU-side version of this. // Why does we store two identifiers: TexID and BackendUserData? // - ImTextureID TexID = lower-level identifier stored in ImDrawCmd. ImDrawCmd can refer to textures not created by the backend, and for which there's no ImTextureData. // - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. @@ -3407,7 +3426,7 @@ struct IMGUI_API ImTextureData unsigned char* GetPixelsAt(int x, int y) { IM_ASSERT(Pixels != NULL); return Pixels + (x + y * Width) * BytesPerPixel; } int GetSizeInBytes() const { return Width * Height * BytesPerPixel; } int GetPitch() const { return Width * BytesPerPixel; } - ImTextureRef GetTexRef() { ImTextureRef tex_ref; tex_ref._TexData = this; tex_ref._TexID = TexID; return tex_ref; } + ImTextureRef GetTexRef() { ImTextureRef tex_ref; tex_ref._TexData = this; tex_ref._TexID = TexID; return tex_ref; } // FIXME-TEXREF ImTextureID GetTexID() const { return TexID; } // Called by Renderer backend @@ -3502,7 +3521,7 @@ enum ImFontAtlasFlags_ // It is the rendering backend responsibility to upload texture into your graphics API: // - ImGui_ImplXXXX_RenderDrawData() functions generally iterate platform_io->Textures[] to create/update/destroy each ImTextureData instance. // - Backend then set ImTextureData's TexID and BackendUserData. -// - Texture id are passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. +// - Texture id are passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID/ImTextureRef for more details. // Legacy path: // - Call Build() + GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. // - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. @@ -3554,7 +3573,7 @@ struct ImFontAtlas // Glyph Ranges //------------------------------------------- - // Since 1.92: specifying glyph ranges is only useful/necessary if your backend doesn't support ImGuiBackendFlags_HasTextures! + // Since 1.92: specifying glyph ranges is only useful/necessary if your backend doesn't support ImGuiBackendFlags_RendererHasTextures! IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) @@ -3607,9 +3626,14 @@ struct ImFontAtlas int TexMaxHeight; // Maximum desired texture height. Must be a power of two. Default to 8192. void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). + // Output + ImTextureRef TexRef; // Current texture identifier == TexData->GetTexRef(). +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImTextureRef& TexID = TexRef; // RENAMED in 1.92.x +#endif + ImTextureData* TexData; // Current texture. + // [Internal] - ImTextureRef TexRef; // User data to refer to the latest texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. - ImTextureData* TexData; // Current texture ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetDrawData().Textures[]/GetPlatformIO().Textures[] instead! bool Locked; // Marked as locked during ImGui::NewFrame()..EndFrame() scope if TexUpdates are not supported. Any attempt to modify the atlas will assert. bool RendererHasTextures;// Copy of (BackendFlags & ImGuiBackendFlags_RendererHasTextures) from supporting context. @@ -3622,10 +3646,8 @@ struct ImFontAtlas ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines int TexNextUniqueID; // Next value to be stored in TexData->UniqueID int FontNextUniqueID; // Next value to be stored in ImFont->SourceID - ImVector DrawListSharedDatas; - - // [Internal] Font builder - ImFontAtlasBuilder* Builder; // Opaque interface to our data that doesn't need to be public + ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. + ImFontAtlasBuilder* Builder; // Opaque interface to our data that doesn't need to be public and may be discarded when rebuilding. const ImFontLoader* FontLoader; // Font loader opaque interface (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). Don't set directly! const char* FontLoaderName; // Font loader name (for display e.g. in About box) == FontLoader->Name void* FontLoaderData; // Font backend opaque storage @@ -3717,7 +3739,7 @@ struct ImFont IMGUI_API ImFontBaked* GetFontBaked(float font_size); // Get or create baked data for given size IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } - const char* GetDebugName() const { return Sources ? Sources[0].Name : ""; } + const char* GetDebugName() const { return Sources ? Sources[0].Name : ""; } // Fill ImFontConfig::Name. // [Internal] Don't use! // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 43c3cf9db..dda817eb8 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1777,13 +1777,12 @@ static void DemoWindowWidgetsImages() "Hover the texture for a zoomed view!"); // Below we are displaying the font texture because it is the only texture we have access to inside the demo! - // Remember that ImTextureID is just storage for whatever you want it to be. It is essentially a value that - // will be passed to the rendering backend via the ImDrawCmd structure. + // Read description about ImTextureID/ImTextureRef and FAQ for details about texture identifiers. // If you use one of the default imgui_impl_XXXX.cpp rendering backend, they all have comments at the top - // of their respective source file to specify what they expect to be stored in ImTextureID, for example: - // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer + // of their respective source file to specify what they are using as texture identifier, for example: + // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. // - The imgui_impl_opengl3.cpp renderer expect a GLuint OpenGL texture identifier, etc. - // More: + // So with the DirectX11 backend, you call ImGui::Image() with a 'ID3D11ShaderResourceView*' cast to ImTextureID. // - If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers // to ImGui::Image(), and gather width/height through your own functions, etc. // - You can use ShowMetricsWindow() to inspect the draw data that are being passed to your renderer, @@ -1791,14 +1790,19 @@ static void DemoWindowWidgetsImages() // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples + + // Grab the current texture identifier used by the font atlas. ImTextureRef my_tex_id = io.Fonts->TexRef; + + // Regular user code should never have to care about TexData-> fields, but since we want to display the entire texture here, we pull Width/Height from it. float my_tex_w = (float)io.Fonts->TexData->Width; float my_tex_h = (float)io.Fonts->TexData->Height; + { ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left - ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right + ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left + ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize)); ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); if (ImGui::BeginItemTooltip()) From a548cd9934e4064b9eb4c7565973499fc5357fa0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 19 Mar 2025 15:00:13 +0100 Subject: [PATCH 107/191] Fonts: avoid both ImTextureRef fields being set simultaneously. --- imgui.h | 5 +++-- imgui_draw.cpp | 6 ++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/imgui.h b/imgui.h index f8e5b6c55..320f4f1c1 100644 --- a/imgui.h +++ b/imgui.h @@ -3426,7 +3426,7 @@ struct IMGUI_API ImTextureData unsigned char* GetPixelsAt(int x, int y) { IM_ASSERT(Pixels != NULL); return Pixels + (x + y * Width) * BytesPerPixel; } int GetSizeInBytes() const { return Width * Height * BytesPerPixel; } int GetPitch() const { return Width * BytesPerPixel; } - ImTextureRef GetTexRef() { ImTextureRef tex_ref; tex_ref._TexData = this; tex_ref._TexID = TexID; return tex_ref; } // FIXME-TEXREF + ImTextureRef GetTexRef() { ImTextureRef tex_ref; tex_ref._TexData = this; tex_ref._TexID = ImTextureID_Invalid; return tex_ref; } ImTextureID GetTexID() const { return TexID; } // Called by Renderer backend @@ -3761,6 +3761,7 @@ struct ImFont // We added an indirection to avoid patching ImDrawCmd after texture updates but this could be a solution too. inline ImTextureID ImTextureRef::GetTexID() const { + IM_ASSERT(!(_TexData != NULL && _TexID != ImTextureID_Invalid)); return _TexData ? _TexData->TexID : _TexID; } @@ -3768,7 +3769,7 @@ inline ImTextureID ImDrawCmd::GetTexID() const { // If you are getting this assert: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) // must iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. - ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; + ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; // == TexRef.GetTexID() above. if (TexRef._TexData != NULL) IM_ASSERT(tex_id != ImTextureID_Invalid && "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!"); return tex_id; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index fa15be25e..8ecc2ecdb 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2615,7 +2615,6 @@ ImFontAtlas::ImFontAtlas() TexMaxWidth = 8192; TexMaxHeight = 8192; RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. - TexRef._TexData = NULL;// this; TexNextUniqueID = 1; FontNextUniqueID = 1; Builder = NULL; @@ -3882,9 +3881,8 @@ static void ImFontAtlasBuildSetTexture(ImFontAtlas* atlas, ImTextureData* tex) atlas->TexData = tex; atlas->TexUvScale = ImVec2(1.0f / tex->Width, 1.0f / tex->Height); atlas->TexRef._TexData = tex; - //atlas->TexID._TexID = tex->TexID; // <-- We intentionally don't do that and leave it 0, to allow late upload. - ImTextureRef new_tex_ref = atlas->TexRef; - ImFontAtlasUpdateDrawListsTextures(atlas, old_tex_ref, new_tex_ref); + //atlas->TexRef._TexID = tex->TexID; // <-- We intentionally don't do that. It would be misleading and betray promise that both fields aren't set. + ImFontAtlasUpdateDrawListsTextures(atlas, old_tex_ref, atlas->TexRef); } // Create a new texture, discard previous one From cb4c03756a825516f66ddd6e981ef284ab6a20ba Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 19 Mar 2025 19:57:03 +0100 Subject: [PATCH 108/191] Fonts: detect if backend assign to texture on creation but doesn't update Status. --- imgui_draw.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8ecc2ecdb..991b45f8d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2765,10 +2765,12 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count) tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; tex->UpdateRect.w = tex->UpdateRect.h = 0; } + 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."); if (tex->Status == ImTextureStatus_Destroyed) { - IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!"); if (tex->WantDestroyNextFrame) remove_from_list = true; // Destroy was scheduled by us else From 5460903f9674b9ed8fb31802f44032ca1cdff7aa Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 19 Mar 2025 20:12:02 +0100 Subject: [PATCH 109/191] Fonts: awkwardly alias old TexID name to TexRef using an union (may backtrack and just keep old name) --- imgui.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index 320f4f1c1..a04ac423a 100644 --- a/imgui.h +++ b/imgui.h @@ -3627,9 +3627,10 @@ struct ImFontAtlas void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). // Output - ImTextureRef TexRef; // Current texture identifier == TexData->GetTexRef(). #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImTextureRef& TexID = TexRef; // RENAMED in 1.92.x + union { ImTextureRef TexRef; ImTextureRef TexID; }; // Current texture identifier == TexData->GetTexRef(). // RENAMED TexID to TexRef in 1.92.x +#else + ImTextureRef TexRef; // Current texture identifier == TexData->GetTexRef(). #endif ImTextureData* TexData; // Current texture. From 2de15dc64bd3e6a3d4fc1746721b97b2cf6ef11b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 20 Mar 2025 15:44:35 +0100 Subject: [PATCH 110/191] Fonts: fixed legacy backend path preloading all sources sizes erroneously + failing to use ellipsis. --- imgui_draw.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 991b45f8d..e23431584 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3383,16 +3383,22 @@ void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas) { atlas->Builder->PreloadedAllGlyphsRanges = true; for (ImFont* font : atlas->Fonts) + { + ImFontConfig* src = &font->Sources[0]; + ImFontBaked* baked = font->GetFontBaked(src->SizePixels); + if (font->FallbackChar != 0) + baked->FindGlyph(font->FallbackChar); + if (font->EllipsisChar != 0) + baked->FindGlyph(font->EllipsisChar); for (int src_n = 0; src_n < font->SourcesCount; src_n++) { - ImFontConfig* src = &font->Sources[src_n]; - ImFontBaked* baked = font->GetFontBaked(src->SizePixels); + src = &font->Sources[src_n]; const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); - IM_ASSERT(ranges != NULL); for (; ranges[0]; ranges += 2) for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 baked->FindGlyph((ImWchar)c); } + } } void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) From 168b97c2912e7261bd26af09619cc1a60bc05954 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 20 Mar 2025 15:45:27 +0100 Subject: [PATCH 111/191] Fonts: removed size rounding in AddFont() which breaks relative sizing of merged fonts (8502) # Conflicts: # imgui.cpp --- imgui.cpp | 7 ++++++- imgui_draw.cpp | 12 ++++++------ imgui_internal.h | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 0e5a4c775..334ace32c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8628,7 +8628,12 @@ void ImGui::UpdateCurrentFontSize() final_size *= g.Font->Scale; if (ImGuiWindow* window = g.CurrentWindow) final_size *= window->FontWindowScale; - final_size = ImMax(1.0f, IM_ROUND(final_size)); + + // Round font size + // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. + // - We may support it better later and remove this rounding. + final_size = GetRoundedFontSize(final_size); + final_size = ImMax(1.0f, final_size); g.FontSize = final_size; g.FontBaked = (g.Font != NULL) ? g.Font->GetFontBaked(g.FontSize) : NULL; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e23431584..db95fd14d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3024,6 +3024,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) } // Sanity check + // We don't round cfg.SizePixels yet as relative size of merged fonts are used afterwards. if (font_cfg->GlyphExcludeRanges != NULL) { int size = 0; @@ -3039,12 +3040,6 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) } IM_ASSERT(font_cfg->FontLoaderData == NULL); - // Round font size - // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. - // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes. - // - We may support it better later and remove this rounding. - new_font_cfg.SizePixels = ImTrunc(new_font_cfg.SizePixels); - // Pointers to Sources are otherwise dangling ImFontAtlasBuildUpdatePointers(this); if (!ImFontAtlasBuildAddFont(this, &new_font_cfg)) @@ -5164,6 +5159,11 @@ ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size) ImFontBaked* ImFont::GetFontBaked(float size) { ImFontBaked* baked = LastBaked; + + // Round font size + // - ImGui::PushFontSize() will already round, but other paths calling GetFontBaked() directly also needs it (e.g. ImFontAtlasBuildPreloadAllGlyphRanges) + size = ImGui::GetRoundedFontSize(size); + if (baked && baked->Size == size) return baked; diff --git a/imgui_internal.h b/imgui_internal.h index 83a16aa9d..6c46fde5c 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3111,6 +3111,7 @@ namespace ImGui // Fonts, drawing IMGUI_API void SetCurrentFont(ImFont* font, float font_size); IMGUI_API void UpdateCurrentFontSize(); + inline float GetRoundedFontSize(float size) { return IM_ROUND(size); } inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } IMGUI_API void PushPasswordFont(); IMGUI_API void PopPasswordFont(); From 44498825cd9a5bf8c3f2e7c5b4bb439b537fb91e Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 21 Mar 2025 19:13:23 +0100 Subject: [PATCH 112/191] (Breaking) Fonts: PushFont() default to preserve current font size. --- imgui.cpp | 11 ++++++++--- imgui.h | 12 +++++++++--- imgui_draw.cpp | 3 ++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 334ace32c..20ebdc56a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8582,7 +8582,7 @@ void ImGui::UpdateFontsNewFrame() atlas->Locked = true; // We do this really unusual thing of calling *push_front()*, the reason behind that we want to support the PushFont()/NewFrame()/PopFont() idiom. - ImFontStackData font_stack_data = { ImGui::GetDefaultFont(), ImGui::GetDefaultFont()->Sources[0].SizePixels }; + ImFontStackData font_stack_data = { ImGui::GetDefaultFont(), ImGui::GetDefaultFont()->DefaultSize }; g.FontStack.push_front(font_stack_data); if (g.FontStack.Size == 1) ImGui::SetCurrentFont(font_stack_data.Font, font_stack_data.FontSize); @@ -8647,8 +8647,13 @@ void ImGui::PushFont(ImFont* font, float font_size) ImGuiContext& g = *GImGui; if (font == NULL) font = GetDefaultFont(); - if (font_size < 0.0f) - font_size = font->Sources[0].SizePixels; // g.FontSize; + if (font_size <= 0.0f) + { + if (font->Flags & ImFontFlags_UseDefaultSize) + font_size = font->DefaultSize; // Legacy: use default font size. Same as doing PushFont(font, font->DefaultSize). // FIXME-NEWATLAS + else + font_size = g.FontSizeBeforeScaling; // Keep current font size + } g.FontStack.push_back({ font, font_size }); SetCurrentFont(font, font_size); } diff --git a/imgui.h b/imgui.h index a04ac423a..5e1737a5d 100644 --- a/imgui.h +++ b/imgui.h @@ -491,9 +491,13 @@ namespace ImGui IMGUI_API void SetScrollFromPosY(float local_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. // Parameters stacks (font) - IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. + // *IMPORTANT* before 1.92, fonts had a single size. They can now be dynamically be adjusted. + // - Before 1.92: PushFont() always used font default size. + // - Since 1.92: PushFont() preserve the current shared font size. + // - To use old behavior: use 'PushFont(font, font->DefaultSize)' in call site, or set 'ImFontConfig::Flags |= ImFontFlags_UseDefaultSize' before calling AddFont(). + IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. Use font->DefaultSize to revert to font default size. IMGUI_API void PopFont(); - IMGUI_API void PushFontSize(float size); + IMGUI_API void PushFontSize(float font_size); IMGUI_API void PopFontSize(); // Parameters stacks (shared) @@ -3709,6 +3713,7 @@ enum ImFontFlags_ ImFontFlags_LockBakedSizes = 1 << 0, // Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. ImFontFlags_NoLoadGlyphs = 1 << 1, // Disable loading new glyphs. ImFontFlags_NoLoadError = 1 << 2, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. + ImFontFlags_UseDefaultSize = 1 << 3, // Legacy compatibility: make PushFont() calls without explicit size use font->DefaultSize instead of current font size. }; // Font runtime data and rendering @@ -3726,6 +3731,7 @@ struct ImFont // [Internal] Members: Cold ~24-52 bytes // Conceptually Sources[] is the list of font sources merged to create this font. ImGuiID FontId; // Unique identifier for the font + float DefaultSize; // 4 // in // Default font size short SourcesCount; // 2 // in // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont. ImFontConfig* Sources; // 4-8 // in // Pointer within ContainerAtlas->Sources[], to SourcesCount instances ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). @@ -3750,7 +3756,7 @@ struct ImFont IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip = NULL); IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(Sources[0].SizePixels * scale, text, text_end, wrap_width); } + inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(DefaultSize * scale, text, text_end, wrap_width); } #endif // [Internal] Don't use! diff --git a/imgui_draw.cpp b/imgui_draw.cpp index db95fd14d..4a7bff240 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3005,6 +3005,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) font = IM_NEW(ImFont)(); font->FontId = FontNextUniqueID++; font->Flags = font_cfg->Flags; + font->DefaultSize = font_cfg->SizePixels; Fonts.push_back(font); } else @@ -3248,7 +3249,7 @@ int ImFontAtlas::AddCustomRectRegular(int width, int height) // myfont->Flags |= ImFontFlags_LockBakedSizes; int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { - float font_size = font->Sources[0].SizePixels; + float font_size = font->DefaultSize; return AddCustomRectFontGlyphForSize(font, font_size, codepoint, width, height, advance_x, offset); } // FIXME: we automatically set glyph.Colored=true by default. From 9324961cd28bb2690a6f9867ba098c771750769c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 26 Mar 2025 14:38:49 +0100 Subject: [PATCH 113/191] Fonts: fixed calling AddFontXXX not invalidating texture for legacy backends. --- imgui_draw.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 4a7bff240..8535c32c2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3580,6 +3580,7 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) if (!loader->FontSrcInit(atlas, src)) return false; + atlas->TexIsBuilt = false; // For legacy backends ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); return true; } From fc870811333e4d6d2d8f0e78e26247762020d5f4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 22 Mar 2025 15:15:10 +0100 Subject: [PATCH 114/191] Fonts: fixed GetCustomRectUV(). Fixing typo. Broken by fe598f7 --- imgui_draw.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8535c32c2..fe639e99b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2501,7 +2501,7 @@ void ImTextureData::DestroyPixels() // - ImFontAtlas::AddCustomRectRegular() // - ImFontAtlas::AddCustomRectFontGlyph() // - ImFontAtlas::GetCustomRect() -// - ImFontAtlas::CalcCustomRectUV() +// - ImFontAtlas::GetCustomRectUV() // - ImFontAtlasGetMouseCursorTexData() //----------------------------------------------------------------------------- // - ImFontAtlasBuildMain() @@ -3298,8 +3298,10 @@ const ImTextureRect* ImFontAtlas::GetCustomRect(int id) void ImFontAtlas::GetCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const { IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates - *out_uv_min = ImVec2((float)rect->x * TexUvScale.x, (float)rect->y * TexUvScale.y); - *out_uv_max = ImVec2((float)(rect->x + rect->w) * TexUvScale.x, (float)(rect->y + rect->w) * TexUvScale.y); + ImVec2 pos = ImVec2((float)rect->x, (float)rect->y); + ImVec2 size = ImVec2((float)rect->w, (float)rect->h); + *out_uv_min = (pos) * TexUvScale; + *out_uv_max = (pos + size) * TexUvScale; } bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) From 253dff76565b10491ccc2205706b033bb7f209af Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 30 Mar 2025 16:00:29 +0200 Subject: [PATCH 115/191] Fonts: Comments. --- imgui.h | 41 +++++++++++++++++++++++------------------ imgui_internal.h | 2 +- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/imgui.h b/imgui.h index 5e1737a5d..ae488a177 100644 --- a/imgui.h +++ b/imgui.h @@ -3484,9 +3484,9 @@ struct ImFontGlyph unsigned int Colored : 1; // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops) unsigned int Visible : 1; // Flag to indicate glyph has no visible pixels (e.g. space). Allow early out when rendering. unsigned int Codepoint : 30; // 0x0000..0x10FFFF - float AdvanceX; // Horizontal distance to advance layout with - float X0, Y0, X1, Y1; // Glyph corners - float U0, V0, U1, V1; // Texture coordinates + float AdvanceX; // Horizontal distance to advance cursor/layout position. + float X0, Y0, X1, Y1; // Glyph corners. Offsets from current cursor/layout position. + float U0, V0, U1, V1; // Texture coordinates for the current value of ImFontAtlas->TexRef. Cached equivalent of calling GetCustomRectUV() with PackId. int PackId; // [Internal] ImFontAtlasRectId value (FIXME: Cold data, could be moved elsewhere?) ImFontGlyph() { memset(this, 0, sizeof(*this)); PackId = -1; } @@ -3600,21 +3600,23 @@ struct ImFontAtlas // Register and retrieve custom rectangles // - You can request arbitrary rectangles to be packed into the atlas, for your own purpose. - // - You can request your rectangles to be mapped as font glyph (given a font + Unicode point), - // so you can render e.g. custom colorful icons and use them as regular glyphs. - // - Since 1.92.X, packing is done immediately in the function call. - // - You can render your pixels into the texture right after calling the AddCustomRectXXX() functions. + // - Since 1.92.X, packing is done immediately in the function call (previously packing was done during the Build call) + // - You can render your pixels into the texture right after calling the AddCustomRect() functions. // - Texture may be resized, so you cannot cache UV coordinates: always use GetCustomRectUV()! - // - If you render colored output into your AddCustomRectRegular() rectangle: set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. + // VERY IMPORTANT: + // - RECTANGLE DATA AND UV COORDINATES MAY BE INVALIDATED BY *ANY* CALL TO IMGUI FUNCTIONS (e.g. ImGui::Text call) OR BY atlas->AddCustomRectegular(). NEVER CACHE THOSE!!! + // - RECTANGLE DATA AND UV COORDINATES ARE ASSOCIATED TO THE CURRENT TEXTURE IDENTIFIER AKA 'atlas->TexRef'. Both are typically invalidated at the same time. + // - If you render colored output into your AddCustomRect() rectangle: set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. // - Note: this API may be reworked further in order to facilitate supporting e.g. multi-monitor, varying DPI settings. - // - Pre-1.92 names: - // - AddCustomRectFontGlyph() --> Use custom ImFontLoader inside ImFontConfig + // - (Pre-1.92 names) ------------> (1.92 names) // - GetCustomRectByIndex() --> Use GetCustomRect() // - CalcCustomRectUV() --> Use GetCustomRectUV() + // - AddCustomRectFontGlyph() --> Prefer using custom ImFontLoader inside ImFontConfig + // - ImFontAtlasCustomRect --> ImTextureRect IMGUI_API int AddCustomRectRegular(int width, int height); // Register a rectangle. Return -1 on error. - IMGUI_API const ImTextureRect* GetCustomRect(int id); // Get rectangle coordinate in current texture. - IMGUI_API void GetCustomRectUV(const ImTextureRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const; // Get UV coordinates for a given rectangle + IMGUI_API const ImTextureRect* GetCustomRect(int id); // Get rectangle coordinate in current texture. Valid immediately, never store this (read above)! + IMGUI_API void GetCustomRectUV(const ImTextureRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const; // Get UV coordinates for a given rectangle. Valid immediately, never store this (read above)! //------------------------------------------- // Members @@ -3631,12 +3633,14 @@ struct ImFontAtlas void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). // Output -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - union { ImTextureRef TexRef; ImTextureRef TexID; }; // Current texture identifier == TexData->GetTexRef(). // RENAMED TexID to TexRef in 1.92.x + // - Because textures are dynamically created/resized, the current texture identifier may changed at *ANY TIME* during the frame. + // - This should not affect you as you can always use the latest value. But note that any precomputed UV coordinates are only valid for the current TexRef. +#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImTextureRef TexRef; // Latest texture identifier == TexData->GetTexRef(). #else - ImTextureRef TexRef; // Current texture identifier == TexData->GetTexRef(). + union { ImTextureRef TexRef; ImTextureRef TexID; }; // Latest texture identifier == TexData->GetTexRef(). // RENAMED TexID to TexRef in 1.92.x #endif - ImTextureData* TexData; // Current texture. + ImTextureData* TexData; // Latest texture. // [Internal] ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetDrawData().Textures[]/GetPlatformIO().Textures[] instead! @@ -3644,8 +3648,8 @@ struct ImFontAtlas bool RendererHasTextures;// Copy of (BackendFlags & ImGuiBackendFlags_RendererHasTextures) from supporting context. bool TexIsBuilt; // Set when texture was built matching current font input. Mostly useful for legacy IsBuilt() call. bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. - ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight) - ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight). May change as new texture gets created. + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created. ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines @@ -3661,6 +3665,7 @@ struct ImFontAtlas // [Obsolete] #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy: You can request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. --> Prefer using a custom ImFontLoader. IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X inline const ImTextureRect* GetCustomRectByIndex(int id) { return GetCustomRect(id); } // OBSOLETED in 1.92.X diff --git a/imgui_internal.h b/imgui_internal.h index 6c46fde5c..aa5da5777 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3695,7 +3695,7 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); //----------------------------------------------------------------------------- // An identifier to a rectangle in the atlas. -1 when invalid. -// The rectangle may move, use GetCustomRect() to retrieve it. +// The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it. typedef int ImFontAtlasRectId; // Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) From f40274702dcf15985c257b6b32677bfe7bae1595 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Mar 2025 17:03:03 +0200 Subject: [PATCH 116/191] (Breaking) Fonts: renamed AddCustomRectRegular() -> AddCustomRect(). --- imgui.h | 4 +++- imgui_draw.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index ae488a177..cbf353dd8 100644 --- a/imgui.h +++ b/imgui.h @@ -3612,9 +3612,10 @@ struct ImFontAtlas // - (Pre-1.92 names) ------------> (1.92 names) // - GetCustomRectByIndex() --> Use GetCustomRect() // - CalcCustomRectUV() --> Use GetCustomRectUV() + // - AddCustomRectRegular() --> Renamed to AddCustomRect() // - AddCustomRectFontGlyph() --> Prefer using custom ImFontLoader inside ImFontConfig // - ImFontAtlasCustomRect --> ImTextureRect - IMGUI_API int AddCustomRectRegular(int width, int height); // Register a rectangle. Return -1 on error. + IMGUI_API int AddCustomRect(int width, int height); // Register a rectangle. Return -1 on error. IMGUI_API const ImTextureRect* GetCustomRect(int id); // Get rectangle coordinate in current texture. Valid immediately, never store this (read above)! IMGUI_API void GetCustomRectUV(const ImTextureRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const; // Get UV coordinates for a given rectangle. Valid immediately, never store this (read above)! @@ -3666,6 +3667,7 @@ struct ImFontAtlas // [Obsolete] #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy: You can request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. --> Prefer using a custom ImFontLoader. + IMGUI_API int AddCustomRectRegular(int width, int height) { return AddCustomRect(width, height); } // RENAMED in 1.92.X IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X inline const ImTextureRect* GetCustomRectByIndex(int id) { return GetCustomRect(id); } // OBSOLETED in 1.92.X diff --git a/imgui_draw.cpp b/imgui_draw.cpp index fe639e99b..0a39cfabe 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2498,7 +2498,7 @@ void ImTextureData::DestroyPixels() // - ImFontAtlas::RemoveFont() // - ImFontAtlasBuildNotifySetFont() //----------------------------------------------------------------------------- -// - ImFontAtlas::AddCustomRectRegular() +// - ImFontAtlas::AddCustomRect() // - ImFontAtlas::AddCustomRectFontGlyph() // - ImFontAtlas::GetCustomRect() // - ImFontAtlas::GetCustomRectUV() @@ -3223,7 +3223,7 @@ void ImFontAtlas::RemoveFont(ImFont* font) ImFontAtlasBuildNotifySetFont(this, font, new_current_font); } -int ImFontAtlas::AddCustomRectRegular(int width, int height) +int ImFontAtlas::AddCustomRect(int width, int height) { IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); From db30e1b5b6d55423a967fe6b6947bf6296f08786 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Mar 2025 17:36:24 +0200 Subject: [PATCH 117/191] (Breaking) Fonts: rework GetCustomRect() api. Reintroduce ImFontAtlasRect. --- imgui.h | 48 ++++++++++++++++++++++++++++++------------------ imgui_draw.cpp | 22 +++++++++++----------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/imgui.h b/imgui.h index cbf353dd8..fbd9d5b6c 100644 --- a/imgui.h +++ b/imgui.h @@ -172,6 +172,7 @@ struct ImDrawVert; // A single vertex (pos + uv + col = 20 byte struct ImFont; // Runtime data for a single font within a parent ImFontAtlas struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader struct ImFontAtlasBuilder; // Opaque storage for building a ImFontAtlas +struct ImFontAtlasRect; // Output of ImFontAtlas::GetCustomRect() when using custom rectangles. struct ImFontBaked; // Baked data for a ImFont at a given size. struct ImFontConfig; // Configuration data when adding a font or merging fonts struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) @@ -3486,7 +3487,7 @@ struct ImFontGlyph unsigned int Codepoint : 30; // 0x0000..0x10FFFF float AdvanceX; // Horizontal distance to advance cursor/layout position. float X0, Y0, X1, Y1; // Glyph corners. Offsets from current cursor/layout position. - float U0, V0, U1, V1; // Texture coordinates for the current value of ImFontAtlas->TexRef. Cached equivalent of calling GetCustomRectUV() with PackId. + float U0, V0, U1, V1; // Texture coordinates for the current value of ImFontAtlas->TexRef. Cached equivalent of calling GetCustomRect() with PackId. int PackId; // [Internal] ImFontAtlasRectId value (FIXME: Cold data, could be moved elsewhere?) ImFontGlyph() { memset(this, 0, sizeof(*this)); PackId = -1; } @@ -3508,6 +3509,17 @@ struct ImFontGlyphRangesBuilder IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges }; +// Output of ImFontAtlas::GetCustomRect() when using custom rectangles. +// Those values may not be cached/stored as they are only valid for the current value of atlas->TexRef +struct ImFontAtlasRect +{ + unsigned short x, y; // Position (in current texture) + unsigned short w, h; // Size + ImVec2 uv0, uv1; // UV coordinates (in current texture) + + ImFontAtlasRect() { memset(this, 0, sizeof(*this)); } +}; + // Flags for ImFontAtlas build enum ImFontAtlasFlags_ { @@ -3602,22 +3614,21 @@ struct ImFontAtlas // - You can request arbitrary rectangles to be packed into the atlas, for your own purpose. // - Since 1.92.X, packing is done immediately in the function call (previously packing was done during the Build call) // - You can render your pixels into the texture right after calling the AddCustomRect() functions. - // - Texture may be resized, so you cannot cache UV coordinates: always use GetCustomRectUV()! - // VERY IMPORTANT: - // - RECTANGLE DATA AND UV COORDINATES MAY BE INVALIDATED BY *ANY* CALL TO IMGUI FUNCTIONS (e.g. ImGui::Text call) OR BY atlas->AddCustomRectegular(). NEVER CACHE THOSE!!! - // - RECTANGLE DATA AND UV COORDINATES ARE ASSOCIATED TO THE CURRENT TEXTURE IDENTIFIER AKA 'atlas->TexRef'. Both are typically invalidated at the same time. - // - If you render colored output into your AddCustomRect() rectangle: set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. + // - VERY IMPORTANT: + // - Texture may be created/resized at any time when calling ImGui or ImFontAtlas functions. + // - IT WILL INVALIDATE RECTANGLE DATA SUCH AS UV COORDINATES. Always use latest values from GetCustomRect(). + // - UV coordinates are associated to the current texture identifier aka 'atlas->TexRef'. Both TexRef and UV coordinates are typically changed at the same time. + // - If you render colored output into your custom rectangles: set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. // - Note: this API may be reworked further in order to facilitate supporting e.g. multi-monitor, varying DPI settings. // - (Pre-1.92 names) ------------> (1.92 names) // - GetCustomRectByIndex() --> Use GetCustomRect() - // - CalcCustomRectUV() --> Use GetCustomRectUV() + // - CalcCustomRectUV() --> Use GetCustomRect() and read uv0, uv1 fields. // - AddCustomRectRegular() --> Renamed to AddCustomRect() // - AddCustomRectFontGlyph() --> Prefer using custom ImFontLoader inside ImFontConfig - // - ImFontAtlasCustomRect --> ImTextureRect - IMGUI_API int AddCustomRect(int width, int height); // Register a rectangle. Return -1 on error. - IMGUI_API const ImTextureRect* GetCustomRect(int id); // Get rectangle coordinate in current texture. Valid immediately, never store this (read above)! - IMGUI_API void GetCustomRectUV(const ImTextureRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const; // Get UV coordinates for a given rectangle. Valid immediately, never store this (read above)! + // - ImFontAtlasCustomRect --> Renamed to ImFontAtlasRect + IMGUI_API int AddCustomRect(int width, int height); // Register a rectangle. Return -1 on error. + IMGUI_API bool GetCustomRect(int id, ImFontAtlasRect* out_r) const; // Get rectangle coordinates for current texture. Valid immediately, never store this (read above)! //------------------------------------------- // Members @@ -3667,14 +3678,15 @@ struct ImFontAtlas // [Obsolete] #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy: You can request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. --> Prefer using a custom ImFontLoader. - IMGUI_API int AddCustomRectRegular(int width, int height) { return AddCustomRect(width, height); } // RENAMED in 1.92.X - IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig - IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X - inline const ImTextureRect* GetCustomRectByIndex(int id) { return GetCustomRect(id); } // OBSOLETED in 1.92.X - inline void CalcCustomRectUV(const ImTextureRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const { return GetCustomRectUV(r, out_uv_min, out_uv_max); } // OBSOLETED in 1.92.X + ImFontAtlasRect TempRect; // For old GetCustomRectByIndex() API + inline int AddCustomRectRegular(int w, int h) { return AddCustomRect(w, h); } // RENAMED in 1.92.X + inline const ImFontAtlasRect* GetCustomRectByIndex(int id) { return GetCustomRect(id, &TempRect) ? &TempRect : NULL; } // OBSOLETED in 1.92.X + inline void CalcCustomRectUV(const ImFontAtlasRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const { *out_uv_min = r->uv0; *out_uv_max = r->uv1; } // OBSOLETED in 1.92.X + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig + IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // ADDED AND OBSOLETED in 1.92.X #endif //int TexDesiredWidth; // OBSOLETED in 1.92.X (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) - //typedef ImTextureRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.X + //typedef ImFontAtlasRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.X //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ }; @@ -3996,7 +4008,7 @@ namespace ImGui // - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h // - ImFontAtlasCustomRect::GlyphColored --> if you need to write to this, instead you can write to 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph() // We could make ImTextureRect an union to use old names, but 1) this would be confusing 2) the fix is easy 3) ImFontAtlasCustomRect was always a rather esoteric api. -typedef ImTextureRect ImFontAtlasCustomRect; +typedef ImFontAtlasRect ImFontAtlasCustomRect; /*struct ImFontAtlasCustomRect { unsigned short X, Y; // Output // Packed position in Atlas diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0a39cfabe..f6849701c 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2501,7 +2501,6 @@ void ImTextureData::DestroyPixels() // - ImFontAtlas::AddCustomRect() // - ImFontAtlas::AddCustomRectFontGlyph() // - ImFontAtlas::GetCustomRect() -// - ImFontAtlas::GetCustomRectUV() // - ImFontAtlasGetMouseCursorTexData() //----------------------------------------------------------------------------- // - ImFontAtlasBuildMain() @@ -3290,18 +3289,19 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im } #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -const ImTextureRect* ImFontAtlas::GetCustomRect(int id) -{ - return ImFontAtlasPackGetRect(this, (ImFontAtlasRectId)id); -} - -void ImFontAtlas::GetCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const +bool ImFontAtlas::GetCustomRect(int id, ImFontAtlasRect* out_r) const { + ImTextureRect* r = ImFontAtlasPackGetRect((ImFontAtlas*)this, (ImFontAtlasRectId)id); + if (r == NULL) + return false; IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates - ImVec2 pos = ImVec2((float)rect->x, (float)rect->y); - ImVec2 size = ImVec2((float)rect->w, (float)rect->h); - *out_uv_min = (pos) * TexUvScale; - *out_uv_max = (pos + size) * TexUvScale; + out_r->x = r->x; + out_r->y = r->y; + out_r->w = r->w; + out_r->h = r->h; + out_r->uv0 = ImVec2((float)(r->x), (float)(r->y)) * TexUvScale; + out_r->uv1 = ImVec2((float)(r->x + r->w), (float)(r->y + r->h)) * TexUvScale; + return true; } bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) From 69d28f867cdc04e2ade221ea14304058fe4055d7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Mar 2025 18:36:32 +0200 Subject: [PATCH 118/191] Fonts: added ImFontAtlasRectId_Invalid == -1 --- imgui_draw.cpp | 34 +++++++++++++++++----------------- imgui_internal.h | 1 + 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f6849701c..7556fc593 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3231,8 +3231,8 @@ int ImFontAtlas::AddCustomRect(int width, int height) ImFontAtlasBuildInit(this); ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); - if (r_id == -1) - return -1; + if (r_id == ImFontAtlasRectId_Invalid) + return ImFontAtlasRectId_Invalid; ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); if (RendererHasTextures) ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); @@ -3265,8 +3265,8 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im ImFontBaked* baked = font->GetFontBaked(font_size); ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); - if (r_id == -1) - return -1; + if (r_id == ImFontAtlasRectId_Invalid) + return ImFontAtlasRectId_Invalid; ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); if (RendererHasTextures) ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); @@ -3452,7 +3452,7 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_ if (add_and_draw) builder->PackIdMouseCursors = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); - if (builder->PackIdMouseCursors == -1) + if (builder->PackIdMouseCursors == ImFontAtlasRectId_Invalid) return; ImTextureRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdMouseCursors); @@ -3488,7 +3488,7 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ ImFontAtlasBuilder* builder = atlas->Builder; if (add_and_draw) builder->PackIdLinesTexData = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); - if (builder->PackIdLinesTexData == -1) + if (builder->PackIdLinesTexData == ImFontAtlasRectId_Invalid) return; ImTextureRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdLinesTexData); @@ -3711,10 +3711,10 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, Im void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph) { - if (glyph->PackId != -1) + if (glyph->PackId != ImFontAtlasRectId_Invalid) { ImFontAtlasPackDiscardRect(atlas, glyph->PackId); - glyph->PackId = -1; + glyph->PackId = ImFontAtlasRectId_Invalid; } ImWchar c = (ImWchar)glyph->Codepoint; IM_ASSERT(font->FallbackChar != c && font->EllipsisChar != c); // Unsupported for simplicity @@ -3786,7 +3786,7 @@ void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBa IMGUI_DEBUG_LOG_FONT("[font] Discard baked %.2f for \"%s\"\n", baked->Size, font->GetDebugName()); for (ImFontGlyph& glyph : baked->Glyphs) - if (glyph.PackId != -1) + if (glyph.PackId != ImFontAtlasRectId_Invalid) ImFontAtlasPackDiscardRect(atlas, glyph.PackId); char* loader_data_p = (char*)baked->FontLoaderDatas; @@ -3974,7 +3974,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) if (old_r.w == 0 && old_r.h == 0) continue; ImFontAtlasRectId new_r_id = ImFontAtlasPackAddRect(atlas, old_r.w, old_r.h, &index_entry); - if (new_r_id == -1) + if (new_r_id == ImFontAtlasRectId_Invalid) { // Undo, grow texture and try repacking again. // FIXME-NEWATLAS-TESTS: This is a very rarely exercised path! It needs to be automatically tested properly. @@ -3997,7 +3997,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) // Patch glyphs UV for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) for (ImFontGlyph& glyph : builder->BakedPool[baked_n].Glyphs) - if (glyph.PackId != -1) + if (glyph.PackId != ImFontAtlasRectId_Invalid) { ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); glyph.U0 = (r->x) * atlas->TexUvScale.x; @@ -4240,7 +4240,7 @@ static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int r // This is expected to be called in batches and followed by a repack void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { - IM_ASSERT(id != -1); + IM_ASSERT(id != ImFontAtlasRectId_Invalid); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; IM_ASSERT(index_entry->Used && index_entry->TargetIndex >= 0); @@ -4286,7 +4286,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon if (attempts_remaining == 0 || builder->LockDisableResize) { IMGUI_DEBUG_LOG_FONT("[font] Failed packing %dx%d rectangle. Returning fallback.\n", w, h); - return -1; + return ImFontAtlasRectId_Invalid; } // Resize or repack atlas! (this should be a rare event) @@ -4315,7 +4315,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon // Important: don'return pointer valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { - IM_ASSERT(id != -1); + IM_ASSERT(id != ImFontAtlasRectId_Invalid); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; IM_ASSERT(index_entry->Used); @@ -4546,10 +4546,10 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, const int w = (x1 - x0 + oversample_h - 1); const int h = (y1 - y0 + oversample_v - 1); ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); - if (pack_id == -1) + if (pack_id == ImFontAtlasRectId_Invalid) { // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) - IM_ASSERT_USER_ERROR(pack_id != -1, "Out of texture memory."); + IM_ASSERT_USER_ERROR(pack_id != ImFontAtlasRectId_Invalid, "Out of texture memory."); return NULL; } ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); @@ -4998,7 +4998,7 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked IM_ASSERT(baked->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. // Set UV from packed rectangle - if (in_glyph->PackId != -1) + if (in_glyph->PackId != ImFontAtlasRectId_Invalid) { ImTextureRect* r = ImFontAtlasPackGetRect(atlas, in_glyph->PackId); IM_ASSERT(in_glyph->U0 == 0.0f && in_glyph->V0 == 0.0f && in_glyph->U1 == 0.0f && in_glyph->V1 == 0.0f); diff --git a/imgui_internal.h b/imgui_internal.h index aa5da5777..a064a8dab 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3697,6 +3697,7 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // An identifier to a rectangle in the atlas. -1 when invalid. // The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it. typedef int ImFontAtlasRectId; +#define ImFontAtlasRectId_Invalid -1 // Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) // User are returned ImFontAtlasRectId values which are meant to be persistent. From e9cf3de58ff7e53e82d08bfd2dc7615bcb8a002e Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Mar 2025 18:37:11 +0200 Subject: [PATCH 119/191] Fonts: moved ImFontAtlasRectId back to public API. --- imgui.h | 18 ++++++++++++------ imgui_draw.cpp | 6 +++--- imgui_internal.h | 5 ----- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/imgui.h b/imgui.h index fbd9d5b6c..ce5decfa4 100644 --- a/imgui.h +++ b/imgui.h @@ -3509,8 +3509,14 @@ struct ImFontGlyphRangesBuilder IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges }; +// An identifier to a rectangle in the atlas. -1 when invalid. +// The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it. +typedef int ImFontAtlasRectId; +#define ImFontAtlasRectId_Invalid -1 + // Output of ImFontAtlas::GetCustomRect() when using custom rectangles. // Those values may not be cached/stored as they are only valid for the current value of atlas->TexRef +// (this is in theory derived from ImTextureRect but we use separate structures for reasons) struct ImFontAtlasRect { unsigned short x, y; // Position (in current texture) @@ -3627,8 +3633,8 @@ struct ImFontAtlas // - AddCustomRectRegular() --> Renamed to AddCustomRect() // - AddCustomRectFontGlyph() --> Prefer using custom ImFontLoader inside ImFontConfig // - ImFontAtlasCustomRect --> Renamed to ImFontAtlasRect - IMGUI_API int AddCustomRect(int width, int height); // Register a rectangle. Return -1 on error. - IMGUI_API bool GetCustomRect(int id, ImFontAtlasRect* out_r) const; // Get rectangle coordinates for current texture. Valid immediately, never store this (read above)! + IMGUI_API ImFontAtlasRectId AddCustomRect(int width, int height); // Register a rectangle. Return -1 (ImFontAtlasRectId_Invalid) on error. + IMGUI_API bool GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const; // Get rectangle coordinates for current texture. Valid immediately, never store this (read above)! //------------------------------------------- // Members @@ -3679,11 +3685,11 @@ struct ImFontAtlas #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy: You can request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. --> Prefer using a custom ImFontLoader. ImFontAtlasRect TempRect; // For old GetCustomRectByIndex() API - inline int AddCustomRectRegular(int w, int h) { return AddCustomRect(w, h); } // RENAMED in 1.92.X - inline const ImFontAtlasRect* GetCustomRectByIndex(int id) { return GetCustomRect(id, &TempRect) ? &TempRect : NULL; } // OBSOLETED in 1.92.X + inline ImFontAtlasRectId AddCustomRectRegular(int w, int h) { return AddCustomRect(w, h); } // RENAMED in 1.92.X + inline const ImFontAtlasRect* GetCustomRectByIndex(ImFontAtlasRectId id) { return GetCustomRect(id, &TempRect) ? &TempRect : NULL; } // OBSOLETED in 1.92.X inline void CalcCustomRectUV(const ImFontAtlasRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const { *out_uv_min = r->uv0; *out_uv_max = r->uv1; } // OBSOLETED in 1.92.X - IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig - IMGUI_API int AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // ADDED AND OBSOLETED in 1.92.X + IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig + IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // ADDED AND OBSOLETED in 1.92.X #endif //int TexDesiredWidth; // OBSOLETED in 1.92.X (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) //typedef ImFontAtlasRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.X diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7556fc593..83db3576b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3222,7 +3222,7 @@ void ImFontAtlas::RemoveFont(ImFont* font) ImFontAtlasBuildNotifySetFont(this, font, new_current_font); } -int ImFontAtlas::AddCustomRect(int width, int height) +ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height) { IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); @@ -3289,9 +3289,9 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im } #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -bool ImFontAtlas::GetCustomRect(int id, ImFontAtlasRect* out_r) const +bool ImFontAtlas::GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const { - ImTextureRect* r = ImFontAtlasPackGetRect((ImFontAtlas*)this, (ImFontAtlasRectId)id); + ImTextureRect* r = ImFontAtlasPackGetRect((ImFontAtlas*)this, id); if (r == NULL) return false; IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates diff --git a/imgui_internal.h b/imgui_internal.h index a064a8dab..7389c0216 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3694,11 +3694,6 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- -// An identifier to a rectangle in the atlas. -1 when invalid. -// The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it. -typedef int ImFontAtlasRectId; -#define ImFontAtlasRectId_Invalid -1 - // Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) // User are returned ImFontAtlasRectId values which are meant to be persistent. // We handle this with an indirection. While Rects[] may be in theory shuffled, compacted etc., RectsIndex[] cannot it is keyed by ImFontAtlasRectId. From 23dc46c4f8bf6aaf83d70f54441916404bf1aba5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Mar 2025 19:24:59 +0200 Subject: [PATCH 120/191] Fonts: added RemoveCustomRect(). + internally add ImFontAtlasPackReuseRectEntry() --- imgui.h | 1 + imgui_draw.cpp | 29 +++++++++++++++++++---------- imgui_internal.h | 4 ++-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/imgui.h b/imgui.h index ce5decfa4..a3d70bb98 100644 --- a/imgui.h +++ b/imgui.h @@ -3634,6 +3634,7 @@ struct ImFontAtlas // - AddCustomRectFontGlyph() --> Prefer using custom ImFontLoader inside ImFontConfig // - ImFontAtlasCustomRect --> Renamed to ImFontAtlasRect IMGUI_API ImFontAtlasRectId AddCustomRect(int width, int height); // Register a rectangle. Return -1 (ImFontAtlasRectId_Invalid) on error. + IMGUI_API void RemoveCustomRect(ImFontAtlasRectId id); // Unregister a rectangle. Existing pixels will stay in texture until resized / garbage collected. IMGUI_API bool GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const; // Get rectangle coordinates for current texture. Valid immediately, never store this (read above)! //------------------------------------------- diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 83db3576b..7cc1351fb 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2499,8 +2499,10 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildNotifySetFont() //----------------------------------------------------------------------------- // - ImFontAtlas::AddCustomRect() -// - ImFontAtlas::AddCustomRectFontGlyph() +// - ImFontAtlas::RemoveCustomRect() // - ImFontAtlas::GetCustomRect() +// - ImFontAtlas::AddCustomRectFontGlyph() [legacy] +// - ImFontAtlas::AddCustomRectFontGlyphForSize() [legacy] // - ImFontAtlasGetMouseCursorTexData() //----------------------------------------------------------------------------- // - ImFontAtlasBuildMain() @@ -2538,6 +2540,7 @@ void ImTextureData::DestroyPixels() //----------------------------------------------------------------------------- // - ImFontAtlasPackInit() // - ImFontAtlasPackAllocRectEntry() +// - ImFontAtlasPackReuseRectEntry() // - ImFontAtlasPackDiscardRect() // - ImFontAtlasPackAddRect() // - ImFontAtlasPackGetRect() @@ -3239,6 +3242,12 @@ ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height) return r_id; } +void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id) +{ + IM_ASSERT(id != ImFontAtlasRectId_Invalid); + ImFontAtlasPackDiscardRect(this, id); +} + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // This API does not make sense anymore with scalable fonts. // - Prefer adding a font source (ImFontConfig) using a custom/procedural loader. @@ -4095,7 +4104,7 @@ ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) return ImVec2i(new_tex_w, new_tex_h); } -// Clear all output. Invalidates all AddCustomRectXXX return values. +// Clear all output. Invalidates all AddCustomRect() return values! void ImFontAtlasBuildClear(ImFontAtlas* atlas) { ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); @@ -4237,6 +4246,13 @@ static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int r return (ImFontAtlasRectId)index_idx; } +static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFontAtlasRectEntry* overwrite_entry) +{ + IM_ASSERT(overwrite_entry->Used); + overwrite_entry->TargetIndex = atlas->Builder->Rects.Size - 1; + return atlas->Builder->RectsIndex.index_from_ptr(overwrite_entry); +} + // This is expected to be called in batches and followed by a repack void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { @@ -4300,16 +4316,9 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon builder->Rects.push_back(r); if (overwrite_entry != NULL) - { - // Write into an existing entry instead of adding one (used during repack) - IM_ASSERT(overwrite_entry->Used); - overwrite_entry->TargetIndex = builder->Rects.Size - 1; - return builder->RectsIndex.index_from_ptr(overwrite_entry); - } + return ImFontAtlasPackReuseRectEntry(atlas, overwrite_entry); // Write into an existing entry instead of adding one (used during repack) else - { return ImFontAtlasPackAllocRectEntry(atlas, builder->Rects.Size - 1); - } } // Important: don'return pointer valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. diff --git a/imgui_internal.h b/imgui_internal.h index 7389c0216..0a9961a45 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3701,8 +3701,8 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // Having this also makes it easier to e.g. sort rectangles during repack. struct ImFontAtlasRectEntry { - int TargetIndex : 31; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. - unsigned int Used : 1; + int TargetIndex : 31; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. + unsigned int Used : 1; }; // Data available to potential texture post-processing functions From 074bf39e403fd4123b605bd8062624876381e59f Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Mar 2025 21:43:15 +0200 Subject: [PATCH 121/191] Fonts: GC Compact All exposed in Metrics->Memory Allocations includes compacting texture data. --- imgui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/imgui.cpp b/imgui.cpp index 20ebdc56a..00bb950fc 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4394,6 +4394,7 @@ void ImGui::GcCompactTransientMiscBuffers() g.MultiSelectTempDataStacked = 0; g.MultiSelectTempData.clear_destruct(); TableGcCompactSettings(); + g.IO.Fonts->CompactCache(); } // Free up/compact internal window buffers, we can use this when a window becomes unused. From 1ea9ff36771ef4d77566f4f738404ea20573ea4b Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Mar 2025 22:18:42 +0200 Subject: [PATCH 122/191] Fonts: add optional out parameter to AddCustomRect() --- imgui.h | 2 +- imgui_draw.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/imgui.h b/imgui.h index a3d70bb98..001cc414d 100644 --- a/imgui.h +++ b/imgui.h @@ -3633,7 +3633,7 @@ struct ImFontAtlas // - AddCustomRectRegular() --> Renamed to AddCustomRect() // - AddCustomRectFontGlyph() --> Prefer using custom ImFontLoader inside ImFontConfig // - ImFontAtlasCustomRect --> Renamed to ImFontAtlasRect - IMGUI_API ImFontAtlasRectId AddCustomRect(int width, int height); // Register a rectangle. Return -1 (ImFontAtlasRectId_Invalid) on error. + IMGUI_API ImFontAtlasRectId AddCustomRect(int width, int height, ImFontAtlasRect* out_r = NULL);// Register a rectangle. Return -1 (ImFontAtlasRectId_Invalid) on error. IMGUI_API void RemoveCustomRect(ImFontAtlasRectId id); // Unregister a rectangle. Existing pixels will stay in texture until resized / garbage collected. IMGUI_API bool GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const; // Get rectangle coordinates for current texture. Valid immediately, never store this (read above)! diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7cc1351fb..945122b35 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3225,7 +3225,8 @@ void ImFontAtlas::RemoveFont(ImFont* font) ImFontAtlasBuildNotifySetFont(this, font, new_current_font); } -ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height) +// At it is common to do an AddCustomRect() followed by a GetCustomRect(), we provide an optional 'ImFontAtlasRect* out_r = NULL' argument to retrieve the info straight away. +ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height, ImFontAtlasRect* out_r) { IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); @@ -3236,9 +3237,14 @@ ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height) ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); if (r_id == ImFontAtlasRectId_Invalid) return ImFontAtlasRectId_Invalid; - ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); + if (out_r != NULL) + GetCustomRect(r_id, out_r); + if (RendererHasTextures) + { + ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + } return r_id; } @@ -4321,7 +4327,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon return ImFontAtlasPackAllocRectEntry(atlas, builder->Rects.Size - 1); } -// Important: don'return pointer valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. +// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { IM_ASSERT(id != ImFontAtlasRectId_Invalid); From 526a5d0f8a6363c850fc82aaca1645109cb9336f Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 2 Apr 2025 15:10:11 +0200 Subject: [PATCH 123/191] Fonts: tidying up. --- imgui_draw.cpp | 22 +++++++++------------- imgui_internal.h | 14 +++++++------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 945122b35..f3a79b4e7 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2654,17 +2654,14 @@ void ImFontAtlas::ClearInputData() const ImFontLoader* loader = font_cfg.FontLoader ? font_cfg.FontLoader : FontLoader; if (loader && loader->FontSrcDestroy != NULL) loader->FontSrcDestroy(this, &font_cfg); - ImFontAtlasBuildDiscardFontSource(this, &font_cfg); + ImFontAtlasBuildDestroyFontSourceData(this, &font_cfg); } - // When clearing this we lose access to the font name and other information used to build the font. for (ImFont* font : Fonts) { - if (font->Sources >= Sources.Data && font->Sources < Sources.Data + Sources.Size) - { - font->Sources = NULL; - font->SourcesCount = 0; - } + // When clearing this we lose access to the font name and other information used to build the font. + font->Sources = NULL; + font->SourcesCount = 0; font->Flags |= ImFontFlags_NoLoadGlyphs; } Sources.clear(); @@ -2978,14 +2975,14 @@ bool ImFontAtlas::Build() } #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -void ImFontAtlasBuildDiscardFontSource(ImFontAtlas* atlas, ImFontConfig* src) +void ImFontAtlasBuildDestroyFontSourceData(ImFontAtlas* atlas, ImFontConfig* src) { IM_UNUSED(atlas); if (src->FontDataOwnedByAtlas) IM_FREE(src->FontData); + src->FontData = NULL; if (src->GlyphExcludeRanges) IM_FREE((void*)src->GlyphExcludeRanges); - src->FontData = NULL; src->GlyphExcludeRanges = NULL; } @@ -3048,7 +3045,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) if (!ImFontAtlasBuildAddFont(this, &new_font_cfg)) { // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) - ImFontAtlasBuildDiscardFontSource(this, &new_font_cfg); + ImFontAtlasBuildDestroyFontSourceData(this, &new_font_cfg); Sources.pop_back(); if (!font_cfg->MergeMode) { @@ -3593,9 +3590,8 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) } const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; - if (loader->FontSrcInit != NULL) - if (!loader->FontSrcInit(atlas, src)) - return false; + if (loader->FontSrcInit != NULL && !loader->FontSrcInit(atlas, src)) + return false; atlas->TexIsBuilt = false; // For legacy backends ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); diff --git a/imgui_internal.h b/imgui_internal.h index 0a9961a45..41b99b438 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3770,20 +3770,20 @@ IMGUI_API void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas); +IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy +IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); +IMGUI_API void ImFontAtlasBuildDestroyFontSourceData(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildReloadAll(ImFontAtlas* atlas); // Reinit/rebuild, notably if font loader params have changed. IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFontConfig* src); // Reinit/rebuild, notably if font loader params have changed. -IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); -IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); -IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); -IMGUI_API void ImFontAtlasBuildDiscardFontBakes(ImFontAtlas* atlas, ImFont* font); -IMGUI_API void ImFontAtlasBuildDiscardFontSource(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); IMGUI_API ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont* font, float font_size); +IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); +IMGUI_API void ImFontAtlasBuildDiscardFontBakes(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); IMGUI_API void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); -IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy -IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); From fb5c53708075709a4f859d8b5721893aad89a244 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 3 Apr 2025 19:10:16 +0200 Subject: [PATCH 124/191] Fonts: changing loader/backend or loader flags may be done without losing custom rects. Sharing more code. --- imgui.cpp | 27 ++++++++++++----- imgui_draw.cpp | 79 ++++++++++++++++++++++++------------------------ imgui_internal.h | 4 +-- 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 00bb950fc..a713390c3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15695,9 +15695,16 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) ImFontAtlasBuildSetupFontLoader(atlas, loader_freetype); if (loader_current == loader_freetype) { - Text("Shared FreeType Loader Flags:"); - if (ImGuiFreeType::DebugEditFontBuilderFlags(&atlas->FontBuilderFlags)) - ImFontAtlasBuildReloadAll(atlas); + unsigned int loader_flags = atlas->FontBuilderFlags; + Text("Shared FreeType Loader Flags: 0x%08", loader_flags); + if (ImGuiFreeType::DebugEditFontBuilderFlags(&loader_flags)) + { + for (ImFont* font : atlas->Fonts) + ImFontAtlasBuildDestroyFontOutput(atlas, font); + atlas->FontBuilderFlags = loader_flags; + for (ImFont* font : atlas->Fonts) + ImFontAtlasBuildInitFontOutput(atlas, font); + } } #else BeginDisabled(); @@ -15724,8 +15731,9 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) if (Button("Grow")) ImFontAtlasBuildGrowTexture(atlas); SameLine(); - if (Button("Clear Output")) + if (Button("Clear All")) ImFontAtlasBuildClear(atlas); + SetItemTooltip("Destroy cache and custom rectangles."); for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { @@ -16613,9 +16621,14 @@ void ImGui::DebugNodeFont(ImFont* font) #ifdef IMGUI_ENABLE_FREETYPE if (loader->Name != NULL && strcmp(loader->Name, "FreeType") == 0) { - Text("FreeType Loader Flags: 0x%08X", src->FontBuilderFlags); - if (ImGuiFreeType::DebugEditFontBuilderFlags(&src->FontBuilderFlags)) - ImFontAtlasBuildReloadFont(atlas, src); + unsigned int loader_flags = src->FontBuilderFlags; + Text("FreeType Loader Flags: 0x%08X", loader_flags); + if (ImGuiFreeType::DebugEditFontBuilderFlags(&loader_flags)) + { + ImFontAtlasBuildDestroyFontOutput(atlas, font); + src->FontBuilderFlags = loader_flags; + ImFontAtlasBuildInitFontOutput(atlas, font); + } } #endif TreePop(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f3a79b4e7..b1683e204 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2649,14 +2649,11 @@ void ImFontAtlas::CompactCache() void ImFontAtlas::ClearInputData() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - for (ImFontConfig& font_cfg : Sources) - { - const ImFontLoader* loader = font_cfg.FontLoader ? font_cfg.FontLoader : FontLoader; - if (loader && loader->FontSrcDestroy != NULL) - loader->FontSrcDestroy(this, &font_cfg); - ImFontAtlasBuildDestroyFontSourceData(this, &font_cfg); - } + for (ImFont* font : Fonts) + ImFontAtlasBuildDestroyFontOutput(this, font); + for (ImFontConfig& font_cfg : Sources) + ImFontAtlasBuildDestroyFontSourceData(this, &font_cfg); for (ImFont* font : Fonts) { // When clearing this we lose access to the font name and other information used to build the font. @@ -3197,15 +3194,9 @@ void ImFontAtlas::RemoveFont(ImFont* font) IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); font->ClearOutputData(); + ImFontAtlasBuildDestroyFontOutput(this, font); for (int src_n = 0; src_n < font->SourcesCount; src_n++) - { - ImFontConfig* src = (ImFontConfig*)(void*)&font->Sources[src_n]; - const ImFontLoader* loader = src->FontLoader ? src->FontLoader : FontLoader; - if (loader && loader->FontSrcDestroy != NULL) - loader->FontSrcDestroy(this, src); - if (src->FontData != NULL && src->FontDataOwnedByAtlas) - IM_FREE(src->FontData); - } + ImFontAtlasBuildDestroyFontSourceData(this, &font->Sources[src_n]); bool removed = Fonts.find_erase(font); IM_ASSERT(removed); @@ -3376,15 +3367,19 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon return; IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); - // Note that texture size estimate is likely incorrect in this situation, as FreeType backend doesn't use oversampling. - ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); - ImFontAtlasBuildDestroy(atlas); + for (ImFont* font : atlas->Fonts) + ImFontAtlasBuildDestroyFontOutput(atlas, font); + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + atlas->FontLoader->LoaderShutdown(atlas); atlas->FontLoader = font_loader; atlas->FontLoaderName = font_loader ? font_loader->Name : "NULL"; + IM_ASSERT(atlas->FontLoaderData == NULL); - ImFontAtlasBuildAddTexture(atlas, new_tex_size.x, new_tex_size.y); - ImFontAtlasBuildInit(atlas); + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); + for (ImFont* font : atlas->Fonts) + ImFontAtlasBuildInitFontOutput(atlas, font); } // Preload all glyph ranges for legacy backends. @@ -3562,18 +3557,31 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ //----------------------------------------------------------------------------------------------------------------------------- -void ImFontAtlasBuildReloadAll(ImFontAtlas* atlas) +bool ImFontAtlasBuildInitFontOutput(ImFontAtlas* atlas, ImFont* font) { - const ImFontLoader* main_loader = atlas->FontLoader; - ImFontAtlasBuildSetupFontLoader(atlas, NULL); - ImFontAtlasBuildSetupFontLoader(atlas, main_loader); + bool ret = true; + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = &font->Sources[src_n]; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader && loader->FontSrcInit != NULL && !loader->FontSrcInit(atlas, src)) + ret = false; + } + IM_ASSERT(ret); // Unclear how to react to this meaningfully. Assume that result will be same as initial AddFont() call. + return ret; } -void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFontConfig* src) +// Keep source/input FontData +void ImFontAtlasBuildDestroyFontOutput(ImFontAtlas* atlas, ImFont* font) { - // FIXME-NEWATLAS: rebuild single font not supported yet. - IM_UNUSED(src); - ImFontAtlasBuildReloadAll(atlas); + font->ClearOutputData(); + for (int src_n = 0; src_n < font->SourcesCount; src_n++) + { + ImFontConfig* src = &font->Sources[src_n]; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader && loader->FontSrcDestroy != NULL) + loader->FontSrcDestroy(atlas, src); + } } //----------------------------------------------------------------------------------------------------------------------------- @@ -4148,13 +4156,8 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) #else IM_ASSERT(0); // Invalid Build function #endif - return; // ImFontAtlasBuildSetupFontLoader() automatically call ImFontAtlasBuildInit() } - IM_ASSERT(atlas->FontLoaderData == NULL); - if (atlas->FontLoader->LoaderInit) - atlas->FontLoader->LoaderInit(atlas); - // Create initial texture size if (atlas->TexData == NULL || atlas->TexData->Pixels == NULL) ImFontAtlasBuildAddTexture(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); @@ -4165,6 +4168,8 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) { IM_ASSERT(atlas->Builder == NULL); builder = atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); + if (atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); } ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); @@ -4193,13 +4198,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) { for (ImFont* font : atlas->Fonts) - font->ClearOutputData(); - for (ImFontConfig& font_cfg : atlas->Sources) - { - const ImFontLoader* loader = font_cfg.FontLoader ? font_cfg.FontLoader : atlas->FontLoader; - if (loader && loader->FontSrcDestroy != NULL) - loader->FontSrcDestroy(atlas, &font_cfg); - } + ImFontAtlasBuildDestroyFontOutput(atlas, font); if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) { atlas->FontLoader->LoaderShutdown(atlas); diff --git a/imgui_internal.h b/imgui_internal.h index 41b99b438..009f6f9c9 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3774,9 +3774,9 @@ IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontCo IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); +IMGUI_API bool ImFontAtlasBuildInitFontOutput(ImFontAtlas* atlas, ImFont* font); // Using DestroyFontOutput/InitFontOutput sequence useful notably if font loader params have changed +IMGUI_API void ImFontAtlasBuildDestroyFontOutput(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildDestroyFontSourceData(ImFontAtlas* atlas, ImFontConfig* src); -IMGUI_API void ImFontAtlasBuildReloadAll(ImFontAtlas* atlas); // Reinit/rebuild, notably if font loader params have changed. -IMGUI_API void ImFontAtlasBuildReloadFont(ImFontAtlas* atlas, ImFontConfig* src); // Reinit/rebuild, notably if font loader params have changed. IMGUI_API ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); IMGUI_API ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont* font, float font_size); From 12599da53df30612a7f13f6b1c0532a4d9e67b81 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 9 Apr 2025 15:16:59 +0200 Subject: [PATCH 125/191] Fonts: do not mark whole ImTextureData struct as IMGUI_API to fix warning when used in ImVector<> (8559) --- imgui.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index 001cc414d..3d9ff1b72 100644 --- a/imgui.h +++ b/imgui.h @@ -3403,7 +3403,7 @@ struct ImTextureRect // Why does we store two identifiers: TexID and BackendUserData? // - ImTextureID TexID = lower-level identifier stored in ImDrawCmd. ImDrawCmd can refer to textures not created by the backend, and for which there's no ImTextureData. // - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. -struct IMGUI_API ImTextureData +struct ImTextureData { ImTextureStatus Status; // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify! ImTextureFormat Format; // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 @@ -3425,8 +3425,8 @@ struct IMGUI_API ImTextureData // Functions ImTextureData() { memset(this, 0, sizeof(*this)); } ~ImTextureData() { DestroyPixels(); } - void Create(ImTextureFormat format, int w, int h); - void DestroyPixels(); + IMGUI_API void Create(ImTextureFormat format, int w, int h); + IMGUI_API void DestroyPixels(); unsigned char* GetPixels() { IM_ASSERT(Pixels != NULL); return Pixels; } unsigned char* GetPixelsAt(int x, int y) { IM_ASSERT(Pixels != NULL); return Pixels + (x + y * Width) * BytesPerPixel; } int GetSizeInBytes() const { return Width * Height * BytesPerPixel; } From d789263e08383beab8a6482109071712792316f3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 7 Apr 2025 18:42:06 +0200 Subject: [PATCH 126/191] Fonts: internal rendering uses higher level functions. --- imgui_draw.cpp | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b1683e204..562a4b6e4 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3461,7 +3461,8 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_ builder->PackIdMouseCursors = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); if (builder->PackIdMouseCursors == ImFontAtlasRectId_Invalid) return; - ImTextureRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdMouseCursors); + ImFontAtlasRect r; + atlas->GetCustomRect(builder->PackIdMouseCursors, &r); // Draw to texture if (add_and_draw) @@ -3469,19 +3470,19 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_ if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) { // 2x2 white pixels - ImFontAtlasBuildRenderBitmapFromString(atlas, r->x, r->y, 2, 2, "XX" "XX", 'X'); + ImFontAtlasBuildRenderBitmapFromString(atlas, r.x, r.y, 2, 2, "XX" "XX", 'X'); } else { // 2x2 white pixels + mouse cursors - const int x_for_white = r->x; - const int x_for_black = r->x + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; - ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_white, r->y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.'); - ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r->y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); + const int x_for_white = r.x; + const int x_for_black = r.x + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_white, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.'); + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); } } - ImFontAtlasTextureBlockQueueUpload(atlas, atlas->TexData, r->x, r->y, r->w, r->h); - atlas->TexUvWhitePixel = ImVec2((r->x + 0.5f) * atlas->TexUvScale.x, (r->y + 0.5f) * atlas->TexUvScale.y); + ImFontAtlasTextureBlockQueueUpload(atlas, atlas->TexData, r.x, r.y, r.w, r.h); + atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); } static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_draw) @@ -3497,7 +3498,8 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ builder->PackIdLinesTexData = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); if (builder->PackIdLinesTexData == ImFontAtlasRectId_Invalid) return; - ImTextureRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdLinesTexData); + ImFontAtlasRect r; + atlas->GetCustomRect(builder->PackIdLinesTexData, &r); // Register texture region for thick lines // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row @@ -3508,18 +3510,18 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle int y = n; int line_width = n; - int pad_left = (r->w - line_width) / 2; - int pad_right = r->w - (pad_left + line_width); + int pad_left = (r.w - line_width) / 2; + int pad_right = r.w - (pad_left + line_width); // Write each slice - IM_ASSERT(pad_left + line_width + pad_right == r->w && y < r->h); // Make sure we're inside the texture bounds before we start writing pixels + IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels if (add_and_draw) { switch (tex->Format) { case ImTextureFormat_Alpha8: { - ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r->x, r->y + y); + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); for (int i = 0; i < pad_left; i++) *(write_ptr + i) = 0x00; @@ -3532,7 +3534,7 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ } case ImTextureFormat_RGBA32: { - ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r->x, r->y + y); + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); for (int i = 0; i < pad_left; i++) *(write_ptr + i) = IM_COL32(255, 255, 255, 0); @@ -3547,12 +3549,12 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ } // Calculate UVs for this line - ImVec2 uv0 = ImVec2((float)(r->x + pad_left - 1), (float)(r->y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r->x + pad_left + line_width + 1), (float)(r->y + y + 1)) * atlas->TexUvScale; + ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } - ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r.x, r.y, r.w, r.h); } //----------------------------------------------------------------------------------------------------------------------------- From e8035b94e5c739cfd0d1d8bb52ece31629ccb3bd Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 9 Apr 2025 17:16:40 +0200 Subject: [PATCH 127/191] Fonts: misc tidying up. --- imgui_draw.cpp | 72 +++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 562a4b6e4..a20d63bd0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3451,11 +3451,10 @@ void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, in static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_draw) { - ImVec2i pack_size = (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) ? ImVec2i(2, 2) : ImVec2i(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); - // Pack and store identifier so we can refresh UV coordinates on texture resize. // FIXME-NEWATLAS: User/custom rects where user code wants to store UV coordinates will need to do the same thing. ImFontAtlasBuilder* builder = atlas->Builder; + ImVec2i pack_size = (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) ? ImVec2i(2, 2) : ImVec2i(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); if (add_and_draw) builder->PackIdMouseCursors = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); @@ -3480,8 +3479,10 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_ ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_white, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.'); ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); } + ImFontAtlasTextureBlockQueueUpload(atlas, atlas->TexData, r.x, r.y, r.w, r.h); } - ImFontAtlasTextureBlockQueueUpload(atlas, atlas->TexData, r.x, r.y, r.w, r.h); + + // Refresh UV coordinates atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); } @@ -3490,71 +3491,64 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) return; - ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); - // Pack and store identifier so we can refresh UV coordinates on texture resize. + ImTextureData* tex = atlas->TexData; ImFontAtlasBuilder* builder = atlas->Builder; + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); if (add_and_draw) builder->PackIdLinesTexData = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); if (builder->PackIdLinesTexData == ImFontAtlasRectId_Invalid) return; + ImFontAtlasRect r; atlas->GetCustomRect(builder->PackIdLinesTexData, &r); // Register texture region for thick lines // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them - ImTextureData* tex = atlas->TexData; for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - int y = n; - int line_width = n; - int pad_left = (r.w - line_width) / 2; - int pad_right = r.w - (pad_left + line_width); + const int y = n; + const int line_width = n; + const int pad_left = (r.w - line_width) / 2; + const int pad_right = r.w - (pad_left + line_width); + IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels // Write each slice - IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels - if (add_and_draw) + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) { - switch (tex->Format) - { - case ImTextureFormat_Alpha8: - { - ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = 0x00; + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = 0x00; - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = 0xFF; + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = 0xFF; - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = 0x00; - break; - } - case ImTextureFormat_RGBA32: - { - ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = 0x00; + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = IM_COL32(255, 255, 255, 0); - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = IM_COL32_WHITE; + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = IM_COL32_WHITE; - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); - break; - } - } + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); } - // Calculate UVs for this line + // Refresh UV coordinates ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } - ImFontAtlasTextureBlockQueueUpload(atlas, tex, r.x, r.y, r.w, r.h); + if (add_and_draw) + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r.x, r.y, r.w, r.h); } //----------------------------------------------------------------------------------------------------------------------------- From 0436fba13cea6ad527c3fcbf6a6e20c6a490fd78 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Mar 2025 22:38:05 +0200 Subject: [PATCH 128/191] Fonts: fixed compaction gc-ing baked fonts used in the current frame + rename. --- imgui_draw.cpp | 23 ++++++++++++----------- imgui_internal.h | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a20d63bd0..3feba78a1 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3983,7 +3983,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) for (ImFontAtlasRectEntry& index_entry : builder->RectsIndex) { - if (index_entry.Used == false) + if (index_entry.IsUsed == false) continue; ImTextureRect& old_r = old_rects[index_entry.TargetIndex]; if (old_r.w == 0 && old_r.h == 0) @@ -4124,7 +4124,7 @@ void ImFontAtlasBuildClear(ImFontAtlas* atlas) void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) { ImFontAtlasBuilder* builder = atlas->Builder; - ImFontAtlasBuildDiscardBakes(atlas, 0); + ImFontAtlasBuildDiscardBakes(atlas, 1); ImTextureData* old_tex = atlas->TexData; ImVec2i old_tex_size = ImVec2i(old_tex->Width, old_tex->Height); @@ -4235,19 +4235,20 @@ static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int r { index_idx = builder->RectsIndexFreeListStart; index_entry = &builder->RectsIndex[index_idx]; - IM_ASSERT(index_entry->Used == false); + IM_ASSERT(index_entry->IsUsed == false); builder->RectsIndexFreeListStart = index_entry->TargetIndex; } index_entry->TargetIndex = rect_idx; - index_entry->Used = 1; + index_entry->IsUsed = 1; return (ImFontAtlasRectId)index_idx; } -static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFontAtlasRectEntry* overwrite_entry) +// Overwrite existing entry +static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFontAtlasRectEntry* index_entry) { - IM_ASSERT(overwrite_entry->Used); - overwrite_entry->TargetIndex = atlas->Builder->Rects.Size - 1; - return atlas->Builder->RectsIndex.index_from_ptr(overwrite_entry); + IM_ASSERT(index_entry->IsUsed); + index_entry->TargetIndex = atlas->Builder->Rects.Size - 1; + return atlas->Builder->RectsIndex.index_from_ptr(index_entry); } // This is expected to be called in batches and followed by a repack @@ -4256,10 +4257,10 @@ void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) IM_ASSERT(id != ImFontAtlasRectId_Invalid); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; - IM_ASSERT(index_entry->Used && index_entry->TargetIndex >= 0); + IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0); ImTextureRect* rect = ImFontAtlasPackGetRect(atlas, id); - index_entry->Used = false; + index_entry->IsUsed = false; index_entry->TargetIndex = builder->RectsIndexFreeListStart; const int pack_padding = atlas->TexGlyphPadding; @@ -4324,7 +4325,7 @@ ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) IM_ASSERT(id != ImFontAtlasRectId_Invalid); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; - IM_ASSERT(index_entry->Used); + IM_ASSERT(index_entry->IsUsed); return &builder->Rects[index_entry->TargetIndex]; } diff --git a/imgui_internal.h b/imgui_internal.h index 009f6f9c9..3e519325a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3702,7 +3702,7 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); struct ImFontAtlasRectEntry { int TargetIndex : 31; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. - unsigned int Used : 1; + unsigned int IsUsed : 1; }; // Data available to potential texture post-processing functions From ed2bb2cff08d4d1e9b26bf744902b2131fa5fc4b Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 11 Apr 2025 17:16:06 +0200 Subject: [PATCH 129/191] Fonts: encode additional data in ImFontAtlasRectId to detect invalid id + added Rects debug browser. --- imgui.cpp | 41 ++++++++++++++++++++++++++++++++++-- imgui.h | 2 +- imgui_draw.cpp | 54 +++++++++++++++++++++++++++++++++++++----------- imgui_internal.h | 14 +++++++++++-- 4 files changed, 94 insertions(+), 17 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index a713390c3..881d40727 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15747,17 +15747,47 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) Text("Packed rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsPackedCount, atlas->Builder->RectsPackedSurface, packed_surface_sqrt, packed_surface_sqrt); Text("incl. Discarded rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsDiscardedCount, atlas->Builder->RectsDiscardedSurface, discarded_surface_sqrt, discarded_surface_sqrt); + ImFontAtlasRectId highlight_r_id = ImFontAtlasRectId_Invalid; + if (TreeNode("Rects Index", "Rects Index (%d)", atlas->Builder->RectsPackedCount)) // <-- Use count of used rectangles + { + PushStyleVar(ImGuiStyleVar_ImageBorderSize, 1.0f); + if (BeginTable("##table", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, ImVec2(0.0f, GetTextLineHeightWithSpacing() * 12))) + { + for (const ImFontAtlasRectEntry& entry : atlas->Builder->RectsIndex) + if (entry.IsUsed) + { + ImFontAtlasRectId id = ImFontAtlasRectId_Make(atlas->Builder->RectsIndex.index_from_ptr(&entry), entry.Generation); + ImFontAtlasRect r = {}; + atlas->GetCustomRect(id, &r); + const char* buf; + ImFormatStringToTempBuffer(&buf, NULL, "ID:%08X, used:%d, { w:%3d, h:%3d } { x:%4d, y:%4d }", id, entry.IsUsed, r.w, r.h, r.x, r.y); + TableNextColumn(); + Selectable(buf); + if (IsItemHovered()) + highlight_r_id = id; + TableNextColumn(); + Image(atlas->TexID, ImVec2(r.w, r.h), r.uv0, r.uv1); + } + EndTable(); + } + PopStyleVar(); + TreePop(); + } + // Texture list // (ensure the last texture always use the same ID, so we can keep it open neatly) + ImFontAtlasRect highlight_r; + if (highlight_r_id != ImFontAtlasRectId_Invalid) + atlas->GetCustomRect(highlight_r_id, &highlight_r); for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { if (tex_n == atlas->TexList.Size - 1) SetNextItemOpen(true, ImGuiCond_Once); - DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n); + DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n, (highlight_r_id != ImFontAtlasRectId_Invalid) ? &highlight_r : NULL); } } -void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id) +void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect) { ImGuiContext& g = *GImGui; PushID(int_id); @@ -15773,6 +15803,13 @@ void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id) ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); if (cfg->ShowTextureUsedRect) GetWindowDrawList()->AddRect(ImVec2(p.x + tex->UsedRect.x, p.y + tex->UsedRect.y), ImVec2(p.x + tex->UsedRect.x + tex->UsedRect.w, p.y + tex->UsedRect.y + tex->UsedRect.h), IM_COL32(255, 0, 255, 255)); + if (highlight_rect != NULL) + { + ImRect r_outer(p.x, p.y, p.x + tex->Width, p.y + tex->Height); + ImRect r_inner(p.x + highlight_rect->x, p.y + highlight_rect->y, p.x + highlight_rect->x + highlight_rect->w, p.y + highlight_rect->y + highlight_rect->h); + RenderRectFilledWithHole(GetWindowDrawList(), r_outer, r_inner, IM_COL32(0, 0, 0, 100), 0.0f); + GetWindowDrawList()->AddRect(r_inner.Min - ImVec2(1, 1), r_inner.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255)); + } PopStyleVar(); char texid_desc[20]; diff --git a/imgui.h b/imgui.h index 3d9ff1b72..2a0cf3546 100644 --- a/imgui.h +++ b/imgui.h @@ -3509,7 +3509,7 @@ struct ImFontGlyphRangesBuilder IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges }; -// An identifier to a rectangle in the atlas. -1 when invalid. +// An opaque identifier to a rectangle in the atlas. -1 when invalid. // The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it. typedef int ImFontAtlasRectId; #define ImFontAtlasRectId_Invalid -1 diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3feba78a1..f1d1cf0b2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3238,7 +3238,8 @@ ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height, ImFontAtlasR void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id) { - IM_ASSERT(id != ImFontAtlasRectId_Invalid); + if (ImFontAtlasPackGetRectSafe(this, id) == NULL) + return; ImFontAtlasPackDiscardRect(this, id); } @@ -3294,10 +3295,12 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im bool ImFontAtlas::GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const { - ImTextureRect* r = ImFontAtlasPackGetRect((ImFontAtlas*)this, id); + ImTextureRect* r = ImFontAtlasPackGetRectSafe((ImFontAtlas*)this, id); if (r == NULL) return false; IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates + if (out_r == NULL) + return true; out_r->x = r->x; out_r->y = r->y; out_r->w = r->w; @@ -4001,7 +4004,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) ImFontAtlasBuildGrowTexture(atlas, w, h); // Recurse return; } - IM_ASSERT(new_r_id == builder->RectsIndex.index_from_ptr(&index_entry)); + IM_ASSERT(ImFontAtlasRectId_GetIndex(new_r_id) == builder->RectsIndex.index_from_ptr(&index_entry)); ImTextureRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h); } @@ -4230,17 +4233,18 @@ static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int r builder->RectsIndex.resize(builder->RectsIndex.Size + 1); index_idx = builder->RectsIndex.Size - 1; index_entry = &builder->RectsIndex[index_idx]; + memset(index_entry, 0, sizeof(*index_entry)); } else { index_idx = builder->RectsIndexFreeListStart; index_entry = &builder->RectsIndex[index_idx]; - IM_ASSERT(index_entry->IsUsed == false); + IM_ASSERT(index_entry->IsUsed == false && index_entry->Generation > 0); // Generation is incremented during DiscardRect builder->RectsIndexFreeListStart = index_entry->TargetIndex; } index_entry->TargetIndex = rect_idx; index_entry->IsUsed = 1; - return (ImFontAtlasRectId)index_idx; + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); } // Overwrite existing entry @@ -4248,23 +4252,29 @@ static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFon { IM_ASSERT(index_entry->IsUsed); index_entry->TargetIndex = atlas->Builder->Rects.Size - 1; - return atlas->Builder->RectsIndex.index_from_ptr(index_entry); + int index_idx = atlas->Builder->RectsIndex.index_from_ptr(index_entry); + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); } // This is expected to be called in batches and followed by a repack void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { IM_ASSERT(id != ImFontAtlasRectId_Invalid); - ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; - ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; - IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0); ImTextureRect* rect = ImFontAtlasPackGetRect(atlas, id); + if (rect == NULL) + return; + + ImFontAtlasBuilder* builder = atlas->Builder; + int index_idx = ImFontAtlasRectId_GetIndex(id); + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0); index_entry->IsUsed = false; index_entry->TargetIndex = builder->RectsIndexFreeListStart; + index_entry->Generation++; const int pack_padding = atlas->TexGlyphPadding; - builder->RectsIndexFreeListStart = id; + builder->RectsIndexFreeListStart = index_idx; builder->RectsDiscardedCount++; builder->RectsDiscardedSurface += (rect->w + pack_padding) * (rect->h + pack_padding); rect->w = rect->h = 0; // Clear rectangle so it won't be packed again @@ -4319,16 +4329,36 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon return ImFontAtlasPackAllocRectEntry(atlas, builder->Rects.Size - 1); } -// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. +// Generally for non-user facing functions: assert on invalid ID. ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { IM_ASSERT(id != ImFontAtlasRectId_Invalid); + int index_idx = ImFontAtlasRectId_GetIndex(id); + int generation = ImFontAtlasRectId_GetGeneration(id); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; - ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->Generation == generation); IM_ASSERT(index_entry->IsUsed); return &builder->Rects[index_entry->TargetIndex]; } +// For user-facing functions: return NULL on invalid ID. +// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. +ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + if (id == ImFontAtlasRectId_Invalid) + return NULL; + int index_idx = ImFontAtlasRectId_GetIndex(id); + int generation = ImFontAtlasRectId_GetGeneration(id); + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + if (index_idx >= builder->RectsIndex.Size) + return NULL; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + if (index_entry->Generation != generation || !index_entry->IsUsed) + return NULL; + return &builder->Rects[index_entry->TargetIndex]; +} + // Important! This assume by ImFontConfig::GlyphFilter is a SMALL ARRAY (e.g. <10 entries) static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint) { diff --git a/imgui_internal.h b/imgui_internal.h index 3e519325a..cb8ba5169 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3627,7 +3627,7 @@ namespace ImGui IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); - IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id); // ID used to facilitate persisting the "current" texture. + IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect = NULL); // ID used to facilitate persisting the "current" texture. IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); @@ -3694,6 +3694,14 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- +// Refer to ImFontAtlasPackGetRect() to better understand how this works. +#define ImFontAtlasRectId_IndexMask_ (0x000FFFFF) // 20-bits: index to access builder->RectsIndex[]. +#define ImFontAtlasRectId_GenerationMask_ (0x3FF00000) // 10-bits: entry generation, so each ID is unique and get can safely detected old identifiers. +#define ImFontAtlasRectId_GenerationShift_ (20) +inline int ImFontAtlasRectId_GetIndex(ImFontAtlasRectId id) { return id & ImFontAtlasRectId_IndexMask_; } +inline int ImFontAtlasRectId_GetGeneration(ImFontAtlasRectId id) { return (id & ImFontAtlasRectId_GenerationMask_) >> ImFontAtlasRectId_GenerationShift_; } +inline ImFontAtlasRectId ImFontAtlasRectId_Make(int index_idx, int gen_idx) { IM_ASSERT(index_idx < ImFontAtlasRectId_IndexMask_ && gen_idx < (ImFontAtlasRectId_GenerationMask_ >> ImFontAtlasRectId_GenerationShift_)); return (ImFontAtlasRectId)(index_idx | (gen_idx << ImFontAtlasRectId_GenerationShift_)); } + // Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) // User are returned ImFontAtlasRectId values which are meant to be persistent. // We handle this with an indirection. While Rects[] may be in theory shuffled, compacted etc., RectsIndex[] cannot it is keyed by ImFontAtlasRectId. @@ -3701,7 +3709,8 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // Having this also makes it easier to e.g. sort rectangles during repack. struct ImFontAtlasRectEntry { - int TargetIndex : 31; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. + int TargetIndex : 20; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. + int Generation : 10; // Increased each time the entry is reused for a new rectangle. unsigned int IsUsed : 1; }; @@ -3792,6 +3801,7 @@ IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_s IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); IMGUI_API ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count); From cdfa537adf060c1d3fe13c87ad4ed25d967e814b Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 14 Apr 2025 16:20:31 +0200 Subject: [PATCH 130/191] Fonts: packing of shared basic/line/cursor data uses more public API. --- imgui_draw.cpp | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f1d1cf0b2..b7507821e 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3452,23 +3452,21 @@ void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, in } } -static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_draw) +static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas) { // Pack and store identifier so we can refresh UV coordinates on texture resize. // FIXME-NEWATLAS: User/custom rects where user code wants to store UV coordinates will need to do the same thing. ImFontAtlasBuilder* builder = atlas->Builder; ImVec2i pack_size = (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) ? ImVec2i(2, 2) : ImVec2i(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); - if (add_and_draw) - builder->PackIdMouseCursors = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); - if (builder->PackIdMouseCursors == ImFontAtlasRectId_Invalid) - return; ImFontAtlasRect r; - atlas->GetCustomRect(builder->PackIdMouseCursors, &r); - - // Draw to texture + bool add_and_draw = (atlas->GetCustomRect(builder->PackIdMouseCursors, &r) == false); if (add_and_draw) { + builder->PackIdMouseCursors = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdMouseCursors != ImFontAtlasRectId_Invalid); + + // Draw to texture if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) { // 2x2 white pixels @@ -3482,14 +3480,13 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_ ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_white, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.'); ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); } - ImFontAtlasTextureBlockQueueUpload(atlas, atlas->TexData, r.x, r.y, r.w, r.h); } // Refresh UV coordinates atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); } -static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_draw) +static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas) { if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) return; @@ -3497,14 +3494,15 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ // Pack and store identifier so we can refresh UV coordinates on texture resize. ImTextureData* tex = atlas->TexData; ImFontAtlasBuilder* builder = atlas->Builder; - ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); - if (add_and_draw) - builder->PackIdLinesTexData = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); - if (builder->PackIdLinesTexData == ImFontAtlasRectId_Invalid) - return; ImFontAtlasRect r; - atlas->GetCustomRect(builder->PackIdLinesTexData, &r); + bool add_and_draw = atlas->GetCustomRect(builder->PackIdLinesTexData, &r) == false; + if (add_and_draw) + { + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + builder->PackIdLinesTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdLinesTexData != ImFontAtlasRectId_Invalid); + } // Register texture region for thick lines // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row @@ -3550,8 +3548,6 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_ float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } - if (add_and_draw) - ImFontAtlasTextureBlockQueueUpload(atlas, tex, r.x, r.y, r.w, r.h); } //----------------------------------------------------------------------------------------------------------------------------- @@ -4025,8 +4021,8 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) } // Update other cached UV - ImFontAtlasBuildUpdateLinesTexData(atlas, false); - ImFontAtlasBuildUpdateBasicTexData(atlas, false); + ImFontAtlasBuildUpdateLinesTexData(atlas); + ImFontAtlasBuildUpdateBasicTexData(atlas); builder->LockDisableResize = false; ImFontAtlasUpdateDrawListsSharedData(atlas); @@ -4176,8 +4172,8 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) ImFontAtlasPackInit(atlas); // Add required texture data - ImFontAtlasBuildUpdateLinesTexData(atlas, true); - ImFontAtlasBuildUpdateBasicTexData(atlas, true); + ImFontAtlasBuildUpdateLinesTexData(atlas); + ImFontAtlasBuildUpdateBasicTexData(atlas); // Register fonts if (builder_is_new) From c43b138a6988ada530d03b821cfdbc8a578149d2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Apr 2025 15:00:35 +0200 Subject: [PATCH 131/191] Fonts: no need to load current baked on SkipItems window? + removed unused field. Avoid baked staying active after GC. Might cause issues. # Conflicts: # imgui.cpp --- imgui.cpp | 6 +++++- imgui_internal.h | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 881d40727..e2765edac 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8625,9 +8625,13 @@ void ImGui::SetCurrentFont(ImFont* font, float font_size) void ImGui::UpdateCurrentFontSize() { ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window != NULL && window->SkipItems) + return; + float final_size = g.FontSizeBeforeScaling * g.IO.FontGlobalScale; final_size *= g.Font->Scale; - if (ImGuiWindow* window = g.CurrentWindow) + if (window != NULL) final_size *= window->FontWindowScale; // Round font size diff --git a/imgui_internal.h b/imgui_internal.h index cb8ba5169..c4e064052 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2144,7 +2144,6 @@ struct ImGuiContext float FontScale; // == FontBaked->Size / Font->FontSize. Scale factor over baked size. float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; - ImVectorTextures; double Time; int FrameCount; int FrameCountEnded; From eb650c468a42b71ff83d1b3405068ad4c8cebbf6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Apr 2025 16:47:54 +0200 Subject: [PATCH 132/191] Fonts: fixed unused variable warning. --- imgui_draw.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b7507821e..f982df4e6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4330,10 +4330,9 @@ ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { IM_ASSERT(id != ImFontAtlasRectId_Invalid); int index_idx = ImFontAtlasRectId_GetIndex(id); - int generation = ImFontAtlasRectId_GetGeneration(id); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; - IM_ASSERT(index_entry->Generation == generation); + IM_ASSERT(index_entry->Generation == ImFontAtlasRectId_GetGeneration(id)); IM_ASSERT(index_entry->IsUsed); return &builder->Rects[index_entry->TargetIndex]; } @@ -4345,12 +4344,11 @@ ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId if (id == ImFontAtlasRectId_Invalid) return NULL; int index_idx = ImFontAtlasRectId_GetIndex(id); - int generation = ImFontAtlasRectId_GetGeneration(id); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; if (index_idx >= builder->RectsIndex.Size) return NULL; ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; - if (index_entry->Generation != generation || !index_entry->IsUsed) + if (index_entry->Generation != ImFontAtlasRectId_GetGeneration(id) || !index_entry->IsUsed) return NULL; return &builder->Rects[index_entry->TargetIndex]; } From 7840e453b58f8b0950166e67adce8df15ddebfa2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Apr 2025 16:53:12 +0200 Subject: [PATCH 133/191] Fonts: ImFontAtlasBuildInit() is always called with atlas->Builder == NULL. --- imgui_draw.cpp | 22 +++++++--------------- imgui_internal.h | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f982df4e6..f78ff9901 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3552,6 +3552,7 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas) //----------------------------------------------------------------------------------------------------------------------------- +// Was tempted to lazily init FontSrc but wouldn't save much + makes it more complicated to detect invalid data at AddFont() bool ImFontAtlasBuildInitFontOutput(ImFontAtlas* atlas, ImFont* font) { bool ret = true; @@ -4157,15 +4158,9 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) if (atlas->TexData == NULL || atlas->TexData->Pixels == NULL) ImFontAtlasBuildAddTexture(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); - ImFontAtlasBuilder* builder = atlas->Builder; // Do not move above - const bool builder_is_new = (builder == NULL); - if (builder_is_new) - { - IM_ASSERT(atlas->Builder == NULL); - builder = atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); - if (atlas->FontLoader->LoaderInit) - atlas->FontLoader->LoaderInit(atlas); - } + atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); + if (atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); @@ -4176,12 +4171,9 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) ImFontAtlasBuildUpdateBasicTexData(atlas); // Register fonts - if (builder_is_new) - { - ImFontAtlasBuildUpdatePointers(atlas); - for (ImFontConfig& cfg : atlas->Sources) - ImFontAtlasBuildAddFont(atlas, &cfg); - } + ImFontAtlasBuildUpdatePointers(atlas); + for (ImFontConfig& cfg : atlas->Sources) + ImFontAtlasBuildAddFont(atlas, &cfg); // Update UV coordinates etc. stored in bound ImDrawListSharedData instance ImFontAtlasUpdateDrawListsSharedData(atlas); diff --git a/imgui_internal.h b/imgui_internal.h index c4e064052..7c615d323 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3753,7 +3753,7 @@ struct ImFontAtlasBuilder // Cache of all ImFontBaked ImStableVector BakedPool; - ImGuiStorage BakedMap; + ImGuiStorage BakedMap; // BakedId --> ImFontBaked* int BakedDiscardedCount; // Custom rectangle identifiers From bcd1a94b89c478de8beee33d92e30ed00e5cda50 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Apr 2025 17:48:29 +0200 Subject: [PATCH 134/191] Fonts: Extract ImFontAtlasBuildGetFontBaked() out of ImFont::GetFontBaked() mostly for consistency with upcoming changes + tweak locals in AddFont(). --- imgui.cpp | 2 +- imgui.h | 2 +- imgui_draw.cpp | 87 ++++++++++++++++++++++++------------------------ imgui_internal.h | 1 + 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e2765edac..2b8c8f8db 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15770,7 +15770,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) if (IsItemHovered()) highlight_r_id = id; TableNextColumn(); - Image(atlas->TexID, ImVec2(r.w, r.h), r.uv0, r.uv1); + Image(atlas->TexRef, ImVec2(r.w, r.h), r.uv0, r.uv1); } EndTable(); } diff --git a/imgui.h b/imgui.h index 2a0cf3546..6ae7c9e52 100644 --- a/imgui.h +++ b/imgui.h @@ -3673,7 +3673,7 @@ struct ImFontAtlas ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines int TexNextUniqueID; // Next value to be stored in TexData->UniqueID - int FontNextUniqueID; // Next value to be stored in ImFont->SourceID + int FontNextUniqueID; // Next value to be stored in ImFont->FontID ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. ImFontAtlasBuilder* Builder; // Opaque interface to our data that doesn't need to be public and may be discarded when rebuilding. const ImFontLoader* FontLoader; // Font loader opaque interface (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). Don't set directly! diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f78ff9901..dffe5ccf5 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2983,12 +2983,12 @@ void ImFontAtlasBuildDestroyFontSourceData(ImFontAtlas* atlas, ImFontConfig* src src->GlyphExcludeRanges = NULL; } -ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - IM_ASSERT((font_cfg->FontData != NULL && font_cfg->FontDataSize > 0) || (font_cfg->FontLoader != NULL)); - IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); - IM_ASSERT(font_cfg->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); + IM_ASSERT((font_cfg_in->FontData != NULL && font_cfg_in->FontDataSize > 0) || (font_cfg_in->FontLoader != NULL)); + IM_ASSERT(font_cfg_in->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); + IM_ASSERT(font_cfg_in->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); // Lazily create builder on the first call to AddFont if (Builder == NULL) @@ -2996,12 +2996,12 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) // Create new font ImFont* font; - if (!font_cfg->MergeMode) + if (!font_cfg_in->MergeMode) { font = IM_NEW(ImFont)(); font->FontId = FontNextUniqueID++; - font->Flags = font_cfg->Flags; - font->DefaultSize = font_cfg->SizePixels; + font->Flags = font_cfg_in->Flags; + font->DefaultSize = font_cfg_in->SizePixels; Fonts.push_back(font); } else @@ -3010,14 +3010,14 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) font = Fonts.back(); } - Sources.push_back(*font_cfg); - ImFontConfig& new_font_cfg = Sources.back(); - if (new_font_cfg.DstFont == NULL) - new_font_cfg.DstFont = font; - if (!new_font_cfg.FontDataOwnedByAtlas) + Sources.push_back(*font_cfg_in); + ImFontConfig* font_cfg = &Sources.back(); + if (font_cfg->DstFont == NULL) + font_cfg->DstFont = font; + if (font_cfg->FontDataOwnedByAtlas == false) { - new_font_cfg.FontDataOwnedByAtlas = true; - new_font_cfg.FontData = ImMemdup(font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + font_cfg->FontDataOwnedByAtlas = true; + font_cfg->FontData = ImMemdup(font_cfg->FontData, (size_t)font_cfg->FontDataSize); } // Sanity check @@ -3028,7 +3028,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) for (const ImWchar* p = font_cfg->GlyphExcludeRanges; p[0] != 0; p++, size++) {} IM_ASSERT((size & 1) == 0 && "GlyphExcludeRanges[] size must be multiple of two!"); IM_ASSERT((size <= 64) && "GlyphExcludeRanges[] size must be small!"); - new_font_cfg.GlyphExcludeRanges = (ImWchar*)ImMemdup(font_cfg->GlyphExcludeRanges, sizeof(font_cfg->GlyphExcludeRanges[0]) * (size + 1)); + font_cfg->GlyphExcludeRanges = (ImWchar*)ImMemdup(font_cfg->GlyphExcludeRanges, sizeof(font_cfg->GlyphExcludeRanges[0]) * (size + 1)); } if (font_cfg->FontLoader != NULL) { @@ -3038,12 +3038,14 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) IM_ASSERT(font_cfg->FontLoaderData == NULL); // Pointers to Sources are otherwise dangling + font->SourcesCount++; ImFontAtlasBuildUpdatePointers(this); - if (!ImFontAtlasBuildAddFont(this, &new_font_cfg)) + if (!ImFontAtlasBuildAddFont(this, font_cfg)) { // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) - ImFontAtlasBuildDestroyFontSourceData(this, &new_font_cfg); + ImFontAtlasBuildDestroyFontSourceData(this, font_cfg); Sources.pop_back(); + font->SourcesCount--; if (!font_cfg->MergeMode) { IM_DELETE(font); @@ -3051,8 +3053,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) } return NULL; } - - return new_font_cfg.DstFont; + return font; } // Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) @@ -3416,11 +3417,7 @@ void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) ImFontConfig* src = &atlas->Sources[src_n]; ImFont* font = src->DstFont; if (!src->MergeMode) - { font->Sources = src; - font->SourcesCount = 0; - } - font->SourcesCount++; } } @@ -3739,11 +3736,11 @@ void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImF baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; } -ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float size, ImGuiID baked_id) +ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id) { - IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", size); + IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", font_size); ImFontBaked* baked = atlas->Builder->BakedPool.push_back(ImFontBaked()); - baked->Size = size; + baked->Size = font_size; baked->BakedId = baked_id; baked->ContainerFont = font; baked->LastUsedFrame = atlas->Builder->FrameCount; @@ -5021,10 +5018,10 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked IM_ASSERT(baked->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. // Set UV from packed rectangle - if (in_glyph->PackId != ImFontAtlasRectId_Invalid) + if (glyph.PackId != ImFontAtlasRectId_Invalid) { - ImTextureRect* r = ImFontAtlasPackGetRect(atlas, in_glyph->PackId); - IM_ASSERT(in_glyph->U0 == 0.0f && in_glyph->V0 == 0.0f && in_glyph->U1 == 0.0f && in_glyph->V1 == 0.0f); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); + IM_ASSERT(glyph.U0 == 0.0f && glyph.V0 == 0.0f && glyph.U1 == 0.0f && glyph.V1 == 0.0f); glyph.U0 = (r->x) * atlas->TexUvScale.x; glyph.V0 = (r->y) * atlas->TexUvScale.y; glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; @@ -5196,31 +5193,35 @@ ImFontBaked* ImFont::GetFontBaked(float size) ImFontAtlas* atlas = ContainerAtlas; ImFontAtlasBuilder* builder = atlas->Builder; + baked = ImFontAtlasBuildGetFontBaked(atlas, this, size); + if (baked == NULL) + return NULL; + baked->LastUsedFrame = builder->FrameCount; + LastBaked = baked; + return baked; +} - // FIXME-NEWATLAS: Design for picking a nearest size based on some criterias? +ImFontBaked* ImFontAtlasBuildGetFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size) +{ + // FIXME-NEWATLAS: Design for picking a nearest size based on some criteria? // FIXME-NEWATLAS: Altering font density won't work right away. - ImGuiID baked_id = ImFontAtlasBakedGetId(FontId, size); + ImGuiID baked_id = ImFontAtlasBakedGetId(font->FontId, font_size); + ImFontAtlasBuilder* builder = atlas->Builder; ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); - baked = *p_baked_in_map; + ImFontBaked* baked = *p_baked_in_map; if (baked != NULL) { - IM_ASSERT(baked->Size == size && baked->ContainerFont == this && baked->BakedId == baked_id); - baked->LastUsedFrame = builder->FrameCount; - LastBaked = baked; + IM_ASSERT(baked->Size == font_size && baked->ContainerFont == font && baked->BakedId == baked_id); return baked; } // If atlas is locked, find closest match // FIXME-OPT: This is not an optimal query. - if ((Flags & ImFontFlags_LockBakedSizes) || atlas->Locked) + if ((font->Flags & ImFontFlags_LockBakedSizes) || atlas->Locked) { - baked = ImFontAtlasBuildGetClosestFontBakedMatch(atlas, this, size); + baked = ImFontAtlasBuildGetClosestFontBakedMatch(atlas, font, font_size); if (baked != NULL) - { - baked->LastUsedFrame = builder->FrameCount; - LastBaked = baked; return baked; - } if (atlas->Locked) { IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! @@ -5229,10 +5230,8 @@ ImFontBaked* ImFont::GetFontBaked(float size) } // Create new - baked = ImFontAtlasBuildAddFontBaked(atlas, this, size, baked_id); - LastBaked = baked; + baked = ImFontAtlasBuildAddFontBaked(atlas, font, font_size, baked_id); *p_baked_in_map = baked; // To avoid 'builder->BakedMap.SetVoidPtr(baked_id, baked);' while we can. - return baked; } diff --git a/imgui_internal.h b/imgui_internal.h index 7c615d323..7f9420cda 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3786,6 +3786,7 @@ IMGUI_API bool ImFontAtlasBuildInitFontOutput(ImFontAtlas* atlas, I IMGUI_API void ImFontAtlasBuildDestroyFontOutput(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasBuildDestroyFontSourceData(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API ImFontBaked* ImFontAtlasBuildGetFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size); IMGUI_API ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); IMGUI_API ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont* font, float font_size); IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); From c4fa9bb61fb69fa5f322b3e621baedfd101a72a6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Apr 2025 18:06:50 +0200 Subject: [PATCH 135/191] Fonts: add ImFontGlyph::SourceIdx. Extract code out of DebugNodeFont() into DebugNodeFontGlyphesForSrcMask(). (src_mask unused in this commit) --- imgui.cpp | 100 +++++++++++++++++++++++++---------------------- imgui.h | 3 +- imgui_draw.cpp | 6 ++- imgui_internal.h | 1 + 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2b8c8f8db..66b0609cd 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16700,52 +16700,7 @@ void ImGui::DebugNodeFont(ImFont* font) src_n, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); } - ImDrawList* draw_list = GetWindowDrawList(); - const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); - const float cell_size = baked->Size * 1; - const float cell_spacing = GetStyle().ItemSpacing.y; - for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) - { - // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) - // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT - // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) - if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191)) - { - base += 8192 - 256; - continue; - } - - int count = 0; - for (unsigned int n = 0; n < 256; n++) - if (baked->IsGlyphLoaded((ImWchar)(base + n))) - count++; - if (count <= 0) - continue; - if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) - continue; - - // Draw a 16x16 grid of glyphs - ImVec2 base_pos = GetCursorScreenPos(); - for (unsigned int n = 0; n < 256; n++) - { - // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions - // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. - ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); - ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL; - draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); - if (!glyph) - continue; - font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); - if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) - { - DebugNodeFontGlyph(font, glyph); - EndTooltip(); - } - } - Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); - TreePop(); - } + DebugNodeFontGlyphesForSrcMask(font, baked, ~0); TreePop(); } PopID(); @@ -16754,6 +16709,57 @@ void ImGui::DebugNodeFont(ImFont* font) Unindent(); } +void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask) +{ + ImDrawList* draw_list = GetWindowDrawList(); + const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); + const float cell_size = baked->Size * 1; + const float cell_spacing = GetStyle().ItemSpacing.y; + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + { + // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) + // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT + // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) + if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191)) + { + base += 8192 - 256; + continue; + } + + int count = 0; + for (unsigned int n = 0; n < 256; n++) + if (const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL) + if (src_mask & (1 << glyph->SourceIdx)) + count++; + if (count <= 0) + continue; + if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) + continue; + + // Draw a 16x16 grid of glyphs + ImVec2 base_pos = GetCursorScreenPos(); + for (unsigned int n = 0; n < 256; n++) + { + // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions + // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); + const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL; + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); + if (!glyph || (src_mask & (1 << glyph->SourceIdx)) == 0) + continue; + font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); + if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) + { + DebugNodeFontGlyph(font, glyph); + EndTooltip(); + } + } + Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); + TreePop(); + } +} + void ImGui::DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) { Text("Codepoint: U+%04X", glyph->Codepoint); @@ -16767,6 +16773,7 @@ void ImGui::DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) ImTextureRect* r = ImFontAtlasPackGetRect(font->ContainerAtlas, glyph->PackId); Text("PackId: %d (%dx%d rect at %d,%d)", glyph->PackId, r->w, r->h, r->x, r->y); } + Text("SourceIdx: %d", glyph->SourceIdx); } // [DEBUG] Display contents of ImGuiStorage @@ -17429,6 +17436,7 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns*) {} void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} void ImGui::DebugNodeFont(ImFont*) {} +void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont*, ImFontBaked*, int) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} diff --git a/imgui.h b/imgui.h index 6ae7c9e52..f99eaedd3 100644 --- a/imgui.h +++ b/imgui.h @@ -3484,7 +3484,8 @@ struct ImFontGlyph { unsigned int Colored : 1; // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops) unsigned int Visible : 1; // Flag to indicate glyph has no visible pixels (e.g. space). Allow early out when rendering. - unsigned int Codepoint : 30; // 0x0000..0x10FFFF + unsigned int SourceIdx : 4; // Index of source in parent font + unsigned int Codepoint : 26; // 0x0000..0x10FFFF float AdvanceX; // Horizontal distance to advance cursor/layout position. float X0, Y0, X1, Y1; // Glyph corners. Offsets from current cursor/layout position. float U0, V0, U1, V1; // Texture coordinates for the current value of ImFontAtlas->TexRef. Cached equivalent of calling GetCustomRect() with PackId. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index dffe5ccf5..a0c34901d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4390,7 +4390,11 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) if (ImFontGlyph* glyph = loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint)) - return glyph; // FIXME: Add hooks for e.g. #7962 + { + // FIXME: Add hooks for e.g. #7962 + glyph->SourceIdx = src_n; + return glyph; + } loader_user_data_p += loader->FontBakedSrcLoaderDataSize; } diff --git a/imgui_internal.h b/imgui_internal.h index 7f9420cda..137ba7d46 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3625,6 +3625,7 @@ namespace ImGui IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label); IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); + IMGUI_API void DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect = NULL); // ID used to facilitate persisting the "current" texture. IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); From 890fff92fd071e2c70139205e065eca0c1061761 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Apr 2025 12:08:28 +0200 Subject: [PATCH 136/191] Fonts: rename many internal functions for consistency. No other changes. --- imgui.cpp | 10 ++-- imgui_draw.cpp | 118 +++++++++++++++++++++++------------------------ imgui_internal.h | 38 +++++++-------- 3 files changed, 83 insertions(+), 83 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 66b0609cd..8f7c8fe62 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15704,10 +15704,10 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) if (ImGuiFreeType::DebugEditFontBuilderFlags(&loader_flags)) { for (ImFont* font : atlas->Fonts) - ImFontAtlasBuildDestroyFontOutput(atlas, font); + ImFontAtlasFontDestroyOutput(atlas, font); atlas->FontBuilderFlags = loader_flags; for (ImFont* font : atlas->Fonts) - ImFontAtlasBuildInitFontOutput(atlas, font); + ImFontAtlasFontInitOutput(atlas, font); } } #else @@ -15733,7 +15733,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) atlas->CompactCache(); SameLine(); if (Button("Grow")) - ImFontAtlasBuildGrowTexture(atlas); + ImFontAtlasTextureGrow(atlas); SameLine(); if (Button("Clear All")) ImFontAtlasBuildClear(atlas); @@ -16666,9 +16666,9 @@ void ImGui::DebugNodeFont(ImFont* font) Text("FreeType Loader Flags: 0x%08X", loader_flags); if (ImGuiFreeType::DebugEditFontBuilderFlags(&loader_flags)) { - ImFontAtlasBuildDestroyFontOutput(atlas, font); + ImFontAtlasFontDestroyOutput(atlas, font); src->FontBuilderFlags = loader_flags; - ImFontAtlasBuildInitFontOutput(atlas, font); + ImFontAtlasFontInitOutput(atlas, font); } } #endif diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a0c34901d..40ae02ed5 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2643,7 +2643,7 @@ void ImFontAtlas::Clear() void ImFontAtlas::CompactCache() { - ImFontAtlasBuildCompactTexture(this); + ImFontAtlasTextureCompact(this); } void ImFontAtlas::ClearInputData() @@ -2651,9 +2651,9 @@ void ImFontAtlas::ClearInputData() IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); for (ImFont* font : Fonts) - ImFontAtlasBuildDestroyFontOutput(this, font); + ImFontAtlasFontDestroyOutput(this, font); for (ImFontConfig& font_cfg : Sources) - ImFontAtlasBuildDestroyFontSourceData(this, &font_cfg); + ImFontAtlasFontDestroySourceData(this, &font_cfg); for (ImFont* font : Fonts) { // When clearing this we lose access to the font name and other information used to build the font. @@ -2972,17 +2972,6 @@ bool ImFontAtlas::Build() } #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -void ImFontAtlasBuildDestroyFontSourceData(ImFontAtlas* atlas, ImFontConfig* src) -{ - IM_UNUSED(atlas); - if (src->FontDataOwnedByAtlas) - IM_FREE(src->FontData); - src->FontData = NULL; - if (src->GlyphExcludeRanges) - IM_FREE((void*)src->GlyphExcludeRanges); - src->GlyphExcludeRanges = NULL; -} - ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); @@ -3040,10 +3029,10 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) // Pointers to Sources are otherwise dangling font->SourcesCount++; ImFontAtlasBuildUpdatePointers(this); - if (!ImFontAtlasBuildAddFont(this, font_cfg)) + if (!ImFontAtlasFontInitSource(this, font_cfg)) { // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) - ImFontAtlasBuildDestroyFontSourceData(this, font_cfg); + ImFontAtlasFontDestroySourceData(this, font_cfg); Sources.pop_back(); font->SourcesCount--; if (!font_cfg->MergeMode) @@ -3195,9 +3184,9 @@ void ImFontAtlas::RemoveFont(ImFont* font) IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); font->ClearOutputData(); - ImFontAtlasBuildDestroyFontOutput(this, font); + ImFontAtlasFontDestroyOutput(this, font); for (int src_n = 0; src_n < font->SourcesCount; src_n++) - ImFontAtlasBuildDestroyFontSourceData(this, &font->Sources[src_n]); + ImFontAtlasFontDestroySourceData(this, &font->Sources[src_n]); bool removed = Fonts.find_erase(font); IM_ASSERT(removed); @@ -3251,14 +3240,14 @@ void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id) // ImFont* myfont = io.Fonts->AddFontFromFileTTF(....); // myfont->GetFontBaked(16.0f); // myfont->Flags |= ImFontFlags_LockBakedSizes; -int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) +ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { float font_size = font->DefaultSize; return AddCustomRectFontGlyphForSize(font, font_size, codepoint, width, height, advance_x, offset); } // FIXME: we automatically set glyph.Colored=true by default. // If you need to alter this, you can write 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph(). -int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) +ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { #ifdef IMGUI_USE_WCHAR32 IM_ASSERT(codepoint <= IM_UNICODE_CODEPOINT_MAX); @@ -3277,7 +3266,7 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); if (baked->IsGlyphLoaded(codepoint)) - ImFontAtlasBuildDiscardFontBakedGlyph(this, font, baked, baked->FindGlyph(codepoint)); + ImFontAtlasBakedDiscardFontGlyph(this, font, baked, baked->FindGlyph(codepoint)); ImFontGlyph glyph; glyph.Codepoint = codepoint; @@ -3337,9 +3326,9 @@ void ImFontAtlasBuildMain(ImFontAtlas* atlas) IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); if (atlas->TexData && atlas->TexData->Format != atlas->TexDesiredFormat) { - ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); ImFontAtlasBuildDestroy(atlas); - ImFontAtlasBuildAddTexture(atlas, new_tex_size.x, new_tex_size.y); + ImFontAtlasTextureAdd(atlas, new_tex_size.x, new_tex_size.y); } if (atlas->Builder == NULL) @@ -3372,7 +3361,7 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); for (ImFont* font : atlas->Fonts) - ImFontAtlasBuildDestroyFontOutput(atlas, font); + ImFontAtlasFontDestroyOutput(atlas, font); if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) atlas->FontLoader->LoaderShutdown(atlas); @@ -3383,7 +3372,7 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderInit) atlas->FontLoader->LoaderInit(atlas); for (ImFont* font : atlas->Fonts) - ImFontAtlasBuildInitFontOutput(atlas, font); + ImFontAtlasFontInitOutput(atlas, font); } // Preload all glyph ranges for legacy backends. @@ -3550,7 +3539,7 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas) //----------------------------------------------------------------------------------------------------------------------------- // Was tempted to lazily init FontSrc but wouldn't save much + makes it more complicated to detect invalid data at AddFont() -bool ImFontAtlasBuildInitFontOutput(ImFontAtlas* atlas, ImFont* font) +bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font) { bool ret = true; for (int src_n = 0; src_n < font->SourcesCount; src_n++) @@ -3565,7 +3554,7 @@ bool ImFontAtlasBuildInitFontOutput(ImFontAtlas* atlas, ImFont* font) } // Keep source/input FontData -void ImFontAtlasBuildDestroyFontOutput(ImFontAtlas* atlas, ImFont* font) +void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font) { font->ClearOutputData(); for (int src_n = 0; src_n < font->SourcesCount; src_n++) @@ -3579,7 +3568,7 @@ void ImFontAtlasBuildDestroyFontOutput(ImFontAtlas* atlas, ImFont* font) //----------------------------------------------------------------------------------------------------------------------------- -bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) +bool ImFontAtlasFontInitSource(ImFontAtlas* atlas, ImFontConfig* src) { ImFont* font = src->DstFont; if (src->MergeMode == false) @@ -3599,6 +3588,17 @@ bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) return true; } +void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + if (src->FontDataOwnedByAtlas) + IM_FREE(src->FontData); + src->FontData = NULL; + if (src->GlyphExcludeRanges) + IM_FREE((void*)src->GlyphExcludeRanges); + src->GlyphExcludeRanges = NULL; +} + // Create a compact, baked "..." if it doesn't exist, by using the ".". // This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. // FIXME-NEWATLAS: This borrows too much from FontLoader's FontLoadGlyph() handlers and suggest that we should add further helpers. @@ -3721,7 +3721,7 @@ void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, Im } } -void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph) +void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph) { if (glyph->PackId != ImFontAtlasRectId_Invalid) { @@ -3736,7 +3736,7 @@ void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImF baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; } -ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id) +ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id) { IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", font_size); ImFontBaked* baked = atlas->Builder->BakedPool.push_back(ImFontBaked()); @@ -3769,7 +3769,7 @@ ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, floa } // FIXME-OPT: This is not a fast query. Adding a BakedCount field in Font might allow to take a shortcut for the most common case. -ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont* font, float font_size) +ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size) { ImFontAtlasBuilder* builder = atlas->Builder; ImFontBaked* closest_larger_match = NULL; @@ -3792,7 +3792,7 @@ ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont return NULL; } -void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) +void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) { ImFontAtlasBuilder* builder = atlas->Builder; IMGUI_DEBUG_LOG_FONT("[font] Discard baked %.2f for \"%s\"\n", baked->Size, font->GetDebugName()); @@ -3822,14 +3822,14 @@ void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBa font->LastBaked = NULL; } -void ImFontAtlasBuildDiscardFontBakes(ImFontAtlas* atlas, ImFont* font) +void ImFontAtlasFontDiscardOutputBakes(ImFontAtlas* atlas, ImFont* font) { if (ImFontAtlasBuilder* builder = atlas->Builder) // This can be called from font destructor for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) { ImFontBaked* baked = &builder->BakedPool[baked_n]; if (baked->ContainerFont == font && !baked->WantDestroy) - ImFontAtlasBuildDiscardFontBaked(atlas, font, baked); + ImFontAtlasBakedDiscard(atlas, font, baked); } } @@ -3844,7 +3844,7 @@ void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames) continue; if (baked->WantDestroy || (baked->ContainerFont->Flags & ImFontFlags_LockBakedSizes)) continue; - ImFontAtlasBuildDiscardFontBaked(atlas, baked->ContainerFont, baked); + ImFontAtlasBakedDiscard(atlas, baked->ContainerFont, baked); } } @@ -3905,7 +3905,7 @@ static void ImFontAtlasBuildSetTexture(ImFontAtlas* atlas, ImTextureData* tex) } // Create a new texture, discard previous one -ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h) +ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h) { ImTextureData* old_tex = atlas->TexData; ImTextureData* new_tex; @@ -3955,13 +3955,13 @@ static void ImFontAtlasDebugWriteTexToDisk(ImTextureData* tex, const char* descr } #endif -void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) +void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h) { ImFontAtlasBuilder* builder = atlas->Builder; builder->LockDisableResize = true; ImTextureData* old_tex = atlas->TexData; - ImTextureData* new_tex = ImFontAtlasBuildAddTexture(atlas, w, h); + ImTextureData* new_tex = ImFontAtlasTextureAdd(atlas, w, h); new_tex->UseColors = old_tex->UseColors; IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize+repack %dx%d => Texture #%03d: %dx%d\n", old_tex->UniqueID, old_tex->Width, old_tex->Height, new_tex->UniqueID, new_tex->Width, new_tex->Height); //for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) @@ -3995,7 +3995,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) builder->Rects.swap(old_rects); builder->RectsIndex = old_index; ImFontAtlasBuildSetTexture(atlas, old_tex); - ImFontAtlasBuildGrowTexture(atlas, w, h); // Recurse + ImFontAtlasTextureGrow(atlas, w, h); // Recurse return; } IM_ASSERT(ImFontAtlasRectId_GetIndex(new_r_id) == builder->RectsIndex.index_from_ptr(&index_entry)); @@ -4027,7 +4027,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) //ImFontAtlasDebugWriteTexToDisk(new_tex, "After Pack"); } -void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_h) +void ImFontAtlasTextureGrow(ImFontAtlas* atlas, int old_tex_w, int old_tex_h) { //ImFontAtlasDebugWriteTexToDisk(atlas->TexData, "Before Grow"); ImFontAtlasBuilder* builder = atlas->Builder; @@ -4056,10 +4056,10 @@ void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_ if (new_tex_w == old_tex_w && new_tex_h == old_tex_h) return; - ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); + ImFontAtlasTextureRepack(atlas, new_tex_w, new_tex_h); } -void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas) +void ImFontAtlasTextureMakeSpace(ImFontAtlas* atlas) { // Can some baked contents be ditched? //IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlasBuildMakeSpace()\n"); @@ -4068,12 +4068,12 @@ void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas) // Currently using a heuristic for repack without growing. if (builder->RectsDiscardedSurface < builder->RectsPackedSurface * 0.20f) - ImFontAtlasBuildGrowTexture(atlas); + ImFontAtlasTextureGrow(atlas); else - ImFontAtlasBuildRepackTexture(atlas, atlas->TexData->Width, atlas->TexData->Height); + ImFontAtlasTextureRepack(atlas, atlas->TexData->Width, atlas->TexData->Height); } -ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) +ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas) { int min_w = ImUpperPowerOfTwo(atlas->TexMinWidth); int min_h = ImUpperPowerOfTwo(atlas->TexMinHeight); @@ -4110,26 +4110,26 @@ ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas) // Clear all output. Invalidates all AddCustomRect() return values! void ImFontAtlasBuildClear(ImFontAtlas* atlas) { - ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); ImFontAtlasBuildDestroy(atlas); - ImFontAtlasBuildAddTexture(atlas, new_tex_size.x, new_tex_size.y); + ImFontAtlasTextureAdd(atlas, new_tex_size.x, new_tex_size.y); ImFontAtlasBuildInit(atlas); } // You should not need to call this manually! // If you think you do, let us know and we can advise about policies auto-compact. -void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) +void ImFontAtlasTextureCompact(ImFontAtlas* atlas) { ImFontAtlasBuilder* builder = atlas->Builder; ImFontAtlasBuildDiscardBakes(atlas, 1); ImTextureData* old_tex = atlas->TexData; ImVec2i old_tex_size = ImVec2i(old_tex->Width, old_tex->Height); - ImVec2i new_tex_size = ImFontAtlasBuildGetTextureSizeEstimate(atlas); + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); if (builder->RectsDiscardedCount == 0 && new_tex_size.x == old_tex_size.x && new_tex_size.y == old_tex_size.y) return; - ImFontAtlasBuildRepackTexture(atlas, new_tex_size.x, new_tex_size.y); + ImFontAtlasTextureRepack(atlas, new_tex_size.x, new_tex_size.y); } // Start packing over current empty texture @@ -4153,7 +4153,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) // Create initial texture size if (atlas->TexData == NULL || atlas->TexData->Pixels == NULL) - ImFontAtlasBuildAddTexture(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); + ImFontAtlasTextureAdd(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); if (atlas->FontLoader->LoaderInit) @@ -4170,7 +4170,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) // Register fonts ImFontAtlasBuildUpdatePointers(atlas); for (ImFontConfig& cfg : atlas->Sources) - ImFontAtlasBuildAddFont(atlas, &cfg); + ImFontAtlasFontInitSource(atlas, &cfg); // Update UV coordinates etc. stored in bound ImDrawListSharedData instance ImFontAtlasUpdateDrawListsSharedData(atlas); @@ -4182,7 +4182,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) { for (ImFont* font : atlas->Fonts) - ImFontAtlasBuildDestroyFontOutput(atlas, font); + ImFontAtlasFontDestroyOutput(atlas, font); if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) { atlas->FontLoader->LoaderShutdown(atlas); @@ -4299,7 +4299,7 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon } // Resize or repack atlas! (this should be a rare event) - ImFontAtlasBuildMakeSpace(atlas); + ImFontAtlasTextureMakeSpace(atlas); } builder->MaxRectBounds.x = ImMax(builder->MaxRectBounds.x, r.x + r.w + pack_padding); @@ -4992,7 +4992,7 @@ ImFont::~ImFont() void ImFont::ClearOutputData() { if (ImFontAtlas* atlas = ContainerAtlas) - ImFontAtlasBuildDiscardFontBakes(atlas, this); + ImFontAtlasFontDiscardOutputBakes(atlas, this); FallbackChar = EllipsisChar = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); LastBaked = NULL; @@ -5197,7 +5197,7 @@ ImFontBaked* ImFont::GetFontBaked(float size) ImFontAtlas* atlas = ContainerAtlas; ImFontAtlasBuilder* builder = atlas->Builder; - baked = ImFontAtlasBuildGetFontBaked(atlas, this, size); + baked = ImFontAtlasBakedGetOrAdd(atlas, this, size); if (baked == NULL) return NULL; baked->LastUsedFrame = builder->FrameCount; @@ -5205,7 +5205,7 @@ ImFontBaked* ImFont::GetFontBaked(float size) return baked; } -ImFontBaked* ImFontAtlasBuildGetFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size) +ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size) { // FIXME-NEWATLAS: Design for picking a nearest size based on some criteria? // FIXME-NEWATLAS: Altering font density won't work right away. @@ -5223,7 +5223,7 @@ ImFontBaked* ImFontAtlasBuildGetFontBaked(ImFontAtlas* atlas, ImFont* font, floa // FIXME-OPT: This is not an optimal query. if ((font->Flags & ImFontFlags_LockBakedSizes) || atlas->Locked) { - baked = ImFontAtlasBuildGetClosestFontBakedMatch(atlas, font, font_size); + baked = ImFontAtlasBakedGetClosestMatch(atlas, font, font_size); if (baked != NULL) return baked; if (atlas->Locked) @@ -5234,7 +5234,7 @@ ImFontBaked* ImFontAtlasBuildGetFontBaked(ImFontAtlas* atlas, ImFont* font, floa } // Create new - baked = ImFontAtlasBuildAddFontBaked(atlas, font, font_size, baked_id); + baked = ImFontAtlasBakedAdd(atlas, font, font_size, baked_id); *p_baked_in_map = baked; // To avoid 'builder->BakedMap.SetVoidPtr(baked_id, baked);' while we can. return baked; } diff --git a/imgui_internal.h b/imgui_internal.h index 137ba7d46..3274940c0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3772,32 +3772,32 @@ IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char); IMGUI_API void ImFontAtlasBuildClear(ImFontAtlas* atlas); // Clear output and custom rects -IMGUI_API ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h); -IMGUI_API void ImFontAtlasBuildMakeSpace(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h); -IMGUI_API void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); -IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); -IMGUI_API ImVec2i ImFontAtlasBuildGetTextureSizeEstimate(ImFontAtlas* atlas); +IMGUI_API ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasTextureMakeSpace(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasTextureGrow(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); +IMGUI_API void ImFontAtlasTextureCompact(ImFontAtlas* atlas); +IMGUI_API ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas); -IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); -IMGUI_API bool ImFontAtlasBuildInitFontOutput(ImFontAtlas* atlas, ImFont* font); // Using DestroyFontOutput/InitFontOutput sequence useful notably if font loader params have changed -IMGUI_API void ImFontAtlasBuildDestroyFontOutput(ImFontAtlas* atlas, ImFont* font); -IMGUI_API void ImFontAtlasBuildDestroyFontSourceData(ImFontAtlas* atlas, ImFontConfig* src); - -IMGUI_API ImFontBaked* ImFontAtlasBuildGetFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size); -IMGUI_API ImFontBaked* ImFontAtlasBuildAddFontBaked(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); -IMGUI_API ImFontBaked* ImFontAtlasBuildGetClosestFontBakedMatch(ImFontAtlas* atlas, ImFont* font, float font_size); IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); -IMGUI_API void ImFontAtlasBuildDiscardFontBakes(ImFontAtlas* atlas, ImFont* font); -IMGUI_API void ImFontAtlasBuildDiscardFontBaked(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); -IMGUI_API void ImFontAtlasBuildDiscardFontBakedGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); -IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); -IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); +IMGUI_API bool ImFontAtlasFontInitSource(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font); // Using FontDestroyOutput/FontInitOutput sequence useful notably if font loader params have changed +IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasFontDiscardOutputBakes(ImFontAtlas* atlas, ImFont* font); + IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size); +IMGUI_API ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); +IMGUI_API void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); +IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); +IMGUI_API void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); +IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); From e7efe94fd2fe8c8707ad2692083f26c64062b624 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Apr 2025 17:56:00 +0200 Subject: [PATCH 137/191] Fonts: shallow rework of ImFontAtlasBakedAddFontGlyph() to facilitate upcoming change. --- imgui.h | 2 +- imgui_draw.cpp | 38 +++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/imgui.h b/imgui.h index f99eaedd3..b63b0ed4e 100644 --- a/imgui.h +++ b/imgui.h @@ -3479,7 +3479,7 @@ struct ImFontConfig }; // Hold rendering data for one glyph. -// (Note: some language parsers may fail to convert the 31+1 bitfield members, in this case maybe drop store a single u32 or we can rework this) +// (Note: some language parsers may fail to convert the bitfield members, in this case maybe drop store a single u32 or we can rework this) struct ImFontGlyph { unsigned int Colored : 1; // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 40ae02ed5..c311b18af 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5013,23 +5013,23 @@ bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. // Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). -// 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. +// - 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph) { int glyph_idx = baked->Glyphs.Size; baked->Glyphs.push_back(*in_glyph); - ImFontGlyph& glyph = baked->Glyphs[glyph_idx]; + ImFontGlyph* glyph = &baked->Glyphs[glyph_idx]; IM_ASSERT(baked->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. // Set UV from packed rectangle - if (glyph.PackId != ImFontAtlasRectId_Invalid) + if (glyph->PackId != ImFontAtlasRectId_Invalid) { - ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); - IM_ASSERT(glyph.U0 == 0.0f && glyph.V0 == 0.0f && glyph.U1 == 0.0f && glyph.V1 == 0.0f); - glyph.U0 = (r->x) * atlas->TexUvScale.x; - glyph.V0 = (r->y) * atlas->TexUvScale.y; - glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; - glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph->PackId); + IM_ASSERT(glyph->U0 == 0.0f && glyph->V0 == 0.0f && glyph->U1 == 0.0f && glyph->V1 == 0.0f); + glyph->U0 = (r->x) * atlas->TexUvScale.x; + glyph->V0 = (r->y) * atlas->TexUvScale.y; + glyph->U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph->V1 = (r->y + r->h) * atlas->TexUvScale.y; baked->MetricsTotalSurface += r->w * r->h; } @@ -5037,12 +5037,12 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked { // Clamp & recenter if needed const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0].SizePixels; - float advance_x = ImClamp(glyph.AdvanceX, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); - if (advance_x != glyph.AdvanceX) + float advance_x = ImClamp(glyph->AdvanceX, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); + if (advance_x != glyph->AdvanceX) { - float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - glyph.AdvanceX) * 0.5f) : (advance_x - glyph.AdvanceX) * 0.5f; - glyph.X0 += char_off_x; - glyph.X1 += char_off_x; + float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - glyph->AdvanceX) * 0.5f) : (advance_x - glyph->AdvanceX) * 0.5f; + glyph->X0 += char_off_x; + glyph->X1 += char_off_x; } // Snap to pixel @@ -5050,20 +5050,20 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked advance_x = IM_ROUND(advance_x); // Bake spacing - glyph.AdvanceX = advance_x + src->GlyphExtraAdvanceX; + glyph->AdvanceX = advance_x + src->GlyphExtraAdvanceX; } - if (glyph.Colored) + if (glyph->Colored) atlas->TexPixelsUseColors = atlas->TexData->UseColors = true; // Update lookup tables - int codepoint = glyph.Codepoint; + const int codepoint = glyph->Codepoint; ImFontBaked_BuildGrowIndex(baked, codepoint + 1); - baked->IndexAdvanceX[codepoint] = glyph.AdvanceX; + baked->IndexAdvanceX[codepoint] = glyph->AdvanceX; baked->IndexLookup[codepoint] = (ImU16)glyph_idx; const int page_n = codepoint / 8192; baked->ContainerFont->Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); - return &glyph; + return glyph; } // Copy to texture, post-process and queue update for backend From 2b0d49a9054d21bc5cea57a8b21b8a7e4c885b43 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 24 Apr 2025 17:54:16 +0200 Subject: [PATCH 138/191] Fonts: make ImFont::Sources a vector. Later it should become a ImSpan<> --- imgui.cpp | 44 +++++++++---------- imgui.h | 5 +-- imgui_draw.cpp | 72 ++++++++++++++------------------ misc/freetype/imgui_freetype.cpp | 4 +- 4 files changed, 58 insertions(+), 67 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 8f7c8fe62..04cb11264 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16612,7 +16612,7 @@ void ImGui::DebugNodeFont(ImFont* font) ImGuiContext& g = *GImGui; ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; ImFontAtlas* atlas = font->ContainerAtlas; - bool opened = TreeNode(font, "Font: \"%s\": %d sources(s)", font->GetDebugName(), font->SourcesCount); + bool opened = TreeNode(font, "Font: \"%s\": %d sources(s)", font->GetDebugName(), font->Sources.Size); // Display preview text if (!opened) @@ -16652,28 +16652,30 @@ void ImGui::DebugNodeFont(ImFont* font) Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar); Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar); - for (int src_n = 0; src_n < font->SourcesCount; src_n++) - if (ImFontConfig* src = &font->Sources[src_n]) - if (TreeNode(src, "Input %d: \'%s\', Oversample: %d,%d, PixelSnapH: %d, Offset: (%.1f,%.1f)", - src_n, src->Name, src->OversampleH, src->OversampleV, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y)) - { - const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; - Text("Loader: '%s'", loader->Name ? loader->Name : "N/A"); + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + { + ImFontConfig* src = font->Sources[src_n]; + if (TreeNode(src, "Input %d: \'%s\', Oversample: %d,%d, PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->OversampleH, src->OversampleV, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y)) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + Text("Loader: '%s'", loader->Name ? loader->Name : "N/A"); #ifdef IMGUI_ENABLE_FREETYPE - if (loader->Name != NULL && strcmp(loader->Name, "FreeType") == 0) + if (loader->Name != NULL && strcmp(loader->Name, "FreeType") == 0) + { + unsigned int loader_flags = src->FontBuilderFlags; + Text("FreeType Loader Flags: 0x%08X", loader_flags); + if (ImGuiFreeType::DebugEditFontBuilderFlags(&loader_flags)) { - unsigned int loader_flags = src->FontBuilderFlags; - Text("FreeType Loader Flags: 0x%08X", loader_flags); - if (ImGuiFreeType::DebugEditFontBuilderFlags(&loader_flags)) - { - ImFontAtlasFontDestroyOutput(atlas, font); - src->FontBuilderFlags = loader_flags; - ImFontAtlasFontInitOutput(atlas, font); - } + ImFontAtlasFontDestroyOutput(atlas, font); + src->FontBuilderFlags = loader_flags; + ImFontAtlasFontInitOutput(atlas, font); } -#endif - TreePop(); } +#endif + TreePop(); + } + } // Display all glyphs of the fonts in separate pages of 256 characters for (int baked_n = 0; baked_n < atlas->Builder->BakedPool.Size; baked_n++) @@ -16691,9 +16693,9 @@ void ImGui::DebugNodeFont(ImFont* font) const int surface_sqrt = (int)ImSqrt((float)baked->MetricsTotalSurface); Text("Ascent: %f, Descent: %f, Ascent-Descent: %f", baked->Ascent, baked->Descent, baked->Ascent - baked->Descent); Text("Texture Area: about %d px ~%dx%d px", baked->MetricsTotalSurface, surface_sqrt, surface_sqrt); - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + for (int src_n = 0; src_n < font->Sources.Size; src_n++) { - ImFontConfig* src = &font->Sources[src_n]; + ImFontConfig* src = font->Sources[src_n]; int oversample_h, oversample_v; ImFontAtlasBuildGetOversampleFactors(src, baked->Size, &oversample_h, &oversample_v); BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", diff --git a/imgui.h b/imgui.h index b63b0ed4e..005c9c01b 100644 --- a/imgui.h +++ b/imgui.h @@ -3759,8 +3759,7 @@ struct ImFont // Conceptually Sources[] is the list of font sources merged to create this font. ImGuiID FontId; // Unique identifier for the font float DefaultSize; // 4 // in // Default font size - short SourcesCount; // 2 // in // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont. - ImFontConfig* Sources; // 4-8 // in // Pointer within ContainerAtlas->Sources[], to SourcesCount instances + ImVector Sources; // 16 // in // List of sources. Pointers within ContainerAtlas->Sources[] ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() @@ -3773,7 +3772,7 @@ struct ImFont IMGUI_API ImFontBaked* GetFontBaked(float font_size); // Get or create baked data for given size IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } - const char* GetDebugName() const { return Sources ? Sources[0].Name : ""; } // Fill ImFontConfig::Name. + const char* GetDebugName() const { return Sources.Size ? Sources[0]->Name : ""; } // Fill ImFontConfig::Name. // [Internal] Don't use! // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c311b18af..30bdba58c 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2657,8 +2657,7 @@ void ImFontAtlas::ClearInputData() for (ImFont* font : Fonts) { // When clearing this we lose access to the font name and other information used to build the font. - font->Sources = NULL; - font->SourcesCount = 0; + font->Sources.clear(); font->Flags |= ImFontFlags_NoLoadGlyphs; } Sources.clear(); @@ -3027,14 +3026,14 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) IM_ASSERT(font_cfg->FontLoaderData == NULL); // Pointers to Sources are otherwise dangling - font->SourcesCount++; + font->Sources.push_back(font_cfg); ImFontAtlasBuildUpdatePointers(this); if (!ImFontAtlasFontInitSource(this, font_cfg)) { // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) ImFontAtlasFontDestroySourceData(this, font_cfg); Sources.pop_back(); - font->SourcesCount--; + font->Sources.pop_back(); if (!font_cfg->MergeMode) { IM_DELETE(font); @@ -3185,14 +3184,16 @@ void ImFontAtlas::RemoveFont(ImFont* font) font->ClearOutputData(); ImFontAtlasFontDestroyOutput(this, font); - for (int src_n = 0; src_n < font->SourcesCount; src_n++) - ImFontAtlasFontDestroySourceData(this, &font->Sources[src_n]); + for (ImFontConfig* src : font->Sources) + { + ImFontAtlasFontDestroySourceData(this, src); + Sources.erase(src); + } bool removed = Fonts.find_erase(font); IM_ASSERT(removed); IM_UNUSED(removed); - Sources.erase(font->Sources, font->Sources + font->SourcesCount); ImFontAtlasBuildUpdatePointers(this); font->ContainerAtlas = NULL; @@ -3278,7 +3279,7 @@ ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float glyph.Visible = true; glyph.Colored = true; // FIXME: Arbitrary glyph.PackId = r_id; - ImFontAtlasBakedAddFontGlyph(this, baked, &font->Sources[0], &glyph); + ImFontAtlasBakedAddFontGlyph(this, baked, font->Sources[0], &glyph); return r_id; } #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -3382,15 +3383,13 @@ void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas) atlas->Builder->PreloadedAllGlyphsRanges = true; for (ImFont* font : atlas->Fonts) { - ImFontConfig* src = &font->Sources[0]; - ImFontBaked* baked = font->GetFontBaked(src->SizePixels); + ImFontBaked* baked = font->GetFontBaked(font->Sources[0]->SizePixels); if (font->FallbackChar != 0) baked->FindGlyph(font->FallbackChar); if (font->EllipsisChar != 0) baked->FindGlyph(font->EllipsisChar); - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + for (ImFontConfig* src : font->Sources) { - src = &font->Sources[src_n]; const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); for (; ranges[0]; ranges += 2) for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 @@ -3399,15 +3398,13 @@ void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas) } } +// FIXME: May make ImFont::Sources a ImSpan<> and move ownership to ImFontAtlas void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) { - for (int src_n = 0; src_n < atlas->Sources.Size; src_n++) - { - ImFontConfig* src = &atlas->Sources[src_n]; - ImFont* font = src->DstFont; - if (!src->MergeMode) - font->Sources = src; - } + for (ImFont* font : atlas->Fonts) + font->Sources.resize(0); + for (ImFontConfig& src : atlas->Sources) + src.DstFont->Sources.push_back(&src); } // Render a white-colored bitmap encoded in a string @@ -3542,9 +3539,8 @@ static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas) bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font) { bool ret = true; - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + for (ImFontConfig* src : font->Sources) { - ImFontConfig* src = &font->Sources[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (loader && loader->FontSrcInit != NULL && !loader->FontSrcInit(atlas, src)) ret = false; @@ -3557,9 +3553,8 @@ bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font) void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font) { font->ClearOutputData(); - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + for (ImFontConfig* src : font->Sources) { - ImFontConfig* src = &font->Sources[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (loader && loader->FontSrcDestroy != NULL) loader->FontSrcDestroy(atlas, src); @@ -3576,7 +3571,7 @@ bool ImFontAtlasFontInitSource(ImFontAtlas* atlas, ImFontConfig* src) font->ClearOutputData(); //font->FontSize = src->SizePixels; font->ContainerAtlas = atlas; - IM_ASSERT(font->Sources == src); + IM_ASSERT(font->Sources[0] == src); } const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; @@ -3688,10 +3683,8 @@ static void ImFontAtlasBuildSetupFontBakedBlanks(ImFontAtlas* atlas, ImFontBaked // (note that this is called again for fonts with MergeMode) void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src) { - const int src_idx_in_font = (int)(src - font->Sources); - IM_ASSERT(src_idx_in_font >= 0 && src_idx_in_font < font->SourcesCount); IM_UNUSED(atlas); - IM_UNUSED(src_idx_in_font); + IM_ASSERT(font->Sources.contains(src)); // Find Fallback character. Actual glyph loaded in GetFontBaked(). const ImWchar fallback_chars[] = { font->FallbackChar, (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; @@ -3747,17 +3740,15 @@ ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_si // Initialize backend data size_t loader_data_size = 0; - for (int src_n = 0; src_n < font->SourcesCount; src_n++) // Cannot easily be cached as we allow changing backend + for (ImFontConfig* src : font->Sources) // Cannot easily be cached as we allow changing backend { - ImFontConfig* src = &font->Sources[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; loader_data_size += loader->FontBakedSrcLoaderDataSize; } baked->FontLoaderDatas = (loader_data_size > 0) ? IM_ALLOC(loader_data_size) : NULL; char* loader_data_p = (char*)baked->FontLoaderDatas; - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + for (ImFontConfig* src : font->Sources) { - ImFontConfig* src = &font->Sources[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (loader->FontBakedInit) loader->FontBakedInit(atlas, src, baked, loader_data_p); @@ -3802,9 +3793,8 @@ void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* bake ImFontAtlasPackDiscardRect(atlas, glyph.PackId); char* loader_data_p = (char*)baked->FontLoaderDatas; - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + for (ImFontConfig* src : font->Sources) { - ImFontConfig* src = &font->Sources[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (loader->FontBakedDestroy) loader->FontBakedDestroy(atlas, src, baked, loader_data_p); @@ -4384,9 +4374,9 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep // Call backend char* loader_user_data_p = (char*)baked->FontLoaderDatas; - for (int src_n = 0; src_n < font->SourcesCount; src_n++) + int src_n = 0; + for (ImFontConfig* src : font->Sources) { - ImFontConfig* src = &font->Sources[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) if (ImFontGlyph* glyph = loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint)) @@ -4396,6 +4386,7 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep return glyph; } loader_user_data_p += loader->FontBakedSrcLoaderDataSize; + src_n++; } // Lazily load fallback glyph @@ -4491,8 +4482,8 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); else bd_font_data->ScaleFactor = -stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); - if (src > src->DstFont->Sources) - bd_font_data->ScaleFactor *= src->SizePixels / src->DstFont->Sources[0].SizePixels; // FIXME-NEWATLAS: Should tidy up that a bit + if (src != src->DstFont->Sources[0]) + bd_font_data->ScaleFactor *= src->SizePixels / src->DstFont->Sources[0]->SizePixels; // FIXME-NEWATLAS: Should tidy up that a bit return true; } @@ -4594,7 +4585,7 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, if (oversample_v > 1) stbtt__v_prefilter(bitmap_pixels, r->w, r->h, r->w, oversample_v); - const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0].SizePixels; + const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0]->SizePixels; float font_off_x = (src->GlyphOffset.x * offsets_scale); float font_off_y = (src->GlyphOffset.y * offsets_scale); if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. @@ -5036,7 +5027,7 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked if (src != NULL) { // Clamp & recenter if needed - const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0].SizePixels; + const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0]->SizePixels; float advance_x = ImClamp(glyph->AdvanceX, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); if (advance_x != glyph->AdvanceX) { @@ -5147,9 +5138,8 @@ bool ImFontBaked::IsGlyphLoaded(ImWchar c) bool ImFont::IsGlyphInFont(ImWchar c) { ImFontAtlas* atlas = ContainerAtlas; - for (int src_n = 0; src_n < SourcesCount; src_n++) + for (ImFontConfig* src : Sources) { - ImFontConfig* src = &Sources[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (loader->FontSrcContainsGlyph != NULL && loader->FontSrcContainsGlyph(atlas, src, c)) return true; diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 5151a90d5..b4263fc82 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -436,7 +436,7 @@ void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) { IM_UNUSED(atlas); - const float size = baked->Size * (src->SizePixels / baked->ContainerFont->Sources[0].SizePixels); // FIXME-NEWATLAS: Should tidy up that a bit + const float size = baked->Size * (src->SizePixels / baked->ContainerFont->Sources[0]->SizePixels); // FIXME-NEWATLAS: Should tidy up that a bit ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; bd_font_data->BakedLastActivated = baked; @@ -541,7 +541,7 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data; bd_font_data->BlitGlyph(ft_bitmap, temp_buffer, w); - const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0].SizePixels; + const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0]->SizePixels; float font_off_x = (src->GlyphOffset.x * offsets_scale); float font_off_y = (src->GlyphOffset.y * offsets_scale) + baked->Ascent; if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. From 5310f5fba347d32572206fe80439314507c90ab1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 24 Apr 2025 18:27:55 +0200 Subject: [PATCH 139/191] Fonts: rework toward reducing reliance on ImFontConfig::DstFont since we ought to separate them. --- imgui_draw.cpp | 35 +++++++++++++++++++++-------------- imgui_internal.h | 3 ++- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 30bdba58c..7bb6ae108 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2998,10 +2998,14 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) font = Fonts.back(); } + // Add to list Sources.push_back(*font_cfg_in); ImFontConfig* font_cfg = &Sources.back(); if (font_cfg->DstFont == NULL) font_cfg->DstFont = font; + font->Sources.push_back(font_cfg); + ImFontAtlasBuildUpdatePointers(this); // Pointers to Sources are otherwise dangling after we called Sources.push_back(). + if (font_cfg->FontDataOwnedByAtlas == false) { font_cfg->FontDataOwnedByAtlas = true; @@ -3025,10 +3029,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) } IM_ASSERT(font_cfg->FontLoaderData == NULL); - // Pointers to Sources are otherwise dangling - font->Sources.push_back(font_cfg); - ImFontAtlasBuildUpdatePointers(this); - if (!ImFontAtlasFontInitSource(this, font_cfg)) + if (!ImFontAtlasFontSourceInit(this, font_cfg)) { // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) ImFontAtlasFontDestroySourceData(this, font_cfg); @@ -3041,6 +3042,8 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) } return NULL; } + ImFontAtlasFontSourceAddToFont(this, font, font_cfg); + return font; } @@ -3563,9 +3566,16 @@ void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font) //----------------------------------------------------------------------------------------------------------------------------- -bool ImFontAtlasFontInitSource(ImFontAtlas* atlas, ImFontConfig* src) +bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src) +{ + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcInit != NULL && !loader->FontSrcInit(atlas, src)) + return false; + return true; +} + +void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src) { - ImFont* font = src->DstFont; if (src->MergeMode == false) { font->ClearOutputData(); @@ -3573,14 +3583,8 @@ bool ImFontAtlasFontInitSource(ImFontAtlas* atlas, ImFontConfig* src) font->ContainerAtlas = atlas; IM_ASSERT(font->Sources[0] == src); } - - const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; - if (loader->FontSrcInit != NULL && !loader->FontSrcInit(atlas, src)) - return false; - atlas->TexIsBuilt = false; // For legacy backends ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); - return true; } void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src) @@ -4104,6 +4108,11 @@ void ImFontAtlasBuildClear(ImFontAtlas* atlas) ImFontAtlasBuildDestroy(atlas); ImFontAtlasTextureAdd(atlas, new_tex_size.x, new_tex_size.y); ImFontAtlasBuildInit(atlas); + for (ImFontConfig& src : atlas->Sources) + ImFontAtlasFontSourceInit(atlas, &src); + for (ImFont* font : atlas->Fonts) + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontSourceAddToFont(atlas, font, src); } // You should not need to call this manually! @@ -4159,8 +4168,6 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) // Register fonts ImFontAtlasBuildUpdatePointers(atlas); - for (ImFontConfig& cfg : atlas->Sources) - ImFontAtlasFontInitSource(atlas, &cfg); // Update UV coordinates etc. stored in bound ImDrawListSharedData instance ImFontAtlasUpdateDrawListsSharedData(atlas); diff --git a/imgui_internal.h b/imgui_internal.h index 3274940c0..28e907488 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3784,7 +3784,8 @@ IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* a IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); -IMGUI_API bool ImFontAtlasFontInitSource(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); IMGUI_API void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font); // Using FontDestroyOutput/FontInitOutput sequence useful notably if font loader params have changed IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font); From 6a455e1281535bc7d79eb40445892b7894fbbbac Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 26 Apr 2025 17:05:36 +0200 Subject: [PATCH 140/191] imgui_freetype: moving data out of ImGui_ImplFreeType_FontSrcData. The reasoning behind that we would ideally transition ImGui_ImplFreeType_FontSrcData to be shared between fonts using same source. --- misc/freetype/imgui_freetype.cpp | 45 +++++++++++++------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index b4263fc82..a667f458d 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -151,17 +151,11 @@ namespace ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); } }; - // Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE. - struct ImGui_ImplFreeType_FontSrcBakedData - { - FT_Size FtSize; // This represent a FT_Face with a given size. - ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); } - }; - // Stored in ImFontConfig::FontLoaderData. ALLOCATED BY US. struct ImGui_ImplFreeType_FontSrcData { - bool InitFont(FT_Library ft_library, ImFontConfig* src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. + // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. + bool InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeBuilderFlags extra_user_flags); void CloseFont(); const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint); void BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch); @@ -170,15 +164,19 @@ namespace // Members FT_Face FtFace; - unsigned int UserFlags; // = ImFontConfig::RasterizerFlags + ImGuiFreeTypeBuilderFlags UserFlags; // = ImFontConfig::FontBuilderFlags FT_Int32 LoadFlags; - FT_Render_Mode RenderMode; - float RasterizationDensity; - float InvRasterizationDensity; ImFontBaked* BakedLastActivated; }; - bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, ImFontConfig* src, unsigned int extra_font_builder_flags) + // Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE. + struct ImGui_ImplFreeType_FontSrcBakedData + { + FT_Size FtSize; // This represent a FT_Face with a given size. + ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); } + }; + + bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeBuilderFlags extra_font_builder_flags) { FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)src->FontData, (uint32_t)src->FontDataSize, (uint32_t)src->FontNo, &FtFace); if (error != 0) @@ -188,7 +186,7 @@ namespace return false; // Convert to FreeType flags (NB: Bold and Oblique are processed separately) - UserFlags = src->FontBuilderFlags | extra_font_builder_flags; + UserFlags = (ImGuiFreeTypeBuilderFlags)(src->FontBuilderFlags | extra_font_builder_flags); LoadFlags = 0; if ((UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) == 0) @@ -210,17 +208,9 @@ namespace else LoadFlags |= FT_LOAD_TARGET_NORMAL; - if (UserFlags & ImGuiFreeTypeBuilderFlags_Monochrome) - RenderMode = FT_RENDER_MODE_MONO; - else - RenderMode = FT_RENDER_MODE_NORMAL; - if (UserFlags & ImGuiFreeTypeBuilderFlags_LoadColor) LoadFlags |= FT_LOAD_COLOR; - RasterizationDensity = src->RasterizerDensity; - InvRasterizationDensity = 1.0f / RasterizationDensity; - return true; } @@ -415,7 +405,7 @@ bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) IM_ASSERT(src->FontLoaderData == NULL); src->FontLoaderData = bd_font_data; - if (!bd_font_data->InitFont(bd->Library, src, atlas->FontBuilderFlags)) + if (!bd_font_data->InitFont(bd->Library, src, (ImGuiFreeTypeBuilderFlags)atlas->FontBuilderFlags)) { IM_DELETE(bd_font_data); src->FontLoaderData = NULL; @@ -456,7 +446,7 @@ bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImF FT_Size_RequestRec req; req.type = (bd_font_data->UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM; req.width = 0; - req.height = (uint32_t)(size * 64 * bd_font_data->RasterizationDensity); + req.height = (uint32_t)(size * 64 * src->RasterizerDensity); req.horiResolution = 0; req.vertResolution = 0; FT_Request_Size(bd_font_data->FtFace, &req); @@ -466,7 +456,7 @@ bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImF { // Read metrics FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics; - const float scale = bd_font_data->InvRasterizationDensity; + const float scale = 1.0f / src->RasterizerDensity; baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive). baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative). //LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate. @@ -509,7 +499,8 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon // Render glyph into a bitmap (currently held by FreeType) FT_Face face = bd_font_data->FtFace; FT_GlyphSlot slot = face->glyph; - FT_Error error = FT_Render_Glyph(slot, bd_font_data->RenderMode); + FT_Render_Mode render_mode = (bd_font_data->UserFlags & ImGuiFreeTypeBuilderFlags_Monochrome) ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL; + FT_Error error = FT_Render_Glyph(slot, render_mode); const FT_Bitmap* ft_bitmap = &slot->bitmap; if (error != 0 || ft_bitmap == nullptr) return NULL; @@ -522,7 +513,7 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon ImFontGlyph glyph_in = {}; ImFontGlyph* glyph = &glyph_in; glyph->Codepoint = codepoint; - glyph->AdvanceX = (slot->advance.x / FT_SCALEFACTOR) * bd_font_data->InvRasterizationDensity; + glyph->AdvanceX = (slot->advance.x / FT_SCALEFACTOR) / src->RasterizerDensity; // Pack and retrieve position inside texture atlas if (is_visible) From 42e7bb80b642b48c7a5c54775be3bec19bac571b Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 26 Apr 2025 17:24:38 +0200 Subject: [PATCH 141/191] imgui_freetype: removed anonymous namespace + extracting two functions outside of ImGui_ImplFreeType_FontSrcData. --- misc/freetype/imgui_freetype.cpp | 377 +++++++++++++++---------------- 1 file changed, 186 insertions(+), 191 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index a667f458d..8ad2d6cea 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -110,210 +110,205 @@ static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_ #define FT_CEIL(X) (((X + 63) & -64) / 64) // From SDL_ttf: Handy routines for converting from fixed point #define FT_SCALEFACTOR 64.0f -namespace +// Glyph metrics: +// -------------- +// +// xmin xmax +// | | +// |<-------- width -------->| +// | | +// | +-------------------------+----------------- ymax +// | | ggggggggg ggggg | ^ ^ +// | | g:::::::::ggg::::g | | | +// | | g:::::::::::::::::g | | | +// | | g::::::ggggg::::::gg | | | +// | | g:::::g g:::::g | | | +// offsetX -|-------->| g:::::g g:::::g | offsetY | +// | | g:::::g g:::::g | | | +// | | g::::::g g:::::g | | | +// | | g:::::::ggggg:::::g | | | +// | | g::::::::::::::::g | | height +// | | gg::::::::::::::g | | | +// baseline ---*---------|---- gggggggg::::::g-----*-------- | +// / | | g:::::g | | +// origin | | gggggg g:::::g | | +// | | g:::::gg gg:::::g | | +// | | g::::::ggg:::::::g | | +// | | gg:::::::::::::g | | +// | | ggg::::::ggg | | +// | | gggggg | v +// | +-------------------------+----------------- ymin +// | | +// |------------- advanceX ----------->| + +// Stored in ImFontAtlas::FontLoaderData. ALLOCATED BY US. +struct ImGui_ImplFreeType_Data { - // Glyph metrics: - // -------------- - // - // xmin xmax - // | | - // |<-------- width -------->| - // | | - // | +-------------------------+----------------- ymax - // | | ggggggggg ggggg | ^ ^ - // | | g:::::::::ggg::::g | | | - // | | g:::::::::::::::::g | | | - // | | g::::::ggggg::::::gg | | | - // | | g:::::g g:::::g | | | - // offsetX -|-------->| g:::::g g:::::g | offsetY | - // | | g:::::g g:::::g | | | - // | | g::::::g g:::::g | | | - // | | g:::::::ggggg:::::g | | | - // | | g::::::::::::::::g | | height - // | | gg::::::::::::::g | | | - // baseline ---*---------|---- gggggggg::::::g-----*-------- | - // / | | g:::::g | | - // origin | | gggggg g:::::g | | - // | | g:::::gg gg:::::g | | - // | | g::::::ggg:::::::g | | - // | | gg:::::::::::::g | | - // | | ggg::::::ggg | | - // | | gggggg | v - // | +-------------------------+----------------- ymin - // | | - // |------------- advanceX ----------->| + FT_Library Library; + FT_MemoryRec_ MemoryManager; + ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); } +}; - // Stored in ImFontAtlas::FontLoaderData. ALLOCATED BY US. - struct ImGui_ImplFreeType_Data +// Stored in ImFontConfig::FontLoaderData. ALLOCATED BY US. +struct ImGui_ImplFreeType_FontSrcData +{ + // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. + bool InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeBuilderFlags extra_user_flags); + void CloseFont(); + ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); } + ~ImGui_ImplFreeType_FontSrcData() { CloseFont(); } + + // Members + FT_Face FtFace; + ImGuiFreeTypeBuilderFlags UserFlags; // = ImFontConfig::FontBuilderFlags + FT_Int32 LoadFlags; + ImFontBaked* BakedLastActivated; +}; + +// Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE. +struct ImGui_ImplFreeType_FontSrcBakedData +{ + FT_Size FtSize; // This represent a FT_Face with a given size. + ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); } +}; + +bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeBuilderFlags extra_font_builder_flags) +{ + FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)src->FontData, (uint32_t)src->FontDataSize, (uint32_t)src->FontNo, &FtFace); + if (error != 0) + return false; + error = FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE); + if (error != 0) + return false; + + // Convert to FreeType flags (NB: Bold and Oblique are processed separately) + UserFlags = (ImGuiFreeTypeBuilderFlags)(src->FontBuilderFlags | extra_font_builder_flags); + + LoadFlags = 0; + if ((UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) == 0) + LoadFlags |= FT_LOAD_NO_BITMAP; + + if (UserFlags & ImGuiFreeTypeBuilderFlags_NoHinting) + LoadFlags |= FT_LOAD_NO_HINTING; + else + src->PixelSnapH = true; // FIXME: A bit weird to do this this way. + + if (UserFlags & ImGuiFreeTypeBuilderFlags_NoAutoHint) + LoadFlags |= FT_LOAD_NO_AUTOHINT; + if (UserFlags & ImGuiFreeTypeBuilderFlags_ForceAutoHint) + LoadFlags |= FT_LOAD_FORCE_AUTOHINT; + if (UserFlags & ImGuiFreeTypeBuilderFlags_LightHinting) + LoadFlags |= FT_LOAD_TARGET_LIGHT; + else if (UserFlags & ImGuiFreeTypeBuilderFlags_MonoHinting) + LoadFlags |= FT_LOAD_TARGET_MONO; + else + LoadFlags |= FT_LOAD_TARGET_NORMAL; + + if (UserFlags & ImGuiFreeTypeBuilderFlags_LoadColor) + LoadFlags |= FT_LOAD_COLOR; + + return true; +} + +void ImGui_ImplFreeType_FontSrcData::CloseFont() +{ + if (FtFace) { - FT_Library Library; - FT_MemoryRec_ MemoryManager; - ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); } - }; - - // Stored in ImFontConfig::FontLoaderData. ALLOCATED BY US. - struct ImGui_ImplFreeType_FontSrcData - { - // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. - bool InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeBuilderFlags extra_user_flags); - void CloseFont(); - const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint); - void BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch); - ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); } - ~ImGui_ImplFreeType_FontSrcData() { CloseFont(); } - - // Members - FT_Face FtFace; - ImGuiFreeTypeBuilderFlags UserFlags; // = ImFontConfig::FontBuilderFlags - FT_Int32 LoadFlags; - ImFontBaked* BakedLastActivated; - }; - - // Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE. - struct ImGui_ImplFreeType_FontSrcBakedData - { - FT_Size FtSize; // This represent a FT_Face with a given size. - ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); } - }; - - bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeBuilderFlags extra_font_builder_flags) - { - FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)src->FontData, (uint32_t)src->FontDataSize, (uint32_t)src->FontNo, &FtFace); - if (error != 0) - return false; - error = FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE); - if (error != 0) - return false; - - // Convert to FreeType flags (NB: Bold and Oblique are processed separately) - UserFlags = (ImGuiFreeTypeBuilderFlags)(src->FontBuilderFlags | extra_font_builder_flags); - - LoadFlags = 0; - if ((UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) == 0) - LoadFlags |= FT_LOAD_NO_BITMAP; - - if (UserFlags & ImGuiFreeTypeBuilderFlags_NoHinting) - LoadFlags |= FT_LOAD_NO_HINTING; - else - src->PixelSnapH = true; // FIXME: A bit weird to do this this way. - - if (UserFlags & ImGuiFreeTypeBuilderFlags_NoAutoHint) - LoadFlags |= FT_LOAD_NO_AUTOHINT; - if (UserFlags & ImGuiFreeTypeBuilderFlags_ForceAutoHint) - LoadFlags |= FT_LOAD_FORCE_AUTOHINT; - if (UserFlags & ImGuiFreeTypeBuilderFlags_LightHinting) - LoadFlags |= FT_LOAD_TARGET_LIGHT; - else if (UserFlags & ImGuiFreeTypeBuilderFlags_MonoHinting) - LoadFlags |= FT_LOAD_TARGET_MONO; - else - LoadFlags |= FT_LOAD_TARGET_NORMAL; - - if (UserFlags & ImGuiFreeTypeBuilderFlags_LoadColor) - LoadFlags |= FT_LOAD_COLOR; - - return true; + FT_Done_Face(FtFace); + FtFace = nullptr; } +} - void ImGui_ImplFreeType_FontSrcData::CloseFont() - { - if (FtFace) - { - FT_Done_Face(FtFace); - FtFace = nullptr; - } - } +static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_FontSrcData* src_data, uint32_t codepoint) +{ + uint32_t glyph_index = FT_Get_Char_Index(src_data->FtFace, codepoint); + if (glyph_index == 0) + return nullptr; - const FT_Glyph_Metrics* ImGui_ImplFreeType_FontSrcData::LoadGlyph(uint32_t codepoint) - { - uint32_t glyph_index = FT_Get_Char_Index(FtFace, codepoint); - if (glyph_index == 0) - return nullptr; + // If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts. + // - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076 + // - https://github.com/ocornut/imgui/issues/4567 + // - https://github.com/ocornut/imgui/issues/4566 + // You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version. + FT_Error error = FT_Load_Glyph(src_data->FtFace, glyph_index, src_data->LoadFlags); + if (error) + return nullptr; - // If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts. - // - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076 - // - https://github.com/ocornut/imgui/issues/4567 - // - https://github.com/ocornut/imgui/issues/4566 - // You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version. - FT_Error error = FT_Load_Glyph(FtFace, glyph_index, LoadFlags); - if (error) - return nullptr; - - // Need an outline for this to work - FT_GlyphSlot slot = FtFace->glyph; + // Need an outline for this to work + FT_GlyphSlot slot = src_data->FtFace->glyph; #if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG) - IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP || slot->format == FT_GLYPH_FORMAT_SVG); + IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP || slot->format == FT_GLYPH_FORMAT_SVG); #else #if ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12)) - IM_ASSERT(slot->format != FT_GLYPH_FORMAT_SVG && "The font contains SVG glyphs, you'll need to enable IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG in imconfig.h and install required libraries in order to use this font"); + IM_ASSERT(slot->format != FT_GLYPH_FORMAT_SVG && "The font contains SVG glyphs, you'll need to enable IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG in imconfig.h and install required libraries in order to use this font"); #endif - IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP); + IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP); #endif // IMGUI_ENABLE_FREETYPE_LUNASVG - // Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting) - if (UserFlags & ImGuiFreeTypeBuilderFlags_Bold) - FT_GlyphSlot_Embolden(slot); - if (UserFlags & ImGuiFreeTypeBuilderFlags_Oblique) - { - FT_GlyphSlot_Oblique(slot); - //FT_BBox bbox; - //FT_Outline_Get_BBox(&slot->outline, &bbox); - //slot->metrics.width = bbox.xMax - bbox.xMin; - //slot->metrics.height = bbox.yMax - bbox.yMin; - } - - return &slot->metrics; - } - - void ImGui_ImplFreeType_FontSrcData::BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch) + // Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting) + if (src_data->UserFlags & ImGuiFreeTypeBuilderFlags_Bold) + FT_GlyphSlot_Embolden(slot); + if (src_data->UserFlags & ImGuiFreeTypeBuilderFlags_Oblique) { - IM_ASSERT(ft_bitmap != nullptr); - const uint32_t w = ft_bitmap->width; - const uint32_t h = ft_bitmap->rows; - const uint8_t* src = ft_bitmap->buffer; - const uint32_t src_pitch = ft_bitmap->pitch; - - switch (ft_bitmap->pixel_mode) - { - case FT_PIXEL_MODE_GRAY: // Grayscale image, 1 byte per pixel. - { - for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) - for (uint32_t x = 0; x < w; x++) - dst[x] = IM_COL32(255, 255, 255, src[x]); - break; - } - case FT_PIXEL_MODE_MONO: // Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB. - { - for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) - { - uint8_t bits = 0; - const uint8_t* bits_ptr = src; - for (uint32_t x = 0; x < w; x++, bits <<= 1) - { - if ((x & 7) == 0) - bits = *bits_ptr++; - dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? 255 : 0); - } - } - break; - } - case FT_PIXEL_MODE_BGRA: - { - // FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good. - #define DE_MULTIPLY(color, alpha) ImMin((ImU32)(255.0f * (float)color / (float)(alpha + FLT_MIN) + 0.5f), 255u) - for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) - for (uint32_t x = 0; x < w; x++) - { - uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3]; - dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a); - } - #undef DE_MULTIPLY - break; - } - default: - IM_ASSERT(0 && "FreeTypeFont::BlitGlyph(): Unknown bitmap pixel mode!"); - } + FT_GlyphSlot_Oblique(slot); + //FT_BBox bbox; + //FT_Outline_Get_BBox(&slot->outline, &bbox); + //slot->metrics.width = bbox.xMax - bbox.xMin; + //slot->metrics.height = bbox.yMax - bbox.yMin; } -} // namespace + + return &slot->metrics; +} + +static void ImGui_ImplFreeType_BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch) +{ + IM_ASSERT(ft_bitmap != nullptr); + const uint32_t w = ft_bitmap->width; + const uint32_t h = ft_bitmap->rows; + const uint8_t* src = ft_bitmap->buffer; + const uint32_t src_pitch = ft_bitmap->pitch; + + switch (ft_bitmap->pixel_mode) + { + case FT_PIXEL_MODE_GRAY: // Grayscale image, 1 byte per pixel. + { + for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) + for (uint32_t x = 0; x < w; x++) + dst[x] = IM_COL32(255, 255, 255, src[x]); + break; + } + case FT_PIXEL_MODE_MONO: // Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB. + { + for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) + { + uint8_t bits = 0; + const uint8_t* bits_ptr = src; + for (uint32_t x = 0; x < w; x++, bits <<= 1) + { + if ((x & 7) == 0) + bits = *bits_ptr++; + dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? 255 : 0); + } + } + break; + } + case FT_PIXEL_MODE_BGRA: + { + // FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good. + #define DE_MULTIPLY(color, alpha) ImMin((ImU32)(255.0f * (float)color / (float)(alpha + FLT_MIN) + 0.5f), 255u) + for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) + for (uint32_t x = 0; x < w; x++) + { + uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3]; + dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a); + } + #undef DE_MULTIPLY + break; + } + default: + IM_ASSERT(0 && "FreeTypeFont::BlitGlyph(): Unknown bitmap pixel mode!"); + } +} // FreeType memory allocation callbacks static void* FreeType_Alloc(FT_Memory /*memory*/, long size) @@ -492,7 +487,7 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon bd_font_data->BakedLastActivated = baked; } - const FT_Glyph_Metrics* metrics = bd_font_data->LoadGlyph(codepoint); + const FT_Glyph_Metrics* metrics = ImGui_ImplFreeType_LoadGlyph(bd_font_data, codepoint); if (metrics == NULL) return NULL; @@ -530,7 +525,7 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon // Render pixels to our temporary buffer atlas->Builder->TempBuffer.resize(w * h * 4); uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data; - bd_font_data->BlitGlyph(ft_bitmap, temp_buffer, w); + ImGui_ImplFreeType_BlitGlyph(ft_bitmap, temp_buffer, w); const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0]->SizePixels; float font_off_x = (src->GlyphOffset.x * offsets_scale); From 8140a9d8a69951dd99ba3bc1bcac73350cec412c Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 27 Apr 2025 15:33:44 +0200 Subject: [PATCH 142/191] Fonts: comments on ImTextureData fields. --- imgui.cpp | 59 +++++++++++++++++++++++++++++++++++-------------------- imgui.h | 36 +++++++++++++++++---------------- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 04cb11264..6c6ead519 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -298,7 +298,7 @@ CODE // Any application code here ImGui::Text("Hello, world!"); - // Render dear imgui into screen + // Render dear imgui into framebuffer ImGui::Render(); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); g_pSwapChain->Present(1, 0); @@ -311,24 +311,14 @@ CODE EXHIBIT 2: IMPLEMENTING CUSTOM BACKEND / CUSTOM ENGINE - // Application init: create a dear imgui context, setup some options, load fonts + // Application init: create a Dear ImGui context, setup some options, load fonts ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); - // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls. - // TODO: Fill optional fields of the io structure later. + // TODO: set io.ConfigXXX values, e.g. + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable keyboard controls + // TODO: Load TTF/OTF fonts if you don't want to use the default font. - - // Build and load the texture atlas into a texture - // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer) - int width, height; - unsigned char* pixels = nullptr; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - // At this point you've got the texture data and you need to upload that to your graphic system: - // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'. - // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about ImTextureID. - MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32) - io.Fonts->SetTexID((void*)texture); + io.Fonts->AddFontFromFileTTF("NotoSans.ttf", 18.0f); // Application main loop while (true) @@ -351,12 +341,19 @@ CODE MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); MyGameRender(); // may use any Dear ImGui functions as well! - // Render dear imgui, swap buffers + // End the dear imgui frame // (You want to try calling EndFrame/Render as late as you can, to be able to use Dear ImGui in your own game rendering code) - ImGui::EndFrame(); + ImGui::EndFrame(); // this is automatically called by Render(), but available ImGui::Render(); + + // Update textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->Status != ImTextureStatus_OK) + MyImGuiBackend_UpdateTexture(tex); + + // Render dear imgui contents, swap buffers ImDrawData* draw_data = ImGui::GetDrawData(); - MyImGuiRenderFunction(draw_data); + MyImGuiBackend_RenderDrawData(draw_data); SwapBuffers(); } @@ -367,12 +364,32 @@ CODE you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! Please read the FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" about this. - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE --------------------------------------------- The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function. - void MyImGuiRenderFunction(ImDrawData* draw_data) + void MyImGuiBackend_UpdateTexture(ImTextureData* tex) + { + if (tex->Status == ImTextureStatus_WantCreate) + { + // create texture based on tex->Width/Height/Pixels + // call tex->SetTexID() to specify backend-specific identifiers + // tex->Status = ImTextureStatus_OK; + } + if (tex->Status == ImTextureStatus_WantUpdates) + { + // update texture blocks based on tex->UpdateRect + // tex->Status = ImTextureStatus_OK; + } + if (tex->Status == ImTextureStatus_WantDestroy) + { + // destroy texture + // call tex->SetTexID(ImTextureID_Invalid) + // tex->Status = ImTextureStatus_Destroyed; + } + } + + void MyImGuiBackend_RenderDrawData(ImDrawData* draw_data) { // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled // TODO: Setup texture sampling state: sample with bilinear filtering (NOT point/nearest filtering). Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering. diff --git a/imgui.h b/imgui.h index 005c9c01b..e38cce771 100644 --- a/imgui.h +++ b/imgui.h @@ -3079,7 +3079,7 @@ typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* c struct ImDrawCmd { ImVec4 ClipRect; // 4*4 // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in "viewport" coordinates - ImTextureRef TexRef; // 16 // User-provided texture ID. Set by user in ImFontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImTextureRef TexRef; // 16 // Reference to a font/texture atlas (where backend called ImTextureData::SetTexID()) or to a user-provided texture ID (via e.g. ImGui::Image() calls). Both will lead to a ImTextureID value. unsigned int VtxOffset; // 4 // Start offset in vertex buffer. ImGuiBackendFlags_RendererHasVtxOffset: always 0, otherwise may be >0 to support meshes larger than 64K vertices with 16-bit indices. unsigned int IdxOffset; // 4 // Start offset in index buffer. unsigned int ElemCount; // 4 // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. @@ -3403,24 +3403,26 @@ struct ImTextureRect // Why does we store two identifiers: TexID and BackendUserData? // - ImTextureID TexID = lower-level identifier stored in ImDrawCmd. ImDrawCmd can refer to textures not created by the backend, and for which there's no ImTextureData. // - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. + // In columns below: who reads/writes each fields? 'r'=read, 'w'=write, 'core'=main library, 'backend'=renderer backend struct ImTextureData { - ImTextureStatus Status; // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify! - ImTextureFormat Format; // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 - int Width; // Texture width - int Height; // Texture height - int BytesPerPixel; // 4 or 1 - int UniqueID; // Sequential index to facilitate identifying a texture when debugging/printing. Only unique per atlas. - unsigned char* Pixels; // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. - ImTextureID TexID; // Always use SetTexID() to modify! Identifier stored in ImDrawCmd::GetTexID() and passed to backend RenderDrawData loop. - void* BackendUserData; // Convenience storage for backend. Some backends may have enough with TexID. - ImTextureRect UsedRect; // Bounding box encompassing all past and queued Updates[]. - ImTextureRect UpdateRect; // Bounding box encompassing all queued Updates[]. - ImVector Updates; // Array of individual updates. - int UnusedFrames; // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. Always >0 when Status==WantDestroy. - unsigned short RefCount; // Number of contexts using this texture. - bool UseColors; // Tell whether our texture data is known to use colors (rather than just white + alpha). - bool WantDestroyNextFrame; // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. + //------------------------------------------ core / backend --------------------------------------- + int UniqueID; // w - // Sequential index to facilitate identifying a texture when debugging/printing. Unique per atlas. + ImTextureStatus Status; // rw rw // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify! + void* BackendUserData; // - rw // Convenience storage for backend. Some backends may have enough with TexID. + ImTextureID TexID; // r w // Backend-specific texture identifier. Always use SetTexID() to modify! The identifier will stored in ImDrawCmd::GetTexID() and passed to backend's RenderDrawData function. + ImTextureFormat Format; // w r // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 + int Width; // w r // Texture width + int Height; // w r // Texture height + int BytesPerPixel; // w r // 4 or 1 + unsigned char* Pixels; // w r // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. + ImTextureRect UsedRect; // w r // Bounding box encompassing all past and queued Updates[]. + ImTextureRect UpdateRect; // w r // Bounding box encompassing all queued Updates[]. + ImVector Updates; // w r // Array of individual updates. + int UnusedFrames; // w r // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. Always >0 when Status==WantDestroy. + unsigned short RefCount; // w r // Number of contexts using this texture. Used during backend shutdown. + bool UseColors; // w r // Tell whether our texture data is known to use colors (rather than just white + alpha). + bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. // Functions ImTextureData() { memset(this, 0, sizeof(*this)); } From b32ef3c05dab094995944b982cbb38328628ab2a Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 30 Apr 2025 21:29:09 +0200 Subject: [PATCH 143/191] Fonts: make RasterizerDensity a dynamic field. (temporarily exposed as SetFontRasterizerDensity()). # Conflicts: # imgui.cpp # imgui.h --- imgui.cpp | 20 ++++++++++++---- imgui.h | 10 ++++---- imgui_draw.cpp | 40 ++++++++++++++++++-------------- imgui_internal.h | 13 +++++++---- misc/freetype/imgui_freetype.cpp | 12 ++++++---- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6c6ead519..68560fdb6 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3966,6 +3966,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) Font = NULL; FontBaked = NULL; FontSize = FontSizeBeforeScaling = FontScale = CurrentDpiScale = 0.0f; + FontRasterizerDensity = 1.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); IO.Fonts->RefCount++; Time = 0.0f; @@ -8656,14 +8657,25 @@ void ImGui::UpdateCurrentFontSize() // - We may support it better later and remove this rounding. final_size = GetRoundedFontSize(final_size); final_size = ImMax(1.0f, final_size); - + if (g.Font != NULL) + g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; + g.FontBaked = (g.Font != NULL) ? g.Font->GetFontBaked(final_size) : NULL; g.FontSize = final_size; - g.FontBaked = (g.Font != NULL) ? g.Font->GetFontBaked(g.FontSize) : NULL; g.FontScale = (g.Font != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; g.DrawListSharedData.FontSize = g.FontSize; g.DrawListSharedData.FontScale = g.FontScale; } +// FIXME-DPI: Not sure how to expose this. It may be automatically applied based on current viewport, if we had this information stored in viewport or monitor. +void ImGui::SetFontRasterizerDensity(float rasterizer_density) +{ + ImGuiContext& g = *GImGui; + if (g.FontRasterizerDensity == rasterizer_density) + return; + g.FontRasterizerDensity = rasterizer_density; + UpdateCurrentFontSize(); +} + void ImGui::PushFont(ImFont* font, float font_size) { ImGuiContext& g = *GImGui; @@ -16701,7 +16713,7 @@ void ImGui::DebugNodeFont(ImFont* font) if (baked->ContainerFont != font) continue; PushID(baked_n); - if (TreeNode("Glyphs", "Baked at %.2fpx: %d glyphs%s", baked->Size, baked->Glyphs.Size, (baked->LastUsedFrame < atlas->Builder->FrameCount - 1) ? " *Unused*" : "")) + if (TreeNode("Glyphs", "Baked at { %.2fpx, d.%.1f }: %d glyphs%s", baked->Size, baked->RasterizerDensity, baked->Glyphs.Size, (baked->LastUsedFrame < atlas->Builder->FrameCount - 1) ? " *Unused*" : "")) { if (SmallButton("Load all")) for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base++) @@ -16714,7 +16726,7 @@ void ImGui::DebugNodeFont(ImFont* font) { ImFontConfig* src = font->Sources[src_n]; int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(src, baked->Size, &oversample_h, &oversample_v); + ImFontAtlasBuildGetOversampleFactors(src, baked, &oversample_h, &oversample_v); BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", src_n, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); } diff --git a/imgui.h b/imgui.h index e38cce771..9bf4db914 100644 --- a/imgui.h +++ b/imgui.h @@ -3467,7 +3467,7 @@ struct ImFontConfig float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. - float RasterizerDensity; // 1.0f // DPI scale for rasterization, not altering other font metrics: make it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. + float RasterizerDensity; // 1.0f // DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. // [Internal] @@ -3709,6 +3709,7 @@ struct ImFontBaked ImVector IndexAdvanceX; // 12-16 // out // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). float FallbackAdvanceX; // 4 // out // FindGlyph(FallbackChar)->AdvanceX float Size; // 4 // in // Height of characters/line, set during loading (doesn't change after loading) + float RasterizerDensity; // 4 // in // Density this is baked at // [Internal] Members: Hot ~28/36 bytes (for RenderText loop) ImVector IndexLookup; // 12-16 // out // Sparse. Index glyphs by Unicode code-point. @@ -3753,9 +3754,10 @@ enum ImFontFlags_ struct ImFont { // [Internal] Members: Hot ~12-20 bytes - ImFontBaked* LastBaked; // 4-8 // Cache last bound baked. DO NOT USE. Use GetFontBaked(). - ImFontAtlas* ContainerAtlas; // 4-8 // What we has been loaded into - ImFontFlags Flags; // 4 // Font flags + ImFontBaked* LastBaked; // 4-8 // Cache last bound baked. NEVER USE DIRECTLY. Use GetFontBaked(). + ImFontAtlas* ContainerAtlas; // 4-8 // What we have been loaded into. + ImFontFlags Flags; // 4 // Font flags. + float CurrentRasterizerDensity; // Current rasterizer density. This is a varying state of the font. // [Internal] Members: Cold ~24-52 bytes // Conceptually Sources[] is the list of font sources merged to create this font. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7bb6ae108..14440592e 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3349,10 +3349,11 @@ void ImFontAtlasBuildMain(ImFontAtlas* atlas) atlas->TexIsBuilt = true; } -void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v) +void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v) { // Automatically disable horizontal oversampling over size 36 - *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : ((size * src->RasterizerDensity > 36.0f) || src->PixelSnapH) ? 1 : 2; + const float raster_size = baked->Size * baked->RasterizerDensity * src->RasterizerDensity; + *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (raster_size > 36.0f || src->PixelSnapH) ? 1 : 2; *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1; } @@ -3733,11 +3734,12 @@ void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBa baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; } -ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id) +ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density, ImGuiID baked_id) { IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", font_size); ImFontBaked* baked = atlas->Builder->BakedPool.push_back(ImFontBaked()); baked->Size = font_size; + baked->RasterizerDensity = font_rasterizer_density; baked->BakedId = baked_id; baked->ContainerFont = font; baked->LastUsedFrame = atlas->Builder->FrameCount; @@ -3764,7 +3766,7 @@ ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_si } // FIXME-OPT: This is not a fast query. Adding a BakedCount field in Font might allow to take a shortcut for the most common case. -ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size) +ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) { ImFontAtlasBuilder* builder = atlas->Builder; ImFontBaked* closest_larger_match = NULL; @@ -3774,6 +3776,8 @@ ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, f ImFontBaked* baked = &builder->BakedPool[baked_n]; if (baked->ContainerFont != font || baked->WantDestroy) continue; + if (baked->RasterizerDensity != font_rasterizer_density) + continue; if (baked->Size > font_size && (closest_larger_match == NULL || baked->Size < closest_larger_match->Size)) closest_larger_match = baked; if (baked->Size < font_size && (closest_smaller_match == NULL || baked->Size > closest_smaller_match->Size)) @@ -4543,10 +4547,11 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, // Fonts unit to pixels int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(src, baked->Size, &oversample_h, &oversample_v); + ImFontAtlasBuildGetOversampleFactors(src, baked, &oversample_h, &oversample_v); const float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; - const float scale_for_raster_x = bd_font_data->ScaleFactor * baked->Size * src->RasterizerDensity * oversample_h; - const float scale_for_raster_y = bd_font_data->ScaleFactor * baked->Size * src->RasterizerDensity * oversample_v; + const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity; + const float scale_for_raster_x = bd_font_data->ScaleFactor * baked->Size * rasterizer_density * oversample_h; + const float scale_for_raster_y = bd_font_data->ScaleFactor * baked->Size * rasterizer_density * oversample_v; // Obtain size and advance int x0, y0, x1, y1; @@ -4601,8 +4606,8 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, font_off_y = IM_ROUND(font_off_y); font_off_x += stbtt__oversample_shift(oversample_h); font_off_y += stbtt__oversample_shift(oversample_v) + IM_ROUND(baked->Ascent); - float recip_h = 1.0f / (oversample_h * src->RasterizerDensity); - float recip_v = 1.0f / (oversample_v * src->RasterizerDensity); + float recip_h = 1.0f / (oversample_h * rasterizer_density); + float recip_v = 1.0f / (oversample_v * rasterizer_density); // Register glyph // r->x r->y are coordinates inside texture (in pixels) @@ -5172,11 +5177,12 @@ float ImFontBaked::GetCharAdvance(ImWchar c) } IM_MSVC_RUNTIME_CHECKS_RESTORE -ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size) +ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density) { - struct { ImGuiID FontId; float BakedSize; } hashed_data; + struct { ImGuiID FontId; float BakedSize; float RasterizerDensity; } hashed_data; hashed_data.FontId = font_id; hashed_data.BakedSize = baked_size; + hashed_data.RasterizerDensity = rasterizer_density; return ImHashData(&hashed_data, sizeof(hashed_data)); } @@ -5189,12 +5195,12 @@ ImFontBaked* ImFont::GetFontBaked(float size) // - ImGui::PushFontSize() will already round, but other paths calling GetFontBaked() directly also needs it (e.g. ImFontAtlasBuildPreloadAllGlyphRanges) size = ImGui::GetRoundedFontSize(size); - if (baked && baked->Size == size) + if (baked && baked->Size == size && baked->RasterizerDensity == CurrentRasterizerDensity) return baked; ImFontAtlas* atlas = ContainerAtlas; ImFontAtlasBuilder* builder = atlas->Builder; - baked = ImFontAtlasBakedGetOrAdd(atlas, this, size); + baked = ImFontAtlasBakedGetOrAdd(atlas, this, size, CurrentRasterizerDensity); if (baked == NULL) return NULL; baked->LastUsedFrame = builder->FrameCount; @@ -5202,11 +5208,11 @@ ImFontBaked* ImFont::GetFontBaked(float size) return baked; } -ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size) +ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) { // FIXME-NEWATLAS: Design for picking a nearest size based on some criteria? // FIXME-NEWATLAS: Altering font density won't work right away. - ImGuiID baked_id = ImFontAtlasBakedGetId(font->FontId, font_size); + ImGuiID baked_id = ImFontAtlasBakedGetId(font->FontId, font_size, font_rasterizer_density); ImFontAtlasBuilder* builder = atlas->Builder; ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); ImFontBaked* baked = *p_baked_in_map; @@ -5220,7 +5226,7 @@ ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float fo // FIXME-OPT: This is not an optimal query. if ((font->Flags & ImFontFlags_LockBakedSizes) || atlas->Locked) { - baked = ImFontAtlasBakedGetClosestMatch(atlas, font, font_size); + baked = ImFontAtlasBakedGetClosestMatch(atlas, font, font_size, font_rasterizer_density); if (baked != NULL) return baked; if (atlas->Locked) @@ -5231,7 +5237,7 @@ ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float fo } // Create new - baked = ImFontAtlasBakedAdd(atlas, font, font_size, baked_id); + baked = ImFontAtlasBakedAdd(atlas, font, font_size, font_rasterizer_density, baked_id); *p_baked_in_map = baked; // To avoid 'builder->BakedMap.SetVoidPtr(baked_id, baked);' while we can. return baked; } diff --git a/imgui_internal.h b/imgui_internal.h index 28e907488..fbd0ec148 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2142,6 +2142,7 @@ struct ImGuiContext float FontSize; // == FontSizeBeforeScaling * io.FontGlobalScale * font->Scale * g.CurrentWindow->FontWindowScale. Current text height. float FontSizeBeforeScaling; // == value passed to PushFontSize() float FontScale; // == FontBaked->Size / Font->FontSize. Scale factor over baked size. + float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; double Time; @@ -3109,6 +3110,8 @@ namespace ImGui // Fonts, drawing IMGUI_API void SetCurrentFont(ImFont* font, float font_size); + IMGUI_API void SetFontRasterizerDensity(float rasterizer_density); + inline float GetFontRasterizerDensity() { return GImGui->FontRasterizerDensity; } IMGUI_API void UpdateCurrentFontSize(); inline float GetRoundedFontSize(float size) { return IM_ROUND(size); } inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } @@ -3781,7 +3784,7 @@ IMGUI_API ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy -IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, float size, int* out_oversample_h, int* out_oversample_v); +IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v); IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); IMGUI_API bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src); @@ -3791,10 +3794,10 @@ IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasFontDiscardOutputBakes(ImFontAtlas* atlas, ImFont* font); -IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size); -IMGUI_API ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size); -IMGUI_API ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size); -IMGUI_API ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, ImGuiID baked_id); +IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density, ImGuiID baked_id); IMGUI_API void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); IMGUI_API void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 8ad2d6cea..121320b09 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -438,10 +438,11 @@ bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImF // is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me. // FT_Set_Pixel_Sizes() doesn't seem to get us the same result." // (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL) + const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity; FT_Size_RequestRec req; req.type = (bd_font_data->UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM; req.width = 0; - req.height = (uint32_t)(size * 64 * src->RasterizerDensity); + req.height = (uint32_t)(size * 64 * rasterizer_density); req.horiResolution = 0; req.vertResolution = 0; FT_Request_Size(bd_font_data->FtFace, &req); @@ -451,7 +452,7 @@ bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImF { // Read metrics FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics; - const float scale = 1.0f / src->RasterizerDensity; + const float scale = 1.0f / rasterizer_density; baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive). baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative). //LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate. @@ -503,12 +504,13 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon const int w = (int)ft_bitmap->width; const int h = (int)ft_bitmap->rows; const bool is_visible = (w != 0 && h != 0); + const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity; // Prepare glyph ImFontGlyph glyph_in = {}; ImFontGlyph* glyph = &glyph_in; glyph->Codepoint = codepoint; - glyph->AdvanceX = (slot->advance.x / FT_SCALEFACTOR) / src->RasterizerDensity; + glyph->AdvanceX = (slot->advance.x / FT_SCALEFACTOR) / rasterizer_density; // Pack and retrieve position inside texture atlas if (is_visible) @@ -534,8 +536,8 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon font_off_x = IM_ROUND(font_off_x); if (src->PixelSnapV) font_off_y = IM_ROUND(font_off_y); - float recip_h = 1.0f / src->RasterizerDensity; - float recip_v = 1.0f / src->RasterizerDensity; + float recip_h = 1.0f / rasterizer_density; + float recip_v = 1.0f / rasterizer_density; // Register glyph float glyph_off_x = (float)face->glyph->bitmap_left; From 4dec946ae671099bbf0e846778b7aebdb7b481d8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 5 May 2025 20:58:38 +0200 Subject: [PATCH 144/191] Fonts: don't pretend to half recover from OOM for now + debug log filename on load failure. --- imgui_draw.cpp | 5 ++++- misc/freetype/imgui_freetype.cpp | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 14440592e..2cec1439d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3102,7 +3102,10 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, if (!data) { if (font_cfg_template == NULL || (font_cfg_template->Flags & ImFontFlags_NoLoadError) == 0) + { + IMGUI_DEBUG_LOG("While loading '%s'\n", filename); IM_ASSERT_USER_ERROR(0, "Could not load font file!"); + } return NULL; } ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); @@ -4576,7 +4579,7 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, if (pack_id == ImFontAtlasRectId_Invalid) { // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) - IM_ASSERT_USER_ERROR(pack_id != ImFontAtlasRectId_Invalid, "Out of texture memory."); + IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory."); return NULL; } ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 121320b09..afd3e0cec 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -516,10 +516,10 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon if (is_visible) { ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); - if (pack_id < 0) + if (pack_id == ImFontAtlasRectId_Invalid) { // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) - IM_ASSERT_USER_ERROR(pack_id >= 0, "Out of texture memory."); + IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory."); return NULL; } ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); From 8523cbdf58f1851c007751cf7e43317fb146a259 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 8 May 2025 17:03:52 +0200 Subject: [PATCH 145/191] Fonts: rework ImFontLoader::FontBakedLoadGlyph() interface --- imgui_draw.cpp | 42 +++++++++++++++----------------- imgui_internal.h | 4 +-- misc/freetype/imgui_freetype.cpp | 36 ++++++++++++--------------- 3 files changed, 36 insertions(+), 46 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2cec1439d..968dc47d4 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4393,12 +4393,15 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep { const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) - if (ImFontGlyph* glyph = loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint)) + { + ImFontGlyph glyph_buf; + if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, &glyph_buf)) { // FIXME: Add hooks for e.g. #7962 - glyph->SourceIdx = src_n; - return glyph; + glyph_buf.SourceIdx = src_n; + return ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph_buf); } + } loader_user_data_p += loader->FontBakedSrcLoaderDataSize; src_n++; } @@ -4539,14 +4542,14 @@ static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig return true; } -static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint) +static bool ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint, ImFontGlyph* out_glyph) { // Search for first font which has the glyph ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; IM_ASSERT(bd_font_data); int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); if (glyph_index == 0) - return NULL; + return false; // Fonts unit to pixels int oversample_h, oversample_v; @@ -4564,10 +4567,8 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, const bool is_visible = (x0 != x1 && y0 != y1); // Prepare glyph - ImFontGlyph glyph_in = {}; - ImFontGlyph* glyph = &glyph_in; - glyph->Codepoint = codepoint; - glyph->AdvanceX = advance * scale_for_layout; + out_glyph->Codepoint = codepoint; + out_glyph->AdvanceX = advance * scale_for_layout; // Pack and retrieve position inside texture atlas // (generally based on stbtt_PackFontRangesRenderIntoRects) @@ -4580,7 +4581,7 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, { // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory."); - return NULL; + return false; } ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); @@ -4615,21 +4616,16 @@ static ImFontGlyph* ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, // Register glyph // r->x r->y are coordinates inside texture (in pixels) // glyph.X0, glyph.Y0 are drawing coordinates from base text position, and accounting for oversampling. - glyph->X0 = x0 * recip_h + font_off_x; - glyph->Y0 = y0 * recip_v + font_off_y; - glyph->X1 = (x0 + (int)r->w) * recip_h + font_off_x; - glyph->Y1 = (y0 + (int)r->h) * recip_v + font_off_y; - glyph->Visible = true; - glyph->PackId = pack_id; - glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); - ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, glyph, r, bitmap_pixels, ImTextureFormat_Alpha8, w); - } - else - { - glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); + out_glyph->X0 = x0 * recip_h + font_off_x; + out_glyph->Y0 = y0 * recip_v + font_off_y; + out_glyph->X1 = (x0 + (int)r->w) * recip_h + font_off_x; + out_glyph->Y1 = (y0 + (int)r->h) * recip_v + font_off_y; + out_glyph->Visible = true; + out_glyph->PackId = pack_id; + ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, bitmap_pixels, ImTextureFormat_Alpha8, w); } - return glyph; + return true; } const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() diff --git a/imgui_internal.h b/imgui_internal.h index fbd0ec148..00cb159d2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3669,7 +3669,7 @@ namespace ImGui // Hooks and storage for a given font backend. // This structure is likely to evolve as we add support for incremental atlas updates. -// Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public. +// Conceptually this could be public, but API is still going to be evolve. struct ImFontLoader { const char* Name; @@ -3680,7 +3680,7 @@ struct ImFontLoader bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); bool (*FontBakedInit)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); - ImFontGlyph* (*FontBakedLoadGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint); + bool (*FontBakedLoadGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph); // Size of backend data, Per Baked * Per Source. Buffers are managed by core to avoid excessive allocations. // FIXME: At this point the two other types of buffers may be managed by core to be consistent? diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index afd3e0cec..a0fc96926 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -473,12 +473,12 @@ void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE() } -ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint) +bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph) { ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint); if (glyph_index == 0) - return NULL; + return false; if (bd_font_data->BakedLastActivated != baked) // <-- could use id { @@ -490,7 +490,7 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon const FT_Glyph_Metrics* metrics = ImGui_ImplFreeType_LoadGlyph(bd_font_data, codepoint); if (metrics == NULL) - return NULL; + return false; // Render glyph into a bitmap (currently held by FreeType) FT_Face face = bd_font_data->FtFace; @@ -507,10 +507,8 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity; // Prepare glyph - ImFontGlyph glyph_in = {}; - ImFontGlyph* glyph = &glyph_in; - glyph->Codepoint = codepoint; - glyph->AdvanceX = (slot->advance.x / FT_SCALEFACTOR) / rasterizer_density; + out_glyph->Codepoint = codepoint; + out_glyph->AdvanceX = (slot->advance.x / FT_SCALEFACTOR) / rasterizer_density; // Pack and retrieve position inside texture atlas if (is_visible) @@ -542,21 +540,17 @@ ImFontGlyph* ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontCon // Register glyph float glyph_off_x = (float)face->glyph->bitmap_left; float glyph_off_y = (float)-face->glyph->bitmap_top; - glyph->X0 = glyph_off_x * recip_h + font_off_x; - glyph->Y0 = glyph_off_y * recip_v + font_off_y; - glyph->X1 = (glyph_off_x + w) * recip_h + font_off_x; - glyph->Y1 = (glyph_off_y + h) * recip_v + font_off_y; - glyph->Visible = true; - glyph->Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); - glyph->PackId = pack_id; - glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); - ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, glyph, r, (const unsigned char*)temp_buffer, ImTextureFormat_RGBA32, w * 4); + out_glyph->X0 = glyph_off_x * recip_h + font_off_x; + out_glyph->Y0 = glyph_off_y * recip_v + font_off_y; + out_glyph->X1 = (glyph_off_x + w) * recip_h + font_off_x; + out_glyph->Y1 = (glyph_off_y + h) * recip_v + font_off_y; + out_glyph->Visible = true; + out_glyph->Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); + out_glyph->PackId = pack_id; + ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, (const unsigned char*)temp_buffer, ImTextureFormat_RGBA32, w * 4); } - else - { - glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, src, glyph); - } - return glyph; + + return true; } bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) From 89e880dfd18d7a7ebfc684831d787f6b620db6c7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 8 May 2025 17:35:20 +0200 Subject: [PATCH 146/191] Fonts: adding ImFontHooks for codepoint remapping. --- imgui.h | 3 +++ imgui_draw.cpp | 16 +++++++++++++++- imgui_internal.h | 12 ++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index 9bf4db914..8cf3f7ff4 100644 --- a/imgui.h +++ b/imgui.h @@ -177,6 +177,7 @@ struct ImFontBaked; // Baked data for a ImFont at a given size. struct ImFontConfig; // Configuration data when adding a font or merging fonts struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data +struct ImFontHooks; // Opaque interface to font hooks struct ImFontLoader; // Opaque interface to a font loading backend (stb_truetype, FreeType etc.). struct ImTextureData; // Specs and pixel storage for a texture used by Dear ImGui. struct ImTextureRect; // Coordinates of a rectangle within a texture. @@ -3682,6 +3683,7 @@ struct ImFontAtlas const ImFontLoader* FontLoader; // Font loader opaque interface (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). Don't set directly! const char* FontLoaderName; // Font loader name (for display e.g. in About box) == FontLoader->Name void* FontLoaderData; // Font backend opaque storage + const ImFontHooks* FontHooks; // Shared font hooks for all fonts. unsigned int FontBuilderFlags; // [FIXME: Should be called FontLoaderFlags] Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. . Per-font override is also available in ImFontConfig. int RefCount; // Number of contexts using this atlas @@ -3769,6 +3771,7 @@ struct ImFont float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated. + const ImFontHooks* FontHooks; // 8 // in // Custom font hooks for the font. // Methods IMGUI_API ImFont(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 968dc47d4..8661de762 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4365,6 +4365,14 @@ static void ImFontBaked_BuildGrowIndex(ImFontBaked* baked, int new_size) baked->IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); } +static void ImFont_FontHookRemapCodepoint(ImFontAtlas* atlas, ImFont* font, ImWchar* c) +{ + if (font->FontHooks && font->FontHooks->FontHookRemapCodepoint != NULL) + font->FontHooks->FontHookRemapCodepoint(atlas, font, c); + else if (atlas->FontHooks && atlas->FontHooks->FontHookRemapCodepoint != NULL) + atlas->FontHooks->FontHookRemapCodepoint(atlas, font, c); +} + static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codepoint) { ImFont* font = baked->ContainerFont; @@ -4377,6 +4385,10 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep return NULL; } + // User remapping hooks + ImWchar src_codepoint = codepoint; + ImFont_FontHookRemapCodepoint(atlas, font, &codepoint); + //char utf8_buf[5]; //IMGUI_DEBUG_LOG("[font] BuildLoadGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); @@ -4398,6 +4410,7 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, &glyph_buf)) { // FIXME: Add hooks for e.g. #7962 + glyph_buf.Codepoint = src_codepoint; glyph_buf.SourceIdx = src_n; return ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph_buf); } @@ -5079,7 +5092,7 @@ void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } -// FIXME-NEWATLAS: Implement AddRemapChar() which was removed since transitioning to baked logic. +// FIXME: Use ImFontHooks::FontHookRemapCodepoint() hooks. void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst) { IM_UNUSED(from_codepoint); @@ -5149,6 +5162,7 @@ bool ImFontBaked::IsGlyphLoaded(ImWchar c) bool ImFont::IsGlyphInFont(ImWchar c) { ImFontAtlas* atlas = ContainerAtlas; + ImFont_FontHookRemapCodepoint(atlas, this, &c); for (ImFontConfig* src : Sources) { const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; diff --git a/imgui_internal.h b/imgui_internal.h index 00cb159d2..b486430ad 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -37,7 +37,7 @@ Index of this file: // [SECTION] Tab bar, Tab item support // [SECTION] Table support // [SECTION] ImGui internal API -// [SECTION] ImFontLoader +// [SECTION] ImFontLoader, ImFontHooks // [SECTION] ImFontAtlas internal API // [SECTION] Test Engine specific hooks (imgui_test_engine) @@ -3664,7 +3664,7 @@ namespace ImGui //----------------------------------------------------------------------------- -// [SECTION] ImFontLoader +// [SECTION] ImFontLoader, ImFontHooks //----------------------------------------------------------------------------- // Hooks and storage for a given font backend. @@ -3693,6 +3693,14 @@ struct ImFontLoader IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); #endif +// User hooks +// Conceptually this could be public, but API is still going to be evolve. +struct ImFontHooks +{ + // Modify codepoint to map to another value. + void (*FontHookRemapCodepoint)(ImFontAtlas* atlas, ImFont* font, ImWchar* io_codepoint); +}; + //----------------------------------------------------------------------------- // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- From f6735c223c469ac0952fbf0f2bf336f8f43e3fe1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 8 May 2025 17:59:18 +0200 Subject: [PATCH 147/191] Fonts: remove ImFontHooks in favor of a AddRemapChar() implementation. --- imgui.h | 6 ++---- imgui_draw.cpp | 33 ++++++++------------------------- imgui_internal.h | 12 ++---------- 3 files changed, 12 insertions(+), 39 deletions(-) diff --git a/imgui.h b/imgui.h index 8cf3f7ff4..4639e2c2e 100644 --- a/imgui.h +++ b/imgui.h @@ -177,7 +177,6 @@ struct ImFontBaked; // Baked data for a ImFont at a given size. struct ImFontConfig; // Configuration data when adding a font or merging fonts struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data -struct ImFontHooks; // Opaque interface to font hooks struct ImFontLoader; // Opaque interface to a font loading backend (stb_truetype, FreeType etc.). struct ImTextureData; // Specs and pixel storage for a texture used by Dear ImGui. struct ImTextureRect; // Coordinates of a rectangle within a texture. @@ -3683,7 +3682,6 @@ struct ImFontAtlas const ImFontLoader* FontLoader; // Font loader opaque interface (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). Don't set directly! const char* FontLoaderName; // Font loader name (for display e.g. in About box) == FontLoader->Name void* FontLoaderData; // Font backend opaque storage - const ImFontHooks* FontHooks; // Shared font hooks for all fonts. unsigned int FontBuilderFlags; // [FIXME: Should be called FontLoaderFlags] Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. . Per-font override is also available in ImFontConfig. int RefCount; // Number of contexts using this atlas @@ -3771,7 +3769,7 @@ struct ImFont float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated. - const ImFontHooks* FontHooks; // 8 // in // Custom font hooks for the font. + ImGuiStorage RemapPairs; // 16 // // Remapping pairs when using AddRemapChar(), otherwise empty. // Methods IMGUI_API ImFont(); @@ -3794,7 +3792,7 @@ struct ImFont // [Internal] Don't use! IMGUI_API void ClearOutputData(); - IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst);// , bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. + IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint); // Makes 'from_codepoint' character points to 'to_codepoint' glyph. IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); }; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8661de762..541043dd4 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4365,12 +4365,11 @@ static void ImFontBaked_BuildGrowIndex(ImFontBaked* baked, int new_size) baked->IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); } -static void ImFont_FontHookRemapCodepoint(ImFontAtlas* atlas, ImFont* font, ImWchar* c) +static void ImFontAtlas_FontHookRemapCodepoint(ImFontAtlas* atlas, ImFont* font, ImWchar* c) { - if (font->FontHooks && font->FontHooks->FontHookRemapCodepoint != NULL) - font->FontHooks->FontHookRemapCodepoint(atlas, font, c); - else if (atlas->FontHooks && atlas->FontHooks->FontHookRemapCodepoint != NULL) - atlas->FontHooks->FontHookRemapCodepoint(atlas, font, c); + IM_UNUSED(atlas); + if (font->RemapPairs.Data.Size != 0) + *c = (ImWchar)font->RemapPairs.GetInt((ImGuiID)*c, (int)*c); } static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codepoint) @@ -4387,7 +4386,7 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep // User remapping hooks ImWchar src_codepoint = codepoint; - ImFont_FontHookRemapCodepoint(atlas, font, &codepoint); + ImFontAtlas_FontHookRemapCodepoint(atlas, font, &codepoint); //char utf8_buf[5]; //IMGUI_DEBUG_LOG("[font] BuildLoadGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); @@ -5092,25 +5091,9 @@ void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } -// FIXME: Use ImFontHooks::FontHookRemapCodepoint() hooks. -void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst) +void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint) { - IM_UNUSED(from_codepoint); - IM_UNUSED(to_codepoint); - IM_UNUSED(overwrite_dst); - /* - IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. - unsigned int index_size = (unsigned int)IndexLookup.Size; - - if (from_codepoint < index_size && IndexLookup.Data[from_codepoint] == (ImU16)-1 && !overwrite_dst) // 'from_codepoint' already exists - return; - if (to_codepoint >= index_size && from_codepoint >= index_size) // both 'from_codepoint' and 'to_codepoint' don't exist -> no-op - return; - - BuildGrowIndex(from_codepoint + 1); - IndexLookup[from_codepoint] = (to_codepoint < index_size) ? IndexLookup.Data[to_codepoint] : (ImU16)-1; - IndexAdvanceX[from_codepoint] = (to_codepoint < index_size) ? IndexAdvanceX.Data[to_codepoint] : 1.0f; - */ + RemapPairs.SetInt((ImGuiID)from_codepoint, (int)to_codepoint); } // Find glyph, load if necessary, return fallback if missing @@ -5162,7 +5145,7 @@ bool ImFontBaked::IsGlyphLoaded(ImWchar c) bool ImFont::IsGlyphInFont(ImWchar c) { ImFontAtlas* atlas = ContainerAtlas; - ImFont_FontHookRemapCodepoint(atlas, this, &c); + ImFontAtlas_FontHookRemapCodepoint(atlas, this, &c); for (ImFontConfig* src : Sources) { const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; diff --git a/imgui_internal.h b/imgui_internal.h index b486430ad..00cb159d2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -37,7 +37,7 @@ Index of this file: // [SECTION] Tab bar, Tab item support // [SECTION] Table support // [SECTION] ImGui internal API -// [SECTION] ImFontLoader, ImFontHooks +// [SECTION] ImFontLoader // [SECTION] ImFontAtlas internal API // [SECTION] Test Engine specific hooks (imgui_test_engine) @@ -3664,7 +3664,7 @@ namespace ImGui //----------------------------------------------------------------------------- -// [SECTION] ImFontLoader, ImFontHooks +// [SECTION] ImFontLoader //----------------------------------------------------------------------------- // Hooks and storage for a given font backend. @@ -3693,14 +3693,6 @@ struct ImFontLoader IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); #endif -// User hooks -// Conceptually this could be public, but API is still going to be evolve. -struct ImFontHooks -{ - // Modify codepoint to map to another value. - void (*FontHookRemapCodepoint)(ImFontAtlas* atlas, ImFont* font, ImWchar* io_codepoint); -}; - //----------------------------------------------------------------------------- // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- From 46fa9e8efb4e09244704699e69c0487915ec7812 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 9 May 2025 21:55:07 +0200 Subject: [PATCH 148/191] Fonts: Debug display status. Fixed truncated raw texture id. Fixed FormatTextureIDForDebugDisplay(). Comments. --- imgui.cpp | 27 ++++++++++++++++++--------- imgui_draw.cpp | 16 +++++++++++++++- imgui_internal.h | 1 + 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 68560fdb6..92f0f92bf 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15666,7 +15666,7 @@ static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, const char* buf_end = buf + buf_size; if (cmd->TexRef._TexData != NULL) buf += ImFormatString(buf, buf_end - buf, "#%03d: ", cmd->TexRef._TexData->UniqueID); - return FormatTextureIDForDebugDisplay(buf, (int)(buf_end - buf), cmd->GetTexID()); + return FormatTextureIDForDebugDisplay(buf, (int)(buf_end - buf), cmd->TexRef.GetTexID()); // Calling TexRef::GetTexID() to avoid assert of cmd->GetTexID() } // Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. @@ -15690,18 +15690,26 @@ namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); IMGUI_A void ImGui::ShowFontAtlas(ImFontAtlas* atlas) { ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + + Text("Read "); SameLine(0, 0); + TextLinkOpenURL("https://www.dearimgui.com/faq/"); SameLine(0, 0); + Text(" for details on font loading."); SeparatorText("Backend Support for Dynamic Fonts"); BeginDisabled(); - CheckboxFlags("io.BackendFlags: RendererHasTextures", &GetIO().BackendFlags, ImGuiBackendFlags_RendererHasTextures); + CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); + EndDisabled(); + + BeginDisabled((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); + SetNextItemWidth(GetFontSize() * 5); + DragFloat("io.FontGlobalScale", &io.FontGlobalScale, 0.05f, 0.5f, 5.0f); + BulletText("This is scaling font only. General scaling will come later."); + BulletText("Load an actual font that's not the default for best result!"); + BulletText("Please submit feedback:"); SameLine(); TextLinkOpenURL("https://github.com/ocornut/imgui/issues/8465"); EndDisabled(); SeparatorText("Fonts"); - Text("Read "); - SameLine(0, 0); - TextLinkOpenURL("https://www.dearimgui.com/faq/"); - SameLine(0, 0); - Text(" for details on font loading."); ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; Checkbox("Show font preview", &cfg->ShowFontPreview); @@ -15845,7 +15853,8 @@ void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRe } PopStyleVar(); - char texid_desc[20]; + char texid_desc[30]; + Text("Status = %s (%d)", ImTextureDataGetStatusName(tex->Status), tex->Status); Text("Format = %s (%d)", ImTextureDataGetFormatName(tex->Format), tex->Format); Text("TexID = %s", FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->TexID)); Text("BackendUserData = %p", tex->BackendUserData); @@ -16545,7 +16554,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con continue; } - char texid_desc[20]; + char texid_desc[30]; FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd); char buf[300]; ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 541043dd4..a76767b9b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2431,6 +2431,19 @@ int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format) return 0; } +const char* ImTextureDataGetStatusName(ImTextureStatus status) +{ + switch (status) + { + case ImTextureStatus_OK: return "OK"; + case ImTextureStatus_Destroyed: return "Destroyed"; + case ImTextureStatus_WantCreate: return "WantCreate"; + case ImTextureStatus_WantUpdates: return "WantUpdates"; + case ImTextureStatus_WantDestroy: return "WantDestroy"; + } + return "N/A"; +} + const char* ImTextureDataGetFormatName(ImTextureFormat format) { switch (format) @@ -2441,7 +2454,6 @@ const char* ImTextureDataGetFormatName(ImTextureFormat format) return "N/A"; } - void ImTextureData::Create(ImTextureFormat format, int w, int h) { DestroyPixels(); @@ -3156,6 +3168,7 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed return font; } +// On font removal we need to remove references (otherwise we could queue removal?) // We allow old_font == new_font which forces updating all values (e.g. sizes) static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, ImFont* new_font) { @@ -3884,6 +3897,7 @@ void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex } // Update texture coordinates in all draw list shared context +// FIXME-NEWATLAS FIXME-OPT: Doesn't seem necessary to update for all, only one bound to current context? void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) { for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) diff --git a/imgui_internal.h b/imgui_internal.h index 00cb159d2..5f2a06fe4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3823,6 +3823,7 @@ IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format); +IMGUI_API const char* ImTextureDataGetStatusName(ImTextureStatus status); IMGUI_API const char* ImTextureDataGetFormatName(ImTextureFormat format); #ifndef IMGUI_DISABLE_DEBUG_TOOLS From 65e60399794295d28b552941114f41da8be148a0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 9 May 2025 22:46:39 +0200 Subject: [PATCH 149/191] Fonts: remove unnecessary ImDrawListSharedData::FontAtlas which is actually getting in the way of using multiple atlases. --- imgui_draw.cpp | 7 ++----- imgui_internal.h | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a76767b9b..eaba28b06 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3865,16 +3865,14 @@ void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames) // Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) { - IM_ASSERT(!atlas->DrawListSharedDatas.contains(data) && data->FontAtlas == NULL); + IM_ASSERT(!atlas->DrawListSharedDatas.contains(data)); atlas->DrawListSharedDatas.push_back(data); - data->FontAtlas = atlas; } void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) { - IM_ASSERT(atlas->DrawListSharedDatas.contains(data) && data->FontAtlas == atlas); + IM_ASSERT(atlas->DrawListSharedDatas.contains(data)); atlas->DrawListSharedDatas.find_erase(data); - data->FontAtlas = NULL; } // Update texture identifier in all active draw lists @@ -3902,7 +3900,6 @@ void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) { for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) { - shared_data->FontAtlas = atlas; shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; shared_data->TexUvLines = atlas->TexUvLines; } diff --git a/imgui_internal.h b/imgui_internal.h index 5f2a06fe4..f145fd012 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -838,7 +838,6 @@ struct IMGUI_API ImDrawListSharedData { ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) - ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current/default font (optional, for simplified AddText overload) float FontSize; // Current/default font size (optional, for simplified AddText overload) float FontScale; // Current/default font scale (== FontSize / Font->FontSize) From fad5280d4c131f3ebbbc30f6d1dc0d4e8463df4c Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 May 2025 23:51:45 +0200 Subject: [PATCH 150/191] Fonts: fixed broken support for legacy backend due to a mismatch with initial pre-build baked id. --- imgui.cpp | 2 +- imgui.h | 2 +- imgui_draw.cpp | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 92f0f92bf..bd01afed5 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8657,7 +8657,7 @@ void ImGui::UpdateCurrentFontSize() // - We may support it better later and remove this rounding. final_size = GetRoundedFontSize(final_size); final_size = ImMax(1.0f, final_size); - if (g.Font != NULL) + if (g.Font != NULL && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; g.FontBaked = (g.Font != NULL) ? g.Font->GetFontBaked(final_size) : NULL; g.FontSize = final_size; diff --git a/imgui.h b/imgui.h index 4639e2c2e..71e49efe4 100644 --- a/imgui.h +++ b/imgui.h @@ -3467,7 +3467,7 @@ struct ImFontConfig float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. - float RasterizerDensity; // 1.0f // DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. + float RasterizerDensity; // 1.0f // (Legacy: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported). DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. // [Internal] diff --git a/imgui_draw.cpp b/imgui_draw.cpp index eaba28b06..65485fb20 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3002,6 +3002,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) font->FontId = FontNextUniqueID++; font->Flags = font_cfg_in->Flags; font->DefaultSize = font_cfg_in->SizePixels; + font->CurrentRasterizerDensity = font_cfg_in->RasterizerDensity; Fonts.push_back(font); } else From 91ed6e67b439b9dabc213ab2a05e4f88c70e526a Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 9 May 2025 22:17:48 +0200 Subject: [PATCH 151/191] Fonts: fixed support for multiple atlases. Moved FontAtlasOwnedByContext to OwnerContext # Conflicts: # imgui.cpp # imgui_internal.h --- imgui.cpp | 91 +++++++++++++++++++++++++++++------------------- imgui.h | 1 + imgui_internal.h | 4 ++- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index bd01afed5..d79690088 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3962,13 +3962,13 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) InputTextState.Ctx = this; Initialized = false; - FontAtlasOwnedByContext = shared_font_atlas ? false : true; Font = NULL; FontBaked = NULL; FontSize = FontSizeBeforeScaling = FontScale = CurrentDpiScale = 0.0f; FontRasterizerDensity = 1.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); - IO.Fonts->RefCount++; + if (shared_font_atlas == NULL) + IO.Fonts->OwnerContext = this; Time = 0.0f; FrameCount = 0; FrameCountEnded = FrameCountRendered = -1; @@ -4226,8 +4226,9 @@ void ImGui::Initialize() // ImDrawList/ImFontAtlas are designed to function without ImGui, and 99% of it works without an ImGui context. // But this link allows us to facilitate/handle a few edge cases better. + ImFontAtlas* atlas = g.IO.Fonts; g.DrawListSharedData.Context = &g; - ImFontAtlasAddDrawListSharedData(g.IO.Fonts, &g.DrawListSharedData); + RegisterFontAtlas(atlas); g.Initialized = true; } @@ -4240,17 +4241,15 @@ void ImGui::Shutdown() IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == NULL, "Forgot to shutdown Renderer backend?"); // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) - if (ImFontAtlas* atlas = g.IO.Fonts) + for (ImFontAtlas* atlas : g.FontAtlases) { - ImFontAtlasRemoveDrawListSharedData(atlas, &g.DrawListSharedData); - atlas->RefCount--; - if (g.FontAtlasOwnedByContext) + UnregisterFontAtlas(atlas); + if (atlas->OwnerContext == &g) { atlas->Locked = false; IM_DELETE(atlas); } } - g.IO.Fonts = NULL; g.DrawListSharedData.TempBuffer.clear(); // Cleanup of other data are conditional on actually having initialized Dear ImGui. @@ -4412,7 +4411,8 @@ void ImGui::GcCompactTransientMiscBuffers() g.MultiSelectTempDataStacked = 0; g.MultiSelectTempData.clear_destruct(); TableGcCompactSettings(); - g.IO.Fonts->CompactCache(); + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->CompactCache(); } // Free up/compact internal window buffers, we can use this when a window becomes unused. @@ -5210,29 +5210,27 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos) static void ImGui::UpdateTexturesNewFrame() { ImGuiContext& g = *GImGui; - ImFontAtlas* atlas = g.IO.Fonts; - if (g.FontAtlasOwnedByContext) - { - atlas->RendererHasTextures = (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; - ImFontAtlasUpdateNewFrame(atlas, g.FrameCount); - } + for (ImFontAtlas* atlas : g.FontAtlases) + if (atlas->OwnerContext == &g) + { + atlas->RendererHasTextures = (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + ImFontAtlasUpdateNewFrame(atlas, g.FrameCount); + } } // Build a single texture list -// We want to avoid user reading from atlas->TexList[] in order to facilitate better support for multiple atlases. static void ImGui::UpdateTexturesEndFrame() { ImGuiContext& g = *GImGui; - ImFontAtlas* atlas = g.IO.Fonts; g.PlatformIO.Textures.resize(0); - g.PlatformIO.Textures.reserve(atlas->TexList.Size); - for (ImTextureData* tex : atlas->TexList) - { - // We provide this information so backends can decide whether to destroy textures. - // This means in practice that if N imgui contexts are created with a shared atlas, we assume all of them have a backend initialized. - tex->RefCount = (unsigned short)atlas->RefCount; - g.PlatformIO.Textures.push_back(tex); - } + for (ImFontAtlas* atlas : g.FontAtlases) + for (ImTextureData* tex : atlas->TexList) + { + // We provide this information so backends can decide whether to destroy textures. + // This means in practice that if N imgui contexts are created with a shared atlas, we assume all of them have a backend initialized. + tex->RefCount = (unsigned short)atlas->RefCount; + g.PlatformIO.Textures.push_back(tex); + } } // Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. @@ -5810,8 +5808,8 @@ void ImGui::EndFrame() UpdateTexturesEndFrame(); // Unlock font atlas - ImFontAtlas* atlas = g.IO.Fonts; - atlas->Locked = false; + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->Locked = false; // Clear Input data for next frame g.IO.MousePosPrev = g.IO.MousePos; @@ -5890,7 +5888,8 @@ void ImGui::Render() #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) - ImFontAtlasDebugLogTextureRequests(g.IO.Fonts); + for (ImFontAtlas* atlas : g.FontAtlases) + ImFontAtlasDebugLogTextureRequests(atlas); #endif CallContextHooks(&g, ImGuiContextHookType_RenderPost); @@ -8596,9 +8595,9 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) void ImGui::UpdateFontsNewFrame() { ImGuiContext& g = *GImGui; - ImFontAtlas* atlas = g.IO.Fonts; if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) - atlas->Locked = true; + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->Locked = true; // We do this really unusual thing of calling *push_front()*, the reason behind that we want to support the PushFont()/NewFrame()/PopFont() idiom. ImFontStackData font_stack_data = { ImGui::GetDefaultFont(), ImGui::GetDefaultFont()->DefaultSize }; @@ -8614,6 +8613,25 @@ void ImGui::UpdateFontsEndFrame() PopFont(); } +void ImGui::RegisterFontAtlas(ImFontAtlas* atlas) +{ + ImGuiContext& g = *GImGui; + if (g.FontAtlases.Size == 0) + IM_ASSERT(atlas == g.IO.Fonts); + atlas->RefCount++; + g.FontAtlases.push_back(atlas); + ImFontAtlasAddDrawListSharedData(atlas, &g.DrawListSharedData); +} + +void ImGui::UnregisterFontAtlas(ImFontAtlas* atlas) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(atlas->RefCount > 0); + ImFontAtlasRemoveDrawListSharedData(atlas, &g.DrawListSharedData); + g.FontAtlases.find_erase(atlas); + atlas->RefCount--; +} + // Use ImDrawList::_SetTexture(), making our shared g.FontStack[] authoritative against window-local ImDrawList. // - Whereas ImDrawList::PushTexture()/PopTexture() is not to be used across Begin() calls. // - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did... @@ -8670,6 +8688,7 @@ void ImGui::UpdateCurrentFontSize() void ImGui::SetFontRasterizerDensity(float rasterizer_density) { ImGuiContext& g = *GImGui; + IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures); if (g.FontRasterizerDensity == rasterizer_density) return; g.FontRasterizerDensity = rasterizer_density; @@ -16104,12 +16123,12 @@ void ImGui::ShowMetricsWindow(bool* p_open) } // Details for Fonts - ImFontAtlas* atlas = g.IO.Fonts; - if (TreeNode("Fonts", "Fonts (%d), Textures (%d)", atlas->Fonts.Size, atlas->TexList.Size)) - { - ShowFontAtlas(atlas); - TreePop(); - } + for (ImFontAtlas* atlas : g.FontAtlases) + if (TreeNode((void*)atlas, "Fonts (%d), Textures (%d)", atlas->Fonts.Size, atlas->TexList.Size)) + { + ShowFontAtlas(atlas); + TreePop(); + } // Details for Popups if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) diff --git a/imgui.h b/imgui.h index 71e49efe4..4ae0bd84b 100644 --- a/imgui.h +++ b/imgui.h @@ -3684,6 +3684,7 @@ struct ImFontAtlas void* FontLoaderData; // Font backend opaque storage unsigned int FontBuilderFlags; // [FIXME: Should be called FontLoaderFlags] Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. . Per-font override is also available in ImFontConfig. int RefCount; // Number of contexts using this atlas + ImGuiContext* OwnerContext; // Context which own the atlas will be in charge of updating and destroying it. // [Obsolete] #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/imgui_internal.h b/imgui_internal.h index f145fd012..fc44490d4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2132,10 +2132,10 @@ struct ImGuiContextHook struct ImGuiContext { bool Initialized; - bool FontAtlasOwnedByContext; // IO.Fonts-> is owned by the ImGuiContext and will be destructed along with it. ImGuiIO IO; ImGuiPlatformIO PlatformIO; ImGuiStyle Style; + ImVector FontAtlases; // List of font atlases used by the context (generally only contains g.IO.Fonts aka the main font atlas) ImFont* Font; // == FontStack.back().Font ImFontBaked* FontBaked; // == Font->GetFontBaked(FontSize) float FontSize; // == FontSizeBeforeScaling * io.FontGlobalScale * font->Scale * g.CurrentWindow->FontWindowScale. Current text height. @@ -3108,6 +3108,8 @@ namespace ImGui IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); // Fonts, drawing + IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas); + IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); IMGUI_API void SetCurrentFont(ImFont* font, float font_size); IMGUI_API void SetFontRasterizerDensity(float rasterizer_density); inline float GetFontRasterizerDensity() { return GImGui->FontRasterizerDensity; } From 39f6c793b33aefa7769d262b3a12610fb49bd7ef Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 12 May 2025 10:22:15 +0200 Subject: [PATCH 152/191] Fonts: proof of concept support for user textures. # Conflicts: # imgui.h # imgui_internal.h --- imgui.cpp | 15 +++++++++++++++ imgui.h | 2 +- imgui_internal.h | 6 ++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index d79690088..2a4001d65 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5231,6 +5231,8 @@ static void ImGui::UpdateTexturesEndFrame() tex->RefCount = (unsigned short)atlas->RefCount; g.PlatformIO.Textures.push_back(tex); } + for (ImTextureData* tex : g.UserTextures) + g.PlatformIO.Textures.push_back(tex); } // Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. @@ -8613,6 +8615,19 @@ void ImGui::UpdateFontsEndFrame() PopFont(); } +void ImGui::RegisterUserTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(tex->RefCount > 0); + g.UserTextures.push_back(tex); +} + +void ImGui::UnregisterUserTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + g.UserTextures.find_erase(tex); +} + void ImGui::RegisterFontAtlas(ImFontAtlas* atlas) { ImGuiContext& g = *GImGui; diff --git a/imgui.h b/imgui.h index 4ae0bd84b..3a8d05377 100644 --- a/imgui.h +++ b/imgui.h @@ -3905,7 +3905,7 @@ struct ImGuiPlatformIO // Textures list (the list is updated by calling ImGui::EndFrame or ImGui::Render) // The ImGui_ImplXXXX_RenderDrawData() function of each backend generally access this via ImDrawData::Textures which points to this. The array is available here mostly because backends will want to destroy textures on shutdown. - ImVector Textures; // List of textures used by Dear ImGui (most often 1). + ImVector Textures; // List of textures used by Dear ImGui (most often 1) + contents of external texture list is automatically appended into this. }; // (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function. Handler is called during EndFrame(). diff --git a/imgui_internal.h b/imgui_internal.h index fc44490d4..a390add19 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2426,6 +2426,10 @@ struct ImGuiContext ImGuiPlatformImeData PlatformImeData; // Data updated by current frame. Will be applied at end of the frame. For some backends, this is required to have WantVisible=true in order to receive text message. ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data. When changed we call the platform_io.Platform_SetImeDataFn() handler. + // Extensions + // FIXME: We could provide an API to register one slot in an array held in ImGuiContext? + ImVector UserTextures; // List of textures created/managed by user or third-party extension. Automatically appended into platform_io.Textures[]. + // Settings bool SettingsLoaded; float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero @@ -3108,6 +3112,8 @@ namespace ImGui IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); // Fonts, drawing + IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture + IMGUI_API void UnregisterUserTexture(ImTextureData* tex); IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas); IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); IMGUI_API void SetCurrentFont(ImFont* font, float font_size); From 1b51a88bba9315f656ad9280bd98caddac0dc243 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 15 May 2025 16:08:05 +0200 Subject: [PATCH 153/191] Fonts: moved compare operators to internal. Removed commented out ones aimed legacy backends: not needed anymore since we didn't rename ImTextureID. --- imgui.h | 9 --------- imgui_internal.h | 5 +++++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/imgui.h b/imgui.h index 3a8d05377..c431a8320 100644 --- a/imgui.h +++ b/imgui.h @@ -2836,15 +2836,6 @@ static inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return IM_MSVC_RUNTIME_CHECKS_RESTORE #endif -// Helpers: ImTextureRef ==/!= operators provided as convenience -// (note that _TexID and _TexData are never set simultaneously) -static inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._TexData == rhs._TexData; } -static inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._TexData != rhs._TexData; } -//#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // For legacy backends -//static inline bool operator==(ImTextureID lhs, const ImTextureRef& rhs) { return lhs == rhs._TexID && rhs._TexData == NULL; } -//static inline bool operator==(const ImTextureRef& lhs, ImTextureID rhs) { return lhs._TexID == rhs && lhs._TexData == NULL; } -//#endif - // Helpers macros to generate 32-bit encoded colors // - User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. // - Any setting other than the default will need custom backend support. The only standard backend that supports anything else than the default is DirectX9. diff --git a/imgui_internal.h b/imgui_internal.h index a390add19..7c6305734 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3704,6 +3704,11 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- +// Helpers: ImTextureRef ==/!= operators provided as convenience +// (note that _TexID and _TexData are never set simultaneously) +static inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._TexData == rhs._TexData; } +static inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._TexData != rhs._TexData; } + // Refer to ImFontAtlasPackGetRect() to better understand how this works. #define ImFontAtlasRectId_IndexMask_ (0x000FFFFF) // 20-bits: index to access builder->RectsIndex[]. #define ImFontAtlasRectId_GenerationMask_ (0x3FF00000) // 10-bits: entry generation, so each ID is unique and get can safely detected old identifiers. From ea756ede16fde3634ea3099cfd379fa2692d9625 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 15 May 2025 16:14:08 +0200 Subject: [PATCH 154/191] Fonts: reorder ImFontFlags according likelihood of being useful. --- imgui.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/imgui.h b/imgui.h index c431a8320..61056bb6f 100644 --- a/imgui.h +++ b/imgui.h @@ -3732,10 +3732,10 @@ struct ImFontBaked enum ImFontFlags_ { ImFontFlags_None = 0, - ImFontFlags_LockBakedSizes = 1 << 0, // Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. - ImFontFlags_NoLoadGlyphs = 1 << 1, // Disable loading new glyphs. - ImFontFlags_NoLoadError = 1 << 2, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. - ImFontFlags_UseDefaultSize = 1 << 3, // Legacy compatibility: make PushFont() calls without explicit size use font->DefaultSize instead of current font size. + ImFontFlags_UseDefaultSize = 1 << 0, // Legacy compatibility: make PushFont() calls without explicit size use font->DefaultSize instead of current font size. + ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. + ImFontFlags_NoLoadGlyphs = 1 << 2, // Disable loading new glyphs. + ImFontFlags_LockBakedSizes = 1 << 3, // Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. }; // Font runtime data and rendering @@ -3788,13 +3788,14 @@ struct ImFont IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); }; -// We added an indirection to avoid patching ImDrawCmd after texture updates but this could be a solution too. +// This is provided for consistency (but we don't actually use this) inline ImTextureID ImTextureRef::GetTexID() const { IM_ASSERT(!(_TexData != NULL && _TexID != ImTextureID_Invalid)); return _TexData ? _TexData->TexID : _TexID; } +// Using an indirection to avoid patching ImDrawCmd after a SetTexID() call (but this could be an alternative solution too) inline ImTextureID ImDrawCmd::GetTexID() const { // If you are getting this assert: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) From 5ee984555984979e39e0fd5584be6dc442686499 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 16 May 2025 18:04:44 +0200 Subject: [PATCH 155/191] Fonts: automatically set current rasterizer density to viewport density. Effectively should fix most things on macOS. # Conflicts: # imgui.cpp # imgui.h --- docs/FONTS.md | 2 +- imgui.cpp | 6 +++++- imgui.h | 13 +++++++------ imgui_draw.cpp | 45 +++++++++++++++++++++++++-------------------- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/docs/FONTS.md b/docs/FONTS.md index 452499059..baa53adde 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -130,7 +130,7 @@ ImGui::PopFont(); **For advanced options create a ImFontConfig structure and pass it to the AddFont() function (it will be copied internally):** ```cpp ImFontConfig config; -config.RasterizerDensity = 2.0f; +config.OversampleH = 1.0f; ImFont* font = io.Fonts->AddFontFromFileTTF("font.ttf", size_pixels, &config); ``` diff --git a/imgui.cpp b/imgui.cpp index 2a4001d65..58455df8d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4398,6 +4398,8 @@ static void SetCurrentWindow(ImGuiWindow* window) g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking if (window) { + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + g.FontRasterizerDensity = window->Viewport->FramebufferScale.x; // == SetFontRasterizerDensity() ImGui::UpdateCurrentFontSize(); ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } @@ -8699,7 +8701,8 @@ void ImGui::UpdateCurrentFontSize() g.DrawListSharedData.FontScale = g.FontScale; } -// FIXME-DPI: Not sure how to expose this. It may be automatically applied based on current viewport, if we had this information stored in viewport or monitor. +// Exposed in case user may want to override setting density. +// IMPORTANT: Begin()/End() is overriding density. Be considerate of this you change it. void ImGui::SetFontRasterizerDensity(float rasterizer_density) { ImGuiContext& g = *GImGui; @@ -15240,6 +15243,7 @@ static void ImGui::UpdateViewportsNewFrame() main_viewport->Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp; main_viewport->Pos = ImVec2(0.0f, 0.0f); main_viewport->Size = g.IO.DisplaySize; + main_viewport->FramebufferScale = g.IO.DisplayFramebufferScale; for (ImGuiViewportP* viewport : g.Viewports) { diff --git a/imgui.h b/imgui.h index 61056bb6f..0eb83cd9a 100644 --- a/imgui.h +++ b/imgui.h @@ -2322,7 +2322,8 @@ struct ImGuiIO ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Keyboard/Gamepad navigation options, etc. ImGuiBackendFlags BackendFlags; // = 0 // See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend. - ImVec2 DisplaySize; // // Main display size, in pixels (generally == GetMainViewport()->Size). May change every frame. + ImVec2 DisplaySize; // // Main display size, in pixels (== GetMainViewport()->Size). May change every frame. + ImVec2 DisplayFramebufferScale; // = (1, 1) // Main display density. For retina display where window coordinates are different from framebuffer coordinates. This will affect font density + will end up in ImDrawData::FramebufferScale. float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. May change every frame. float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. const char* IniFilename; // = "imgui.ini" // Path to .ini file (important: default "imgui.ini" is relative to current working dir!). Set NULL to disable automatic .ini loading/saving or if you want to manually call LoadIniSettingsXXX() / SaveIniSettingsXXX() functions. @@ -2334,7 +2335,6 @@ struct ImGuiIO float FontGlobalScale; // = 1.0f // Global scale all fonts bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. - ImVec2 DisplayFramebufferScale; // = (1, 1) // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale. // Keyboard/Gamepad Navigation options bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. @@ -3341,7 +3341,7 @@ struct ImDrawData ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. ImVec2 DisplayPos; // Top-left position of the viewport to render (== top-left of the orthogonal projection matrix to use) (== GetMainViewport()->Pos for the main viewport, == (0.0) in most single-viewport applications) ImVec2 DisplaySize; // Size of the viewport to render (== GetMainViewport()->Size for the main viewport, == io.DisplaySize in most single-viewport applications) - ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Based on io.DisplayFramebufferScale. Generally (1,1) on normal display, (2,2) on OSX with Retina display. + ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Copied from viewport->FramebufferScale (== io.DisplayFramebufferScale for main viewport). Generally (1,1) on normal display, (2,2) on OSX with Retina display. ImGuiViewport* OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). ImVector* Textures; // List of textures to update. Most of the times the list is shared by all ImDrawData, has only 1 texture and it doesn't need any update. This almost always points to ImGui::GetPlatformIO().Textures[]. May be overriden or set to NULL if you want to manually update textures. @@ -3734,8 +3734,8 @@ enum ImFontFlags_ ImFontFlags_None = 0, ImFontFlags_UseDefaultSize = 1 << 0, // Legacy compatibility: make PushFont() calls without explicit size use font->DefaultSize instead of current font size. ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. - ImFontFlags_NoLoadGlyphs = 1 << 2, // Disable loading new glyphs. - ImFontFlags_LockBakedSizes = 1 << 3, // Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. + ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs. + ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display. }; // Font runtime data and rendering @@ -3766,7 +3766,7 @@ struct ImFont // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API ImFontBaked* GetFontBaked(float font_size); // Get or create baked data for given size + IMGUI_API ImFontBaked* GetFontBaked(float font_size, float density = -1.0f); // Get or create baked data for given size IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } const char* GetDebugName() const { return Sources.Size ? Sources[0]->Name : ""; } // Fill ImFontConfig::Name. @@ -3832,6 +3832,7 @@ struct ImGuiViewport ImGuiViewportFlags Flags; // See ImGuiViewportFlags_ ImVec2 Pos; // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates) ImVec2 Size; // Main Area: Size of the viewport. + ImVec2 FramebufferScale; // Density of the viewport for Retina display (always 1,1 on Windows, may be 2,2 etc on macOS/iOS). This will affect font rasterizer density. ImVec2 WorkPos; // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos) ImVec2 WorkSize; // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 65485fb20..6306e9c75 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3786,25 +3786,28 @@ ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_si ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) { ImFontAtlasBuilder* builder = atlas->Builder; - ImFontBaked* closest_larger_match = NULL; - ImFontBaked* closest_smaller_match = NULL; - for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + for (int step_n = 0; step_n < 2; step_n++) { - ImFontBaked* baked = &builder->BakedPool[baked_n]; - if (baked->ContainerFont != font || baked->WantDestroy) - continue; - if (baked->RasterizerDensity != font_rasterizer_density) - continue; - if (baked->Size > font_size && (closest_larger_match == NULL || baked->Size < closest_larger_match->Size)) - closest_larger_match = baked; - if (baked->Size < font_size && (closest_smaller_match == NULL || baked->Size > closest_smaller_match->Size)) - closest_smaller_match = baked; + ImFontBaked* closest_larger_match = NULL; + ImFontBaked* closest_smaller_match = NULL; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->ContainerFont != font || baked->WantDestroy) + continue; + if (step_n == 0 && baked->RasterizerDensity != font_rasterizer_density) // First try with same density + continue; + if (baked->Size > font_size && (closest_larger_match == NULL || baked->Size < closest_larger_match->Size)) + closest_larger_match = baked; + if (baked->Size < font_size && (closest_smaller_match == NULL || baked->Size > closest_smaller_match->Size)) + closest_smaller_match = baked; + } + if (closest_larger_match) + if (closest_smaller_match == NULL || (closest_larger_match->Size >= font_size * 2.0f && closest_smaller_match->Size > font_size * 0.5f)) + return closest_larger_match; + if (closest_smaller_match) + return closest_smaller_match; } - if (closest_larger_match) - if (closest_smaller_match == NULL || (closest_larger_match->Size >= font_size * 2.0f && closest_smaller_match->Size > font_size * 0.5f)) - return closest_larger_match; - if (closest_smaller_match) - return closest_smaller_match; return NULL; } @@ -5195,7 +5198,7 @@ ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterize } // ImFontBaked pointers are valid for the entire frame but shall never be kept between frames. -ImFontBaked* ImFont::GetFontBaked(float size) +ImFontBaked* ImFont::GetFontBaked(float size, float density) { ImFontBaked* baked = LastBaked; @@ -5203,12 +5206,14 @@ ImFontBaked* ImFont::GetFontBaked(float size) // - ImGui::PushFontSize() will already round, but other paths calling GetFontBaked() directly also needs it (e.g. ImFontAtlasBuildPreloadAllGlyphRanges) size = ImGui::GetRoundedFontSize(size); - if (baked && baked->Size == size && baked->RasterizerDensity == CurrentRasterizerDensity) + if (density < 0.0f) + density = CurrentRasterizerDensity; + if (baked && baked->Size == size && baked->RasterizerDensity == density) return baked; ImFontAtlas* atlas = ContainerAtlas; ImFontAtlasBuilder* builder = atlas->Builder; - baked = ImFontAtlasBakedGetOrAdd(atlas, this, size, CurrentRasterizerDensity); + baked = ImFontAtlasBakedGetOrAdd(atlas, this, size, density); if (baked == NULL) return NULL; baked->LastUsedFrame = builder->FrameCount; From 822903e56debba39530ecc3bebfdf7709737c217 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 16 May 2025 16:55:29 +0200 Subject: [PATCH 156/191] Fonts: fixed ImFontAtlas::RemoveFont() with multiple sources. Thanks cyfewlp! --- imgui_draw.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6306e9c75..3d719820f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3205,10 +3205,10 @@ void ImFontAtlas::RemoveFont(ImFont* font) ImFontAtlasFontDestroyOutput(this, font); for (ImFontConfig* src : font->Sources) - { ImFontAtlasFontDestroySourceData(this, src); - Sources.erase(src); - } + for (int src_n = 0; src_n < Sources.Size; src_n++) + if (Sources[src_n].DstFont == font) + Sources.erase(&Sources[src_n--]); bool removed = Fonts.find_erase(font); IM_ASSERT(removed); From 3d848a886a4a071e4b3157b08f37254a4f0d9166 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 19 May 2025 13:43:16 +0200 Subject: [PATCH 157/191] Fonts: fixed support for IMGUI_STB_NAMESPACE. --- imgui_internal.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 7c6305734..de68b9b3b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3746,13 +3746,21 @@ struct ImFontAtlasPostProcessData int Height; }; -// Internal storage for incrementally packing and building a ImFontAtlas -struct stbrp_context_opaque { char data[80]; }; +// We avoid dragging imstb_rectpack.h into public header (partly because binding generators are having issues with it) +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE { struct stbrp_node; } +typedef IMGUI_STB_NAMESPACE::stbrp_node stbrp_node_im; +#else struct stbrp_node; +typedef stbrp_node stbrp_node_im; +#endif +struct stbrp_context_opaque { char data[80]; }; + +// Internal storage for incrementally packing and building a ImFontAtlas struct ImFontAtlasBuilder { stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file. - ImVector PackNodes; + ImVector PackNodes; ImVector Rects; ImVector RectsIndex; // ImFontAtlasRectId -> index into Rects[] ImVector TempBuffer; // Misc scratch buffer From 92ff153763cc9f701d288a24957ffd552c172060 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 19 May 2025 14:03:45 +0200 Subject: [PATCH 158/191] Fonts: added notes/comments and dummy type about renaming ImFontBuilderIO::GetBuilderForFreeType() to ImFontLoader::GetFontLoader(). --- imgui_internal.h | 3 +++ misc/freetype/imgui_freetype.h | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index de68b9b3b..b1d8774ce 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3699,6 +3699,9 @@ struct ImFontLoader #ifdef IMGUI_ENABLE_STB_TRUETYPE IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); #endif +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +typedef ImFontLoader ImFontBuilderIO; // [renamed/changed in 1.92] The types are not actually compatible but we provide this as a compile-time error report helper. +#endif //----------------------------------------------------------------------------- // [SECTION] ImFontAtlas internal API diff --git a/misc/freetype/imgui_freetype.h b/misc/freetype/imgui_freetype.h index b4e9ba1fd..2b4810e32 100644 --- a/misc/freetype/imgui_freetype.h +++ b/misc/freetype/imgui_freetype.h @@ -6,7 +6,9 @@ #ifndef IMGUI_DISABLE // Usage: -// - Add '#define IMGUI_ENABLE_FREETYPE' in your imconfig to enable support for imgui_freetype in imgui. +// - Add '#define IMGUI_ENABLE_FREETYPE' in your imconfig to automatically enable support +// for imgui_freetype in imgui. It is equivalent to selecting the default loader with: +// io.Fonts.FontLoader = ImGuiFreeType::GetFontLoader() // Optional support for OpenType SVG fonts: // - Add '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG' to use plutosvg (not provided). See #7927. @@ -52,8 +54,9 @@ namespace ImGuiFreeType // Display UI to edit FontBuilderFlags in ImFontAtlas (shared) or ImFontConfig (single source) IMGUI_API bool DebugEditFontBuilderFlags(unsigned int* p_font_loader_flags); - // Obsolete names (will be removed soon) + // Obsolete names (will be removed) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); // Renamed/changed in 1.92. Change 'io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' to 'io.Fonts.FontLoader = ImGuiFreeType::GetFontLoader()' if you need runtime selection. //static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontBuilderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE' #endif } From f3780c73544466ebe10c6c31749d57d605210b48 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 19 May 2025 18:14:12 +0200 Subject: [PATCH 159/191] Fonts: adding GetFontBaked() in public API. --- imgui.cpp | 5 +++++ imgui.h | 5 +++-- imgui_internal.h | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 58455df8d..13cb78f0b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8419,6 +8419,11 @@ ImFont* ImGui::GetFont() return GImGui->Font; } +ImFontBaked* ImGui::GetFontBaked() +{ + return GImGui->FontBaked; +} + float ImGui::GetFontSize() { return GImGui->FontSize; diff --git a/imgui.h b/imgui.h index 0eb83cd9a..916ad7547 100644 --- a/imgui.h +++ b/imgui.h @@ -495,7 +495,7 @@ namespace ImGui // *IMPORTANT* before 1.92, fonts had a single size. They can now be dynamically be adjusted. // - Before 1.92: PushFont() always used font default size. // - Since 1.92: PushFont() preserve the current shared font size. - // - To use old behavior: use 'PushFont(font, font->DefaultSize)' in call site, or set 'ImFontConfig::Flags |= ImFontFlags_UseDefaultSize' before calling AddFont(). + // - To use old behavior (single size font): use 'PushFont(font, font->DefaultSize)' in call site, or set 'ImFontConfig::Flags |= ImFontFlags_UseDefaultSize' before calling AddFont(). IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. Use font->DefaultSize to revert to font default size. IMGUI_API void PopFont(); IMGUI_API void PushFontSize(float font_size); @@ -526,6 +526,7 @@ namespace ImGui IMGUI_API ImFont* GetFont(); // get current font IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a white pixel, useful to draw custom shapes via the ImDrawList API + IMGUI_API ImFontBaked* GetFontBaked(); // get current font bound at current size // == GetFont()->GetFontBaked(GetFontSize()) IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier, packed as a 32-bit value suitable for ImDrawList IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList IMGUI_API ImU32 GetColorU32(ImU32 col, float alpha_mul = 1.0f); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList @@ -3754,7 +3755,7 @@ struct ImFont // [Internal] Members: Cold ~24-52 bytes // Conceptually Sources[] is the list of font sources merged to create this font. ImGuiID FontId; // Unique identifier for the font - float DefaultSize; // 4 // in // Default font size + float DefaultSize; // 4 // in // Default font size passed to AddFont(). It's unlikely you should use this (use ImGui::GetFontBaked() to get font baked at current bound size). ImVector Sources; // 16 // in // List of sources. Pointers within ContainerAtlas->Sources[] ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') diff --git a/imgui_internal.h b/imgui_internal.h index b1d8774ce..b4ddf4175 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2136,9 +2136,9 @@ struct ImGuiContext ImGuiPlatformIO PlatformIO; ImGuiStyle Style; ImVector FontAtlases; // List of font atlases used by the context (generally only contains g.IO.Fonts aka the main font atlas) - ImFont* Font; // == FontStack.back().Font - ImFontBaked* FontBaked; // == Font->GetFontBaked(FontSize) - float FontSize; // == FontSizeBeforeScaling * io.FontGlobalScale * font->Scale * g.CurrentWindow->FontWindowScale. Current text height. + ImFont* Font; // Currently bound font. (== FontStack.back().Font) + ImFontBaked* FontBaked; // Currently bound font at currently bound size. (== Font->GetFontBaked(FontSize)) + float FontSize; // Currently bound font size == line height (== FontSizeBeforeScaling * io.FontGlobalScale * font->Scale * g.CurrentWindow->FontWindowScale). float FontSizeBeforeScaling; // == value passed to PushFontSize() float FontScale; // == FontBaked->Size / Font->FontSize. Scale factor over baked size. float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). From 83aad812798ff8a0cd7f583f07a4adc6d9b809a7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 20 May 2025 18:12:08 +0200 Subject: [PATCH 160/191] Fonts: comments + made IMGUI_DEBUG_LOG_FONT() work without an ImGui context. --- imgui.h | 5 ++++- imgui_internal.h | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index 916ad7547..0e4c73915 100644 --- a/imgui.h +++ b/imgui.h @@ -350,6 +350,8 @@ typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or // - If you want to bind the current atlas when using custom rectangle, you can use io.Fonts->TexRef. // - Binding generators for languages such as C (which don't have constructors), should provide a helper: // inline ImTextureRef ImTextureRefFromID(ImTextureID tex_id) { ImTextureRef tex_ref = { ._TexData = NULL, .TexID = tex_id }; return tex_ref; } +// In 1.92 we changed most drawing functions using ImTextureID to use ImTextureRef. +// We intentionally do not provide an implicit ImTextureRef -> ImTextureID cast operator because it is technically lossy to convert ImTextureRef to ImTextureID before rendering. IM_MSVC_RUNTIME_CHECKS_OFF struct ImTextureRef { @@ -358,7 +360,8 @@ struct ImTextureRef #if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(ImTextureID) ImTextureRef(void* tex_id) { _TexData = NULL; _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID #endif - inline ImTextureID GetTexID() const; // == (_TexData ? _TexData->TexID : _TexID) // Implemented below in the file. + + inline ImTextureID GetTexID() const; // == (_TexData ? _TexData->TexID : _TexID) // Implemented below in the file. // Members (either are set, never both!) ImTextureData* _TexData; // A texture, generally owned by a ImFontAtlas. Will convert to ImTextureID during render loop, after texture has been uploaded. diff --git a/imgui_internal.h b/imgui_internal.h index b4ddf4175..55ca3ba89 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -246,7 +246,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) -#define IMGUI_DEBUG_LOG_FONT(...) do { ImGuiContext& g2 = *GImGui; if (g2.DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_FONT(...) do { ImGuiContext* g2 = GImGui; if (g2 && g2->DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Called from ImFontAtlas function which may operate without a context. #define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Static Asserts From b2343d62474e87b241c6cd2e27464142bb2dd2ba Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 20 May 2025 19:56:27 +0200 Subject: [PATCH 161/191] Fonts: fallback to default default rasterizer density + pick one from existing viewports at the time of calling AddUpdateViewport(). # Conflicts: # imgui.cpp --- imgui.cpp | 6 +++++- imgui_draw.cpp | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 13cb78f0b..d869da7fe 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4399,7 +4399,10 @@ static void SetCurrentWindow(ImGuiWindow* window) if (window) { if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) - g.FontRasterizerDensity = window->Viewport->FramebufferScale.x; // == SetFontRasterizerDensity() + { + ImGuiViewport* viewport = window->Viewport; + g.FontRasterizerDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetFontRasterizerDensity() + } ImGui::UpdateCurrentFontSize(); ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } @@ -15249,6 +15252,7 @@ static void ImGui::UpdateViewportsNewFrame() main_viewport->Pos = ImVec2(0.0f, 0.0f); main_viewport->Size = g.IO.DisplaySize; main_viewport->FramebufferScale = g.IO.DisplayFramebufferScale; + IM_ASSERT(main_viewport->FramebufferScale.x > 0.0f && main_viewport->FramebufferScale.y > 0.0f); for (ImGuiViewportP* viewport : g.Viewports) { diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3d719820f..93f5fdf8d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5225,6 +5225,7 @@ ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float fo { // FIXME-NEWATLAS: Design for picking a nearest size based on some criteria? // FIXME-NEWATLAS: Altering font density won't work right away. + IM_ASSERT(font_size > 0.0f && font_rasterizer_density > 0.0f); ImGuiID baked_id = ImFontAtlasBakedGetId(font->FontId, font_size, font_rasterizer_density); ImFontAtlasBuilder* builder = atlas->Builder; ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); From 9f8b4bdaf1bc7565ebd9e0f762048d7754868ff3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 21 May 2025 20:35:04 +0200 Subject: [PATCH 162/191] Fonts: fixed edge case calling RenderText() without priming with CalcTextSize(). --- imgui_draw.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 93f5fdf8d..e84de2baa 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5502,6 +5502,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) { // Align to be pixel perfect +begin: float x = IM_TRUNC(pos.x); float y = IM_TRUNC(pos.y); if (y > clip_rect.w) @@ -5556,6 +5557,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im return; // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) + const int cmd_count = draw_list->CmdBuffer.Size; const int vtx_count_max = (int)(text_end - s) * 4; const int idx_count_max = (int)(text_end - s) * 6; const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; @@ -5678,6 +5680,18 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im x += char_width; } + // Edge case: calling RenderText() with unloaded glyphs triggering texture change. It doesn't happen via ImGui:: calls because CalcTextSize() is always used. + if (cmd_count != draw_list->CmdBuffer.Size) + { + IM_ASSERT(draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount == 0); + draw_list->CmdBuffer.pop_back(); + draw_list->PrimUnreserve(idx_count_max, vtx_count_max); + draw_list->AddDrawCmd(); + goto begin; + //RenderText(draw_list, size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip); // FIXME-OPT: Would a 'goto begin' be better for code-gen? + //return; + } + // Give back unused vertices (clipped ones, blanks) ~ this is essentially a PrimUnreserve() action. draw_list->VtxBuffer.Size = (int)(vtx_write - draw_list->VtxBuffer.Data); // Same as calling shrink() draw_list->IdxBuffer.Size = (int)(idx_write - draw_list->IdxBuffer.Data); From 5926c877a115d626260f60fc4d80fdb46e92c3ec Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 22 May 2025 16:32:46 +0200 Subject: [PATCH 163/191] Fonts: detect if ImFontAtlasUpdateNewFrame() is not being called. --- imgui.cpp | 12 ++++++++++-- imgui_draw.cpp | 7 +++++-- imgui_internal.h | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d869da7fe..f5c4330e8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5211,16 +5211,24 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos) io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } -// FIXME-NEWATLAS-V2: If we aim to support multiple atlases used by same context: how to reach/target all atlases? static void ImGui::UpdateTexturesNewFrame() { + // Cannot update every atlases based on atlas's FrameCount < g.FrameCount, because an atlas may be shared by multiple contexts with different frame count. ImGuiContext& g = *GImGui; + const bool has_textures = (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; for (ImFontAtlas* atlas : g.FontAtlases) + { if (atlas->OwnerContext == &g) { - atlas->RendererHasTextures = (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + atlas->RendererHasTextures = has_textures; ImFontAtlasUpdateNewFrame(atlas, g.FrameCount); } + else + { + IM_ASSERT(atlas->Builder != NULL && atlas->Builder->FrameCount != -1 && "If you manage font atlases yourself you need to call ImFontAtlasUpdateNewFrame() on it."); + IM_ASSERT(atlas->RendererHasTextures == has_textures && "If you manage font atlases yourself make sure atlas->RendererHasTextures is set consistently with all contexts using it."); + } + } } // Build a single texture list diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e84de2baa..e0d34c977 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2715,11 +2715,14 @@ static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* at } } -// Called by NewFrame(). When multiple context own the atlas, only the first one calls this. -// If you are calling this yourself, ensure atlas->RendererHasTextures is set. +// Called by NewFrame() for atlases owned by a context. +// If you manually manage font atlases, you'll need to call this yourself + ensure atlas->RendererHasTextures is set. // 'frame_count' needs to be provided because we can gc/prioritize baked fonts based on their age. +// 'frame_count' may not match those of imgui contexts using this atlas, as contexts may be updated as different frequencies. void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count) { + IM_ASSERT(atlas->Builder == NULL || atlas->Builder->FrameCount < frame_count); // Protection against being called twice? + // Check that font atlas was built or backend support texture reload in which case we can build now if (atlas->RendererHasTextures) { diff --git a/imgui_internal.h b/imgui_internal.h index 55ca3ba89..62b98f1f0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3787,7 +3787,7 @@ struct ImFontAtlasBuilder ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. ImFontAtlasRectId PackIdLinesTexData; - ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } + ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } }; IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); From 25f9c318e315e5275ca0993adf3b632574ee490c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 22 May 2025 17:45:22 +0200 Subject: [PATCH 164/191] Fonts: added "Input Glyphs Overlap Detection Tool". Added "Clear bakes", "Clear unused" buttons. Move code. --- imgui.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ imgui.h | 2 +- imgui_draw.cpp | 15 +++++++++----- imgui_internal.h | 2 +- 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f5c4330e8..9b9874a5a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16699,6 +16699,21 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co out_draw_list->Flags = backup_flags; } +// [DEBUG] Compute mask of inputs with the same codepoint. +static int CalcFontGlyphSrcOverlapMask(ImFontAtlas* atlas, ImFont* font, unsigned int codepoint) +{ + int mask = 0, count = 0; + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + { + ImFontConfig* src = font->Sources[src_n]; + if (!(src->FontLoader ? src->FontLoader : atlas->FontLoader)->FontSrcContainsGlyph(atlas, src, (ImWchar)codepoint)) + continue; + mask |= (1 << src_n); + count++; + } + return count > 1 ? mask : 0; +} + // [DEBUG] Display details for a single font, called by ShowStyleEditor(). void ImGui::DebugNodeFont(ImFont* font) { @@ -16730,6 +16745,12 @@ void ImGui::DebugNodeFont(ImFont* font) if (SmallButton("Remove")) atlas->RemoveFont(font); EndDisabled(); + SameLine(); + if (SmallButton("Clear bakes")) + ImFontAtlasFontDiscardBakes(atlas, font, 0); + SameLine(); + if (SmallButton("Clear unused")) + ImFontAtlasFontDiscardBakes(atlas, font, 2); // Display details SetNextItemWidth(GetFontSize() * 8); @@ -16769,6 +16790,37 @@ void ImGui::DebugNodeFont(ImFont* font) TreePop(); } } + if (font->Sources.Size > 1 && TreeNode("Input Glyphs Overlap Detection Tool")) + { + TextWrapped("- First Input that contains the glyph is used.\n- Use ImFontConfig::GlyphExcludeRanges[] to specify ranges to ignore glyph in given Input.\n- This tool doesn't cache results and is slow, don't keep it open!"); + if (BeginTable("table", 2)) + { + for (unsigned int c = 0; c < 0x10000; c++) + if (int overlap_mask = CalcFontGlyphSrcOverlapMask(atlas, font, c)) + { + unsigned int c_end = c + 1; + while (c_end < 0x10000 && CalcFontGlyphSrcOverlapMask(atlas, font, c_end) == overlap_mask) + c_end++; + if (TableNextColumn() && TreeNode((void*)(intptr_t)c, "U+%04X-U+%04X: %d codepoints in %d inputs", c, c_end - 1, c_end - c, ImCountSetBits(overlap_mask))) + { + char utf8_buf[5]; + for (unsigned int n = c; n < c_end; n++) + BulletText("Codepoint U+%04X (%s)", n, ImTextCharToUtf8(utf8_buf, n)); + TreePop(); + } + TableNextColumn(); + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + if (overlap_mask & (1 << src_n)) + { + Text("%d ", src_n); + SameLine(); + } + c = c_end - 1; + } + EndTable(); + } + TreePop(); + } // Display all glyphs of the fonts in separate pages of 256 characters for (int baked_n = 0; baked_n < atlas->Builder->BakedPool.Size; baked_n++) diff --git a/imgui.h b/imgui.h index 0e4c73915..8ed8f2318 100644 --- a/imgui.h +++ b/imgui.h @@ -3456,7 +3456,7 @@ struct ImFontConfig //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. ImVec2 GlyphOffset; // 0, 0 // Offset (in pixels) all glyphs from this font input. Absolute value for default size, other sizes will scale this value. const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). - const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a VERY SHORT user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. + const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a VERY SHORT user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. Use "Input Glyphs Overlap Detection Tool" to find about your overlapping ranges. float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e0d34c977..b2380dfc0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3843,14 +3843,18 @@ void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* bake font->LastBaked = NULL; } -void ImFontAtlasFontDiscardOutputBakes(ImFontAtlas* atlas, ImFont* font) +// use unused_frames==0 to discard everything. +void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames) { if (ImFontAtlasBuilder* builder = atlas->Builder) // This can be called from font destructor for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) { ImFontBaked* baked = &builder->BakedPool[baked_n]; - if (baked->ContainerFont == font && !baked->WantDestroy) - ImFontAtlasBakedDiscard(atlas, font, baked); + if (baked->LastUsedFrame + unused_frames > atlas->Builder->FrameCount) + continue; + if (baked->ContainerFont != font || baked->WantDestroy) + continue; + ImFontAtlasBakedDiscard(atlas, font, baked); } } @@ -4364,7 +4368,8 @@ ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId return &builder->Rects[index_entry->TargetIndex]; } -// Important! This assume by ImFontConfig::GlyphFilter is a SMALL ARRAY (e.g. <10 entries) +// Important! This assume by ImFontConfig::GlyphExcludeRanges[] is a SMALL ARRAY (e.g. <10 entries) +// Use "Input Glyphs Overlap Detection Tool" to display a list of glyphs provided by multiple sources in order to set this array up. static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint) { if (const ImWchar* exclude_list = src->GlyphExcludeRanges) @@ -5024,7 +5029,7 @@ ImFont::~ImFont() void ImFont::ClearOutputData() { if (ImFontAtlas* atlas = ContainerAtlas) - ImFontAtlasFontDiscardOutputBakes(atlas, this); + ImFontAtlasFontDiscardBakes(atlas, this, 0); FallbackChar = EllipsisChar = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); LastBaked = NULL; diff --git a/imgui_internal.h b/imgui_internal.h index 62b98f1f0..59666f3e5 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3815,7 +3815,7 @@ IMGUI_API void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, I IMGUI_API void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font); // Using FontDestroyOutput/FontInitOutput sequence useful notably if font loader params have changed IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font); -IMGUI_API void ImFontAtlasFontDiscardOutputBakes(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames); IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density); IMGUI_API ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density); From e3860aa6ac0be98df8df0aeaa194107a27f5f4d4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 26 May 2025 13:52:20 +0200 Subject: [PATCH 165/191] (Breaking) Fonts: removing obsolete ImFont::Scale. --- imgui.cpp | 13 ++++++++++--- imgui.h | 4 +++- imgui_draw.cpp | 2 ++ imgui_internal.h | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 9b9874a5a..79d36db59 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8683,7 +8683,9 @@ void ImGui::SetCurrentFont(ImFont* font, float font_size) if (font != NULL) { IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS IM_ASSERT(font->Scale > 0.0f); +#endif g.DrawListSharedData.Font = g.Font; ImFontAtlasUpdateDrawListsSharedData(g.Font->ContainerAtlas); if (g.CurrentWindow != NULL) @@ -8699,7 +8701,10 @@ void ImGui::UpdateCurrentFontSize() return; float final_size = g.FontSizeBeforeScaling * g.IO.FontGlobalScale; - final_size *= g.Font->Scale; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.Font != NULL) + final_size *= g.Font->Scale; +#endif if (window != NULL) final_size *= window->FontWindowScale; @@ -16753,14 +16758,16 @@ void ImGui::DebugNodeFont(ImFont* font) ImFontAtlasFontDiscardBakes(atlas, font, 2); // Display details +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS SetNextItemWidth(GetFontSize() * 8); DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); - SameLine(); MetricsHelpMarker( + /*SameLine(); MetricsHelpMarker( "Note that the default embedded font is NOT meant to be scaled.\n\n" "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " "You may oversample them to get some flexibility with scaling. " "You can also render at multiple sizes and select which one to use at runtime.\n\n" - "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); + "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)");*/ +#endif char c_str[5]; Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar); diff --git a/imgui.h b/imgui.h index 8ed8f2318..9a9473760 100644 --- a/imgui.h +++ b/imgui.h @@ -3762,10 +3762,12 @@ struct ImFont ImVector Sources; // 16 // in // List of sources. Pointers within ContainerAtlas->Sources[] ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') - float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated. ImGuiStorage RemapPairs; // 16 // // Remapping pairs when using AddRemapChar(), otherwise empty. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + float Scale; // 4 // in // Legacy base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() +#endif // Methods IMGUI_API ImFont(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b2380dfc0..7e5b10f34 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5018,7 +5018,9 @@ void ImFontBaked::ClearOutputData() ImFont::ImFont() { memset(this, 0, sizeof(*this)); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS Scale = 1.0f; +#endif } ImFont::~ImFont() diff --git a/imgui_internal.h b/imgui_internal.h index 59666f3e5..5dacf666e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2139,7 +2139,7 @@ struct ImGuiContext ImFont* Font; // Currently bound font. (== FontStack.back().Font) ImFontBaked* FontBaked; // Currently bound font at currently bound size. (== Font->GetFontBaked(FontSize)) float FontSize; // Currently bound font size == line height (== FontSizeBeforeScaling * io.FontGlobalScale * font->Scale * g.CurrentWindow->FontWindowScale). - float FontSizeBeforeScaling; // == value passed to PushFontSize() + float FontSizeBeforeScaling; // == value passed to PushFont() / PushFontSize() when specified. float FontScale; // == FontBaked->Size / Font->FontSize. Scale factor over baked size. float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale From 69547bd4bdfb50841548b18574809d14dc83f05e Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 26 May 2025 14:54:26 +0200 Subject: [PATCH 166/191] Fonts: ImFont::DefaultSize -> ImFont::LegacySize. ImFontFlags_UseDefaultSize -> ImFontFlags_DefaultToLegacySize. --- imgui.cpp | 6 +++--- imgui.h | 10 +++++----- imgui_draw.cpp | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 79d36db59..0a637be0e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8620,7 +8620,7 @@ void ImGui::UpdateFontsNewFrame() atlas->Locked = true; // We do this really unusual thing of calling *push_front()*, the reason behind that we want to support the PushFont()/NewFrame()/PopFont() idiom. - ImFontStackData font_stack_data = { ImGui::GetDefaultFont(), ImGui::GetDefaultFont()->DefaultSize }; + ImFontStackData font_stack_data = { ImGui::GetDefaultFont(), ImGui::GetDefaultFont()->LegacySize }; g.FontStack.push_front(font_stack_data); if (g.FontStack.Size == 1) ImGui::SetCurrentFont(font_stack_data.Font, font_stack_data.FontSize); @@ -8741,8 +8741,8 @@ void ImGui::PushFont(ImFont* font, float font_size) font = GetDefaultFont(); if (font_size <= 0.0f) { - if (font->Flags & ImFontFlags_UseDefaultSize) - font_size = font->DefaultSize; // Legacy: use default font size. Same as doing PushFont(font, font->DefaultSize). // FIXME-NEWATLAS + if (font->Flags & ImFontFlags_DefaultToLegacySize) + font_size = font->LegacySize; // Legacy: use AddFont() specified font size. Same as doing PushFont(font, font->LegacySize) else font_size = g.FontSizeBeforeScaling; // Keep current font size } diff --git a/imgui.h b/imgui.h index 9a9473760..a10e8d1a3 100644 --- a/imgui.h +++ b/imgui.h @@ -498,8 +498,8 @@ namespace ImGui // *IMPORTANT* before 1.92, fonts had a single size. They can now be dynamically be adjusted. // - Before 1.92: PushFont() always used font default size. // - Since 1.92: PushFont() preserve the current shared font size. - // - To use old behavior (single size font): use 'PushFont(font, font->DefaultSize)' in call site, or set 'ImFontConfig::Flags |= ImFontFlags_UseDefaultSize' before calling AddFont(). - IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. Use font->DefaultSize to revert to font default size. + // - To use old behavior (single size font): use 'PushFont(font, font->LegacySize)' in call site, or set 'ImFontConfig::Flags |= ImFontFlags_DefaultToLegacySize' before calling AddFont(). + IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. Use font->LegacySize to revert to font size specified by AddFont(). IMGUI_API void PopFont(); IMGUI_API void PushFontSize(float font_size); IMGUI_API void PopFontSize(); @@ -3736,7 +3736,7 @@ struct ImFontBaked enum ImFontFlags_ { ImFontFlags_None = 0, - ImFontFlags_UseDefaultSize = 1 << 0, // Legacy compatibility: make PushFont() calls without explicit size use font->DefaultSize instead of current font size. + ImFontFlags_DefaultToLegacySize = 1 << 0, // Legacy compatibility: make PushFont() calls without explicit size use font->LegacySize instead of current font size. ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs. ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display. @@ -3758,7 +3758,7 @@ struct ImFont // [Internal] Members: Cold ~24-52 bytes // Conceptually Sources[] is the list of font sources merged to create this font. ImGuiID FontId; // Unique identifier for the font - float DefaultSize; // 4 // in // Default font size passed to AddFont(). It's unlikely you should use this (use ImGui::GetFontBaked() to get font baked at current bound size). + float LegacySize; // 4 // in // Font size passed to AddFont(). Use for old code calling PushFont() expecting to use that size. (use ImGui::GetFontBaked() to get font baked at current bound size). ImVector Sources; // 16 // in // List of sources. Pointers within ContainerAtlas->Sources[] ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') @@ -3785,7 +3785,7 @@ struct ImFont IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip = NULL); IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(DefaultSize * scale, text, text_end, wrap_width); } + inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(LegacySize * scale, text, text_end, wrap_width); } #endif // [Internal] Don't use! diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7e5b10f34..e067d57ba 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3004,7 +3004,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) font = IM_NEW(ImFont)(); font->FontId = FontNextUniqueID++; font->Flags = font_cfg_in->Flags; - font->DefaultSize = font_cfg_in->SizePixels; + font->LegacySize = font_cfg_in->SizePixels; font->CurrentRasterizerDensity = font_cfg_in->RasterizerDensity; Fonts.push_back(font); } @@ -3266,7 +3266,7 @@ void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id) // myfont->Flags |= ImFontFlags_LockBakedSizes; ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { - float font_size = font->DefaultSize; + float font_size = font->LegacySize; return AddCustomRectFontGlyphForSize(font, font_size, codepoint, width, height, advance_x, offset); } // FIXME: we automatically set glyph.Colored=true by default. From 033cdc41338e3b5947ac85ec57a997d964e31abb Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 26 May 2025 15:08:54 +0200 Subject: [PATCH 167/191] Fonts: comments and slight packing of ImFontConfig fields. --- imgui.cpp | 7 ++----- imgui.h | 17 ++++++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 0a637be0e..38f0cf096 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15914,11 +15914,8 @@ void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRe PopStyleVar(); char texid_desc[30]; - Text("Status = %s (%d)", ImTextureDataGetStatusName(tex->Status), tex->Status); - Text("Format = %s (%d)", ImTextureDataGetFormatName(tex->Format), tex->Format); - Text("TexID = %s", FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->TexID)); - Text("BackendUserData = %p", tex->BackendUserData); - Text("UseColors = %d", tex->UseColors); + Text("Status = %s (%d), Format = %s (%d), UseColors = %d", ImTextureDataGetStatusName(tex->Status), tex->Status, ImTextureDataGetFormatName(tex->Format), tex->Format, tex->UseColors); + Text("TexID = %s, BackendUserData = %p", FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->TexID), tex->BackendUserData); TreePop(); } PopID(); diff --git a/imgui.h b/imgui.h index a10e8d1a3..44aa078ae 100644 --- a/imgui.h +++ b/imgui.h @@ -3443,20 +3443,24 @@ struct ImTextureData // A font input/source (we may rename this to ImFontSource in the future) struct ImFontConfig { + // Data Source + char Name[40]; // // Name (strictly to ease debugging, hence limited size buffer) void* FontData; // // TTF/OTF data int FontDataSize; // // TTF/OTF data size bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + + // Options bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. bool PixelSnapV; // true // Align Scaled GlyphOffset.y to pixel boundaries. - int FontNo; // 0 // Index of font within TTF/OTF file - int OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. - int OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. + ImS8 FontNo; // 0 // Index of font within TTF/OTF file + ImS8 OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. + ImS8 OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). + const ImWchar* GlyphRanges; // NULL // *LEGACY* THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). + const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a VERY SHORT user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. Use "Input Glyphs Overlap Detection Tool" to find about your overlapping ranges. //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. ImVec2 GlyphOffset; // 0, 0 // Offset (in pixels) all glyphs from this font input. Absolute value for default size, other sizes will scale this value. - const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). - const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a VERY SHORT user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. Use "Input Glyphs Overlap Detection Tool" to find about your overlapping ranges. float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. @@ -3466,8 +3470,7 @@ struct ImFontConfig ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. // [Internal] - char Name[40]; // Name (strictly to ease debugging) - ImFontFlags Flags; // Font flags (don't use just yet) + ImFontFlags Flags; // Font flags (don't use just yet, will be exposed in upcoming 1.92.X updates) ImFont* DstFont; // Target font (as we merging fonts, multiple ImFontConfig may target the same font) const ImFontLoader* FontLoader; // Custom font backend for this source (other use one stored in ImFontAtlas) void* FontLoaderData; // Font loader opaque storage (per font config) From b029be6b6c251e4bb647b9b5c60b239b38c93a9c Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 26 May 2025 18:55:55 +0200 Subject: [PATCH 168/191] Fonts: avoid calling GetFontBaked() during SetFontSize(). Also fixes loading extraneous baked on atlas that will be locked e.g. PushFontSize() before NewFrame() on legacy backend. --- imgui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 38f0cf096..7b4fc8638 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8715,9 +8715,9 @@ void ImGui::UpdateCurrentFontSize() final_size = ImMax(1.0f, final_size); if (g.Font != NULL && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; - g.FontBaked = (g.Font != NULL) ? g.Font->GetFontBaked(final_size) : NULL; g.FontSize = final_size; - g.FontScale = (g.Font != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; + g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL; + g.FontScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; g.DrawListSharedData.FontSize = g.FontSize; g.DrawListSharedData.FontScale = g.FontScale; } From 1e118ab8911625bf49938dd5d29c2833af216096 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 26 May 2025 20:24:06 +0200 Subject: [PATCH 169/191] Fonts: added ImGuiStyle::FontSizeBase. Ensuring PushFontSize() works before main loop and across NewFrame(). # Conflicts: # imgui.cpp --- imgui.cpp | 106 +++++++++++++++++++++++++++++++++-------------- imgui.h | 20 +++++++-- imgui_demo.cpp | 44 +++++++++++++------- imgui_draw.cpp | 2 +- imgui_internal.h | 9 ++-- 5 files changed, 125 insertions(+), 56 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7b4fc8638..d18beab96 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1354,6 +1354,7 @@ static void* GImAllocatorUserData = NULL; ImGuiStyle::ImGuiStyle() { + FontSizeBase = 0.0f; // Will default to io.Fonts->Fonts[0] on first frame. Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. WindowPadding = ImVec2(8,8); // Padding within a window @@ -1415,6 +1416,10 @@ ImGuiStyle::ImGuiStyle() HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. HoverFlagsForTooltipNav = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. + // [Internal] + _MainScale = 1.0f; + _NextFrameFontSizeBase = 0.0f; + // Default theme ImGui::StyleColorsDark(this); } @@ -1423,6 +1428,8 @@ ImGuiStyle::ImGuiStyle() // Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. void ImGuiStyle::ScaleAllSizes(float scale_factor) { + _MainScale *= scale_factor; + FontSizeBase = ImTrunc(FontSizeBase * scale_factor); WindowPadding = ImTrunc(WindowPadding * scale_factor); WindowRounding = ImTrunc(WindowRounding * scale_factor); WindowMinSize = ImTrunc(WindowMinSize * scale_factor); @@ -4403,7 +4410,7 @@ static void SetCurrentWindow(ImGuiWindow* window) ImGuiViewport* viewport = window->Viewport; g.FontRasterizerDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetFontRasterizerDensity() } - ImGui::UpdateCurrentFontSize(); + ImGui::UpdateCurrentFontSize(0.0f); ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } } @@ -8450,7 +8457,7 @@ void ImGui::SetWindowFontScale(float scale) IM_ASSERT(scale > 0.0f); ImGuiWindow* window = GetCurrentWindow(); window->FontWindowScale = scale; - UpdateCurrentFontSize(); + UpdateCurrentFontSize(0.0f); } void ImGui::PushFocusScope(ImGuiID id) @@ -8619,12 +8626,24 @@ void ImGui::UpdateFontsNewFrame() for (ImFontAtlas* atlas : g.FontAtlases) atlas->Locked = true; - // We do this really unusual thing of calling *push_front()*, the reason behind that we want to support the PushFont()/NewFrame()/PopFont() idiom. - ImFontStackData font_stack_data = { ImGui::GetDefaultFont(), ImGui::GetDefaultFont()->LegacySize }; - g.FontStack.push_front(font_stack_data); - if (g.FontStack.Size == 1) - ImGui::SetCurrentFont(font_stack_data.Font, font_stack_data.FontSize); + if (g.Style._NextFrameFontSizeBase != 0.0f) + { + g.Style.FontSizeBase = g.Style._NextFrameFontSizeBase; + g.Style._NextFrameFontSizeBase = 0.0f; + } + // Apply default font size the first time + ImFont* font = ImGui::GetDefaultFont(); + if (g.Style.FontSizeBase <= 0.0f) + g.Style.FontSizeBase = font->LegacySize * g.Style._MainScale; + + // Set initial font + g.Font = font; + g.FontSizeBeforeScaling = g.Style.FontSizeBase; + g.FontSize = 0.0f; + ImFontStackData font_stack_data = { font, g.Style.FontSizeBase, g.Style.FontSizeBase }; // <--- Will restore FontSize + SetCurrentFont(font_stack_data.Font, font_stack_data.FontSizeBeforeScaling, 0.0f); // <--- but use 0.0f to enable scale + g.FontStack.push_back(font_stack_data); IM_ASSERT(g.Font->IsLoaded()); } @@ -8633,6 +8652,15 @@ void ImGui::UpdateFontsEndFrame() PopFont(); } +ImFont* ImGui::GetDefaultFont() +{ + ImGuiContext& g = *GImGui; + ImFontAtlas* atlas = g.IO.Fonts; + if (atlas->Builder == NULL) + ImFontAtlasBuildMain(atlas); + return g.IO.FontDefault ? g.IO.FontDefault : atlas->Fonts[0]; +} + void ImGui::RegisterUserTexture(ImTextureData* tex) { ImGuiContext& g = *GImGui; @@ -8673,12 +8701,12 @@ void ImGui::UnregisterFontAtlas(ImFontAtlas* atlas) // the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem // because we have a concrete need and a test bed for multiple atlas textures. // FIXME-NEWATLAS-V2: perhaps we can now leverage ImFontAtlasUpdateDrawListsTextures() ? -void ImGui::SetCurrentFont(ImFont* font, float font_size) +void ImGui::SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling) { ImGuiContext& g = *GImGui; g.Font = font; - g.FontSizeBeforeScaling = font_size; - UpdateCurrentFontSize(); + g.FontSizeBeforeScaling = font_size_before_scaling; + UpdateCurrentFontSize(font_size_after_scaling); if (font != NULL) { @@ -8693,20 +8721,27 @@ void ImGui::SetCurrentFont(ImFont* font, float font_size) } } -void ImGui::UpdateCurrentFontSize() +void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + + g.Style.FontSizeBase = g.FontSizeBeforeScaling; if (window != NULL && window->SkipItems) return; - float final_size = g.FontSizeBeforeScaling * g.IO.FontGlobalScale; + // Restoring is pretty much only used by PopFont()/PopFontSize() + float final_size = (restore_font_size_after_scaling > 0.0f) ? restore_font_size_after_scaling : 0.0f; + if (final_size == 0.0f) + { + final_size = g.FontSizeBeforeScaling * g.IO.FontGlobalScale; #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - if (g.Font != NULL) - final_size *= g.Font->Scale; + if (g.Font != NULL) + final_size *= g.Font->Scale; #endif - if (window != NULL) - final_size *= window->FontWindowScale; + if (window != NULL) + final_size *= window->FontWindowScale; + } // Round font size // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. @@ -8731,12 +8766,16 @@ void ImGui::SetFontRasterizerDensity(float rasterizer_density) if (g.FontRasterizerDensity == rasterizer_density) return; g.FontRasterizerDensity = rasterizer_density; - UpdateCurrentFontSize(); + UpdateCurrentFontSize(0.0f); } +// If you want to scale an existing font size: +// - Use e.g. PushFontSize(style.FontSizeBase * factor) (= value before external scale factors applied). +// - Do NOT use PushFontSize(GetFontSize() * factor) (= value after external scale factors applied). void ImGui::PushFont(ImFont* font, float font_size) { ImGuiContext& g = *GImGui; + g.FontStack.push_back({ g.Font, g.FontSizeBeforeScaling, g.FontSize }); if (font == NULL) font = GetDefaultFont(); if (font_size <= 0.0f) @@ -8746,23 +8785,20 @@ void ImGui::PushFont(ImFont* font, float font_size) else font_size = g.FontSizeBeforeScaling; // Keep current font size } - g.FontStack.push_back({ font, font_size }); - SetCurrentFont(font, font_size); + SetCurrentFont(font, font_size, 0.0f); } void ImGui::PopFont() { ImGuiContext& g = *GImGui; - if (g.FontStack.Size <= 1 && g.WithinFrameScope) + if (g.FontStack.Size <= 0 && g.WithinFrameScope) { IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); return; } + ImFontStackData* font_stack_data = &g.FontStack.back(); + SetCurrentFont(font_stack_data->Font, font_stack_data->FontSizeBeforeScaling, font_stack_data->FontSizeAfterScaling); g.FontStack.pop_back(); - if (ImFontStackData* font_stack_data = (g.FontStack.Size > 0) ? &g.FontStack.back() : NULL) - SetCurrentFont(font_stack_data->Font, font_stack_data->FontSize); - else - SetCurrentFont(NULL, 0.0f); } void ImGui::PushFontSize(float font_size) @@ -15751,10 +15787,11 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + ImGuiStyle& style = g.Style; Text("Read "); SameLine(0, 0); TextLinkOpenURL("https://www.dearimgui.com/faq/"); SameLine(0, 0); - Text(" for details on font loading."); + Text(" for details."); SeparatorText("Backend Support for Dynamic Fonts"); BeginDisabled(); @@ -15762,11 +15799,16 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) EndDisabled(); BeginDisabled((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); - SetNextItemWidth(GetFontSize() * 5); - DragFloat("io.FontGlobalScale", &io.FontGlobalScale, 0.05f, 0.5f, 5.0f); - BulletText("This is scaling font only. General scaling will come later."); - BulletText("Load an actual font that's not the default for best result!"); - BulletText("Please submit feedback:"); SameLine(); TextLinkOpenURL("https://github.com/ocornut/imgui/issues/8465"); + SetNextItemWidth(GetFontSize() * 10); + if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) + style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. + SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); + SameLine(); MetricsHelpMarker("- This is scaling font only. General scaling will come later."); + SetNextItemWidth(GetFontSize() * 10); + DragFloat("io.FontGlobalScale", &io.FontGlobalScale, 0.05f, 0.5f, 5.0f); // <-- This works, but no need to make it too visible. + BulletText("Load a nice font for better results!"); + BulletText("Please submit feedback:"); + SameLine(); TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); EndDisabled(); SeparatorText("Fonts"); @@ -15786,7 +15828,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) #else BeginDisabled(); RadioButton("stb_truetype", false); - SetItemTooltip("Requires IMGUI_ENABLE_STB_TRUETYPE"); + SetItemTooltip("Requires #define IMGUI_ENABLE_STB_TRUETYPE"); EndDisabled(); #endif SameLine(); @@ -15810,7 +15852,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) #else BeginDisabled(); RadioButton("FreeType", false); - SetItemTooltip("Requires IMGUI_ENABLE_FREETYPE + imgui_freetype.cpp."); + SetItemTooltip("Requires #define IMGUI_ENABLE_FREETYPE + imgui_freetype.cpp."); EndDisabled(); #endif EndDisabled(); diff --git a/imgui.h b/imgui.h index 44aa078ae..d2bc7ce77 100644 --- a/imgui.h +++ b/imgui.h @@ -498,8 +498,13 @@ namespace ImGui // *IMPORTANT* before 1.92, fonts had a single size. They can now be dynamically be adjusted. // - Before 1.92: PushFont() always used font default size. // - Since 1.92: PushFont() preserve the current shared font size. - // - To use old behavior (single size font): use 'PushFont(font, font->LegacySize)' in call site, or set 'ImFontConfig::Flags |= ImFontFlags_DefaultToLegacySize' before calling AddFont(). - IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. Use font->LegacySize to revert to font size specified by AddFont(). + // - To use old behavior (single size font, size specified in AddFontXXX() call: + // - Use 'PushFont(font, font->LegacySize)' at call site + // - Or set 'ImFontConfig::Flags |= ImFontFlags_DefaultToLegacySize' before calling AddFont(), and then 'PushFont(font)' will use this size. + // *IMPORTANT* If you want to scale an existing font size: + // - OK: PushFontSize(style.FontSizeBase * factor) (= value before external scale factors applied). + // - KO: PushFontSize(GetFontSize() * factor) (= value after external scale factors applied. external scale factors are io.FontGlobalScale and per-viewport scales.). + IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. IMGUI_API void PopFont(); IMGUI_API void PushFontSize(float font_size); IMGUI_API void PopFontSize(); @@ -2222,6 +2227,8 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE struct ImGuiStyle { + float FontSizeBase; // Current base font size (scaling applied). Use PushFont()/PushFontSize() to modify. Use ImGui::GetFontSize() to obtain scaled value. + float Alpha; // Global alpha applies to everything in Dear ImGui. float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. ImVec2 WindowPadding; // Padding within a window. @@ -2287,8 +2294,13 @@ struct ImGuiStyle ImGuiHoveredFlags HoverFlagsForTooltipMouse;// Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. ImGuiHoveredFlags HoverFlagsForTooltipNav; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. - IMGUI_API ImGuiStyle(); - IMGUI_API void ScaleAllSizes(float scale_factor); + // [Internal] + float _MainScale; // FIXME-WIP: Reference scale, as applied by ScaleAllSizes(). + float _NextFrameFontSizeBase; // FIXME: Temporary hack until we finish remaining work. + + // Functions + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/imgui_demo.cpp b/imgui_demo.cpp index dda817eb8..fc1d16953 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -430,16 +430,25 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Text("dear imgui says hello! (%s) (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); ImGui::Spacing(); - ImGui::SeparatorText("dynamic_fonts branch"); - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5); - ImGui::DragFloat("io.FontGlobalScale", &ImGui::GetIO().FontGlobalScale, 0.05f, 0.5f, 5.0f); - ImGui::BulletText("This is scaling font only. General scaling will come later."); - ImGui::BulletText("Load an actual font that's not the default for best result!"); - ImGui::BulletText("See 'Widgets->Fonts' below for more.."); - ImGui::BulletText("Current font loader: '%s'", ImGui::GetIO().Fonts->FontLoaderName); - ImGui::BulletText("Please submit feedback:"); ImGui::SameLine(); - ImGui::TextLinkOpenURL("https://github.com/ocornut/imgui/issues/8465"); - ImGui::Spacing(); + { + ImGui::SeparatorText("dynamic_fonts branch"); + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); + ImGui::ShowFontSelector("Font"); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); + if (ImGui::DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) + style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. + ImGui::SameLine(0.0f, 0.0f); Text(" (out %.2f)", ImGui::GetFontSize()); + ImGui::SameLine(); HelpMarker("- This is scaling font only. General scaling will come later."); + //ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); + //ImGui::DragFloat("FontGlobalScale", &ImGui::GetIO().FontGlobalScale, 0.05f, 0.5f, 5.0f); + ImGui::BulletText("Load a nice font for better results!"); + ImGui::BulletText("See 'Widgets->Fonts' below for more."); + //ImGui::BulletText("Current font loader: '%s'", ImGui::GetIO().Fonts->FontLoaderName); + ImGui::BulletText("Please submit feedback:"); ImGui::SameLine(); + ImGui::TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); + ImGui::Spacing(); + } IMGUI_DEMO_MARKER("Help"); if (ImGui::CollapsingHeader("Help")) @@ -8207,11 +8216,16 @@ void ImGui::ShowFontSelector(const char* label) ImGui::EndCombo(); } ImGui::SameLine(); - HelpMarker( - "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" - "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" - "- Read FAQ and docs/FONTS.md for more details.\n" - "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + HelpMarker( + "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" + "- Read FAQ and docs/FONTS.md for more details."); + else + HelpMarker( + "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and docs/FONTS.md for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); } // Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e067d57ba..69c207fd2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3190,7 +3190,7 @@ static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, bool need_bind_ctx = ctx != curr_ctx; if (need_bind_ctx) ImGui::SetCurrentContext(ctx); - ImGui::SetCurrentFont(new_font, ctx->FontSize); + ImGui::SetCurrentFont(new_font, ctx->FontSizeBeforeScaling, ctx->FontSize); if (need_bind_ctx) ImGui::SetCurrentContext(curr_ctx); } diff --git a/imgui_internal.h b/imgui_internal.h index 5dacf666e..4c1d1f392 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -871,7 +871,8 @@ struct ImDrawDataBuilder struct ImFontStackData { ImFont* Font; - float FontSize; + float FontSizeBeforeScaling; + float FontSizeAfterScaling; }; //----------------------------------------------------------------------------- @@ -3116,12 +3117,12 @@ namespace ImGui IMGUI_API void UnregisterUserTexture(ImTextureData* tex); IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas); IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); - IMGUI_API void SetCurrentFont(ImFont* font, float font_size); + IMGUI_API void SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling); + IMGUI_API void UpdateCurrentFontSize(float restore_font_size_after_scaling); IMGUI_API void SetFontRasterizerDensity(float rasterizer_density); inline float GetFontRasterizerDensity() { return GImGui->FontRasterizerDensity; } - IMGUI_API void UpdateCurrentFontSize(); inline float GetRoundedFontSize(float size) { return IM_ROUND(size); } - inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } + IMGUI_API ImFont* GetDefaultFont(); IMGUI_API void PushPasswordFont(); IMGUI_API void PopPasswordFont(); inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. From 402db2ef326735fe537f051980d569b668db4179 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 5 Jun 2025 15:21:52 +0200 Subject: [PATCH 170/191] Fonts: fixed passing negative sizes to stb_truetype loader. --- imgui_draw.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 69c207fd2..e74477dc6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4530,10 +4530,10 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* } src->FontLoaderData = bd_font_data; - if (src->SizePixels > 0.0f) + if (src->SizePixels >= 0.0f) bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); else - bd_font_data->ScaleFactor = -stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); + bd_font_data->ScaleFactor = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); if (src != src->DstFont->Sources[0]) bd_font_data->ScaleFactor *= src->SizePixels / src->DstFont->Sources[0]->SizePixels; // FIXME-NEWATLAS: Should tidy up that a bit From 59a11363a52071b438043e35c0865f377b630d43 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 26 May 2025 23:13:34 +0200 Subject: [PATCH 171/191] Fonts: ground work for allowing SizePixels to be optional. --- imgui.cpp | 5 ++++- imgui.h | 4 ++-- imgui_draw.cpp | 11 ++++++++--- misc/freetype/imgui_freetype.cpp | 3 ++- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d18beab96..1ffa0652f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1204,6 +1204,9 @@ CODE #define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Hold CTRL to display for all candidates. CTRL+Arrow to change last direction. #define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window +// Default font size if unspecified in both style.FontSizeBase and AddFontXXX() calls. +static const float FONT_DEFAULT_SIZE = 20.0f; + // When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear @@ -8635,7 +8638,7 @@ void ImGui::UpdateFontsNewFrame() // Apply default font size the first time ImFont* font = ImGui::GetDefaultFont(); if (g.Style.FontSizeBase <= 0.0f) - g.Style.FontSizeBase = font->LegacySize * g.Style._MainScale; + g.Style.FontSizeBase = (font->LegacySize > 0.0f ? font->LegacySize : FONT_DEFAULT_SIZE) * g.Style._MainScale; // Set initial font g.Font = font; diff --git a/imgui.h b/imgui.h index d2bc7ce77..6e99b0be9 100644 --- a/imgui.h +++ b/imgui.h @@ -3475,10 +3475,10 @@ struct ImFontConfig ImVec2 GlyphOffset; // 0, 0 // Offset (in pixels) all glyphs from this font input. Absolute value for default size, other sizes will scale this value. float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs - float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. + float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. // FIXME-NEWATLAS: Intentionally unscaled unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. - float RasterizerDensity; // 1.0f // (Legacy: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported). DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. + float RasterizerDensity; // 1.0f // [LEGACY: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported] DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. // [Internal] diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e74477dc6..6fabbac9e 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2988,10 +2988,13 @@ bool ImFontAtlas::Build() ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) { + // Sanity Checks IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); IM_ASSERT((font_cfg_in->FontData != NULL && font_cfg_in->FontDataSize > 0) || (font_cfg_in->FontLoader != NULL)); - IM_ASSERT(font_cfg_in->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); + //IM_ASSERT(font_cfg_in->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); IM_ASSERT(font_cfg_in->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); + if (font_cfg_in->GlyphOffset.x != 0.0f || font_cfg_in->GlyphOffset.y != 0.0f || font_cfg_in->GlyphMinAdvanceX != 0.0f || font_cfg_in->GlyphMaxAdvanceX != FLT_MAX) + IM_ASSERT(font_cfg_in->SizePixels != 0.0f && "Specifying glyph offset/advances requires a reference size to base it on."); // Lazily create builder on the first call to AddFont if (Builder == NULL) @@ -4636,7 +4639,8 @@ static bool ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontC if (oversample_v > 1) stbtt__v_prefilter(bitmap_pixels, r->w, r->h, r->w, oversample_v); - const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0]->SizePixels; + const float ref_size = baked->ContainerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; float font_off_x = (src->GlyphOffset.x * offsets_scale); float font_off_y = (src->GlyphOffset.y * offsets_scale); if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. @@ -5075,7 +5079,8 @@ ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked if (src != NULL) { // Clamp & recenter if needed - const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0]->SizePixels; + const float ref_size = baked->ContainerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; float advance_x = ImClamp(glyph->AdvanceX, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); if (advance_x != glyph->AdvanceX) { diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index a0fc96926..4c10ff42d 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -527,7 +527,8 @@ bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data; ImGui_ImplFreeType_BlitGlyph(ft_bitmap, temp_buffer, w); - const float offsets_scale = baked->Size / baked->ContainerFont->Sources[0]->SizePixels; + const float ref_size = baked->ContainerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; float font_off_x = (src->GlyphOffset.x * offsets_scale); float font_off_y = (src->GlyphOffset.y * offsets_scale) + baked->Ascent; if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. From 80c08f228667c5921a1b11a26fda05673059481a Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 2 Jun 2025 14:58:30 +0200 Subject: [PATCH 172/191] (Breaking) Fonts: obsoleting SetWindowFontScale(). + Comments # Conflicts: # imgui.cpp --- imgui.cpp | 17 ++++++++++++----- imgui.h | 5 +++-- imgui_demo.cpp | 6 +++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 1ffa0652f..b4a4478b4 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8445,6 +8445,7 @@ ImFontBaked* ImGui::GetFontBaked() return GImGui->FontBaked; } +// Get current font size (= height in pixels) of current font, with external scale factors applied. Use ImGui::GetStyle().FontSizeBase to get value before external scale factors. float ImGui::GetFontSize() { return GImGui->FontSize; @@ -8455,6 +8456,8 @@ ImVec2 ImGui::GetFontTexUvWhitePixel() return GImGui->DrawListSharedData.TexUvWhitePixel; } +// Prefer using PushFontSize(style.FontSize * factor), or use io.FontGlobalScale to scale all windows. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS void ImGui::SetWindowFontScale(float scale) { IM_ASSERT(scale > 0.0f); @@ -8462,6 +8465,7 @@ void ImGui::SetWindowFontScale(float scale) window->FontWindowScale = scale; UpdateCurrentFontSize(0.0f); } +#endif void ImGui::PushFocusScope(ImGuiID id) { @@ -8737,13 +8741,16 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) float final_size = (restore_font_size_after_scaling > 0.0f) ? restore_font_size_after_scaling : 0.0f; if (final_size == 0.0f) { - final_size = g.FontSizeBeforeScaling * g.IO.FontGlobalScale; -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - if (g.Font != NULL) - final_size *= g.Font->Scale; -#endif + final_size = g.FontSizeBeforeScaling; + + // External scale factors + final_size *= g.IO.FontGlobalScale; if (window != NULL) final_size *= window->FontWindowScale; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.Font != NULL) + final_size *= g.Font->Scale; +#endif } // Round font size diff --git a/imgui.h b/imgui.h index 6e99b0be9..e5a61be75 100644 --- a/imgui.h +++ b/imgui.h @@ -474,7 +474,6 @@ namespace ImGui IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / top-most. prefer using SetNextWindowFocus(). - IMGUI_API void SetWindowFontScale(float scale); // [OBSOLETE] set font scale. Adjust IO.FontGlobalScale if you want to scale all windows. This is an old API! For correct scaling, prefer to reload font + rebuild ImFontAtlas + call style.ScaleAllSizes(). IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state @@ -532,7 +531,7 @@ namespace ImGui // Style read access // - Use the ShowStyleEditor() function to interactively see/edit the colors. IMGUI_API ImFont* GetFont(); // get current font - IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied + IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with external scale factors applied. Use ImGui::GetStyle().FontSizeBase to get value before external scale factors. IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a white pixel, useful to draw custom shapes via the ImDrawList API IMGUI_API ImFontBaked* GetFontBaked(); // get current font bound at current size // == GetFont()->GetFontBaked(GetFontSize()) IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier, packed as a 32-bit value suitable for ImDrawList @@ -3943,6 +3942,8 @@ struct ImGuiPlatformImeData #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + // OBSOLETED in 1.92.0 (from June 2025) + IMGUI_API void SetWindowFontScale(float scale); // Set font scale factor for current window. Prefer using PushFontSize(style.FontSize * factor) or use io.FontGlobalScale to scale all windows. // OBSOLETED in 1.91.9 (from February 2025) IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- 'border_col' was removed in favor of ImGuiCol_ImageBorder. If you use 'tint_col', use ImageWithBg() instead. // OBSOLETED in 1.91.0 (from July 2024) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index fc1d16953..581a49eb0 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8485,11 +8485,11 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) "However, the _correct_ way of scaling your UI is currently to reload your font at the designed size, " "rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\n" "Using those settings here will give you poor quality results."); - static float window_scale = 1.0f; PushItemWidth(GetFontSize() * 8); - if (DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window - SetWindowFontScale(window_scale); DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp); // Scale everything + //static float window_scale = 1.0f; + //if (DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window + // SetWindowFontScale(window_scale); PopItemWidth(); EndTabItem(); From 8766efcba6a0c2e62afd5ec22260cd54dd82cc5c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 4 Jun 2025 18:16:14 +0200 Subject: [PATCH 173/191] (Breaking) Renamed io.FontGlobalScale to style.FontScaleMain. # Conflicts: # imgui.cpp --- imgui.cpp | 33 +++++++++++++++++++-------------- imgui.h | 12 +++++++----- imgui_demo.cpp | 18 +++++++++++++----- imgui_internal.h | 2 +- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b4a4478b4..527aae3f4 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1358,6 +1358,8 @@ static void* GImAllocatorUserData = NULL; ImGuiStyle::ImGuiStyle() { FontSizeBase = 0.0f; // Will default to io.Fonts->Fonts[0] on first frame. + FontScaleMain = 1.0f; // Main global scale factor. + Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. WindowPadding = ImVec2(8,8); // Padding within a window @@ -1481,9 +1483,11 @@ ImGuiIO::ImGuiIO() UserData = NULL; Fonts = NULL; - FontGlobalScale = 1.0f; FontDefault = NULL; FontAllowUserScaling = false; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + FontGlobalScale = 1.0f; // Use style.FontScaleMain instead! +#endif DisplayFramebufferScale = ImVec2(1.0f, 1.0f); // Keyboard/Gamepad Navigation options @@ -8456,7 +8460,7 @@ ImVec2 ImGui::GetFontTexUvWhitePixel() return GImGui->DrawListSharedData.TexUvWhitePixel; } -// Prefer using PushFontSize(style.FontSize * factor), or use io.FontGlobalScale to scale all windows. +// Prefer using PushFontSize(style.FontSizeBase * factor), or use style.FontScaleMain to scale all windows. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS void ImGui::SetWindowFontScale(float scale) { @@ -8744,12 +8748,15 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) final_size = g.FontSizeBeforeScaling; // External scale factors - final_size *= g.IO.FontGlobalScale; + final_size *= g.Style.FontScaleMain; if (window != NULL) final_size *= window->FontWindowScale; + + // Legacy scale factors #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + final_size *= g.IO.FontGlobalScale; // Use style.FontScaleMain instead! if (g.Font != NULL) - final_size *= g.Font->Scale; + final_size *= g.Font->Scale; // Was never really useful. #endif } @@ -10544,6 +10551,9 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT(g.IO.ConfigErrorRecoveryEnableAssert || g.IO.ConfigErrorRecoveryEnableDebugLog || g.IO.ConfigErrorRecoveryEnableTooltip || g.ErrorCallback != NULL); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.IO.FontGlobalScale > 1.0f) + IM_ASSERT(g.Style.FontScaleMain == 1.0f && "Since 1.92: use style.FontScaleMain instead of g.IO.FontGlobalScale!"); + // Remap legacy names if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) { @@ -15799,29 +15809,24 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) ImGuiIO& io = g.IO; ImGuiStyle& style = g.Style; - Text("Read "); SameLine(0, 0); - TextLinkOpenURL("https://www.dearimgui.com/faq/"); SameLine(0, 0); - Text(" for details."); - - SeparatorText("Backend Support for Dynamic Fonts"); BeginDisabled(); CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); EndDisabled(); - + ShowFontSelector("Font"); BeginDisabled((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); - SetNextItemWidth(GetFontSize() * 10); if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); SameLine(); MetricsHelpMarker("- This is scaling font only. General scaling will come later."); - SetNextItemWidth(GetFontSize() * 10); - DragFloat("io.FontGlobalScale", &io.FontGlobalScale, 0.05f, 0.5f, 5.0f); // <-- This works, but no need to make it too visible. + DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 5.0f); BulletText("Load a nice font for better results!"); BulletText("Please submit feedback:"); SameLine(); TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); + BulletText("Read FAQ for more details:"); + SameLine(); TextLinkOpenURL("dearimgui.com/faq", "https://www.dearimgui.com/faq/"); EndDisabled(); - SeparatorText("Fonts"); + SeparatorText("Font List"); ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; Checkbox("Show font preview", &cfg->ShowFontPreview); diff --git a/imgui.h b/imgui.h index e5a61be75..8fa8c65e1 100644 --- a/imgui.h +++ b/imgui.h @@ -502,7 +502,7 @@ namespace ImGui // - Or set 'ImFontConfig::Flags |= ImFontFlags_DefaultToLegacySize' before calling AddFont(), and then 'PushFont(font)' will use this size. // *IMPORTANT* If you want to scale an existing font size: // - OK: PushFontSize(style.FontSizeBase * factor) (= value before external scale factors applied). - // - KO: PushFontSize(GetFontSize() * factor) (= value after external scale factors applied. external scale factors are io.FontGlobalScale and per-viewport scales.). + // - KO: PushFontSize(GetFontSize() * factor) (= value after external scale factors applied. external scale factors are style.FontScaleMain + per-viewport scales.). IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. IMGUI_API void PopFont(); IMGUI_API void PushFontSize(float font_size); @@ -2227,6 +2227,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE struct ImGuiStyle { float FontSizeBase; // Current base font size (scaling applied). Use PushFont()/PushFontSize() to modify. Use ImGui::GetFontSize() to obtain scaled value. + float FontScaleMain; // Main global scale factor. Other scale factors may apply. float Alpha; // Global alpha applies to everything in Dear ImGui. float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. @@ -2347,9 +2348,8 @@ struct ImGuiIO // Font system ImFontAtlas*Fonts; // // Font atlas: load, rasterize and pack one or more fonts into a single texture. - float FontGlobalScale; // = 1.0f // Global scale all fonts - bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. + bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. // Keyboard/Gamepad Navigation options bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. @@ -2546,9 +2546,11 @@ struct ImGuiIO //float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + float FontGlobalScale; // Moved io.FontGlobalScale to style.FontScaleMain in 1.92 (June 2025) + // Legacy: before 1.91.1, clipboard functions were stored in ImGuiIO instead of ImGuiPlatformIO. // As this is will affect all users of custom engines/backends, we are providing proper legacy redirection (will obsolete). -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS const char* (*GetClipboardTextFn)(void* user_data); void (*SetClipboardTextFn)(void* user_data, const char* text); void* ClipboardUserData; @@ -3943,7 +3945,7 @@ struct ImGuiPlatformImeData namespace ImGui { // OBSOLETED in 1.92.0 (from June 2025) - IMGUI_API void SetWindowFontScale(float scale); // Set font scale factor for current window. Prefer using PushFontSize(style.FontSize * factor) or use io.FontGlobalScale to scale all windows. + IMGUI_API void SetWindowFontScale(float scale); // Set font scale factor for current window. Prefer using PushFontSize(style.FontSizeBase * factor) or use style.FontScaleMain to scale all windows. // OBSOLETED in 1.91.9 (from February 2025) IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- 'border_col' was removed in favor of ImGuiCol_ImageBorder. If you use 'tint_col', use ImageWithBg() instead. // OBSOLETED in 1.91.0 (from July 2024) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 581a49eb0..5aa9dc02b 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -440,13 +440,13 @@ void ImGui::ShowDemoWindow(bool* p_open) style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. ImGui::SameLine(0.0f, 0.0f); Text(" (out %.2f)", ImGui::GetFontSize()); ImGui::SameLine(); HelpMarker("- This is scaling font only. General scaling will come later."); - //ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); - //ImGui::DragFloat("FontGlobalScale", &ImGui::GetIO().FontGlobalScale, 0.05f, 0.5f, 5.0f); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); + ImGui::DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); ImGui::BulletText("Load a nice font for better results!"); - ImGui::BulletText("See 'Widgets->Fonts' below for more."); //ImGui::BulletText("Current font loader: '%s'", ImGui::GetIO().Fonts->FontLoaderName); ImGui::BulletText("Please submit feedback:"); ImGui::SameLine(); ImGui::TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); + ImGui::BulletText("See 'Widgets->Fonts' below for more."); ImGui::Spacing(); } @@ -8276,9 +8276,16 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) { // General + SeparatorText("General"); if (ShowStyleSelector("Colors##Selector")) ref_saved_style = style; ShowFontSelector("Fonts##Selector"); + BeginDisabled((GetIO().BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); + if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) + style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. + SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); + DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); + EndDisabled(); // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) if (SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) @@ -8301,8 +8308,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) "Save/Revert in local non-persistent storage. Default Colors definition are not affected. " "Use \"Export\" below to save them somewhere."); - Separator(); - + SeparatorText("Details"); if (BeginTabBar("##tabs", ImGuiTabBarFlags_None)) { if (BeginTabItem("Sizes")) @@ -8477,6 +8483,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) // Post-baking font scaling. Note that this is NOT the nice way of scaling fonts, read below. // (we enforce hard clamping manually as by default DragFloat/SliderFloat allows CTRL+Click text to get out of bounds). + /* SeparatorText("Legacy Scaling"); const float MIN_SCALE = 0.3f; const float MAX_SCALE = 2.0f; @@ -8491,6 +8498,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) //if (DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window // SetWindowFontScale(window_scale); PopItemWidth(); + */ EndTabItem(); } diff --git a/imgui_internal.h b/imgui_internal.h index 4c1d1f392..f4db19273 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2139,7 +2139,7 @@ struct ImGuiContext ImVector FontAtlases; // List of font atlases used by the context (generally only contains g.IO.Fonts aka the main font atlas) ImFont* Font; // Currently bound font. (== FontStack.back().Font) ImFontBaked* FontBaked; // Currently bound font at currently bound size. (== Font->GetFontBaked(FontSize)) - float FontSize; // Currently bound font size == line height (== FontSizeBeforeScaling * io.FontGlobalScale * font->Scale * g.CurrentWindow->FontWindowScale). + float FontSize; // Currently bound font size == line height (== FontSizeBeforeScaling + externals scales applied in the UpdateCurrentFontSize() function). float FontSizeBeforeScaling; // == value passed to PushFont() / PushFontSize() when specified. float FontScale; // == FontBaked->Size / Font->FontSize. Scale factor over baked size. float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). From d85e22d205d347695944532fe1fcc6c23cacdc11 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 2 Jun 2025 16:44:42 +0200 Subject: [PATCH 174/191] Added style.FontScaleDpi which is the field overwritten by ImGuiConfigFlags_DpiEnableScaleFonts. # Conflicts: # imgui.cpp # imgui.h # imgui_demo.cpp --- imgui.cpp | 16 ++++++++++++---- imgui.h | 3 ++- imgui_demo.cpp | 4 ++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 527aae3f4..f6dd39c9d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1359,6 +1359,7 @@ ImGuiStyle::ImGuiStyle() { FontSizeBase = 0.0f; // Will default to io.Fonts->Fonts[0] on first frame. FontScaleMain = 1.0f; // Main global scale factor. + FontScaleDpi = 1.0f; // Scale factor from viewport/monitor. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. @@ -1429,12 +1430,12 @@ ImGuiStyle::ImGuiStyle() ImGui::StyleColorsDark(this); } -// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. + +// Scale all spacing/padding/thickness values. Do not scale fonts. // Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. void ImGuiStyle::ScaleAllSizes(float scale_factor) { _MainScale *= scale_factor; - FontSizeBase = ImTrunc(FontSizeBase * scale_factor); WindowPadding = ImTrunc(WindowPadding * scale_factor); WindowRounding = ImTrunc(WindowRounding * scale_factor); WindowMinSize = ImTrunc(WindowMinSize * scale_factor); @@ -8646,7 +8647,7 @@ void ImGui::UpdateFontsNewFrame() // Apply default font size the first time ImFont* font = ImGui::GetDefaultFont(); if (g.Style.FontSizeBase <= 0.0f) - g.Style.FontSizeBase = (font->LegacySize > 0.0f ? font->LegacySize : FONT_DEFAULT_SIZE) * g.Style._MainScale; + g.Style.FontSizeBase = (font->LegacySize > 0.0f ? font->LegacySize : FONT_DEFAULT_SIZE); // Set initial font g.Font = font; @@ -8748,7 +8749,10 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) final_size = g.FontSizeBeforeScaling; // External scale factors - final_size *= g.Style.FontScaleMain; + final_size *= g.Style.FontScaleMain; // Main global scale factor + final_size *= g.Style.FontScaleDpi; // Per-monitor/viewport DPI scale factor, automatically updated when io.ConfigDpiScaleFonts is enabled. + + // Window scale (mostly obsolete now) if (window != NULL) final_size *= window->FontWindowScale; @@ -15819,6 +15823,10 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); SameLine(); MetricsHelpMarker("- This is scaling font only. General scaling will come later."); DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 5.0f); + //BeginDisabled(io.ConfigDpiScaleFonts); + DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 5.0f); + //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); + //EndDisabled(); BulletText("Load a nice font for better results!"); BulletText("Please submit feedback:"); SameLine(); TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); diff --git a/imgui.h b/imgui.h index 8fa8c65e1..3b69ac1ff 100644 --- a/imgui.h +++ b/imgui.h @@ -2228,6 +2228,7 @@ struct ImGuiStyle { float FontSizeBase; // Current base font size (scaling applied). Use PushFont()/PushFontSize() to modify. Use ImGui::GetFontSize() to obtain scaled value. float FontScaleMain; // Main global scale factor. Other scale factors may apply. + float FontScaleDpi; // Scale factor from viewport/monitor. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor. float Alpha; // Global alpha applies to everything in Dear ImGui. float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. @@ -2300,7 +2301,7 @@ struct ImGuiStyle // Functions IMGUI_API ImGuiStyle(); - IMGUI_API void ScaleAllSizes(float scale_factor); + IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 5aa9dc02b..985a490ea 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8285,6 +8285,10 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); + //BeginDisabled(GetIO().ConfigDpiScaleFonts); + DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 5.0f); + //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); + //EndDisabled(); EndDisabled(); // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) From 3c27c643a9c79f2248aee53336c754749b8622e5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 5 Jun 2025 14:40:37 +0200 Subject: [PATCH 175/191] Fonts: internals: renamed g.FontScale to g.FontBakedScale for clarity. Comments. --- imgui.cpp | 20 ++++++++++---------- imgui.h | 11 ++++++----- imgui_internal.h | 8 ++++---- imgui_tables.cpp | 2 +- imgui_widgets.cpp | 4 ++-- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f6dd39c9d..3fb75eb25 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3979,7 +3979,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) Initialized = false; Font = NULL; FontBaked = NULL; - FontSize = FontSizeBeforeScaling = FontScale = CurrentDpiScale = 0.0f; + FontSize = FontSizeBeforeScaling = FontBakedScale = CurrentDpiScale = 0.0f; FontRasterizerDensity = 1.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); if (shared_font_atlas == NULL) @@ -8773,9 +8773,9 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; g.FontSize = final_size; g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL; - g.FontScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; + g.FontBakedScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; g.DrawListSharedData.FontSize = g.FontSize; - g.DrawListSharedData.FontScale = g.FontScale; + g.DrawListSharedData.FontScale = g.FontBakedScale; } // Exposed in case user may want to override setting density. @@ -8793,20 +8793,20 @@ void ImGui::SetFontRasterizerDensity(float rasterizer_density) // If you want to scale an existing font size: // - Use e.g. PushFontSize(style.FontSizeBase * factor) (= value before external scale factors applied). // - Do NOT use PushFontSize(GetFontSize() * factor) (= value after external scale factors applied). -void ImGui::PushFont(ImFont* font, float font_size) +void ImGui::PushFont(ImFont* font, float font_size_base) { ImGuiContext& g = *GImGui; g.FontStack.push_back({ g.Font, g.FontSizeBeforeScaling, g.FontSize }); if (font == NULL) font = GetDefaultFont(); - if (font_size <= 0.0f) + if (font_size_base <= 0.0f) { if (font->Flags & ImFontFlags_DefaultToLegacySize) - font_size = font->LegacySize; // Legacy: use AddFont() specified font size. Same as doing PushFont(font, font->LegacySize) + font_size_base = font->LegacySize; // Legacy: use AddFont() specified font size. Same as doing PushFont(font, font->LegacySize) else - font_size = g.FontSizeBeforeScaling; // Keep current font size + font_size_base = g.FontSizeBeforeScaling; // Keep current font size } - SetCurrentFont(font, font_size, 0.0f); + SetCurrentFont(font, font_size_base, 0.0f); } void ImGui::PopFont() @@ -8822,10 +8822,10 @@ void ImGui::PopFont() g.FontStack.pop_back(); } -void ImGui::PushFontSize(float font_size) +void ImGui::PushFontSize(float font_size_base) { ImGuiContext& g = *GImGui; - PushFont(g.Font, font_size); + PushFont(g.Font, font_size_base); } void ImGui::PopFontSize() diff --git a/imgui.h b/imgui.h index 3b69ac1ff..563d2eb92 100644 --- a/imgui.h +++ b/imgui.h @@ -500,12 +500,13 @@ namespace ImGui // - To use old behavior (single size font, size specified in AddFontXXX() call: // - Use 'PushFont(font, font->LegacySize)' at call site // - Or set 'ImFontConfig::Flags |= ImFontFlags_DefaultToLegacySize' before calling AddFont(), and then 'PushFont(font)' will use this size. + // - External scale factors are applied over the provided value. // *IMPORTANT* If you want to scale an existing font size: // - OK: PushFontSize(style.FontSizeBase * factor) (= value before external scale factors applied). // - KO: PushFontSize(GetFontSize() * factor) (= value after external scale factors applied. external scale factors are style.FontScaleMain + per-viewport scales.). - IMGUI_API void PushFont(ImFont* font, float font_size = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. + IMGUI_API void PushFont(ImFont* font, float font_size_base = -1); // use NULL as a shortcut to push default font. Use <0.0f to keep current font size. IMGUI_API void PopFont(); - IMGUI_API void PushFontSize(float font_size); + IMGUI_API void PushFontSize(float font_size_base); IMGUI_API void PopFontSize(); // Parameters stacks (shared) @@ -2226,9 +2227,9 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE struct ImGuiStyle { - float FontSizeBase; // Current base font size (scaling applied). Use PushFont()/PushFontSize() to modify. Use ImGui::GetFontSize() to obtain scaled value. - float FontScaleMain; // Main global scale factor. Other scale factors may apply. - float FontScaleDpi; // Scale factor from viewport/monitor. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor. + float FontSizeBase; // Current base font size before external scaling factors are applied. Use PushFont()/PushFontSize() to modify. Use ImGui::GetFontSize() to obtain scaled value. Final FontSize = FontSizeBase * (FontScaleBase * FontScaleDpi * other_factors) + float FontScaleMain; // Main scale factor. May be set by application once, or exposed to end-user. + float FontScaleDpi; // Scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor. float Alpha; // Global alpha applies to everything in Dear ImGui. float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. diff --git a/imgui_internal.h b/imgui_internal.h index f4db19273..db308bc7a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -871,8 +871,8 @@ struct ImDrawDataBuilder struct ImFontStackData { ImFont* Font; - float FontSizeBeforeScaling; - float FontSizeAfterScaling; + float FontSizeBeforeScaling; // ~~ style.FontSizeBase + float FontSizeAfterScaling; // ~~ g.FontSize }; //----------------------------------------------------------------------------- @@ -2140,8 +2140,8 @@ struct ImGuiContext ImFont* Font; // Currently bound font. (== FontStack.back().Font) ImFontBaked* FontBaked; // Currently bound font at currently bound size. (== Font->GetFontBaked(FontSize)) float FontSize; // Currently bound font size == line height (== FontSizeBeforeScaling + externals scales applied in the UpdateCurrentFontSize() function). - float FontSizeBeforeScaling; // == value passed to PushFont() / PushFontSize() when specified. - float FontScale; // == FontBaked->Size / Font->FontSize. Scale factor over baked size. + float FontSizeBeforeScaling; // Font size before scaling == style.FontSizeBase == value passed to PushFont() / PushFontSize() when specified. + float FontBakedScale; // == FontBaked->Size / FontSize. Scale factor over baked size. Rarely used nowadays, very often == 1.0f. float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e41041ce1..61bc576ca 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3375,7 +3375,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ButtonBehavior(row_r, row_id, NULL, NULL); KeepAliveID(row_id); - const float ascent_scaled = g.FontBaked->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better + const float ascent_scaled = g.FontBaked->Ascent * g.FontBakedScale; // FIXME: Standardize those scaling factors better const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f); const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component const ImVec2 align = g.Style.TableAngledHeadersTextAlign; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 91d50933f..cdca228c4 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1518,7 +1518,7 @@ bool ImGui::TextLink(const char* label) ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); } - float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontScale * 0.20f); + float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f); window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode // FIXME-DPI PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); @@ -4012,7 +4012,7 @@ namespace ImStb { static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; } static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; } -static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.FontBaked->GetCharAdvance((ImWchar)c) * g.FontScale; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.FontBaked->GetCharAdvance((ImWchar)c) * g.FontBakedScale; } static char STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) { From 2d2b1cee6b7c1e741375b09ddcda8a2d68602655 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 5 Jun 2025 14:54:46 +0200 Subject: [PATCH 176/191] Fonts: internals: renamed g.FontSizeBeforeScaling to g.FontSizeBase for consistency. # Conflicts: # imgui_internal.h --- imgui.cpp | 18 +++++++++--------- imgui.h | 5 +++-- imgui_draw.cpp | 2 +- imgui_internal.h | 6 +++--- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 3fb75eb25..39a79a398 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1358,8 +1358,8 @@ static void* GImAllocatorUserData = NULL; ImGuiStyle::ImGuiStyle() { FontSizeBase = 0.0f; // Will default to io.Fonts->Fonts[0] on first frame. - FontScaleMain = 1.0f; // Main global scale factor. - FontScaleDpi = 1.0f; // Scale factor from viewport/monitor. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. + FontScaleMain = 1.0f; // Main scale factor. May be set by application once, or exposed to end-user. + FontScaleDpi = 1.0f; // Additional scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. @@ -3979,7 +3979,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) Initialized = false; Font = NULL; FontBaked = NULL; - FontSize = FontSizeBeforeScaling = FontBakedScale = CurrentDpiScale = 0.0f; + FontSize = FontSizeBase = FontBakedScale = CurrentDpiScale = 0.0f; FontRasterizerDensity = 1.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); if (shared_font_atlas == NULL) @@ -8651,7 +8651,7 @@ void ImGui::UpdateFontsNewFrame() // Set initial font g.Font = font; - g.FontSizeBeforeScaling = g.Style.FontSizeBase; + g.FontSizeBase = g.Style.FontSizeBase; g.FontSize = 0.0f; ImFontStackData font_stack_data = { font, g.Style.FontSizeBase, g.Style.FontSizeBase }; // <--- Will restore FontSize SetCurrentFont(font_stack_data.Font, font_stack_data.FontSizeBeforeScaling, 0.0f); // <--- but use 0.0f to enable scale @@ -8717,7 +8717,7 @@ void ImGui::SetCurrentFont(ImFont* font, float font_size_before_scaling, float f { ImGuiContext& g = *GImGui; g.Font = font; - g.FontSizeBeforeScaling = font_size_before_scaling; + g.FontSizeBase = font_size_before_scaling; UpdateCurrentFontSize(font_size_after_scaling); if (font != NULL) @@ -8738,7 +8738,7 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - g.Style.FontSizeBase = g.FontSizeBeforeScaling; + g.Style.FontSizeBase = g.FontSizeBase; if (window != NULL && window->SkipItems) return; @@ -8746,7 +8746,7 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) float final_size = (restore_font_size_after_scaling > 0.0f) ? restore_font_size_after_scaling : 0.0f; if (final_size == 0.0f) { - final_size = g.FontSizeBeforeScaling; + final_size = g.FontSizeBase; // External scale factors final_size *= g.Style.FontScaleMain; // Main global scale factor @@ -8796,7 +8796,7 @@ void ImGui::SetFontRasterizerDensity(float rasterizer_density) void ImGui::PushFont(ImFont* font, float font_size_base) { ImGuiContext& g = *GImGui; - g.FontStack.push_back({ g.Font, g.FontSizeBeforeScaling, g.FontSize }); + g.FontStack.push_back({ g.Font, g.FontSizeBase, g.FontSize }); if (font == NULL) font = GetDefaultFont(); if (font_size_base <= 0.0f) @@ -8804,7 +8804,7 @@ void ImGui::PushFont(ImFont* font, float font_size_base) if (font->Flags & ImFontFlags_DefaultToLegacySize) font_size_base = font->LegacySize; // Legacy: use AddFont() specified font size. Same as doing PushFont(font, font->LegacySize) else - font_size_base = g.FontSizeBeforeScaling; // Keep current font size + font_size_base = g.FontSizeBase; // Keep current font size } SetCurrentFont(font, font_size_base, 0.0f); } diff --git a/imgui.h b/imgui.h index 563d2eb92..cdcdae68b 100644 --- a/imgui.h +++ b/imgui.h @@ -2227,9 +2227,10 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE struct ImGuiStyle { - float FontSizeBase; // Current base font size before external scaling factors are applied. Use PushFont()/PushFontSize() to modify. Use ImGui::GetFontSize() to obtain scaled value. Final FontSize = FontSizeBase * (FontScaleBase * FontScaleDpi * other_factors) + // ImGui::GetFontSize() == FontSizeBase * (FontScaleMain * FontScaleDpi * other_scaling_factors) + float FontSizeBase; // Current base font size before external scaling factors are applied. Use PushFont()/PushFontSize() to modify. Use ImGui::GetFontSize() to obtain scaled value. float FontScaleMain; // Main scale factor. May be set by application once, or exposed to end-user. - float FontScaleDpi; // Scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor. + float FontScaleDpi; // Additional scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. float Alpha; // Global alpha applies to everything in Dear ImGui. float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6fabbac9e..c4f342e54 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3193,7 +3193,7 @@ static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, bool need_bind_ctx = ctx != curr_ctx; if (need_bind_ctx) ImGui::SetCurrentContext(ctx); - ImGui::SetCurrentFont(new_font, ctx->FontSizeBeforeScaling, ctx->FontSize); + ImGui::SetCurrentFont(new_font, ctx->FontSizeBase, ctx->FontSize); if (need_bind_ctx) ImGui::SetCurrentContext(curr_ctx); } diff --git a/imgui_internal.h b/imgui_internal.h index db308bc7a..e344cbccf 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2139,8 +2139,8 @@ struct ImGuiContext ImVector FontAtlases; // List of font atlases used by the context (generally only contains g.IO.Fonts aka the main font atlas) ImFont* Font; // Currently bound font. (== FontStack.back().Font) ImFontBaked* FontBaked; // Currently bound font at currently bound size. (== Font->GetFontBaked(FontSize)) - float FontSize; // Currently bound font size == line height (== FontSizeBeforeScaling + externals scales applied in the UpdateCurrentFontSize() function). - float FontSizeBeforeScaling; // Font size before scaling == style.FontSizeBase == value passed to PushFont() / PushFontSize() when specified. + float FontSize; // Currently bound font size == line height (== FontSizeBase + externals scales applied in the UpdateCurrentFontSize() function). + float FontSizeBase; // Font size before scaling == style.FontSizeBase == value passed to PushFont() / PushFontSize() when specified. float FontBakedScale; // == FontBaked->Size / FontSize. Scale factor over baked size. Rarely used nowadays, very often == 1.0f. float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale @@ -2690,7 +2690,7 @@ public: ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } // [Obsolete] ImGuiWindow::CalcFontSize() was removed in 1.92.x because error-prone/misleading. You can use window->FontRefSize for a copy of g.FontSize at the time of the last Begin() call for this window. - //float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontWindowScaleParents; } + //float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontSizeBase * FontWindowScale * FontWindowScaleParents; }; //----------------------------------------------------------------------------- From d72e66cdee550cfa9735e8199dceac2a7ac27ab5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 5 Jun 2025 16:47:58 +0200 Subject: [PATCH 177/191] Examples: remove comments/references about baking and GetGlyphRangesJapanese(). --- examples/example_allegro5/main.cpp | 3 +-- examples/example_android_opengl3/main.cpp | 3 +-- examples/example_apple_metal/main.mm | 3 +-- examples/example_apple_opengl2/main.mm | 3 +-- examples/example_glfw_metal/main.mm | 3 +-- examples/example_glfw_opengl2/main.cpp | 3 +-- examples/example_glfw_opengl3/main.cpp | 3 +-- examples/example_glfw_vulkan/main.cpp | 3 +-- examples/example_glfw_wgpu/main.cpp | 3 +-- examples/example_glut_opengl2/main.cpp | 3 +-- examples/example_sdl2_directx11/main.cpp | 3 +-- examples/example_sdl2_metal/main.mm | 3 +-- examples/example_sdl2_opengl2/main.cpp | 3 +-- examples/example_sdl2_opengl3/main.cpp | 3 +-- examples/example_sdl2_sdlrenderer2/main.cpp | 3 +-- examples/example_sdl2_vulkan/main.cpp | 3 +-- examples/example_sdl3_opengl3/main.cpp | 3 +-- examples/example_sdl3_sdlgpu3/main.cpp | 3 +-- examples/example_sdl3_sdlrenderer3/main.cpp | 3 +-- examples/example_sdl3_vulkan/main.cpp | 3 +-- examples/example_win32_directx10/main.cpp | 3 +-- examples/example_win32_directx11/main.cpp | 3 +-- examples/example_win32_directx12/main.cpp | 3 +-- examples/example_win32_directx9/main.cpp | 3 +-- examples/example_win32_opengl3/main.cpp | 3 +-- examples/example_win32_vulkan/main.cpp | 3 +-- 26 files changed, 26 insertions(+), 52 deletions(-) diff --git a/examples/example_allegro5/main.cpp b/examples/example_allegro5/main.cpp index 3ca061cc6..67fc7ee65 100644 --- a/examples/example_allegro5/main.cpp +++ b/examples/example_allegro5/main.cpp @@ -52,7 +52,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -61,7 +60,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); bool show_demo_window = true; diff --git a/examples/example_android_opengl3/main.cpp b/examples/example_android_opengl3/main.cpp index 42aa622f7..452cb77d9 100644 --- a/examples/example_android_opengl3/main.cpp +++ b/examples/example_android_opengl3/main.cpp @@ -154,7 +154,6 @@ void Init(struct android_app* app) // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! // - Android: The TTF files have to be placed into the assets/ directory (android/app/src/main/assets), we use our GetAssetData() helper to retrieve them. @@ -181,7 +180,7 @@ void Init(struct android_app* app) //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 15.0f); //IM_ASSERT(font != nullptr); //font_data_size = GetAssetData("ArialUni.ttf", &font_data); - //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 18.0f); //IM_ASSERT(font != nullptr); // Arbitrary scale-up diff --git a/examples/example_apple_metal/main.mm b/examples/example_apple_metal/main.mm index 3ad7cff46..9b9db8c61 100644 --- a/examples/example_apple_metal/main.mm +++ b/examples/example_apple_metal/main.mm @@ -72,7 +72,6 @@ // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -81,7 +80,7 @@ //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); return self; diff --git a/examples/example_apple_opengl2/main.mm b/examples/example_apple_opengl2/main.mm index 815c0f72e..5f1fd1e50 100644 --- a/examples/example_apple_opengl2/main.mm +++ b/examples/example_apple_opengl2/main.mm @@ -60,7 +60,6 @@ // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -69,7 +68,7 @@ //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); } diff --git a/examples/example_glfw_metal/main.mm b/examples/example_glfw_metal/main.mm index e9bc63acb..c4380d5c3 100644 --- a/examples/example_glfw_metal/main.mm +++ b/examples/example_glfw_metal/main.mm @@ -42,7 +42,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -51,7 +50,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Setup window diff --git a/examples/example_glfw_opengl2/main.cpp b/examples/example_glfw_opengl2/main.cpp index 1fcec2b2a..af4edbe2b 100644 --- a/examples/example_glfw_opengl2/main.cpp +++ b/examples/example_glfw_opengl2/main.cpp @@ -65,7 +65,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -74,7 +73,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_glfw_opengl3/main.cpp b/examples/example_glfw_opengl3/main.cpp index e5f29d405..e133376bc 100644 --- a/examples/example_glfw_opengl3/main.cpp +++ b/examples/example_glfw_opengl3/main.cpp @@ -99,7 +99,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -109,7 +108,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index 3d2181e89..10b9ab2ed 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -417,7 +417,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -426,7 +425,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_glfw_wgpu/main.cpp b/examples/example_glfw_wgpu/main.cpp index f510987ed..58c5b34f7 100644 --- a/examples/example_glfw_wgpu/main.cpp +++ b/examples/example_glfw_wgpu/main.cpp @@ -114,7 +114,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -126,7 +125,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 15.0f); //io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf", 10.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); #endif diff --git a/examples/example_glut_opengl2/main.cpp b/examples/example_glut_opengl2/main.cpp index 58539ca5f..5720f8558 100644 --- a/examples/example_glut_opengl2/main.cpp +++ b/examples/example_glut_opengl2/main.cpp @@ -83,7 +83,6 @@ int main(int argc, char** argv) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -92,7 +91,7 @@ int main(int argc, char** argv) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Main loop diff --git a/examples/example_sdl2_directx11/main.cpp b/examples/example_sdl2_directx11/main.cpp index 47c852d27..fdb7ad458 100644 --- a/examples/example_sdl2_directx11/main.cpp +++ b/examples/example_sdl2_directx11/main.cpp @@ -84,7 +84,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -93,7 +92,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl2_metal/main.mm b/examples/example_sdl2_metal/main.mm index d54812710..4dc4788a8 100644 --- a/examples/example_sdl2_metal/main.mm +++ b/examples/example_sdl2_metal/main.mm @@ -33,7 +33,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -42,7 +41,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Setup SDL diff --git a/examples/example_sdl2_opengl2/main.cpp b/examples/example_sdl2_opengl2/main.cpp index a70edd225..feba216f0 100644 --- a/examples/example_sdl2_opengl2/main.cpp +++ b/examples/example_sdl2_opengl2/main.cpp @@ -70,7 +70,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -79,7 +78,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl2_opengl3/main.cpp b/examples/example_sdl2_opengl3/main.cpp index 51add5c1f..7b7b17ad2 100644 --- a/examples/example_sdl2_opengl3/main.cpp +++ b/examples/example_sdl2_opengl3/main.cpp @@ -110,7 +110,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -120,7 +119,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl2_sdlrenderer2/main.cpp b/examples/example_sdl2_sdlrenderer2/main.cpp index 31fa3f9bf..eee25c272 100644 --- a/examples/example_sdl2_sdlrenderer2/main.cpp +++ b/examples/example_sdl2_sdlrenderer2/main.cpp @@ -72,7 +72,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -81,7 +80,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl2_vulkan/main.cpp b/examples/example_sdl2_vulkan/main.cpp index c62222577..6a8f68e8b 100644 --- a/examples/example_sdl2_vulkan/main.cpp +++ b/examples/example_sdl2_vulkan/main.cpp @@ -417,7 +417,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -426,7 +425,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl3_opengl3/main.cpp b/examples/example_sdl3_opengl3/main.cpp index 46eeb59b5..ef2573324 100644 --- a/examples/example_sdl3_opengl3/main.cpp +++ b/examples/example_sdl3_opengl3/main.cpp @@ -106,7 +106,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -116,7 +115,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl3_sdlgpu3/main.cpp b/examples/example_sdl3_sdlgpu3/main.cpp index 4178f94b5..4051edc3d 100644 --- a/examples/example_sdl3_sdlgpu3/main.cpp +++ b/examples/example_sdl3_sdlgpu3/main.cpp @@ -83,7 +83,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -92,7 +91,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl3_sdlrenderer3/main.cpp b/examples/example_sdl3_sdlrenderer3/main.cpp index 9a2732b45..2821f7b80 100644 --- a/examples/example_sdl3_sdlrenderer3/main.cpp +++ b/examples/example_sdl3_sdlrenderer3/main.cpp @@ -68,7 +68,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -78,7 +77,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl3_vulkan/main.cpp b/examples/example_sdl3_vulkan/main.cpp index 183965a84..bdaf3aa02 100644 --- a/examples/example_sdl3_vulkan/main.cpp +++ b/examples/example_sdl3_vulkan/main.cpp @@ -422,7 +422,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -431,7 +430,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_directx10/main.cpp b/examples/example_win32_directx10/main.cpp index 21198da9a..d6b23e63f 100644 --- a/examples/example_win32_directx10/main.cpp +++ b/examples/example_win32_directx10/main.cpp @@ -67,7 +67,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -76,7 +75,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_directx11/main.cpp b/examples/example_win32_directx11/main.cpp index 5285df102..838e5493b 100644 --- a/examples/example_win32_directx11/main.cpp +++ b/examples/example_win32_directx11/main.cpp @@ -67,7 +67,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -76,7 +75,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 2df2751c5..ce672f353 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -161,7 +161,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -170,7 +169,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_directx9/main.cpp b/examples/example_win32_directx9/main.cpp index 422248bcb..d13351b0a 100644 --- a/examples/example_win32_directx9/main.cpp +++ b/examples/example_win32_directx9/main.cpp @@ -65,7 +65,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -74,7 +73,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_opengl3/main.cpp b/examples/example_win32_opengl3/main.cpp index 8ecd27a20..b516de7a7 100644 --- a/examples/example_win32_opengl3/main.cpp +++ b/examples/example_win32_opengl3/main.cpp @@ -75,7 +75,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -84,7 +83,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_vulkan/main.cpp b/examples/example_win32_vulkan/main.cpp index 427a2494a..8699b61a9 100644 --- a/examples/example_win32_vulkan/main.cpp +++ b/examples/example_win32_vulkan/main.cpp @@ -408,7 +408,6 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! @@ -417,7 +416,7 @@ int main(int, char**) //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); //IM_ASSERT(font != nullptr); // Our state From 9da3e6696abce7f294d0d4f3bfb1db6ce80c6265 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 6 Jun 2025 15:54:25 +0200 Subject: [PATCH 178/191] Backends: SDL2: added ImGui_ImplSDL2_GetDpiScaleForDisplay(), ImGui_ImplSDL2_GetContentScaleForWindow() helpers. --- backends/imgui_impl_sdl2.cpp | 21 +++++++++++++++++++++ backends/imgui_impl_sdl2.h | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/backends/imgui_impl_sdl2.cpp b/backends/imgui_impl_sdl2.cpp index fa2714bb9..3780b322b 100644 --- a/backends/imgui_impl_sdl2.cpp +++ b/backends/imgui_impl_sdl2.cpp @@ -21,6 +21,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: Added ImGui_ImplSDL2_GetContentScaleForWindow(SDL_Window* window) and ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) helper to facilitate making DPI-aware apps. // 2025-04-09: Don't attempt to call SDL_CaptureMouse() on drivers where we don't call SDL_GetGlobalMouseState(). (#8561) // 2025-03-21: Fill gamepad inputs and set ImGuiBackendFlags_HasGamepad regardless of ImGuiConfigFlags_NavEnableGamepad being set. // 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468) @@ -702,6 +703,26 @@ static void ImGui_ImplSDL2_UpdateMouseCursor() } } +// - On Windows the process needs to be marked DPI-aware!! SDL2 doesn't do it by default. You can call ::SetProcessDPIAware() or call ImGui_ImplWin32_EnableDpiAwareness() from Win32 backend. +// - Apple platforms use FramebufferScale so we always return 1.0f. +// - Some accessibility applications are declaring virtual monitors with a DPI of 0.0f, see #7902. We preserve this value for caller to handle. +float ImGui_ImplSDL2_GetContentScaleForWindow(SDL_Window* window) +{ + return ImGui_ImplSDL2_GetContentScaleForDisplay(SDL_GetWindowDisplayIndex(window)); +} + +float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) +{ +#if SDL_HAS_PER_MONITOR_DPI +#ifndef __APPLE__ + float dpi = 0.0f; + if (SDL_GetDisplayDPI(display_index, &dpi, nullptr, nullptr) == 0) + return dpi / 96.0f; +#endif +#endif + return 1.0f; +} + static void ImGui_ImplSDL2_CloseGamepads() { ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); diff --git a/backends/imgui_impl_sdl2.h b/backends/imgui_impl_sdl2.h index 9f7b551fc..3c0a4a7e4 100644 --- a/backends/imgui_impl_sdl2.h +++ b/backends/imgui_impl_sdl2.h @@ -38,6 +38,10 @@ IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown(); IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(); IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event); +// DPI-related helpers (optional) +IMGUI_IMPL_API float ImGui_ImplSDL2_GetContentScaleForWindow(SDL_Window* window); +IMGUI_IMPL_API float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index); + // Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. You may override this. // When using manual mode, caller is responsible for opening/closing gamepad. enum ImGui_ImplSDL2_GamepadMode { ImGui_ImplSDL2_GamepadMode_AutoFirst, ImGui_ImplSDL2_GamepadMode_AutoAll, ImGui_ImplSDL2_GamepadMode_Manual }; From 8269924c33f3af562a83a5eada77b924bab5d7b1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 6 Jun 2025 16:14:22 +0200 Subject: [PATCH 179/191] Backends: GLFW: added ImGui_ImplGlfw_GetContentScaleForMonitor(), ImGui_ImplGlfw_GetContentScaleForWindow() helpers. # Conflicts: # backends/imgui_impl_glfw.cpp --- backends/imgui_impl_glfw.cpp | 28 ++++++++++++++++++++++++++++ backends/imgui_impl_glfw.h | 3 +++ 2 files changed, 31 insertions(+) diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index ba7569970..70369f38b 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -28,6 +28,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-06-11: Added ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window) and ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor) helper to facilitate making DPI-aware apps. // 2025-03-10: Map GLFW_KEY_WORLD_1 and GLFW_KEY_WORLD_2 into ImGuiKey_Oem102. // 2025-03-03: Fixed clipboard handler assertion when using GLFW <= 3.2.1 compiled with asserts enabled. // 2024-08-22: Moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO: @@ -840,6 +841,33 @@ static void ImGui_ImplGlfw_UpdateGamepads() #undef MAP_ANALOG } +// - On Windows the process needs to be marked DPI-aware!! SDL2 doesn't do it by default. You can call ::SetProcessDPIAware() or call ImGui_ImplWin32_EnableDpiAwareness() from Win32 backend. +// - Apple platforms use FramebufferScale so we always return 1.0f. +// - Some accessibility applications are declaring virtual monitors with a DPI of 0.0f, see #7902. We preserve this value for caller to handle. +float ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window) +{ +#if GLFW_HAS_PER_MONITOR_DPI && !defined(__APPLE__) + float x_scale, y_scale; + glfwGetWindowContentScale(window, &x_scale, &y_scale); + return x_scale; +#else + IM_UNUSED(window); + return 1.0f; +#endif +} + +float ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor) +{ +#if GLFW_HAS_PER_MONITOR_DPI && !defined(__APPLE__) + float x_scale, y_scale; + glfwGetMonitorContentScale(monitor, &x_scale, &y_scale); + return x_scale; +#else + IM_UNUSED(monitor); + return 1.0f; +#endif +} + void ImGui_ImplGlfw_NewFrame() { ImGuiIO& io = ImGui::GetIO(); diff --git a/backends/imgui_impl_glfw.h b/backends/imgui_impl_glfw.h index e203b556f..1ef9ade17 100644 --- a/backends/imgui_impl_glfw.h +++ b/backends/imgui_impl_glfw.h @@ -62,5 +62,8 @@ IMGUI_IMPL_API void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int // GLFW helpers IMGUI_IMPL_API void ImGui_ImplGlfw_Sleep(int milliseconds); +IMGUI_IMPL_API float ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window); +IMGUI_IMPL_API float ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor); + #endif // #ifndef IMGUI_DISABLE From b98e92839c997fcab03f2be4453f42afb8676888 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 11 Jun 2025 16:17:54 +0200 Subject: [PATCH 180/191] Backends: SDL2, SDL3, GLFW: Backport small part of c90ea13 from docking. --- backends/imgui_impl_glfw.cpp | 22 ++++++++++++------- backends/imgui_impl_sdl2.cpp | 41 +++++++++++++++++++++--------------- backends/imgui_impl_sdl3.cpp | 26 ++++++++++++++--------- 3 files changed, 54 insertions(+), 35 deletions(-) diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 70369f38b..aac9eca2e 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -868,20 +868,26 @@ float ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor) #endif } +static void ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(GLFWwindow* window, ImVec2* out_size, ImVec2* out_framebuffer_scale) +{ + int w, h; + int display_w, display_h; + glfwGetWindowSize(window, &w, &h); + glfwGetFramebufferSize(window, &display_w, &display_h); + if (out_size != nullptr) + *out_size = ImVec2((float)w, (float)h); + if (out_framebuffer_scale != nullptr) + *out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / (float)w, (float)display_h / (float)h) : ImVec2(1.0f, 1.0f); +} + void ImGui_ImplGlfw_NewFrame() { ImGuiIO& io = ImGui::GetIO(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplGlfw_InitForXXX()?"); - // Setup display size (every frame to accommodate for window resizing) - int w, h; - int display_w, display_h; - glfwGetWindowSize(bd->Window, &w, &h); - glfwGetFramebufferSize(bd->Window, &display_w, &display_h); - io.DisplaySize = ImVec2((float)w, (float)h); - if (w > 0 && h > 0) - io.DisplayFramebufferScale = ImVec2((float)display_w / (float)w, (float)display_h / (float)h); + // Setup main viewport size (every frame to accommodate for window resizing) + ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(bd->Window, &io.DisplaySize, &io.DisplayFramebufferScale); // Setup time step // (Accept glfwGetTime() not returning a monotonically increasing value. Seems to happens on disconnecting peripherals and probably on VMs and Emscripten, see #6491, #6189, #6114, #3644) diff --git a/backends/imgui_impl_sdl2.cpp b/backends/imgui_impl_sdl2.cpp index 3780b322b..84ecc55e0 100644 --- a/backends/imgui_impl_sdl2.cpp +++ b/backends/imgui_impl_sdl2.cpp @@ -720,6 +720,7 @@ float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) return dpi / 96.0f; #endif #endif + IM_UNUSED(display_index); return 1.0f; } @@ -825,29 +826,35 @@ static void ImGui_ImplSDL2_UpdateGamepads() ImGui_ImplSDL2_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown, SDL_CONTROLLER_AXIS_RIGHTY, +thumb_dead_zone, +32767); } +static void ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale(SDL_Window* window, SDL_Renderer* renderer, ImVec2* out_size, ImVec2* out_framebuffer_scale) +{ + int w, h; + int display_w, display_h; + SDL_GetWindowSize(window, &w, &h); + if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) + w = h = 0; + if (renderer != nullptr) + SDL_GetRendererOutputSize(renderer, &display_w, &display_h); +#if SDL_HAS_VULKAN + else if (SDL_GetWindowFlags(window) & SDL_WINDOW_VULKAN) + SDL_Vulkan_GetDrawableSize(window, &display_w, &display_h); +#endif + else + SDL_GL_GetDrawableSize(window, &display_w, &display_h); + if (out_size != nullptr) + *out_size = ImVec2((float)w, (float)h); + if (out_framebuffer_scale != nullptr) + *out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / w, (float)display_h / h) : ImVec2(1.0f, 1.0f); +} + void ImGui_ImplSDL2_NewFrame() { ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL2_Init()?"); ImGuiIO& io = ImGui::GetIO(); - // Setup display size (every frame to accommodate for window resizing) - int w, h; - int display_w, display_h; - SDL_GetWindowSize(bd->Window, &w, &h); - if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED) - w = h = 0; - if (bd->Renderer != nullptr) - SDL_GetRendererOutputSize(bd->Renderer, &display_w, &display_h); -#if SDL_HAS_VULKAN - else if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_VULKAN) - SDL_Vulkan_GetDrawableSize(bd->Window, &display_w, &display_h); -#endif - else - SDL_GL_GetDrawableSize(bd->Window, &display_w, &display_h); - io.DisplaySize = ImVec2((float)w, (float)h); - if (w > 0 && h > 0) - io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); + // Setup main viewport size (every frame to accommodate for window resizing) + ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale(bd->Window, bd->Renderer, &io.DisplaySize, &io.DisplayFramebufferScale); // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644) diff --git a/backends/imgui_impl_sdl3.cpp b/backends/imgui_impl_sdl3.cpp index 58cc11c44..4f099595a 100644 --- a/backends/imgui_impl_sdl3.cpp +++ b/backends/imgui_impl_sdl3.cpp @@ -771,22 +771,28 @@ static void ImGui_ImplSDL3_UpdateGamepads() ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown, SDL_GAMEPAD_AXIS_RIGHTY, +thumb_dead_zone, +32767); } +static void ImGui_ImplSDL3_GetWindowSizeAndFramebufferScale(SDL_Window* window, ImVec2* out_size, ImVec2* out_framebuffer_scale) +{ + int w, h; + int display_w, display_h; + SDL_GetWindowSize(window, &w, &h); + if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) + w = h = 0; + SDL_GetWindowSizeInPixels(window, &display_w, &display_h); + if (out_size != nullptr) + *out_size = ImVec2((float)w, (float)h); + if (out_framebuffer_scale != nullptr) + *out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / w, (float)display_h / h) : ImVec2(1.0f, 1.0f); +} + void ImGui_ImplSDL3_NewFrame() { ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?"); ImGuiIO& io = ImGui::GetIO(); - // Setup display size (every frame to accommodate for window resizing) - int w, h; - int display_w, display_h; - SDL_GetWindowSize(bd->Window, &w, &h); - if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED) - w = h = 0; - SDL_GetWindowSizeInPixels(bd->Window, &display_w, &display_h); - io.DisplaySize = ImVec2((float)w, (float)h); - if (w > 0 && h > 0) - io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); + // Setup main viewport size (every frame to accommodate for window resizing) + ImGui_ImplSDL3_GetWindowSizeAndFramebufferScale(bd->Window, &io.DisplaySize, &io.DisplayFramebufferScale); // Setup time step (we could also use SDL_GetTicksNS() available since SDL3) // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644) From bc394410a2ad10bc36575eeaff1ec3918ddee830 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 2 Jun 2025 15:32:54 +0200 Subject: [PATCH 181/191] Examples: Win32+DX9/DX10/DX11/DX12, SDL2+DX11/OpenGL2/OpenGL3/SDLRenderer/Vulkan, SDL3+OpenGL/SDLGPU/SDLRenderer/Vulkan: made example DPI aware by default. (master + docking: partial support for multi-dpi by scaling fonts + viewports but not style) We don't bother with WIN32_LEAN_AND_MEAN. # Conflicts: # examples/example_glfw_opengl3/main.cpp # examples/example_sdl2_directx11/main.cpp # examples/example_sdl2_opengl2/main.cpp # examples/example_sdl2_opengl3/main.cpp # examples/example_sdl2_vulkan/main.cpp # examples/example_sdl3_opengl3/main.cpp # examples/example_sdl3_sdlgpu3/main.cpp # examples/example_sdl3_vulkan/main.cpp # examples/example_win32_directx10/main.cpp # examples/example_win32_directx11/main.cpp # examples/example_win32_directx12/main.cpp # examples/example_win32_directx9/main.cpp --- examples/example_glfw_opengl3/main.cpp | 8 +++++++- examples/example_sdl2_directx11/main.cpp | 11 ++++++++++- examples/example_sdl2_opengl2/main.cpp | 14 +++++++++++++- examples/example_sdl2_opengl3/main.cpp | 14 +++++++++++++- examples/example_sdl2_sdlrenderer2/main.cpp | 14 +++++++++++++- examples/example_sdl2_vulkan/main.cpp | 14 +++++++++++++- examples/example_sdl3_opengl3/main.cpp | 8 +++++++- examples/example_sdl3_sdlgpu3/main.cpp | 8 +++++++- examples/example_sdl3_sdlrenderer3/main.cpp | 8 +++++++- examples/example_sdl3_vulkan/main.cpp | 8 +++++++- examples/example_win32_directx10/main.cpp | 12 ++++++++++-- examples/example_win32_directx11/main.cpp | 12 ++++++++++-- examples/example_win32_directx12/main.cpp | 12 ++++++++++-- examples/example_win32_directx9/main.cpp | 12 ++++++++++-- 14 files changed, 137 insertions(+), 18 deletions(-) diff --git a/examples/example_glfw_opengl3/main.cpp b/examples/example_glfw_opengl3/main.cpp index e133376bc..53dc69ce2 100644 --- a/examples/example_glfw_opengl3/main.cpp +++ b/examples/example_glfw_opengl3/main.cpp @@ -71,7 +71,8 @@ int main(int, char**) #endif // Create window with graphics context - GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+OpenGL3 example", nullptr, nullptr); + float main_scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor()); // Valid on GLFW 3.3+ only + GLFWwindow* window = glfwCreateWindow((int)(1280 * main_scale), (int)(800 * main_scale), "Dear ImGui GLFW+OpenGL3 example", nullptr, nullptr); if (window == nullptr) return 1; glfwMakeContextCurrent(window); @@ -88,6 +89,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplGlfw_InitForOpenGL(window, true); #ifdef __EMSCRIPTEN__ diff --git a/examples/example_sdl2_directx11/main.cpp b/examples/example_sdl2_directx11/main.cpp index fdb7ad458..d2e151e54 100644 --- a/examples/example_sdl2_directx11/main.cpp +++ b/examples/example_sdl2_directx11/main.cpp @@ -33,6 +33,9 @@ int main(int, char**) // Setup SDL // (Some versions of SDL before <2.0.10 appears to have performance/stalling issues on a minority of Windows systems, // depending on whether SDL_INIT_GAMECONTROLLER is enabled or disabled.. updating to the latest version of SDL is recommended!) +#ifdef _WIN32 + ::SetProcessDPIAware(); +#endif if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { printf("Error: %s\n", SDL_GetError()); @@ -45,8 +48,9 @@ int main(int, char**) #endif // Setup window + float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+DirectX11 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+DirectX11 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); @@ -76,6 +80,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForD3D(window); ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); diff --git a/examples/example_sdl2_opengl2/main.cpp b/examples/example_sdl2_opengl2/main.cpp index feba216f0..1fe0c5864 100644 --- a/examples/example_sdl2_opengl2/main.cpp +++ b/examples/example_sdl2_opengl2/main.cpp @@ -17,11 +17,17 @@ #include #include #include +#ifdef _WIN32 +#include // SetProcessDPIAware() +#endif // Main code int main(int, char**) { // Setup SDL +#ifdef _WIN32 + ::SetProcessDPIAware(); +#endif if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { printf("Error: %s\n", SDL_GetError()); @@ -39,8 +45,9 @@ int main(int, char**) SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); @@ -62,6 +69,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL2_Init(); diff --git a/examples/example_sdl2_opengl3/main.cpp b/examples/example_sdl2_opengl3/main.cpp index 7b7b17ad2..63934cd88 100644 --- a/examples/example_sdl2_opengl3/main.cpp +++ b/examples/example_sdl2_opengl3/main.cpp @@ -17,6 +17,9 @@ #else #include #endif +#ifdef _WIN32 +#include // SetProcessDPIAware() +#endif // This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. #ifdef __EMSCRIPTEN__ @@ -27,6 +30,9 @@ int main(int, char**) { // Setup SDL +#ifdef _WIN32 + ::SetProcessDPIAware(); +#endif if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { printf("Error: %s\n", SDL_GetError()); @@ -73,8 +79,9 @@ int main(int, char**) SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); @@ -102,6 +109,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init(glsl_version); diff --git a/examples/example_sdl2_sdlrenderer2/main.cpp b/examples/example_sdl2_sdlrenderer2/main.cpp index eee25c272..564729bad 100644 --- a/examples/example_sdl2_sdlrenderer2/main.cpp +++ b/examples/example_sdl2_sdlrenderer2/main.cpp @@ -15,6 +15,9 @@ #include "imgui_impl_sdlrenderer2.h" #include #include +#ifdef _WIN32 +#include // SetProcessDPIAware() +#endif #if !SDL_VERSION_ATLEAST(2,0,17) #error This backend requires SDL 2.0.17+ because of SDL_RenderGeometry() function @@ -24,6 +27,9 @@ int main(int, char**) { // Setup SDL +#ifdef _WIN32 + ::SetProcessDPIAware(); +#endif if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { printf("Error: %s\n", SDL_GetError()); @@ -36,8 +42,9 @@ int main(int, char**) #endif // Create window with SDL_Renderer graphics context + float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+SDL_Renderer example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+SDL_Renderer example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); @@ -64,6 +71,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForSDLRenderer(window, renderer); ImGui_ImplSDLRenderer2_Init(renderer); diff --git a/examples/example_sdl2_vulkan/main.cpp b/examples/example_sdl2_vulkan/main.cpp index 6a8f68e8b..807751b01 100644 --- a/examples/example_sdl2_vulkan/main.cpp +++ b/examples/example_sdl2_vulkan/main.cpp @@ -20,6 +20,9 @@ #include // abort #include #include +#ifdef _WIN32 +#include // SetProcessDPIAware() +#endif // Volk headers #ifdef IMGUI_IMPL_VULKAN_USE_VOLK @@ -340,6 +343,9 @@ static void FramePresent(ImGui_ImplVulkanH_Window* wd) int main(int, char**) { // Setup SDL +#ifdef _WIN32 + ::SetProcessDPIAware(); +#endif if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { printf("Error: %s\n", SDL_GetError()); @@ -352,8 +358,9 @@ int main(int, char**) #endif // Create window with Vulkan graphics context + float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+Vulkan example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+Vulkan example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); @@ -393,6 +400,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForVulkan(window); ImGui_ImplVulkan_InitInfo init_info = {}; diff --git a/examples/example_sdl3_opengl3/main.cpp b/examples/example_sdl3_opengl3/main.cpp index ef2573324..4af533fa3 100644 --- a/examples/example_sdl3_opengl3/main.cpp +++ b/examples/example_sdl3_opengl3/main.cpp @@ -68,8 +68,9 @@ int main(int, char**) SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+OpenGL3 example", 1280, 720, window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+OpenGL3 example", (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); @@ -98,6 +99,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplSDL3_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init(glsl_version); diff --git a/examples/example_sdl3_sdlgpu3/main.cpp b/examples/example_sdl3_sdlgpu3/main.cpp index 4051edc3d..e6678899b 100644 --- a/examples/example_sdl3_sdlgpu3/main.cpp +++ b/examples/example_sdl3_sdlgpu3/main.cpp @@ -34,8 +34,9 @@ int main(int, char**) } // Create SDL window graphics context + float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+SDL_GPU example", 1280, 720, window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+SDL_GPU example", (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); @@ -71,6 +72,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplSDL3_InitForSDLGPU(window); ImGui_ImplSDLGPU3_InitInfo init_info = {}; diff --git a/examples/example_sdl3_sdlrenderer3/main.cpp b/examples/example_sdl3_sdlrenderer3/main.cpp index 2821f7b80..0c8d67b53 100644 --- a/examples/example_sdl3_sdlrenderer3/main.cpp +++ b/examples/example_sdl3_sdlrenderer3/main.cpp @@ -32,8 +32,9 @@ int main(int, char**) } // Create window with SDL_Renderer graphics context + float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+SDL_Renderer example", 1280, 720, window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+SDL_Renderer example", (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); @@ -60,6 +61,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplSDL3_InitForSDLRenderer(window, renderer); ImGui_ImplSDLRenderer3_Init(renderer); diff --git a/examples/example_sdl3_vulkan/main.cpp b/examples/example_sdl3_vulkan/main.cpp index bdaf3aa02..895681544 100644 --- a/examples/example_sdl3_vulkan/main.cpp +++ b/examples/example_sdl3_vulkan/main.cpp @@ -353,8 +353,9 @@ int main(int, char**) } // Create window with Vulkan graphics context + float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); SDL_WindowFlags window_flags = SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+Vulkan example", 1280, 720, window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+Vulkan example", (int)(1280 * main_scale), (int)(720 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); @@ -398,6 +399,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplSDL3_InitForVulkan(window); ImGui_ImplVulkan_InitInfo init_info = {}; diff --git a/examples/example_win32_directx10/main.cpp b/examples/example_win32_directx10/main.cpp index d6b23e63f..2b40e090b 100644 --- a/examples/example_win32_directx10/main.cpp +++ b/examples/example_win32_directx10/main.cpp @@ -30,11 +30,14 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Main code int main(int, char**) { + // Make process DPI aware and obtain main monitor scale + ImGui_ImplWin32_EnableDpiAwareness(); + float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(::MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + // Create application window - //ImGui_ImplWin32_EnableDpiAwareness(); WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr }; ::RegisterClassExW(&wc); - HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX10 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr); + HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX10 Example", WS_OVERLAPPEDWINDOW, 100, 100, (int)(1280 * main_scale), (int)(800 * main_scale), nullptr, nullptr, wc.hInstance, nullptr); // Initialize Direct3D if (!CreateDeviceD3D(hwnd)) @@ -59,6 +62,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX10_Init(g_pd3dDevice); diff --git a/examples/example_win32_directx11/main.cpp b/examples/example_win32_directx11/main.cpp index 838e5493b..fbb6833fd 100644 --- a/examples/example_win32_directx11/main.cpp +++ b/examples/example_win32_directx11/main.cpp @@ -30,11 +30,14 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Main code int main(int, char**) { + // Make process DPI aware and obtain main monitor scale + ImGui_ImplWin32_EnableDpiAwareness(); + float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(::MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + // Create application window - //ImGui_ImplWin32_EnableDpiAwareness(); WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr }; ::RegisterClassExW(&wc); - HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX11 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr); + HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX11 Example", WS_OVERLAPPEDWINDOW, 100, 100, (int)(1280 * main_scale), (int)(800 * main_scale), nullptr, nullptr, wc.hInstance, nullptr); // Initialize Direct3D if (!CreateDeviceD3D(hwnd)) @@ -59,6 +62,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index ce672f353..80aef78c5 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -109,11 +109,14 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Main code int main(int, char**) { + // Make process DPI aware and obtain main monitor scale + ImGui_ImplWin32_EnableDpiAwareness(); + float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(::MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + // Create application window - //ImGui_ImplWin32_EnableDpiAwareness(); WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr }; ::RegisterClassExW(&wc); - HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX12 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr); + HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX12 Example", WS_OVERLAPPEDWINDOW, 100, 100, (int)(1280 * main_scale), (int)(800 * main_scale), nullptr, nullptr, wc.hInstance, nullptr); // Initialize Direct3D if (!CreateDeviceD3D(hwnd)) @@ -138,6 +141,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplWin32_Init(hwnd); diff --git a/examples/example_win32_directx9/main.cpp b/examples/example_win32_directx9/main.cpp index d13351b0a..b7e905360 100644 --- a/examples/example_win32_directx9/main.cpp +++ b/examples/example_win32_directx9/main.cpp @@ -28,11 +28,14 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Main code int main(int, char**) { + // Make process DPI aware and obtain main monitor scale + ImGui_ImplWin32_EnableDpiAwareness(); + float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(::MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + // Create application window - //ImGui_ImplWin32_EnableDpiAwareness(); WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr }; ::RegisterClassExW(&wc); - HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr); + HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, (int)(1280 * main_scale), (int)(800 * main_scale), nullptr, nullptr, wc.hInstance, nullptr); // Initialize Direct3D if (!CreateDeviceD3D(hwnd)) @@ -57,6 +60,11 @@ int main(int, char**) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + // Setup Platform/Renderer backends ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX9_Init(g_pd3dDevice); From 02f58b3207b22c5b038f81bc0d2b6adbcebcafa1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 6 Jun 2025 16:33:02 +0200 Subject: [PATCH 182/191] Fonts: AddFont() functions now allow size_pixels==0.0f (only required when using certain functions) Fonts: AddFont() funcitons allow size_pixels==0 for merged fonts. --- imgui.h | 8 ++++---- imgui_draw.cpp | 5 ++++- misc/freetype/imgui_freetype.cpp | 6 ++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/imgui.h b/imgui.h index cdcdae68b..2a07af801 100644 --- a/imgui.h +++ b/imgui.h @@ -3577,10 +3577,10 @@ struct ImFontAtlas IMGUI_API ~ImFontAtlas(); IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); - IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); - IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, 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, 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, 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 ImFont* AddFontFromFileTTF(const char* filename, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + 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 (input fonts, output glyphs/textures) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c4f342e54..4bde225d6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4533,11 +4533,14 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* } src->FontLoaderData = bd_font_data; + if (src->MergeMode && src->SizePixels == 0.0f) + src->SizePixels = src->DstFont->Sources[0]->SizePixels; + if (src->SizePixels >= 0.0f) bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); else bd_font_data->ScaleFactor = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); - if (src != src->DstFont->Sources[0]) + if (src->MergeMode && src->SizePixels != 0.0f) bd_font_data->ScaleFactor *= src->SizePixels / src->DstFont->Sources[0]->SizePixels; // FIXME-NEWATLAS: Should tidy up that a bit return true; diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 4c10ff42d..a6cd13d0d 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -421,7 +421,9 @@ void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) { IM_UNUSED(atlas); - const float size = baked->Size * (src->SizePixels / baked->ContainerFont->Sources[0]->SizePixels); // FIXME-NEWATLAS: Should tidy up that a bit + float size = baked->Size; + if (src->MergeMode && src->SizePixels != 0.0f) + size *= (src->SizePixels / baked->ContainerFont->Sources[0]->SizePixels); ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; bd_font_data->BakedLastActivated = baked; @@ -637,7 +639,7 @@ static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state) #else state->svg->setMatrix(state->svg->matrix().identity()); // Reset the svg matrix to the default value state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated -#endif +#endif state->err = FT_Err_Ok; return state->err; } From c18301f356caea8ea47db3375b681cf934d90304 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 6 Jun 2025 16:36:58 +0200 Subject: [PATCH 183/191] Examples: remove explicit font sizes from AddFontXXX() calls. Add commented out style.FontSizeBase assignment. --- examples/example_allegro5/main.cpp | 11 ++++++----- examples/example_apple_metal/main.mm | 11 ++++++----- examples/example_apple_opengl2/main.mm | 11 ++++++----- examples/example_glfw_metal/main.mm | 11 ++++++----- examples/example_glfw_opengl2/main.cpp | 11 ++++++----- examples/example_glfw_opengl3/main.cpp | 11 ++++++----- examples/example_glfw_vulkan/main.cpp | 11 ++++++----- examples/example_glfw_wgpu/main.cpp | 13 +++++++------ examples/example_glut_opengl2/main.cpp | 11 ++++++----- examples/example_sdl2_directx11/main.cpp | 11 ++++++----- examples/example_sdl2_metal/main.mm | 11 ++++++----- examples/example_sdl2_opengl2/main.cpp | 11 ++++++----- examples/example_sdl2_opengl3/main.cpp | 11 ++++++----- examples/example_sdl2_sdlrenderer2/main.cpp | 11 ++++++----- examples/example_sdl2_vulkan/main.cpp | 11 ++++++----- examples/example_sdl3_opengl3/main.cpp | 11 ++++++----- examples/example_sdl3_sdlgpu3/main.cpp | 11 ++++++----- examples/example_sdl3_sdlrenderer3/main.cpp | 11 ++++++----- examples/example_sdl3_vulkan/main.cpp | 11 ++++++----- examples/example_win32_directx10/main.cpp | 11 ++++++----- examples/example_win32_directx11/main.cpp | 11 ++++++----- examples/example_win32_directx12/main.cpp | 11 ++++++----- examples/example_win32_directx9/main.cpp | 11 ++++++----- examples/example_win32_opengl3/main.cpp | 11 ++++++----- examples/example_win32_vulkan/main.cpp | 11 ++++++----- 25 files changed, 151 insertions(+), 126 deletions(-) diff --git a/examples/example_allegro5/main.cpp b/examples/example_allegro5/main.cpp index 67fc7ee65..02db84a48 100644 --- a/examples/example_allegro5/main.cpp +++ b/examples/example_allegro5/main.cpp @@ -55,12 +55,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); bool show_demo_window = true; diff --git a/examples/example_apple_metal/main.mm b/examples/example_apple_metal/main.mm index 9b9db8c61..301a2b4ad 100644 --- a/examples/example_apple_metal/main.mm +++ b/examples/example_apple_metal/main.mm @@ -75,12 +75,13 @@ // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); return self; diff --git a/examples/example_apple_opengl2/main.mm b/examples/example_apple_opengl2/main.mm index 5f1fd1e50..c3f0c313b 100644 --- a/examples/example_apple_opengl2/main.mm +++ b/examples/example_apple_opengl2/main.mm @@ -63,12 +63,13 @@ // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); } diff --git a/examples/example_glfw_metal/main.mm b/examples/example_glfw_metal/main.mm index c4380d5c3..ef314702a 100644 --- a/examples/example_glfw_metal/main.mm +++ b/examples/example_glfw_metal/main.mm @@ -45,12 +45,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Setup window diff --git a/examples/example_glfw_opengl2/main.cpp b/examples/example_glfw_opengl2/main.cpp index af4edbe2b..83fcab65f 100644 --- a/examples/example_glfw_opengl2/main.cpp +++ b/examples/example_glfw_opengl2/main.cpp @@ -68,12 +68,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_glfw_opengl3/main.cpp b/examples/example_glfw_opengl3/main.cpp index 53dc69ce2..4bd7bc591 100644 --- a/examples/example_glfw_opengl3/main.cpp +++ b/examples/example_glfw_opengl3/main.cpp @@ -109,12 +109,13 @@ int main(int, char**) // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index 10b9ab2ed..19766b9f3 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -420,12 +420,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_glfw_wgpu/main.cpp b/examples/example_glfw_wgpu/main.cpp index 58c5b34f7..c150b5961 100644 --- a/examples/example_glfw_wgpu/main.cpp +++ b/examples/example_glfw_wgpu/main.cpp @@ -119,13 +119,14 @@ int main(int, char**) // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! // - Emscripten allows preloading a file or folder to be accessible at runtime. See Makefile for details. //io.Fonts->AddFontDefault(); + //style.FontSizeBase = 20.0f; #ifndef IMGUI_DISABLE_FILE_FUNCTIONS - //io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 15.0f); - //io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf", 10.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf"); //IM_ASSERT(font != nullptr); #endif diff --git a/examples/example_glut_opengl2/main.cpp b/examples/example_glut_opengl2/main.cpp index 5720f8558..69f85a245 100644 --- a/examples/example_glut_opengl2/main.cpp +++ b/examples/example_glut_opengl2/main.cpp @@ -86,12 +86,13 @@ int main(int argc, char** argv) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Main loop diff --git a/examples/example_sdl2_directx11/main.cpp b/examples/example_sdl2_directx11/main.cpp index d2e151e54..194dd0e03 100644 --- a/examples/example_sdl2_directx11/main.cpp +++ b/examples/example_sdl2_directx11/main.cpp @@ -96,12 +96,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl2_metal/main.mm b/examples/example_sdl2_metal/main.mm index 4dc4788a8..c1750b16b 100644 --- a/examples/example_sdl2_metal/main.mm +++ b/examples/example_sdl2_metal/main.mm @@ -36,12 +36,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Setup SDL diff --git a/examples/example_sdl2_opengl2/main.cpp b/examples/example_sdl2_opengl2/main.cpp index 1fe0c5864..c8363fece 100644 --- a/examples/example_sdl2_opengl2/main.cpp +++ b/examples/example_sdl2_opengl2/main.cpp @@ -85,12 +85,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl2_opengl3/main.cpp b/examples/example_sdl2_opengl3/main.cpp index 63934cd88..16a73deb6 100644 --- a/examples/example_sdl2_opengl3/main.cpp +++ b/examples/example_sdl2_opengl3/main.cpp @@ -126,12 +126,13 @@ int main(int, char**) // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl2_sdlrenderer2/main.cpp b/examples/example_sdl2_sdlrenderer2/main.cpp index 564729bad..e456b2e9e 100644 --- a/examples/example_sdl2_sdlrenderer2/main.cpp +++ b/examples/example_sdl2_sdlrenderer2/main.cpp @@ -87,12 +87,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl2_vulkan/main.cpp b/examples/example_sdl2_vulkan/main.cpp index 807751b01..300543fba 100644 --- a/examples/example_sdl2_vulkan/main.cpp +++ b/examples/example_sdl2_vulkan/main.cpp @@ -432,12 +432,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl3_opengl3/main.cpp b/examples/example_sdl3_opengl3/main.cpp index 4af533fa3..cfd6f6a1a 100644 --- a/examples/example_sdl3_opengl3/main.cpp +++ b/examples/example_sdl3_opengl3/main.cpp @@ -116,12 +116,13 @@ int main(int, char**) // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl3_sdlgpu3/main.cpp b/examples/example_sdl3_sdlgpu3/main.cpp index e6678899b..581b11c11 100644 --- a/examples/example_sdl3_sdlgpu3/main.cpp +++ b/examples/example_sdl3_sdlgpu3/main.cpp @@ -92,12 +92,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl3_sdlrenderer3/main.cpp b/examples/example_sdl3_sdlrenderer3/main.cpp index 0c8d67b53..6e39429d2 100644 --- a/examples/example_sdl3_sdlrenderer3/main.cpp +++ b/examples/example_sdl3_sdlrenderer3/main.cpp @@ -78,12 +78,13 @@ int main(int, char**) // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_sdl3_vulkan/main.cpp b/examples/example_sdl3_vulkan/main.cpp index 895681544..df7f5efbd 100644 --- a/examples/example_sdl3_vulkan/main.cpp +++ b/examples/example_sdl3_vulkan/main.cpp @@ -431,12 +431,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_directx10/main.cpp b/examples/example_win32_directx10/main.cpp index 2b40e090b..23033d6c1 100644 --- a/examples/example_win32_directx10/main.cpp +++ b/examples/example_win32_directx10/main.cpp @@ -78,12 +78,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_directx11/main.cpp b/examples/example_win32_directx11/main.cpp index fbb6833fd..c80114cfb 100644 --- a/examples/example_win32_directx11/main.cpp +++ b/examples/example_win32_directx11/main.cpp @@ -78,12 +78,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 80aef78c5..3a9ba4fb4 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -172,12 +172,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_directx9/main.cpp b/examples/example_win32_directx9/main.cpp index b7e905360..430a2b448 100644 --- a/examples/example_win32_directx9/main.cpp +++ b/examples/example_win32_directx9/main.cpp @@ -76,12 +76,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_opengl3/main.cpp b/examples/example_win32_opengl3/main.cpp index b516de7a7..820248c64 100644 --- a/examples/example_win32_opengl3/main.cpp +++ b/examples/example_win32_opengl3/main.cpp @@ -78,12 +78,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state diff --git a/examples/example_win32_vulkan/main.cpp b/examples/example_win32_vulkan/main.cpp index 8699b61a9..a98f16fe2 100644 --- a/examples/example_win32_vulkan/main.cpp +++ b/examples/example_win32_vulkan/main.cpp @@ -411,12 +411,13 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); // Our state From 2e67bd4de7a94a9f48a92bfcf487c92594f7cf5a Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 6 Jun 2025 18:07:27 +0200 Subject: [PATCH 184/191] Fonts: rename to ImFontAtlasBuildLegacyPreloadAllGlyphRanges(). --- imgui_draw.cpp | 6 +++--- imgui_internal.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 4bde225d6..b208b6836 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3368,7 +3368,7 @@ void ImFontAtlasBuildMain(ImFontAtlas* atlas) // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); if (atlas->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures - ImFontAtlasBuildPreloadAllGlyphRanges(atlas); + ImFontAtlasBuildLegacyPreloadAllGlyphRanges(atlas); atlas->TexIsBuilt = true; } @@ -3405,12 +3405,12 @@ void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* fon // Preload all glyph ranges for legacy backends. // This may lead to multiple texture creation which might be a little slower than before. -void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas) +void ImFontAtlasBuildLegacyPreloadAllGlyphRanges(ImFontAtlas* atlas) { atlas->Builder->PreloadedAllGlyphsRanges = true; for (ImFont* font : atlas->Fonts) { - ImFontBaked* baked = font->GetFontBaked(font->Sources[0]->SizePixels); + ImFontBaked* baked = font->GetFontBaked(font->LegacySize); if (font->FallbackChar != 0) baked->FindGlyph(font->FallbackChar); if (font->EllipsisChar != 0) diff --git a/imgui_internal.h b/imgui_internal.h index e344cbccf..b80bf50b6 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3807,7 +3807,7 @@ IMGUI_API void ImFontAtlasTextureCompact(ImFontAtlas* atlas); IMGUI_API ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); -IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy +IMGUI_API void ImFontAtlasBuildLegacyPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v); IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); From 573f08135d8ce0d8cde6db2cd0156b2b602e137e Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 10 Jun 2025 17:47:30 +0200 Subject: [PATCH 185/191] Fonts: fixed PopFont() broken recovery. "misc_recover_1" test would assert in EndFrame() --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 39a79a398..faf8ae690 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8812,7 +8812,7 @@ void ImGui::PushFont(ImFont* font, float font_size_base) void ImGui::PopFont() { ImGuiContext& g = *GImGui; - if (g.FontStack.Size <= 0 && g.WithinFrameScope) + if (g.FontStack.Size <= 0) { IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); return; From 0e769c541835af126e344f9a4d0e3f19d6ba9163 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 10 Jun 2025 17:56:09 +0200 Subject: [PATCH 186/191] Fonts: amend UpdateCurentFontSize() early out optimization. --- imgui.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index faf8ae690..e97f5e6fb 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8739,8 +8739,15 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) ImGuiWindow* window = g.CurrentWindow; g.Style.FontSizeBase = g.FontSizeBase; + + // Early out to avoid hidden window keeping bakes referenced and out of GC reach. + // However this would leave a pretty subtle and damning error surface area if g.FontBaked was mismatching, so for now we null it. if (window != NULL && window->SkipItems) - return; + if (g.CurrentTable == NULL || g.CurrentTable->CurrentColumn != -1) // See 8465#issuecomment-2951509561. Ideally the SkipItems=true in tables would be amended with extra data. + { + g.FontBaked = NULL; + return; + } // Restoring is pretty much only used by PopFont()/PopFontSize() float final_size = (restore_font_size_after_scaling > 0.0f) ? restore_font_size_after_scaling : 0.0f; From 29fbf3c1ec458e5765c40762e6cfda2cb2ed81e7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 10 Jun 2025 18:09:44 +0200 Subject: [PATCH 187/191] Fonts: demote ImFont::GetFontBaked() as slighty internal. --- imgui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index 2a07af801..1c5dec57d 100644 --- a/imgui.h +++ b/imgui.h @@ -3791,7 +3791,6 @@ struct ImFont // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API ImFontBaked* GetFontBaked(float font_size, float density = -1.0f); // Get or create baked data for given size IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } const char* GetDebugName() const { return Sources.Size ? Sources[0]->Name : ""; } // Fill ImFontConfig::Name. @@ -3799,6 +3798,7 @@ struct ImFont // [Internal] Don't use! // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImFontBaked* GetFontBaked(float font_size, float density = -1.0f); // Get or create baked data for given size IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL); // utf8 IMGUI_API const char* CalcWordWrapPosition(float size, const char* text, const char* text_end, float wrap_width); IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip = NULL); From e1481a731d0fd9f68157cdfbce0b6e7627456aae Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 11 Jun 2025 13:20:17 +0200 Subject: [PATCH 188/191] Fonts: fixed NewFrame() when atlas builder has been created but fonts not added. Fixed GetCustomRect() after atlas clear. --- imgui.cpp | 2 +- imgui_draw.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index e97f5e6fb..ff2d18a67 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8668,7 +8668,7 @@ ImFont* ImGui::GetDefaultFont() { ImGuiContext& g = *GImGui; ImFontAtlas* atlas = g.IO.Fonts; - if (atlas->Builder == NULL) + if (atlas->Builder == NULL || atlas->Fonts.Size == 0) ImFontAtlasBuildMain(atlas); return g.IO.FontDefault ? g.IO.FontDefault : atlas->Fonts[0]; } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b208b6836..195f5bece 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4362,6 +4362,8 @@ ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId if (id == ImFontAtlasRectId_Invalid) return NULL; int index_idx = ImFontAtlasRectId_GetIndex(id); + if (atlas->Builder == NULL) + ImFontAtlasBuildInit(atlas); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; if (index_idx >= builder->RectsIndex.Size) return NULL; From cc3d4cab21ac1d7f2ca42a4de9512134f663b419 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 11 Jun 2025 14:38:45 +0200 Subject: [PATCH 189/191] (Breaking) renamed ImFontConfig::FontBuilderFlags -> FontLoaderFlags. ImFontAtlas::FontBuilderFlags -> FontLoaderFlags. ImGuiFreeTypeBuilderFlags -> ImGuiFreeTypeLoaderFlags. --- imgui.cpp | 14 ++++----- imgui.h | 14 +++++---- misc/freetype/imgui_freetype.cpp | 12 ++++---- misc/freetype/imgui_freetype.h | 51 ++++++++++++++++++++++---------- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index ff2d18a67..2909dfb8e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15810,7 +15810,7 @@ static void MetricsHelpMarker(const char* desc) } #ifdef IMGUI_ENABLE_FREETYPE -namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); IMGUI_API bool DebugEditFontBuilderFlags(unsigned int* p_font_builder_flags); } +namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); IMGUI_API bool DebugEditFontLoaderFlags(unsigned int* p_font_builder_flags); } #endif // [DEBUG] List fonts in a font atlas and display its texture @@ -15868,13 +15868,13 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) ImFontAtlasBuildSetupFontLoader(atlas, loader_freetype); if (loader_current == loader_freetype) { - unsigned int loader_flags = atlas->FontBuilderFlags; + unsigned int loader_flags = atlas->FontLoaderFlags; Text("Shared FreeType Loader Flags: 0x%08", loader_flags); - if (ImGuiFreeType::DebugEditFontBuilderFlags(&loader_flags)) + if (ImGuiFreeType::DebugEditFontLoaderFlags(&loader_flags)) { for (ImFont* font : atlas->Fonts) ImFontAtlasFontDestroyOutput(atlas, font); - atlas->FontBuilderFlags = loader_flags; + atlas->FontLoaderFlags = loader_flags; for (ImFont* font : atlas->Fonts) ImFontAtlasFontInitOutput(atlas, font); } @@ -16853,12 +16853,12 @@ void ImGui::DebugNodeFont(ImFont* font) #ifdef IMGUI_ENABLE_FREETYPE if (loader->Name != NULL && strcmp(loader->Name, "FreeType") == 0) { - unsigned int loader_flags = src->FontBuilderFlags; + unsigned int loader_flags = src->FontLoaderFlags; Text("FreeType Loader Flags: 0x%08X", loader_flags); - if (ImGuiFreeType::DebugEditFontBuilderFlags(&loader_flags)) + if (ImGuiFreeType::DebugEditFontLoaderFlags(&loader_flags)) { ImFontAtlasFontDestroyOutput(atlas, font); - src->FontBuilderFlags = loader_flags; + src->FontLoaderFlags = loader_flags; ImFontAtlasFontInitOutput(atlas, font); } } diff --git a/imgui.h b/imgui.h index 1c5dec57d..1d27953f2 100644 --- a/imgui.h +++ b/imgui.h @@ -29,9 +29,9 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.92.0 WIP" -#define IMGUI_VERSION_NUM 19197 -#define IMGUI_HAS_TABLE -#define IMGUI_HAS_TEXTURES // 1.92+ WIP branch with ImGuiBackendFlags_RendererHasTextures +#define IMGUI_VERSION_NUM 19198 +#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 +#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 /* @@ -3480,7 +3480,8 @@ struct ImFontConfig float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. // FIXME-NEWATLAS: Intentionally unscaled - unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. + unsigned int FontLoaderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. + //unsigned int FontBuilderFlags; // -- // [Renamed in 1.92] Ue FontLoaderFlags. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. float RasterizerDensity; // 1.0f // [LEGACY: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported] DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. @@ -3696,7 +3697,7 @@ struct ImFontAtlas const ImFontLoader* FontLoader; // Font loader opaque interface (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). Don't set directly! const char* FontLoaderName; // Font loader name (for display e.g. in About box) == FontLoader->Name void* FontLoaderData; // Font backend opaque storage - unsigned int FontBuilderFlags; // [FIXME: Should be called FontLoaderFlags] Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. . Per-font override is also available in ImFontConfig. + unsigned int FontLoaderFlags; // Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. Per-font override is also available in ImFontConfig). int RefCount; // Number of contexts using this atlas ImGuiContext* OwnerContext; // Context which own the atlas will be in charge of updating and destroying it. @@ -3710,7 +3711,8 @@ struct ImFontAtlas IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // ADDED AND OBSOLETED in 1.92.X #endif - //int TexDesiredWidth; // OBSOLETED in 1.92.X (force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) + //unsigned int FontBuilderFlags; // OBSOLETED in 1.92.X: Renamed to FontLoaderFlags. + //int TexDesiredWidth; // OBSOLETED in 1.92.X: Force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) //typedef ImFontAtlasRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.X //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index a6cd13d0d..35a277f7c 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -153,14 +153,14 @@ struct ImGui_ImplFreeType_Data struct ImGui_ImplFreeType_FontSrcData { // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. - bool InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeBuilderFlags extra_user_flags); + bool InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_user_flags); void CloseFont(); ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); } ~ImGui_ImplFreeType_FontSrcData() { CloseFont(); } // Members FT_Face FtFace; - ImGuiFreeTypeBuilderFlags UserFlags; // = ImFontConfig::FontBuilderFlags + ImGuiFreeTypeLoaderFlags UserFlags; // = ImFontConfig::FontLoaderFlags FT_Int32 LoadFlags; ImFontBaked* BakedLastActivated; }; @@ -172,7 +172,7 @@ struct ImGui_ImplFreeType_FontSrcBakedData ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); } }; -bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeBuilderFlags extra_font_builder_flags) +bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_font_loader_flags) { FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)src->FontData, (uint32_t)src->FontDataSize, (uint32_t)src->FontNo, &FtFace); if (error != 0) @@ -182,7 +182,7 @@ bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, ImFontConfi return false; // Convert to FreeType flags (NB: Bold and Oblique are processed separately) - UserFlags = (ImGuiFreeTypeBuilderFlags)(src->FontBuilderFlags | extra_font_builder_flags); + UserFlags = (ImGuiFreeTypeLoaderFlags)(src->FontLoaderFlags | extra_font_loader_flags); LoadFlags = 0; if ((UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) == 0) @@ -400,7 +400,7 @@ bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) IM_ASSERT(src->FontLoaderData == NULL); src->FontLoaderData = bd_font_data; - if (!bd_font_data->InitFont(bd->Library, src, (ImGuiFreeTypeBuilderFlags)atlas->FontBuilderFlags)) + if (!bd_font_data->InitFont(bd->Library, src, (ImGuiFreeTypeLoaderFlags)atlas->FontLoaderFlags)) { IM_DELETE(bd_font_data); src->FontLoaderData = NULL; @@ -587,7 +587,7 @@ void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* u GImGuiFreeTypeAllocatorUserData = user_data; } -bool ImGuiFreeType::DebugEditFontBuilderFlags(unsigned int* p_font_loader_flags) +bool ImGuiFreeType::DebugEditFontLoaderFlags(unsigned int* p_font_loader_flags) { bool edited = false; edited |= ImGui::CheckboxFlags("NoHinting", p_font_loader_flags, ImGuiFreeTypeBuilderFlags_NoHinting); diff --git a/misc/freetype/imgui_freetype.h b/misc/freetype/imgui_freetype.h index 2b4810e32..4f7306790 100644 --- a/misc/freetype/imgui_freetype.h +++ b/misc/freetype/imgui_freetype.h @@ -23,22 +23,41 @@ struct ImFontLoader; // - When disabled, FreeType generates blurrier glyphs, more or less matches the stb_truetype.h // - The Default hinting mode usually looks good, but may distort glyphs in an unusual way. // - The Light hinting mode generates fuzzier glyphs but better matches Microsoft's rasterizer. -// You can set those flags globally in ImFontAtlas::FontBuilderFlags -// You can set those flags on a per font basis in ImFontConfig::FontBuilderFlags -enum ImGuiFreeTypeBuilderFlags +// You can set those flags globally in ImFontAtlas::FontLoaderFlags +// You can set those flags on a per font basis in ImFontConfig::FontLoaderFlags +typedef unsigned int ImGuiFreeTypeLoaderFlags; +enum ImGuiFreeTypeLoaderFlags_ { - ImGuiFreeTypeBuilderFlags_NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes. - ImGuiFreeTypeBuilderFlags_NoAutoHint = 1 << 1, // Disable auto-hinter. - ImGuiFreeTypeBuilderFlags_ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter. - ImGuiFreeTypeBuilderFlags_LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text. - ImGuiFreeTypeBuilderFlags_MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output. - ImGuiFreeTypeBuilderFlags_Bold = 1 << 5, // Styling: Should we artificially embolden the font? - ImGuiFreeTypeBuilderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style? - ImGuiFreeTypeBuilderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results! - ImGuiFreeTypeBuilderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs - ImGuiFreeTypeBuilderFlags_Bitmap = 1 << 9 // Enable FreeType bitmap glyphs + ImGuiFreeTypeLoaderFlags_NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes. + ImGuiFreeTypeLoaderFlags_NoAutoHint = 1 << 1, // Disable auto-hinter. + ImGuiFreeTypeLoaderFlags_ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter. + ImGuiFreeTypeLoaderFlags_LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text. + ImGuiFreeTypeLoaderFlags_MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output. + ImGuiFreeTypeLoaderFlags_Bold = 1 << 5, // Styling: Should we artificially embolden the font? + ImGuiFreeTypeLoaderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style? + ImGuiFreeTypeLoaderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results! + ImGuiFreeTypeLoaderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs + ImGuiFreeTypeLoaderFlags_Bitmap = 1 << 9, // Enable FreeType bitmap glyphs + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiFreeTypeBuilderFlags_NoHinting = ImGuiFreeTypeLoaderFlags_NoHinting, + ImGuiFreeTypeBuilderFlags_NoAutoHint = ImGuiFreeTypeLoaderFlags_NoAutoHint, + ImGuiFreeTypeBuilderFlags_ForceAutoHint = ImGuiFreeTypeLoaderFlags_ForceAutoHint, + ImGuiFreeTypeBuilderFlags_LightHinting = ImGuiFreeTypeLoaderFlags_LightHinting, + ImGuiFreeTypeBuilderFlags_MonoHinting = ImGuiFreeTypeLoaderFlags_MonoHinting, + ImGuiFreeTypeBuilderFlags_Bold = ImGuiFreeTypeLoaderFlags_Bold, + ImGuiFreeTypeBuilderFlags_Oblique = ImGuiFreeTypeLoaderFlags_Oblique, + ImGuiFreeTypeBuilderFlags_Monochrome = ImGuiFreeTypeLoaderFlags_Monochrome, + ImGuiFreeTypeBuilderFlags_LoadColor = ImGuiFreeTypeLoaderFlags_LoadColor, + ImGuiFreeTypeBuilderFlags_Bitmap = ImGuiFreeTypeLoaderFlags_Bitmap, +#endif }; +// Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +typedef ImGuiFreeTypeLoaderFlags_ ImGuiFreeTypeBuilderFlags_; +#endif + namespace ImGuiFreeType { // This is automatically assigned when using '#define IMGUI_ENABLE_FREETYPE'. @@ -51,13 +70,13 @@ namespace ImGuiFreeType // However, as FreeType does lots of allocations we provide a way for the user to redirect it to a separate memory heap if desired. IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data = nullptr); - // Display UI to edit FontBuilderFlags in ImFontAtlas (shared) or ImFontConfig (single source) - IMGUI_API bool DebugEditFontBuilderFlags(unsigned int* p_font_loader_flags); + // Display UI to edit ImFontAtlas::FontLoaderFlags (shared) or ImFontConfig::FontLoaderFlags (single source) + IMGUI_API bool DebugEditFontLoaderFlags(ImGuiFreeTypeLoaderFlags* p_font_loader_flags); // Obsolete names (will be removed) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); // Renamed/changed in 1.92. Change 'io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' to 'io.Fonts.FontLoader = ImGuiFreeType::GetFontLoader()' if you need runtime selection. - //static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontBuilderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE' + //static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontLoaderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE' #endif } From 4acce8565602ef30f29fa13e24da1991c6fe951e Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 11 Jun 2025 17:12:07 +0200 Subject: [PATCH 190/191] Fonts: tweaks demo and exposure to sliders, etc. --- imgui.cpp | 5 +++-- imgui_demo.cpp | 29 +++++------------------------ 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2909dfb8e..b9b5fbbc1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15824,7 +15824,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); EndDisabled(); ShowFontSelector("Font"); - BeginDisabled((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); + //BeginDisabled((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); @@ -15834,12 +15834,13 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 5.0f); //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); //EndDisabled(); + BulletText("Warning: Font scaling will NOT be smooth, because\nImGuiBackendFlags_RendererHasTextures is not set!"); BulletText("Load a nice font for better results!"); BulletText("Please submit feedback:"); SameLine(); TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); BulletText("Read FAQ for more details:"); SameLine(); TextLinkOpenURL("dearimgui.com/faq", "https://www.dearimgui.com/faq/"); - EndDisabled(); + //EndDisabled(); SeparatorText("Font List"); diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 985a490ea..cd48ee0ff 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -430,26 +430,6 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Text("dear imgui says hello! (%s) (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); ImGui::Spacing(); - { - ImGui::SeparatorText("dynamic_fonts branch"); - ImGuiStyle& style = ImGui::GetStyle(); - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); - ImGui::ShowFontSelector("Font"); - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); - if (ImGui::DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) - style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. - ImGui::SameLine(0.0f, 0.0f); Text(" (out %.2f)", ImGui::GetFontSize()); - ImGui::SameLine(); HelpMarker("- This is scaling font only. General scaling will come later."); - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); - ImGui::DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); - ImGui::BulletText("Load a nice font for better results!"); - //ImGui::BulletText("Current font loader: '%s'", ImGui::GetIO().Fonts->FontLoaderName); - ImGui::BulletText("Please submit feedback:"); ImGui::SameLine(); - ImGui::TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); - ImGui::BulletText("See 'Widgets->Fonts' below for more."); - ImGui::Spacing(); - } - IMGUI_DEMO_MARKER("Help"); if (ImGui::CollapsingHeader("Help")) { @@ -603,8 +583,8 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Spacing(); } - IMGUI_DEMO_MARKER("Configuration/Style"); - if (ImGui::TreeNode("Style")) + IMGUI_DEMO_MARKER("Configuration/Style, Fonts"); + if (ImGui::TreeNode("Style, Fonts")) { ImGui::Checkbox("Style Editor", &demo_data.ShowStyleEditor); ImGui::SameLine(); @@ -8277,10 +8257,12 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) { // General SeparatorText("General"); + if ((GetIO().BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + BulletText("Warning: Font scaling will NOT be smooth, because\nImGuiBackendFlags_RendererHasTextures is not set!"); + if (ShowStyleSelector("Colors##Selector")) ref_saved_style = style; ShowFontSelector("Fonts##Selector"); - BeginDisabled((GetIO().BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); @@ -8289,7 +8271,6 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 5.0f); //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); //EndDisabled(); - EndDisabled(); // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) if (SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) From 96be9573156549f6ebcda15c8783a582fe91c09f Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 11 Jun 2025 14:09:38 +0200 Subject: [PATCH 191/191] Docs: update Changelog, FAQ, Fonts docs. --- docs/CHANGELOG.txt | 329 +++++++++++++++++++++++++++++++++++++++------ docs/FAQ.md | 101 ++++++++++---- docs/FONTS.md | 65 ++++++--- imgui.h | 23 ++-- 4 files changed, 421 insertions(+), 97 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 059daf5b5..2a16008f6 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -39,16 +39,161 @@ HOW TO UPDATE? VERSION 1.92.0 WIP (In Progress) ----------------------------------------------------------------------- +THIS VERSION CONTAINS THE LARGEST AMOUNT OF BREAKING CHANGES SINCE 2015! +I TRIED REALLY HARD TO KEEP THEM TO A MINIMUM, REDUCE THE AMOUNT OF INTERFERENCES, +BUT INEVITABLY SOME USERS WILL BE AFFECTED. + +IN ORDER TO HELP US IMPROVE THE TRANSITION PROCESS, INCL. DOCUMENTATION AND COMMENTS, +PLEASE REPORT **ANY** DOUBT, CONFUSION, QUESTIONS, FEEDBACK TO: + https://github.com/ocornut/imgui/issues/ + +As part of the plan to reduce impact of API breaking changes, several unfinished +changes/features/refactors related to font and text systems and scaling will be +part of subsequent releases (1.92.1+). + +If you are updating from an old version, and expecting a massive or difficult update, +consider first updating to 1.91.9 to reduce the amount of changes. + Breaking changes: +- Fonts: **IMPORTANT**: if your app was solving the OSX/iOS Retina screen specific + logical vs display scale problem by setting io.DisplayFramebufferScale (e.g. to 2.0f) + + setting io.FontGlobalScale (e.g. to 1.0f/2.0f) + loading fonts at scaled sizes (e.g. size X * 2.0f): + This WILL NOT map correctly to the new system! Because font will rasterize as requested size. + - With a legacy backend (< 1.92): + - Instead of setting io.FontGlobalScale = 1.0f/N -> set ImFontCfg::RasterizerDensity = N. + - This already worked before, but is now pretty much required. + - With a new backend (1.92+) + - FramebufferScale is automatically used to set current font RasterizerDensity. + - FramebufferScale is a per-viewport property provided by backend through the + Platform_GetWindowFramebufferScale() handler in 'docking' branch. + - So this should be all automatic. +- Fonts: **IMPORTANT** on Font Sizing: + - Before 1.92, fonts were of a single size. They can now be dynamically sized. + - PushFont() API now has an optional size parameter. PushFontSize() was also added. + void PushFont(ImFont* font) --> void PushFont(ImFont* font, float size = 0.0f); + - Before 1.92: ImGui::PushFont() always used font "default" size specified in AddFont() call. + - Since 1.92: ImGui::PushFont() preserve the current font size which is a shared value. + - To use old behavior: + - use 'ImGui::PushFont(font, font->LegacySize)' at call site. + - or set 'ImFontConfig::Flags |= ImFontFlags_DefaultToLegacySize' in AddFont() call + (not desirable as it requires e.g. third-party code to be aware of it). + - ImFont::FontSize was removed and does not make sense anymore. + ImFont::LegacySize is the size passed to AddFont(). + - Renamed/moved 'io.FontGlobalScale' to 'style.FontScaleMain'. + +- Textures: + - All API functions taking a 'ImTextureID' parameter are now taking a 'ImTextureRef': + - ImTextureRef a small composite structure which may be constructed from a ImTextureID. + (or constructed from a ImTextureData* which represent a texture which will generally + be ready by the time of rendering). + - Affected functions are: + - ImGui::Image(), ImGui::ImageWithBg(), ImGui::ImageButton(), + - ImDrawList::AddImage(), ImDrawList::AddImageQuad(), ImDrawList::AddImageRounded(). + - We suggest that C users and any higher-level language bindings generators may + facilitate converting this in some way, aka emulating the trivial C++ constructor. +- Fonts: obsoleted ImFontAtlas::GetTexDataAsRGBA32(), GetTexDataAsAlpha8(), Build(), SetTexID() + and IsBuilt() functions. The new protocol for backends to handle textures doesn't need them. + Kept redirection functions (will obsolete). + - A majority of old backends should still work with new code (behaving like they did before). + - Calling ImFontAtlas::Build() before initializing new backends will erroneously trigger + preloading all glyphs. Will be detected with an assertion after the backend is initialized. +- Fonts: ImFontConfig::OversampleH/OversampleV default to automatic (== 0) + since v1.91.8. It is quite important you keep it automatic until we decide if we want + to provide a way to express finer policy, otherwise you will likely waste texture space + when using large glyphs. Note that the imgui_freetype backend doesn't use and does not + need oversampling. +- Fonts: specifying glyph ranges is now unnecessary. + - The value of ImFontConfig::GlyphRanges[] is only useful for legacy backends. + - All GetGlyphRangesXXXX() functions are now marked obsolete: + - GetGlyphRangesDefault(), GetGlyphRangesGreek(), GetGlyphRangesKorean(), + GetGlyphRangesJapanese(), GetGlyphRangesChineseSimplifiedCommon(), + GetGlyphRangesChineseFull(), GetGlyphRangesCyrillic(), + GetGlyphRangesThai(), GetGlyphRangesVietnamese(). +- Fonts: removed ImFontAtlas::TexDesiredWidth to enforce a texture width. (#327) + (it vaguely made sense with the old system as if unspecified textures width maxed up + to 4096 but that limit isn't necessary anymore, and Renderer_TextureMaxWidth covers this) + However you may set TexMinWidth = TexMaxWidth for the same effect. +- Fonts: if you create and manage ImFontAtlas instances yourself (instead of relying on + ImGuiContext to create one, you'll need to set the atlas->RendererHasTextures field + and call ImFontAtlasUpdateNewFrame() yourself. An assert will trigger if you don't. +- Fonts: obsolete ImGui::SetWindowFontScale() which is not useful anymore. Prefer using + PushFontSize(style.FontSizeBase * factor) or to manipulate other scaling factors. +- Fonts: obsoleted ImFont::Scale which is not useful anymore. +- Fonts: changed ImFont::CalcWordWrapPositionA() to ImFont::CalcWordWrapPosition(): + - old: const char* CalcWordWrapPositionA(float scale, const char* text, ....); + - new: const char* CalcWordWrapPosition (float size, const char* text, ....); + The leading 'float scale' parameters was changed to 'float size'. + This was necessary as 'scale' is assuming a unique font size. + Kept inline redirection function assuming using font->LegacySize. +- Fonts: generally reworked Internals of ImFontAtlas and ImFont. + While in theory a vast majority of users shouldn't be affected, some use cases or + extensions might be. Among other things: + - ImDrawCmd::TextureId has been changed to ImDrawCmd::TexRef. + - ImFontAtlas::ConfigData[] has been renamed to ImFontAtlas::Sources[] + - ImFont::ConfigData[], ConfigDataCount has been renamed to Sources[], SourceCount. + - Each ImFont has a number of ImFontBaked instances corresponding to actively used + sizes. ImFont::GetFontBaked(size) retrieves the one for a given size. + - Things moved from ImFont to ImFontBaked: + - ImFont::IndexAdvanceX[] -> ImFontBaked::IndexAdvanceX[] + - ImFont::Glyphs[] -> ImFontBaked::Glyphs[] + - ImFont::Ascent, Descent -> ImFontBaked::Ascent, Descent + - ImFont::FindGlyph() -> ImFontBaked::FindGlyph() + - ImFont::FindGlyphNoFallback() -> ImFontBaked::FindGlyphNoFallback() + - ImFont::GetCharAdvance() -> ImFontBaked::GetCharAdvance() + - Widget code may use ImGui::GetFontBaked() instead of ImGui::GetFont() to + access font data for current font at current font size. + (and you may use font->GetFontBaked(size) to access it for any other size.) + g.Font == ImGui::GetFont() + g.FontSize == ImGui::GetFontSize() + g.FontBaked == ImGui::GetFontBaked() == ImGui::GetFont()->GetFontBaked(ImGui::GetFontSize()) + Please report if you are affected! +- Fonts: (users of imgui_freetype) + - renamed ImFontAtlas::FontBuilderFlags to ImFontAtlas::FontLoaderFlags. + - renamed ImFontConfig::FontBuilderFlags to ImFontConfig::FontLoaderFlags. + - renamed ImGuiFreeTypeBuilderFlags to ImGuiFreeTypeLoaderFlags. + - if you used runtime imgui_freetype selection rather than the default compile-time + option provided by IMGUI_ENABLE_FREETYPE: + - renamed/reworked ImFontBuilderIO into ImFontLoader, + - renamed ImGuiFreeType::GetBuilderForFreeType() to ImGuiFreeType::GetFontLoader(). + - old: io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType() + - new: io.Fonts.FontLoader = ImGuiFreeType::GetFontLoader() +- DrawList: Renamed ImDrawList::PushTextureID()/PopTextureID() to PushTexture()/PopTexture(). +- Fonts: (users of custom rectangles) + - Renamed AddCustomRectRegular() to AddCustomRect(). (#8466) + - Added GetCustomRect() as a replacement for GetCustomRectByIndex() + CalcCustomRectUV(). (#8466) + - The output type of GetCustomRect() is now ImFontAtlasRect, which include UV coordinates. + - ImFontAtlasCustomRect::X --> ImFontAtlasRect::x + - ImFontAtlasCustomRect::Y --> ImFontAtlasRect::y + - ImFontAtlasCustomRect::Width --> ImFontAtlasRect::w + - ImFontAtlasCustomRect::Height --> ImFontAtlasRect::h + Before: + const ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(custom_rect_id); + ImVec2 uv0, uv1; + atlas->GetCustomRectUV(r, &uv0, &uv1); + ImGui::Image(atlas->TexRef, ImVec2(r->w, r->h), uv0, uv1); + After: + ImFontAtlasRect r; + atlas->GetCustomRect(custom_rect_id, &r); + ImGui::Image(atlas->TexRef, ImVec2(r.w, r.h), r.uv0, r.uv1); + We added a redirecting typedef but haven't attempted to magically redirect + the field names, as this API is rarely used and the fix is simple. + - Obsoleted AddCustomRectFontGlyph() as the API does not make sense for scalable fonts: + - Kept existing function which uses the font "default size" (Sources[0]->LegacySize). + - Added a helper AddCustomRectFontGlyphForSize() which is immediately marked obsolete, + but can facilitate transitioning old code. + - Prefer adding a font source (ImFontConfig) using a custom/procedural loader. +- Backends: removed ImGui_ImplXXXX_CreateFontsTexture()/ImGui_ImplXXXX_DestroyFontsTexture() + for all backends that had them. They should not be necessary any more. + - removed ImGui_ImplMetal_CreateFontsTexture(), ImGui_ImplMetal_DestroyFontsTexture(). + - removed ImGui_ImplOpenGL2_CreateFontsTexture(), ImGui_ImplOpenGL2_DestroyFontsTexture(). + - removed ImGui_ImplOpenGL3_CreateFontsTexture(), ImGui_ImplOpenGL3_DestroyFontsTexture(). + - removed ImGui_ImplSDLGPU3_CreateFontsTexture(), ImGui_ImplSDLGPU3_DestroyFontsTexture(). + - removed ImGui_ImplSDLRenderer2_CreateFontsTexture(), ImGui_ImplSDLRenderer2_DestroyFontsTexture(). + - removed ImGui_ImplSDLRenderer3_CreateFontsTexture(), ImGui_ImplSDLRenderer3_DestroyFontsTexture(). + - removed ImGui_ImplVulkan_CreateFontsTexture(), ImGui_ImplVulkan_DestroyFontsTexture(). - TreeNode: renamed ImGuiTreeNodeFlags_NavLeftJumpsBackHere to ImGuiTreeNodeFlags_NavLeftJumpsToParent for clarity. Kept inline redirection enum (will obsolete). (#1079, #8639) -- Fonts: changed ImFont::CalcWordWrapPositionA() to ImFont::CalcWordWrapPosition(): - - old: const char* CalcWordWrapPositionA(float scale, const char* text, ....); - - new: const char* CalcWordWrapPosition (float size, const char* text, ....); - The leading 'float scale' parameters was changed to 'float size'. - This was necessary as 'scale' is assuming standard font size which is a concept we aim to - eliminate in an upcoming update. Kept inline redirection function. - Commented out PushAllowKeyboardFocus()/PopAllowKeyboardFocus() which was obsoleted in 1.89.4 (March 2023). (#3092) - PushAllowKeyboardFocus(bool tab_stop) --> PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); @@ -63,6 +208,89 @@ Breaking changes: Other changes: +- Textures: added partial texture update protocol. (#8465, #3761) + - The Renderer Backend needs to set io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures + and handle texture updates requests. + - New structs: ImTextureData, ImTextureRect. + - New enums: ImTextureStatus, ImTextureFormat. + - During its ImGui_ImplXXXX_RenderDrawData() call, the backend can now access a texture list + in ImDrawData::Textures[]. Textures may have four distinct states: + - ImTextureStatus_WantCreate: requesting backend to create a texture. + - ImTextureStatus_WantUpdates: requesting backend to copy given blocks from the CPU side + copy of the texture to your graphics pipeline. + A 'tex->Updates[]' list of update is provided as well as a single 'tex->UpdatesRect' bounding box. + - ImTextureStatus_WantDestroy: requesting backend to destroy the texture. + - A 'int UnusedFrames' value is provided to conveniently defer destroying. + - Backend is generally free to destroy textures whenever they like. + - ImTextureStatus_OK: nothing to do. + - Almost all standard backends have been updated to support this. + - Backends have allowed to destroy textures at any time if they desire so. + A list is available in platform_io.Textures[] for this purpose and for backend shutdown. + - Both stb_truetype and FreeType backends have been updated to work with the new + system, and they now share more code than before. + - Added '#define IMGUI_HAS_TEXTURES' to facilitate compile-time checks for third-party + extensions until this is merged with a definitive version number to check. +- Fonts: font backend/loader may easily be changed dynamically, allowing users to compare + rasterizers outputs and features. imgui_freetype is generally beneficial. +- Fonts: ImFontAtlas::AddFontXXX() functions may be called at any time during the frame. +- Fonts: ImFontAtlas::AddFontXXX() can fail more gracefully if error handling is configured + to not assert (this will be better exposed via future font flags). +- Fonts: added ImGui::PushFontSize()/PopFontSize() functions. +- Fonts: added style.FontScaleBase scaling factor (previously called io.FontGlobalScale). +- Fonts: added style.FontScaleDpi scaling factor. This is designed to be be changed on + per-monitor/per-viewport basis, which `io.ConfigDpiScaleFonts` does automatically. + (which is why it is separate from FontScaleBase). +- Fonts: added optional font_size parameter to ImGui::PushFont() function. +- Fonts: added ImFontAtlas::RemoveFont() function. +- Fonts: added ImFontAtlas::CompactCache() function. +- Fonts: added ImFontAtlas::TexDesiredFormat field (default to ImTextureFormat_RGBA32, + can be changed to ImTextureFormat_Alpha8). +- Fonts: added ImFontAtlas::TexMinWidth, TexMinHeight, TexMaxWidth, TexMaxHeight fields. +- Fonts: added ImFontConfig::PixelSnapV to align scaled GlyphOffset.y to pixel boundaries. +- Fonts: added ImFontConfig::GlyphExcludeRanges[], which behave similarly to + ImFontConfig::GlyphRanges[], but has the opposite meaning. It is tailored to situations + where merged fonts have overlapping characters. +- Fonts: added "Input Glyphs Overlap Detection Tool" which dumps a list of glyphs + provided by merged sources, which may help setting up a GlyphExcludeRanges[] filter. +- Fonts: added ImFontAtlas::FontBackendName (which is surfaced in the "About Dear ImGui" + window and other locations). +- Fonts: added ImFontFlags (currently needs to be passed through ImFontConfig until + we revamp font loading API): + - ImFontFlags_DefaultToLegacySize: for legacy compatibility: make PushFont() calls + without explicit size use font->LegacySize instead of current font size. + - ImFontFlags_NoLoadError: disable erroring/assert when calling AddFontXXX() + with missing file/data. Calling code is expected to check AddFontXXX() return value. + - ImFontFlags_NoLoadGlyphs: disable loading new glyphs. + - ImFontFlags_LockBakedSizes: disable loading new baked sizes, disable garbage + collecting current ones. e.g. if you want to lock a font to a single size. +- Fonts: the general design has changed toward meaning that a ImFont doesn't have + have a specific size: it may be bound and drawn with any size. + - We store ImFontBaked structures internally, which are a cache of information + for a given size being drawn. You should not need to deal with ImFontBaked directly. + - ImFontBaked structures may be cleaned up between frames when unused, pointers + to them are only valid for the current frame. + - Added ImFontBaked::IsGlyphLoaded() function. +- Fonts: custom rect packing has generally been reworked. (#8107, #7962, #1282) + - ImFontAtlas::AddCustomRect() (previously AddCustomRectRegular()/AddCustomRectFontGlyph()) + functions will immediately return a packed rectangle identifier, and you can write your + pixels immediately - previously had to wait for Build() to be called. + This is also the case when using a legacy backend. + - Custom packed rectangles may be moved during a texture change, aka practically anytime. + Always refer to latest uvs/position returned by GetCustomRect(). + - AddCustomRect() returns ImFontAtlasRectId_Invalid on failure. + - Added ImFontAtlas::RemoveCustomRect() function. + - GetCustomRect() can safely return false and not crash when passed an invalid or removed id. +- Fonts: texture is now stored in a single format CPU side (save ~25% when using RGBA). +- Fonts: changing current font to one from a different atlas is supported. (#8080) +- Fonts: automatic baking of an "..." ellipsis works better with monospace fonts. +- Fonts: each ImFontConfig font source may provide a custom backend/loader. +- Fonts: added platform_io.Renderer_TextureMaxWidth/Renderer_TextureMaxHeight fields + for Renderer Backend to specify if there is a maximum accepted texture size (not yet used). +- Fonts: added compile-time overridable '#define ImTextureID_Invalid 0' if you need 0 + to be a valid low-level texture identifier. +- Debug Tools: Fonts section: add font preview, add "Remove" button, add "Load all glyphs" + button, add selection to change font backend when both are compiled in. + - IO: variations in analog-only components of gamepad events do not interfere with trickling of mouse position events (#4921, #8508) - Windows: fixed SetNextWindowCollapsed()/SetWindowCollapsed() breaking @@ -132,39 +360,62 @@ Other changes: requires providing a window to the backend. (#8584, #6341) - Misc: added extra operators to ImVec4 in IMGUI_DEFINE_MATH_OPERATORS block. (#8510) [@gan74] - Demo: changed default framed item width to use Min(GetFontSize() * 12, GetContentRegionAvail().x * 0.40f). -- Backends: Win32: Fixed an issue where externally losing mouse capture (due to e.g. focus loss) - would fail to claim it again the next subsequent click. (#8594) -- Backends: SDL2, SDL3, OSX: Fill gamepad inputs and set ImGuiBackendFlags_HasGamepad - regardless of ImGuiConfigFlags_NavEnableGamepad being set. (#8508) -- Backends: SDL2, SDL3: don't attempt to call SDL_CaptureMouse() on drivers where we don't - call SDL_GetGlobalMouseState(). This is specifically for Wayland but we currently use - the same white-list as SDL_GetGlobalMouseState(). (#8561) [@vs49688] -- Backends: GLFW, SDL2, SDL3: include GLFW/SDL version number in io.BackendPlatformName. -- Backends: SDL3: Update for SDL3 api changes: revert SDL_GetClipboardText() - memory ownership change. (#8530, #7801) [@Green-Sky] -- Backends: SDL3: honor ImGuiPlatformImeData->WantTextInput as an alternative - way to call SDL_StartTextInput(), without IME being necessarily visible. (#8584) -- Backends: SDLGPU3: Fixed creating atlas texture earlier than other backends, preventing - to load fonts between the Init and NewFrames calls. -- Backends: SDLGPU3: Made ImGui_ImplSDLGPU3_PrepareDrawData() reuse GPU Transfer Buffers which - were unusually slow to recreate every frame. Much faster now. (#8534) [@ocornut, @TheMode] -- Backends: SDLGPU3: added support for ImDrawCallback_ResetRenderState. (#8599) -- Backends: OpenGL3: made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor - GL_PRIMITIVE_RESTART. (#8664) [@DyXel] -- Backends: DirectX10, DirectX11, DirectX12: Honor FramebufferScale to allow for custom - platform backends and experiments using it (consistently with other renderer backends, - even though in normal condition it is not set under Windows). (#8412) [@WSSDude] -- Backends: Vulkan: Deep-copy ImGui_ImplVulkan_InitInfo::PipelineRenderingCreateInfo's - pColorAttachmentFormats buffer when set, in order to reduce common user-error of - specifying a pointer to data that gets out of scope. (#8282) -- Backends: Vulkan: Load dynamic rendering functions using vkGetDeviceProcAddr() - + try both non-KHR and KHR versions. (#8600, #8326, #8365) [@ChrisTom-94] -- Backends: Vulkan: fixed validation errors in window create/resize helpers used by examples - and by multi-viewports implementation, which would typically trigger errors while detaching - secondary viewports. (#8600, #8176) [@ChrisTom-94] -- Examples: Apple+Metal, Apple+OpenGL: add Makefile (CLion opens them nicely). (#8637) [@pthom] -- Examples: DirectX12+Win32: also test for IsIconic() for sleeping since we don't seem to - get a DXGI_STATUS_OCCLUDED signal when minimized. (#8603) [@dooann] +- Backends: + - Backends: DX9/DX10/DX11/DX12, Vulkan, OpenGL2/3, Metal, SDLGPU3, SDLRenderer2/3, Allegro5: + - Added ImGuiBackendFlags_RendererHasTextures support. (#8465, #3761, #3471) + [@ocornut, @ShironekoBen, @thedmd] + - Added ImGui_ImplXXXX_UpdateTexture(ImTextureData* tex) functions for all backend. + Available if you want to start uploading textures right after ImGui::Render() and without + waiting for the call to ImGui_ImplXXXX_RenderDrawData(). Also useful if you use a staged or + multi-threaded rendering schemes, where you might want to set ImDrawData::Textures = NULL. (#8597, #1860) + - Backends: GLFW: added ImGui_ImplGlfw_GetContentScaleForMonitor(), ImGui_ImplGlfw_GetContentScaleForWindow() + helpers. They are wrappers to glfwGetMonitorContentScale()/glfwGetWindowContentScale(), with compile-time + GLFW version checks + returning 1.0f on Apple platform. + - Backends: SDL2: added ImGui_ImplSDL2_GetDpiScaleForDisplay() and ImGui_ImplSDL2_GetContentScaleForWindow() + helpers. They are wrappers to SDL_GetDisplayDPI(), with compile-time SDL version checks + returning 1.0f + on Apple platforms. SDL3 already does this by default. + - Backends: Win32: Fixed an issue where externally losing mouse capture (due to e.g. focus loss) + would fail to claim it again the next subsequent click. (#8594) + - Backends: SDL2, SDL3, OSX: Fill gamepad inputs and set ImGuiBackendFlags_HasGamepad + regardless of ImGuiConfigFlags_NavEnableGamepad being set. (#8508) + - Backends: SDL2, SDL3: don't attempt to call SDL_CaptureMouse() on drivers where we don't + call SDL_GetGlobalMouseState(). This is specifically for Wayland but we currently use + the same white-list as SDL_GetGlobalMouseState(). (#8561) [@vs49688] + - Backends: GLFW, SDL2, SDL3: include GLFW/SDL version number in io.BackendPlatformName. + - Backends: SDL3: Update for SDL3 api changes: revert SDL_GetClipboardText() + memory ownership change. (#8530, #7801) [@Green-Sky] + - Backends: SDL3: honor ImGuiPlatformImeData->WantTextInput as an alternative + way to call SDL_StartTextInput(), without IME being necessarily visible. (#8584) + - Backends: SDLGPU3: Fixed creating atlas texture earlier than other backends, preventing + to load fonts between the Init and NewFrames calls. + - Backends: SDLGPU3: Made ImGui_ImplSDLGPU3_PrepareDrawData() reuse GPU Transfer Buffers which + were unusually slow to recreate every frame. Much faster now. (#8534) [@ocornut, @TheMode] + - Backends: SDLGPU3: added support for ImDrawCallback_ResetRenderState. (#8599) + - Backends: OpenGL3: made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor + GL_PRIMITIVE_RESTART. (#8664) [@DyXel] + - Backends: DirectX10, DirectX11, DirectX12: Honor FramebufferScale to allow for custom + platform backends and experiments using it (consistently with other renderer backends, + even though in normal condition it is not set under Windows). (#8412) [@WSSDude] + - Backends: Vulkan: Deep-copy ImGui_ImplVulkan_InitInfo::PipelineRenderingCreateInfo's + pColorAttachmentFormats buffer when set, in order to reduce common user-error of + specifying a pointer to data that gets out of scope. (#8282) + - Backends: Vulkan: Load dynamic rendering functions using vkGetDeviceProcAddr() + + try both non-KHR and KHR versions. (#8600, #8326, #8365) [@ChrisTom-94] + - Backends: Vulkan: fixed validation errors in window create/resize helpers used by examples + and by multi-viewports implementation, which would typically trigger errors while detaching + secondary viewports. (#8600, #8176) [@ChrisTom-94] +- Examples: + - Examples: Made many examples DPI aware by default. + The single-viewport is basically: + - Query monitor DPI scale. Helpers are provided in some backends. + - Call style.ScaleAllSizes() and set style.FontScaleDpi with this factor. + Multi-viewport applications may set both of those flags: + - io.ConfigDpiScaleFonts = true; + - io.ConfigDpiScaleViewports = true; + Which will scale fonts but NOT style padding/spacings/thicknesses yet. + - Examples: Apple+Metal, Apple+OpenGL: add Makefile (CLion opens them nicely). (#8637) [@pthom] + - Examples: DirectX12+Win32: also test for IsIconic() for sleeping since we don't seem to + get a DXGI_STATUS_OCCLUDED signal when minimized. (#8603) [@dooann] ----------------------------------------------------------------------- diff --git a/docs/FAQ.md b/docs/FAQ.md index 8c08b4dd7..0e8ba5c77 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -24,7 +24,7 @@ or view this file with any Markdown viewer. | [I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-displaying-outside-their-expected-windows-boundaries) | | **Q&A: Usage** | | **[About the ID Stack system..
Why is my widget not reacting when I click on it?
Why is the wrong widget reacting when I click on one?
How can I have widgets with an empty label?
How can I have multiple widgets with the same label?
How can I have multiple windows with the same label?](#q-about-the-id-stack-system)** | -| [How can I display an image? What is ImTextureID, how does it work?](#q-how-can-i-display-an-image-what-is-imtextureid-how-does-it-work)| +| [How can I display an image?](#q-how-can-i-display-an-image)
[What are ImTextureID/ImTextureRef?](#q-what-are-imtextureidimtextureref)| | [How can I use maths operators with ImVec2?](#q-how-can-i-use-maths-operators-with-imvec2) | | [How can I use my own maths types instead of ImVec2/ImVec4?](#q-how-can-i-use-my-own-maths-types-instead-of-imvec2imvec4) | | [How can I interact with standard C++ types (such as std::string and std::vector)?](#q-how-can-i-interact-with-standard-c-types-such-as-stdstring-and-stdvector) | @@ -161,7 +161,8 @@ Console SDK also sometimes provide equivalent tooling or wrapper for Synergy-lik ### Q: I integrated Dear ImGui in my engine and little squares are showing instead of text... Your renderer backend is not using the font texture correctly or it hasn't been uploaded to the GPU. -- If this happens using the standard backends: A) have you modified the font atlas after `ImGui_ImplXXX_NewFrame()`? B) maybe the texture failed to upload, which **can if your texture atlas is too big**. Also see [docs/FONTS.md](https://github.com/ocornut/imgui/blob/master/docs/FONTS.md). +- If this happens using standard backends (before 1.92): A) have you modified the font atlas after `ImGui_ImplXXX_NewFrame()`? B) maybe the texture failed to upload, which **can if your texture atlas is too big**. Also see [docs/FONTS.md](https://github.com/ocornut/imgui/blob/master/docs/FONTS.md). +- If this happens using standard backends (after 1.92): please report. - If this happens with a custom backend: make sure you have uploaded the font texture to the GPU, that all shaders are rendering states are setup properly (e.g. texture is bound). Compare your code to existing backends and use a graphics debugger such as [RenderDoc](https://renderdoc.org) to debug your rendering states. ##### [Return to Index](#index) @@ -375,23 +376,49 @@ node open/closed state differently. See what makes more sense in your situation! --- -### Q: How can I display an image? What is ImTextureID, how does it work? +### Q: How can I display an image? +### Q: What are ImTextureID/ImTextureRef? -Short explanation: +**Short explanation:** - Refer to [Image Loading and Displaying Examples](https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples) on the [Wiki](https://github.com/ocornut/imgui/wiki). - You may use functions such as `ImGui::Image()`, `ImGui::ImageButton()` or lower-level `ImDrawList::AddImage()` to emit draw calls that will use your own textures. - Actual textures are identified in a way that is up to the user/engine. Those identifiers are stored and passed as an opaque ImTextureID value. - By default ImTextureID can store up to 64-bits. You may `#define` it to a custom type/structure if you need. - Loading image files from the disk and turning them into a texture is not within the scope of Dear ImGui (for a good reason), but the examples linked above may be useful references. +**Details:** + +1.92 introduced `ImTextureRef` in June 2025. +- Most drawing functions using ImTextureID were changed to use ImTextureRef. +- We intentionally do not provide an implicit ImTextureRef -> ImTextureID cast operator because it is technically lossy to convert ImTextureRef to ImTextureID before rendering. + +**ImTextureID = backend specific, low-level identifier for a texture uploaded in GPU/graphics system.** +- When a Rendered Backend creates a texture, it store its native identifier into a ImTextureID value (e.g. Used by DX11 backend to a `ID3D11ShaderResourceView*`; Used by OpenGL backends to store `GLuint`; Used by SDLGPU backend to store a `SDL_GPUTextureSamplerBinding*`, etc.). +- User may submit their own textures to e.g. ImGui::Image() function by passing the same type. +- During the rendering loop, the Renderer Backend retrieve the ImTextureID, which stored inside a ImTextureRef, which is stored inside ImDrawCmd. +- Compile-time type configuration: + - To use something other than a 64-bit value: add '#define ImTextureID MyTextureType*' in your imconfig.h file. + - This can be whatever to you want it to be! read the FAQ entry about textures for details. + - You may decide to store a higher-level structure containing texture, sampler, shader etc. with various constructors if you like. You will need to implement ==/!= operators. + +**ImTextureRef = higher-level identifier for a texture.** +- The identifier is valid even before the texture has been uploaded to the GPU/graphics system. +- This is what gets passed to functions such as `ImGui::Image()`, `ImDrawList::AddImage()`. +- This is what gets stored in draw commands (`ImDrawCmd`) to identify a texture during rendering. + - When a texture is created by user code (e.g. custom images), we directly stores the low-level `ImTextureID`. + - When a texture is created by the backend, we stores a `ImTextureData*` which becomes an indirection to extract the `ImTextureID` value during rendering, after texture upload has happened. + - There is no constructor to create a `ImTextureID` from a `ImTextureData*` as we don't expect this to be useful to the end-user, and it would be erroneously called by many legacy code. + - If you want to bind the current atlas when using custom rectangle, you can use `io.Fonts->TexRef`. + - Binding generators for languages such as C (which don't have constructors), should provide a helper, e.g. `inline ImTextureRef ImTextureRefFromID(ImTextureID tex_id) { ImTextureRef tex_ref = { ._TexData = NULL, .TexID = tex_id }; return tex_ref; }` + **Please read documentations or tutorials on your graphics API to understand how to display textures on the screen before moving onward.** Long explanation: - Dear ImGui's job is to create "meshes", defined in a renderer-agnostic format made of draw commands and vertices. At the end of the frame, those meshes (ImDrawList) will be displayed by your rendering function. They are made up of textured polygons and the code to render them is generally fairly short (a few dozen lines). In the examples/ folder, we provide functions for popular graphics APIs (OpenGL, DirectX, etc.). - Each rendering function decides on a data type to represent "textures". The concept of what is a "texture" is entirely tied to your underlying engine/graphics API. - We carry the information to identify a "texture" in the ImTextureID type. +We carry the information to identify a "texture" in the ImTextureID type, which itself tends to be stored inside a ImTextureRef. ImTextureID default to ImU64 aka 8 bytes worth of data: just enough to store one pointer or integer of your choice. -Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely passes ImTextureID values until they reach your rendering function. +Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely passes values until they reach your rendering function. - In the [examples/](https://github.com/ocornut/imgui/tree/master/examples) backends, for each graphics API we decided on a type that is likely to be a good representation for specifying an image from the end-user perspective. This is what the _examples_ rendering functions are using: ```cpp OpenGL: @@ -539,30 +566,49 @@ ImGui::End(); ### Q: How should I handle DPI in my application? -The short answer is: obtain the desired DPI scale, load your fonts resized with that scale (always round down fonts size to the nearest integer), and scale your Style structure accordingly using `style.ScaleAllSizes()`. +Since 1.92 (June 2025) fonts may be dynamically used at any size. -Your application may want to detect DPI change and reload the fonts and reset style between frames. +**Scaling fonts** + +To change font size: +```cpp +ImGui::PushFontSize(42.0f); +``` +To change font and font size: +```cpp +ImGui::PushFont(new_font, 42.0f); +``` +To scale all fonts: +```cpp +style.FontScaleDpi = 2.0f; +``` +In `docking` branch or with multi-viewports: +```cpp +io.ConfigDpiScaleFonts = true; // [Experimental] Automatically overwrite style.FontScaleDpi in Begin() when Monitor DPI changes. This will scale fonts but _NOT_ scale sizes/padding for now. +io.ConfigDpiScaleViewports = true; // [Experimental] Scale Dear ImGui and Platform Windows when Monitor DPI changes. +``` + +**Scaling style** (paddings, spacings, thicknesses) + +This is still massively work in progress, expect turbulence. +Style values are currently not easily scalable dynamically. +For single viewport application you can call once: +```cpp +style.ScaleAllSizes(factor); // call once! +``` +If you need to change the scaling factor, it is currently most practical to reset the style and call this again with a new value. Your UI code should avoid using hardcoded constants for size and positioning. Prefer to express values as multiple of reference values such as `ImGui::GetFontSize()` or `ImGui::GetFrameHeight()`. So e.g. instead of seeing a hardcoded height of 500 for a given item/window, you may want to use `30*ImGui::GetFontSize()` instead. -Down the line Dear ImGui will provide a variety of standardized reference values to facilitate using this. +Down the line Dear ImGui will provide a variety of standardized reference values to facilitate using this. This is expected to happen during subsequent 1.92.x releases. -Applications in the `examples/` folder are not DPI aware partly because they are unable to load a custom font from the file-system (may change that in the future). +Applications in the `examples/` folder are partly DPI aware but they are unable to load a custom font from the file-system, so they look ugly (may change that in the future). -The reason DPI is not auto-magically solved in stock examples is that we don't yet have a satisfying solution for the "multi-dpi" problem (using the `docking` branch: when multiple viewport windows are over multiple monitors using different DPI scales). The current way to handle this on the application side is: -- Create and maintain one font atlas per active DPI scale (e.g. by iterating `platform_io.Monitors[]` before `NewFrame()`). -- Hook `platform_io.OnChangedViewport()` to detect when a `Begin()` call makes a Dear ImGui window change monitor (and therefore DPI). -- In the hook: swap atlas, swap style with correctly sized one, and remap the current font from one atlas to the other (you may need to maintain a remapping table of your fonts at varying DPI scales). +The reason DPI is not auto-magically solved in stock examples is that we don't yet have a satisfying solution for the "multi-dpi" problem (using the `docking` branch: when multiple viewport windows are over multiple monitors using different DPI scales) specifically for the `ImGuiStyle` structure. Fonts are however now perfectly scalable. -This approach is relatively easy and functional but comes with two issues: -- It's not possibly to reliably size or position a window ahead of `Begin()` without knowing on which monitor it'll land. -- Style override may be lost during the `Begin()` call crossing monitor boundaries. You may need to do some custom scaling mumbo-jumbo if you want your `OnChangedViewport()` handler to preserve style overrides. - -Please note that if you are not using multi-viewports with multi-monitors using different DPI scales, you can ignore that and use the simpler technique recommended at the top. - -On Windows, in addition to scaling the font size (make sure to round to an integer) and using `style.ScaleAllSizes()`, you will need to inform Windows that your application is DPI aware. If this is not done, Windows will scale the application window and the UI text will be blurry. Potential solutions to indicate DPI awareness on Windows are: - -- For SDL2: the flag `SDL_WINDOW_ALLOW_HIGHDPI` needs to be passed to `SDL_CreateWindow()`. +**On Windows, you need to inform Windows that your application is DPI aware!** +If this is not done, Windows will scale the application window and the UI text will be blurry. Potential solutions to indicate DPI awareness on Windows are: +- For SDL2: the flag `SDL_WINDOW_ALLOW_HIGHDPI` needs to be passed to `SDL_CreateWindow()` + call `::SetProcessDPIAware()`. - For SDL3: the flag `SDL_WINDOW_HIGH_PIXEL_DENSITY` needs to be passed to `SDL_CreateWindow()`. - For GLFW: this is done automatically. - For other Windows projects with other backends, or wrapper projects: @@ -615,9 +661,12 @@ Use the font atlas to pack them into a single texture. Read [docs/FONTS.md](http --- ### Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? -When loading a font, pass custom Unicode ranges to specify the glyphs to load. +Since 1.92 (June 2025) and with an updated backend, it is not necessary to specify glyph ranges at all. + +Before 1.92, when loading a font, pass custom Unicode ranges to specify the glyphs to load. ```cpp +// [BEFORE 1.92] // Add default Japanese ranges io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, nullptr, io.Fonts->GetGlyphRangesJapanese()); @@ -641,8 +690,8 @@ Text input: it is up to your application to pass the right character code by cal The applications in examples/ are doing that. Windows: you can use the WM_CHAR or WM_UNICHAR or WM_IME_CHAR message (depending if your app is built using Unicode or MultiByte mode). You may also use `MultiByteToWideChar()` or `ToUnicode()` to retrieve Unicode codepoints from MultiByte characters or keyboard state. -Windows: if your language is relying on an Input Method Editor (IME), you can write your HWND to ImGui::GetMainViewport()->PlatformHandleRaw -for the default implementation of GetPlatformIO().Platform_SetImeDataFn() to set your Microsoft IME position correctly. +Windows: if your language is relying on an Input Method Editor (IME), you can write your HWND to `ImGui::GetMainViewport()->PlatformHandleRaw` +for the default implementation of `GetPlatformIO().Platform_SetImeDataFn()` to set your Microsoft IME position correctly. ##### [Return to Index](#index) diff --git a/docs/FONTS.md b/docs/FONTS.md index baa53adde..7ed8fe026 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -12,7 +12,7 @@ In the [misc/fonts/](https://github.com/ocornut/imgui/tree/master/misc/fonts) fo ## Index - [Troubleshooting](#troubleshooting) -- [New! Dynamic Fonts system in 1.92 (March 2025)](#new-dynamic-fonts-system-in-192-march-2025) +- [New! Dynamic Fonts system in 1.92 (June 2025)](#new-dynamic-fonts-system-in-192-june-2025) - [How should I handle DPI in my application?](#how-should-i-handle-dpi-in-my-application) - [Fonts Loading Instructions](#fonts-loading-instructions) - [Loading Font Data from Memory](#loading-font-data-from-memory) @@ -72,11 +72,11 @@ Future versions of Dear ImGui should solve this problem. --------------------------------------- -## New! Dynamic Fonts system in 1.92 (March 2025+) +## New! Dynamic Fonts system in 1.92 (June 2025) -v1.92 will introduce a newer, dynamic font system. It requires backend to support the `ImGuiBackendFlags_HasTextures` feature: +v1.92 introduces a newer, dynamic font system. It requires backend to support the `ImGuiBackendFlags_HasTextures` feature: - Users of icons, Asian and non-English languages do not need to pre-build all glyphs ahead of time. Saving on loading time, memory, and also reducing issues with missing glyphs. Specifying glyph ranges is not needed anymore. -- PushFontSize() may be used anytime to change font size. +- `PushFontSize()` may be used anytime to change font size. - Packing custom rectangles is more convenient as pixels may be written to immediately. - Any update to fonts previously required backend specific calls to re-upload the texture, and said calls were not portable across backends. It is now possible to scale fonts etc. in a way that doesn't require you to make backend-specific calls. - It is possible to plug a custom loader/backend to any font source. @@ -105,6 +105,12 @@ io.Fonts->AddFontDefault(); ``` **Load .TTF/.OTF file with:** +🆕 **Since 1.92, with an up to date backend: passing a size is not necessary** +```cpp +ImGuiIO& io = ImGui::GetIO(); +io.Fonts->AddFontFromFileTTF("font.ttf"); +``` +**Before 1.92, or without an up to date backend:** ```cpp ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromFileTTF("font.ttf", size_pixels); @@ -115,8 +121,8 @@ If you get an assert stating "Could not load font file!", your font filename is ```cpp // Init ImGuiIO& io = ImGui::GetIO(); -ImFont* font1 = io.Fonts->AddFontFromFileTTF("font.ttf", size_pixels); -ImFont* font2 = io.Fonts->AddFontFromFileTTF("anotherfont.otf", size_pixels); +ImFont* font1 = io.Fonts->AddFontFromFileTTF("font.ttf",); +ImFont* font2 = io.Fonts->AddFontFromFileTTF("anotherfont.otf"); ``` In your application loop, select which font to use: @@ -135,6 +141,18 @@ ImFont* font = io.Fonts->AddFontFromFileTTF("font.ttf", size_pixels, &config); ``` **Combine multiple fonts into one:** + +🆕 **Since 1.92, with an up to date backend: specifying glyph ranges is unnecessary.** +```cpp +// Load a first font +ImFont* font = io.Fonts->AddFontDefault(); +ImFontConfig config; +config.MergeMode = true; +io.Fonts->AddFontFromFileTTF("DroidSans.ttf", 0.0f, &config); // Merge into first font to add e.g. Asian characters +io.Fonts->AddFontFromFileTTF("fontawesome-webfont.ttf", 0.0f, &config); // Merge into first font to add Icons +io.Fonts->Build(); +``` +**Before 1.92, or without an up to date backend:** ```cpp // Load a first font ImFont* font = io.Fonts->AddFontDefault(); @@ -152,8 +170,7 @@ io.Fonts->Build(); **Add a fourth parameter to bake specific font ranges only:** -🆕 **Since 1.92, with an up to date backend: specifying glyph ranges is necessary. All the GetGlyphRangesXXX() functions are marked obsolete.** - +🆕 **Since 1.92, with an up to date backend: specifying glyph ranges is unnecessary. All the GetGlyphRangesXXX() functions are marked obsolete.** ```cpp // Basic Latin, Extended Latin io.Fonts->AddFontFromFileTTF("font.ttf", size_pixels, nullptr, io.Fonts->GetGlyphRangesDefault()); @@ -169,14 +186,12 @@ See [Using Custom Glyph Ranges](#using-custom-glyph-ranges) section to create yo **Example loading and using a Japanese font:** 🆕 **Since 1.92, with an up to date backend:** - ```cpp ImGuiIO& io = ImGui::GetIO(); -io.Fonts->AddFontFromFileTTF("NotoSansCJKjp-Medium.otf", 20.0f); +io.Fonts->AddFontFromFileTTF("NotoSansCJKjp-Medium.otf"); ``` **Before 1.92, or without an up to date backend:** - ```cpp ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromFileTTF("NotoSansCJKjp-Medium.otf", 20.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); @@ -248,14 +263,24 @@ To refer to the icon UTF-8 codepoints from your C++ code, you may use those head So you can use `ICON_FA_SEARCH` as a string that will render as a "Search" icon. -🆕 **Since 1.92, with an up to date backend: specifying glyph ranges is necessary. You can omit this parameter.** - +🆕 **Since 1.92, with an up to date backend: specifying glyph ranges is unnecessary. You can omit this parameter.** Example Setup: ```cpp // Merge icons into default tool font #include "IconsFontAwesome.h" ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontDefault(); +ImFontConfig config; +config.MergeMode = true; +config.GlyphMinAdvanceX = 13.0f; // Use if you want to make the icon monospaced +io.Fonts->AddFontFromFileTTF("fonts/fontawesome-webfont.ttf", 13.0f, &config); +``` +**Before 1.92:** +```cpp +// Merge icons into default tool font +#include "IconsFontAwesome.h" +ImGuiIO& io = ImGui::GetIO(); +io.Fonts->AddFontDefault(); ImFontConfig config; config.MergeMode = true; @@ -275,7 +300,8 @@ See Links below for other icons fonts and related tools. **Monospace Icons?** -To make your icon look more monospace and facilitate alignment, you may want to set the ImFontConfig::GlyphMinAdvanceX value when loading an icon font. +To make your icon look more monospace and facilitate alignment, you may want to set the `ImFontConfig::GlyphMinAdvanceX` value when loading an icon font. +If you `GlyphMinAdvanceX` you need to pass a `font_size` to `AddFontXXX()` calls, as the MinAdvanceX value will be specified for the given size and scaled otherwise. **Screenshot** @@ -288,8 +314,8 @@ Here's an application using icons ("Avoyd", https://www.avoyd.com): ## Using FreeType Rasterizer (imgui_freetype) -- Dear ImGui uses imstb\_truetype.h to rasterize fonts (with optional oversampling). This technique and its implementation are not ideal for fonts rendered at small sizes, which may appear a little blurry or hard to read. -- There is an implementation of the ImFontAtlas builder using FreeType that you can use in the [misc/freetype/](https://github.com/ocornut/imgui/tree/master/misc/freetype) folder. +- Dear ImGui uses [stb_truetype.h](https://github.com/nothings/stb/) to rasterize fonts (with optional oversampling). This technique and its implementation are not ideal for fonts rendered at small sizes, which may appear a little blurry or hard to read. +- You can however use `imgui_freetype.cpp` from the [misc/freetype/](https://github.com/ocornut/imgui/tree/master/misc/freetype) folder. - FreeType supports auto-hinting which tends to improve the readability of small fonts. - Read documentation in the [misc/freetype/](https://github.com/ocornut/imgui/tree/master/misc/freetype) folder. - Correct sRGB space blending will have an important effect on your font rendering quality. @@ -312,10 +338,9 @@ Here's an application using icons ("Avoyd", https://www.avoyd.com): io.Fonts->AddFontFromFileTTF("../../../imgui_dev/data/fonts/NotoSans-Regular.ttf", 16.0f); static ImWchar ranges[] = { 0x1, 0x1FFFF, 0 }; static ImFontConfig cfg; -cfg.OversampleH = cfg.OversampleV = 1; cfg.MergeMode = true; -cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_LoadColor; -io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\seguiemj.ttf", 16.0f, &cfg, ranges); +cfg.FontLoaderFlags |= ImGuiFreeTypeLoaderFlags_LoadColor; +io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\seguiemj.ttf", 16.0f, &cfg); ``` ##### [Return to Index](#index) @@ -348,7 +373,7 @@ io.Fonts->Build(); // Build the atlas while 🆕 **Since 1.92, with an up to date backend: this system has been revamped.** TL;DR; With the new system, it is recommended that you create a custom `ImFontLoader` and register your fonts with it. -`AddCustomRectFontGlyph()` has been obsolete because its API does not make much sense with resizable fonts. +`AddCustomRectFontGlyph()` has been obsoleted because its API does not make much sense with resizable fonts. You can ask questions in [#8466](https://github.com/ocornut/imgui/issues/8466). diff --git a/imgui.h b/imgui.h index 1d27953f2..de0d06645 100644 --- a/imgui.h +++ b/imgui.h @@ -314,18 +314,17 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // ImTextureID = backend specific, low-level identifier for a texture uploaded in GPU/graphics system. // [Compile-time configurable type] -// Overview: // - When a Rendered Backend creates a texture, it store its native identifier into a ImTextureID value. -// (e.g. Used by DX11 backend to a `ID3D11ShaderResourceView*`; Used by OpenGL backends to store `GLuint'; +// (e.g. Used by DX11 backend to a `ID3D11ShaderResourceView*`; Used by OpenGL backends to store `GLuint`; // Used by SDLGPU backend to store a `SDL_GPUTextureSamplerBinding*`, etc.). // - User may submit their own textures to e.g. ImGui::Image() function by passing the same type. -// - During the rendering loop, the Renderer Backend retrieve the ImTextureID, which stored inside -// ImTextureRef, which is stored inside ImDrawCmd. -// Configuring the type: -// - To use something other than a 64-bit value: add '#define ImTextureID MyTextureType*' in your imconfig.h file. -// - This can be whatever to you want it to be! read the FAQ entry about textures for details. -// - You may decide to store a higher-level structure containing texture, sampler, shader etc. with various -// constructors if you like. You will need to implement ==/!= operators. +// - During the rendering loop, the Renderer Backend retrieve the ImTextureID, which stored inside a +// ImTextureRef, which is stored inside a ImDrawCmd. +// - Compile-time type configuration: +// - To use something other than a 64-bit value: add '#define ImTextureID MyTextureType*' in your imconfig.h file. +// - This can be whatever to you want it to be! read the FAQ entry about textures for details. +// - You may decide to store a higher-level structure containing texture, sampler, shader etc. with various +// constructors if you like. You will need to implement ==/!= operators. // History: // - In v1.91.4 (2024/10/08): the default type for ImTextureID was changed from 'void*' to 'ImU64'. This allowed backends requirig 64-bit worth of data to build on 32-bit architectures. Use intermediary intptr_t cast and read FAQ if you have casting warnings. // - In v1.92.0 (2025/XX/XX): added ImTextureRef which carry either a ImTextureID either a pointer to internal texture atlas. All user facing functions taking ImTextureID changed to ImTextureRef @@ -340,15 +339,15 @@ typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or // ImTextureRef = higher-level identifier for a texture. // The identifier is valid even before the texture has been uploaded to the GPU/graphics system. -// This is what gets passed to functions such as ImGui::Image(), ImDrawList::AddImage(). -// This is what gets stored in draw commands (ImDrawCmd) to identify a texture during rendering. +// This is what gets passed to functions such as `ImGui::Image()`, `ImDrawList::AddImage()`. +// This is what gets stored in draw commands (`ImDrawCmd`) to identify a texture during rendering. // - When a texture is created by user code (e.g. custom images), we directly stores the low-level ImTextureID. // - When a texture is created by the backend, we stores a ImTextureData* which becomes an indirection // to extract the ImTextureID value during rendering, after texture upload has happened. // - There is no constructor to create a ImTextureID from a ImTextureData* as we don't expect this // to be useful to the end-user, and it would be erroneously called by many legacy code. // - If you want to bind the current atlas when using custom rectangle, you can use io.Fonts->TexRef. -// - Binding generators for languages such as C (which don't have constructors), should provide a helper: +// - Binding generators for languages such as C (which don't have constructors), should provide a helper, e.g. // inline ImTextureRef ImTextureRefFromID(ImTextureID tex_id) { ImTextureRef tex_ref = { ._TexData = NULL, .TexID = tex_id }; return tex_ref; } // In 1.92 we changed most drawing functions using ImTextureID to use ImTextureRef. // We intentionally do not provide an implicit ImTextureRef -> ImTextureID cast operator because it is technically lossy to convert ImTextureRef to ImTextureID before rendering.