소스 검색

Fonts: Baked system, with auto-bind, v10.

# Conflicts:
#	imgui_internal.h
ocornut 6 달 전
부모
커밋
093d01269a
7개의 변경된 파일542개의 추가작업 그리고 293개의 파일을 삭제
  1. 60 34
      imgui.cpp
  2. 44 18
      imgui.h
  3. 309 165
      imgui_draw.cpp
  4. 26 13
      imgui_internal.h
  5. 1 1
      imgui_tables.cpp
  6. 18 15
      imgui_widgets.cpp
  7. 84 47
      misc/freetype/imgui_freetype.cpp

+ 60 - 34
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();

+ 44 - 18
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<ImFontConfig>      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<ImDrawListSharedData*> 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<float>             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<ImU16>             IndexLookup;        // 12-16 // out // Sparse. Index glyphs by Unicode code-point.
     ImVector<ImFontGlyph>       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 : "<unknown>"; }
@@ -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.

+ 309 - 165
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,44 +3540,55 @@ 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);
-}
+}*/
 
-// Load/identify special glyphs
-// (note that this is called again for fonts with MergeMode)
-void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src)
+static void ImFontAtlasBuildSetupFontBakedSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked)
 {
-    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)src_idx_in_font;
-
-    // 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)))
+    IM_ASSERT(baked->FallbackGlyphIndex == -1);
+    if (font->FallbackChar != 0)
+        if (const ImFontGlyph* glyph = baked->FindGlyphNoFallback(font->FallbackChar))
         {
-            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;
+            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*)font->FindGlyph((ImWchar)' ');
+    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 (font->FindGlyphNoFallback('\t') == NULL && space_glyph != NULL)
+    if (baked->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);
+        ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, &tab_glyph);
     }
+}
+
+// Load/identify special glyphs
+// (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);
+
+    // 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()
+            {
+                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<ImWchar>* 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;
 

+ 26 - 13
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;
     ImVector<ImTextureData*>Textures;
     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<ImFontBaked,32> 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);

+ 1 - 1
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;

+ 18 - 15
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))

+ 84 - 47
misc/freetype/imgui_freetype.cpp

@@ -46,6 +46,7 @@
 #include FT_FREETYPE_H          // <freetype/freetype.h>
 #include FT_MODULE_H            // <freetype/ftmodapi.h>
 #include FT_GLYPH_H             // <freetype/ftglyph.h>
+#include FT_SIZES_H             // <freetype/ftsizes.h>
 #include FT_SYNTHESIS_H         // <freetype/ftsynth.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;
 }