From e8cda61bc101e1545568c7603381267f53d07620 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 29 Nov 2024 13:42:35 +0100 Subject: [PATCH] WIP - Main code for ImGuiBackendFlags_RendererHasTextures feature. --- examples/example_null/main.cpp | 7 +- imgui.cpp | 183 ++- imgui.h | 210 +++- imgui_demo.cpp | 13 +- imgui_draw.cpp | 1993 +++++++++++++++++++----------- imgui_internal.h | 123 +- imgui_widgets.cpp | 5 +- misc/freetype/imgui_freetype.cpp | 4 +- 8 files changed, 1739 insertions(+), 799 deletions(-) diff --git a/examples/example_null/main.cpp b/examples/example_null/main.cpp index f7153cc48..460f33cab 100644 --- a/examples/example_null/main.cpp +++ b/examples/example_null/main.cpp @@ -11,9 +11,10 @@ int main(int, char**) ImGuiIO& io = ImGui::GetIO(); // Build atlas - unsigned char* tex_pixels = nullptr; - int tex_w, tex_h; - io.Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_w, &tex_h); + //unsigned char* tex_pixels = nullptr; + //int tex_w, tex_h; + //io.Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_w, &tex_h); + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; for (int n = 0; n < 20; n++) { diff --git a/imgui.cpp b/imgui.cpp index b24c52ce7..9f86644d8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1274,6 +1274,7 @@ static void UpdateMouseWheel(); static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt); // Misc +static void UpdateTexturesNewFrame(); static void UpdateSettings(); static int UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect); static void RenderWindowOuterBorders(ImGuiWindow* window); @@ -3887,7 +3888,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale))) continue; ImDrawList* draw_list = GetForegroundDrawList(viewport); - ImTextureRef tex_ref = font_atlas->TexID; + ImTextureRef tex_ref = font_atlas->TexRef; draw_list->PushTexture(tex_ref); draw_list->AddImage(tex_ref, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); draw_list->AddImage(tex_ref, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); @@ -4261,6 +4262,11 @@ void ImGui::Initialize() DockContextInitialize(&g); #endif + // ImDrawList/ImFontAtlas are designed to function without ImGui, and 99% of it works without an ImGui context. + // But this link allows us to facilitate/handle a few edge cases better. + g.DrawListSharedData.Context = &g; + ImFontAtlasAddDrawListSharedData(g.IO.Fonts, &g.DrawListSharedData); + g.Initialized = true; } @@ -4272,6 +4278,8 @@ void ImGui::Shutdown() IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == NULL, "Forgot to shutdown Renderer backend?"); // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + if (g.IO.Fonts) + ImFontAtlasRemoveDrawListSharedData(g.IO.Fonts, &g.DrawListSharedData); if (g.IO.Fonts && g.FontAtlasOwnedByContext) { g.IO.Fonts->Locked = false; @@ -4418,7 +4426,7 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL DockOrder = -1; DrawList = &DrawListInst; DrawList->_OwnerName = Name; - DrawList->_Data = &Ctx->DrawListSharedData; + DrawList->_SetDrawListSharedData(&Ctx->DrawListSharedData); NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX); IM_PLACEMENT_NEW(&WindowClass) ImGuiWindowClass(); } @@ -5013,7 +5021,7 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount) { draw_list->_ResetForNewFrame(); - draw_list->PushTexture(g.IO.Fonts->TexID); + draw_list->PushTexture(g.IO.Fonts->TexRef); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount; } @@ -5323,6 +5331,14 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos) io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } +static void ImGui::UpdateTexturesNewFrame() +{ + // FIXME-NEWATLAS: How to reach/target all atlas? + ImGuiContext& g = *GImGui; + ImFontAtlas* atlas = g.IO.Fonts; + ImFontAtlasUpdateNewFrame(atlas); +} + // Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! static void SetupDrawListSharedData() @@ -5359,6 +5375,13 @@ void ImGui::NewFrame() CallContextHooks(&g, ImGuiContextHookType_NewFramePre); + // Check that font atlas was built or backend support texture reload in which case we can build now + ImFontAtlas* atlas = g.IO.Fonts; + if (!atlas->TexIsBuilt && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) + atlas->Build(); + else // Legacy backend + IM_ASSERT(atlas->TexIsBuilt && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); + // Check and assert for various common IO and Configuration mistakes g.ConfigFlagsLastFrame = g.ConfigFlagsCurrFrame; ErrorCheckNewFrameSanityChecks(); @@ -5368,7 +5391,6 @@ void ImGui::NewFrame() UpdateSettings(); g.Time += g.IO.DeltaTime; - g.WithinFrameScope = true; g.FrameCount += 1; g.TooltipOverrideCount = 0; g.WindowsActiveCount = 0; @@ -5388,13 +5410,22 @@ void ImGui::NewFrame() // Update viewports (after processing input queue, so io.MouseHoveredViewport is set) UpdateViewportsNewFrame(); + // Update texture list (collect destroyed textures, etc.) + UpdateTexturesNewFrame(); + // Setup current font and draw list shared data - // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! - g.IO.Fonts->Locked = true; + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + { + g.IO.Fonts->Locked = true; + for (ImFont* font : g.IO.Fonts->Fonts) + font->LockDisableLoading = true; + } SetupDrawListSharedData(); SetCurrentFont(GetDefaultFont()); IM_ASSERT(g.Font->IsLoaded()); + g.WithinFrameScope = true; + // Mark rendering data as invalid to prevent user who may have a handle on it to use it. for (ImGuiViewportP* viewport : g.Viewports) { @@ -6036,6 +6067,11 @@ void ImGui::Render() g.IO.MetricsRenderIndices += draw_data->TotalIdxCount; } +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + ImFontAtlasDebugLogTextureRequests(g.IO.Fonts); +#endif + CallContextHooks(&g, ImGuiContextHookType_RenderPost); } @@ -8062,7 +8098,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Setup draw list and outer clipping rectangle IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0); - window->DrawList->PushTexture(g.Font->ContainerAtlas->TexID); + window->DrawList->PushTexture(g.Font->ContainerAtlas->TexRef); PushClipRect(host_rect.Min, host_rect.Max, false); // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71) @@ -8426,13 +8462,12 @@ void ImGui::SetCurrentFont(ImFont* font) g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale); g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; g.FontScale = g.FontSize / g.Font->FontSize; - - ImFontAtlas* atlas = g.Font->ContainerAtlas; - g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; - g.DrawListSharedData.TexUvLines = atlas->TexUvLines; g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.FontSize = g.FontSize; g.DrawListSharedData.FontScale = g.FontScale; + ImFontAtlasUpdateDrawListsSharedData(g.Font->ContainerAtlas); + if (g.CurrentWindow) + g.CurrentWindow->DrawList->_SetTextureRef(font->ContainerAtlas->TexRef); } // Use ImDrawList::_SetTextureID(), making our shared g.FontStack[] authoritative against window-local ImDrawList. @@ -8442,6 +8477,7 @@ void ImGui::SetCurrentFont(ImFont* font) // - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call PushTextureID()/PopTextureID() // the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem // because we have a concrete need and a test bed for multiple atlas textures. +// FIXME-NEWATLAS: perhaps we can now leverage ImFontAtlasUpdateDrawListsTextures() ? void ImGui::PushFont(ImFont* font) { ImGuiContext& g = *GImGui; @@ -8449,7 +8485,6 @@ void ImGui::PushFont(ImFont* font) font = GetDefaultFont(); g.FontStack.push_back(font); SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureRef(font->ContainerAtlas->TexID); } void ImGui::PopFont() @@ -8463,7 +8498,6 @@ void ImGui::PopFont() g.FontStack.pop_back(); ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back(); SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureRef(font->ContainerAtlas->TexID); } void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) @@ -10861,7 +10895,6 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); - IM_ASSERT(g.IO.Fonts->IsBuilt() && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations @@ -21072,10 +21105,12 @@ void ImGui::DebugTextEncoding(const char* str) Text("0x%02X", (int)(unsigned char)p[byte_index]); } TableNextColumn(); - if (GetFont()->FindGlyphNoFallback((ImWchar)c)) - TextUnformatted(p, p + c_utf8_len); - else - TextUnformatted((c == IM_UNICODE_CODEPOINT_INVALID) ? "[invalid]" : "[missing]"); + TextUnformatted(p, p + c_utf8_len); + if (GetFont()->FindGlyphNoFallback((ImWchar)c) == NULL) + { + SameLine(); + TextUnformatted("[missing]"); + } TableNextColumn(); Text("U+%04X", (int)c); p += c_utf8_len; @@ -21112,17 +21147,25 @@ void ImGui::UpdateDebugToolFlashStyleColor() DebugFlashStyleColorStop(); } -static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureRef tex_ref) +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) { union { void* ptr; int integer; } tex_id_opaque; - memcpy(&tex_id_opaque, &tex_ref._TexID, ImMin(sizeof(void*), sizeof(tex_ref._TexID))); - if (sizeof(tex_ref._TexID) >= sizeof(void*)) + memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); + if (sizeof(tex_id) >= sizeof(void*)) ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); else ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); return buf; } +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, const ImDrawCmd* cmd) +{ + char* buf_end = buf + buf_size; + if (cmd->TexRef._TexData != NULL) + buf += ImFormatString(buf, buf_end - buf, "#%03d: ", cmd->TexRef._TexData->UniqueID); + return FormatTextureIDForDebugDisplay(buf, (int)(buf_end - buf), cmd->GetTexID()); +} + // Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. static void MetricsHelpMarker(const char* desc) { @@ -21136,21 +21179,76 @@ static void MetricsHelpMarker(const char* desc) } } +#ifdef IMGUI_ENABLE_FREETYPE +namespace ImGuiFreeType { IMGUI_API const ImFontBackendIO* GetBackendIOForFreeType(); } +#endif + // [DEBUG] List fonts in a font atlas and display its texture void ImGui::ShowFontAtlas(ImFontAtlas* atlas) { + // Font loaders + if (TreeNode("Loader", "Loader: \'%s\'", atlas->FontBackendName ? atlas->FontBackendName : "NULL")) + { + const ImFontBackendIO* backend_io_current = atlas->FontBackendIO; + BeginDisabled(!atlas->DrawListSharedData || !atlas->DrawListSharedData->RendererHasTextures); +#ifdef IMGUI_ENABLE_STB_TRUETYPE + const ImFontBackendIO* backend_io_stbtruetype = ImFontAtlasGetBackendIOForStbTruetype(); + if (RadioButton("stb_truetype", backend_io_current == backend_io_stbtruetype)) + ImFontAtlasBuildSetupFontBackendIO(atlas, backend_io_stbtruetype); +#else + BeginDisabled(); + RadioButton("stb_truetype", false); + SetItemTooltip("Requires IMGUI_ENABLE_STB_TRUETYPE"); + EndDisabled(); +#endif + SameLine(); +#ifdef IMGUI_ENABLE_FREETYPE + const ImFontBackendIO* backend_io_freetype = ImGuiFreeType::GetBackendIOForFreeType(); + if (RadioButton("FreeType", backend_io_current == backend_io_freetype)) + ImFontAtlasBuildSetupFontBackendIO(atlas, backend_io_freetype); +#else + BeginDisabled(); + RadioButton("FreeType", false); + SetItemTooltip("Requires IMGUI_ENABLE_FREETYPE + imgui_freetype.cpp."); + EndDisabled(); +#endif + EndDisabled(); + TreePop(); + } + + // Font list for (ImFont* font : atlas->Fonts) { PushID(font); DebugNodeFont(font); PopID(); } - if (TreeNode("Font Atlas", "Font Atlas (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + + // Texture list + for (ImTextureData* tex : atlas->TexList) + { + PushID(tex); + DebugNodeTexture(tex); + PopID(); + } +} + +void ImGui::DebugNodeTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + if (TreeNode(tex, "Texture #%03d (%dx%d pixels)", tex->UniqueID, tex->Width, tex->Height)) { - ImGuiContext& g = *GImGui; PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); - ImageWithBg(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImTextureRef tex_id; + tex_id._TexData = tex; // Don't use tex->TexID directly so first frame works. + ImageWithBg(tex_id, ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); PopStyleVar(); + + char texid_desc[20]; + Text("Format = %d", tex->Format); + Text("TexID = %s", FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->TexID)); + Text("BackendUserData = %p", tex->BackendUserData); + Text("UseColors = %d", tex->UseColors); TreePop(); } } @@ -21438,6 +21536,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } + // Details for Fonts + ImFontAtlas* atlas = g.IO.Fonts; + if (TreeNode("Fonts", "Fonts (%d), Textures (%d)", atlas->Fonts.Size, atlas->TexList.Size)) + { + ShowFontAtlas(atlas); + TreePop(); + } + // Details for Popups if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) { @@ -21474,14 +21580,6 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } - // Details for Fonts - ImFontAtlas* atlas = g.IO.Fonts; - if (TreeNode("Fonts", "Fonts (%d)", atlas->Fonts.Size)) - { - ShowFontAtlas(atlas); - TreePop(); - } - // Details for InputText if (TreeNode("InputText")) { @@ -22026,7 +22124,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con } char texid_desc[20]; - FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TexRef); + FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd); char buf[300]; ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount / 3, texid_desc, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); @@ -22155,7 +22253,7 @@ void ImGui::DebugNodeFont(ImFont* font) for (int config_i = 0; config_i < font->SourcesCount; config_i++) if (font->Sources) { - const ImFontConfig* src = &font->Sources[config_i]; + ImFontConfig* src = &font->Sources[config_i]; int oversample_h, oversample_v; ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", @@ -22166,6 +22264,10 @@ void ImGui::DebugNodeFont(ImFont* font) { if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) { + if (SmallButton("Load all")) + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base++) + font->FindGlyph((ImWchar)base); + ImDrawList* draw_list = GetWindowDrawList(); const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); const float cell_size = font->FontSize * 1; @@ -22183,7 +22285,7 @@ void ImGui::DebugNodeFont(ImFont* font) int count = 0; for (unsigned int n = 0; n < 256; n++) - if (font->FindGlyphNoFallback((ImWchar)(base + n))) + if (font->IsGlyphLoaded((ImWchar)(base + n))) count++; if (count <= 0) continue; @@ -22198,7 +22300,7 @@ void ImGui::DebugNodeFont(ImFont* font) // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); + const ImFontGlyph* glyph = font->IsGlyphLoaded((ImWchar)(base + n)) ? font->FindGlyph((ImWchar)(base + n)) : NULL; draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); if (!glyph) continue; @@ -22219,7 +22321,7 @@ void ImGui::DebugNodeFont(ImFont* font) Unindent(); } -void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) +void ImGui::DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) { Text("Codepoint: U+%04X", glyph->Codepoint); Separator(); @@ -22227,6 +22329,11 @@ void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) Text("AdvanceX: %.1f", glyph->AdvanceX); Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + if (glyph->PackId >= 0) + { + ImFontAtlasRect* r = ImFontAtlasPackGetRect(font->ContainerAtlas, glyph->PackId); + Text("PackId: %d (%dx%d rect at %d,%d)", glyph->PackId, r->w, r->h, r->x, r->y);; + } } // [DEBUG] Display contents of ImGuiStorage @@ -22534,7 +22641,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) ShowDebugLogFlag("Docking", ImGuiDebugLogFlags_EventDocking); ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus); ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO); - //ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); + ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); diff --git a/imgui.h b/imgui.h index 1564798a8..3f9b85271 100644 --- a/imgui.h +++ b/imgui.h @@ -33,6 +33,7 @@ #define IMGUI_HAS_TABLE #define IMGUI_HAS_VIEWPORT // Viewport WIP branch #define IMGUI_HAS_DOCK // Docking WIP branch +#define IMGUI_HAS_TEXTURES // 1.92+ WIP branch with ImGuiBackendFlags_RendererHasTextures /* @@ -50,6 +51,7 @@ Index of this file: // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) // [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) // [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformMonitor, ImGuiPlatformImeData) @@ -171,10 +173,13 @@ struct ImDrawListSplitter; // Helper to split a draw list into differen struct ImDrawVert; // A single vertex (pos + uv + col = 20 bytes by default. Override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) struct ImFont; // Runtime data for a single font within a parent ImFontAtlas struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader -struct ImFontBuilderIO; // Opaque interface to a font builder (stb_truetype or FreeType). +struct ImFontAtlasBuilder; // Opaque storage for building a ImFontAtlas +struct ImFontBackendIO; // Opaque interface to a font builder (stb_truetype or FreeType). struct ImFontConfig; // Configuration data when adding a font or merging fonts struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data +struct ImTextureData; // Specs and pixel storage for a texture used by Dear ImGui. +struct ImTextureRect; // Coordinates of a rectangle within a texture. struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) // Forward declarations: ImGui layer @@ -333,8 +338,8 @@ struct ImTextureRef #endif // Members - ImFontAtlas* _Atlas; // Texture/Atlas pointer - ImTextureID _TexID; // _OR_ Underlying user/backend texture identifier, or zero if not yet uploaded. + ImTextureData* _TexData; // Texture, generally owned by a ImFontAtlas + ImTextureID _TexID; // _OR_ Underlying texture identifier for backend, if already uploaded (otherwise pulled from _TexData) }; IM_MSVC_RUNTIME_CHECKS_RESTORE @@ -1699,6 +1704,7 @@ enum ImGuiBackendFlags_ ImGuiBackendFlags_HasMouseCursors = 1 << 1, // Backend Platform supports honoring GetMouseCursor() value to change the OS cursor shape. ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if io.ConfigNavMoveSetMousePos is set). ImGuiBackendFlags_RendererHasVtxOffset = 1 << 3, // Backend Renderer supports ImDrawCmd::VtxOffset. This enables output of large meshes (64K+ vertices) while still using 16-bit indices. + ImGuiBackendFlags_RendererHasTextures = 1 << 4, // Backend Renderer supports ImTextureData requests to create/update/destroy textures. This enables incremental texture updates and texture reloads. // [BETA] Viewports ImGuiBackendFlags_PlatformHasViewports = 1 << 10, // Backend Platform supports multiple viewports. @@ -2895,6 +2901,14 @@ static inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return IM_MSVC_RUNTIME_CHECKS_RESTORE #endif +// Helpers: ImTexture ==/!= operators provided as convenience (not strictly necessary) +static inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._TexData == rhs._TexData; } +static inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._TexData != rhs._TexData; } +//#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // For legacy backends +//static inline bool operator==(ImTextureID lhs, const ImTextureRef& rhs) { return lhs == rhs._TexID && rhs._TexData == NULL; } +//static inline bool operator==(const ImTextureRef& lhs, ImTextureID rhs) { return lhs._TexID == rhs && lhs._TexData == NULL; } +//#endif + // Helpers macros to generate 32-bit encoded colors // - User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. // - Any setting other than the default will need custom backend support. The only standard backend that supports anything else than the default is DirectX9. @@ -3142,7 +3156,8 @@ struct ImDrawCmd // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! - inline ImTextureID GetTexID() const { return TexRef._TexID; } + // If for some reason you non C++ tech stack makes it difficult to call it, we may decide to separate the fields in ImDrawCmd. + inline ImTextureID GetTexID() const; }; // Vertex layout @@ -3374,6 +3389,7 @@ struct ImDrawList //inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } // OBSOLETED in 1.80 (Jan 2021) // [Internal helpers] + IMGUI_API void _SetDrawListSharedData(ImDrawListSharedData* data); IMGUI_API void _ResetForNewFrame(); IMGUI_API void _ClearFreeMemory(); IMGUI_API void _PopUnusedDrawCmd(); @@ -3410,6 +3426,73 @@ struct ImDrawData IMGUI_API void ScaleClipRects(const ImVec2& fb_scale); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than Dear ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. }; +//----------------------------------------------------------------------------- +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureDataUpdate, ImTextureData +//----------------------------------------------------------------------------- + +// We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. +enum ImTextureFormat +{ + ImTextureFormat_RGBA32, // 4 components per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + ImTextureFormat_Alpha8, // 1 component per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight +}; + +// Status of a texture +enum ImTextureStatus +{ + ImTextureStatus_OK, + ImTextureStatus_Destroyed, // Backend destroyed the texture. + ImTextureStatus_WantCreate, // Requesting backend to create the texture. Set status OK when done. + ImTextureStatus_WantUpdates, // Requesting backend to update specific blocks of pixels (write to texture portions which have never been used before). Set status OK when done. + ImTextureStatus_WantDestroy, // Requesting backend to destroy the texture. Set status to Destroyed when done. +}; + +// Coordinates of a rectangle within a texture. +// When a texture is in ImTextureStatus_WantUpdates state, we provide a list of individual rectangles to copy to GPU texture. +// You may use ImTextureData::Updates[] for the list, or ImTextureData::UpdateBox for a single bounding box. +struct ImTextureRect +{ + unsigned short x, y; // Upper-left coordinates of rectangle to update + unsigned short w, h; // Size of rectangle to update (in pixels) +}; + +// Specs and pixel storage for a texture used by Dear ImGui. +// The renderer backend will generally create a GPU-side version of this. +// Why does we store two identifiers: TexID and BackendUserData? +// - ImTextureID TexID = lower-level identifier stored in ImDrawCmd. ImDrawCmd can refer to textures not created by the backend, and for which there's no ImTextureData. +// - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. +struct IMGUI_API ImTextureData +{ + ImTextureStatus Status; // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy + ImTextureFormat Format; // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 + int Width; // Texture width + int Height; // Texture height + int BytesPerPixel; // 4 or 1 + int UniqueID; // Sequential index to facilitate identifying a texture when debugging/printing. Only unique per atlas. + unsigned char* Pixels; // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. + ImTextureID TexID; // Identifier stored in ImDrawCmd::GetTexID() and passed to backend RenderDrawData loop. + void* BackendUserData; // Convenience storage for backend. Some backends may have enough with TexID. + ImTextureRect UpdateRect; // Bounding box encompassing all individual updates. + ImVector Updates; // Array of individual updates. + int UnusedFrames; // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. + + // [Internal] + bool UseColors; // [Internal] Tell whether our texture data is known to use colors (rather than just white + alpha). + bool WantDestroyNextFrame; // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. + + // Functions + ImTextureData() { memset(this, 0, sizeof(*this)); } + ~ImTextureData() { DestroyPixels(); } + void Create(ImTextureFormat format, int w, int h); + void DestroyPixels(); + unsigned char* GetPixels() { IM_ASSERT(Pixels != NULL); return Pixels; } + unsigned char* GetPixelsAt(int x, int y) { IM_ASSERT(Pixels != NULL); return Pixels + (x + y * Width) * BytesPerPixel; } + int GetSizeInBytes() const { return Width * Height * BytesPerPixel; } + int GetPitch() const { return Width * BytesPerPixel; } + ImTextureRef GetTexRef() const { ImTextureRef tex_ref; tex_ref._TexData = (ImTextureData*)(void*)this; tex_ref._TexID = TexID; return tex_ref; } + ImTextureID GetTexID() const { return TexID; } +}; + //----------------------------------------------------------------------------- // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) //----------------------------------------------------------------------------- @@ -3439,7 +3522,8 @@ struct ImFontConfig // [Internal] char Name[40]; // Name (strictly to ease debugging) - ImFont* DstFont; + ImFont* DstFont; // Target font (as we merging fonts, multiple ImFontConfig may target the same font) + void* FontBackendData; // Font backend opaque storage (per font config) IMGUI_API ImFontConfig(); }; @@ -3454,6 +3538,9 @@ struct ImFontGlyph float AdvanceX; // Horizontal distance to advance layout with float X0, Y0, X1, Y1; // Glyph corners float U0, V0, U1, V1; // Texture coordinates + int PackId; // [Internal] ImFontAtlasRectId value (FIXME: Cold data, could be moved elsewhere?) + + ImFontGlyph() { memset(this, 0, sizeof(*this)); PackId = -1; } }; // Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges(). @@ -3501,12 +3588,14 @@ enum ImFontAtlasFlags_ // - One or more fonts. // - Custom graphics data needed to render the shapes needed by Dear ImGui. // - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas). -// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api. -// - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you. -// - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. -// - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples) +// - If you don't call any AddFont*** functions, the default font embedded in the code will be loaded for you. +// It is the rendering backend responsibility to upload texture into your graphics API: +// - ImGui_ImplXXXX_RenderDrawData() functions generally iterate atlas->TexList[] to create/update/destroy each ImTextureData instance. +// - Backend then set ImTextureData's TexID and BackendUserData. +// - Texture id are passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. +// Legacy path: +// - Call Build() + GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. // - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. -// This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. // Common pitfalls: // - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the // atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. @@ -3524,25 +3613,30 @@ struct ImFontAtlas IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + + // FIXME-NEWATLAS: Clarify meaning/purpose IMGUI_API void ClearInputData(); // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. IMGUI_API void ClearFonts(); // Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. IMGUI_API void Clear(); // Clear all input and output. + IMGUI_API void ClearCache(); // Clear cached glyphs + // Build atlas, retrieve pixel data. // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). // The pitch is always = Width * BytesPerPixels (1 or 4) // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. - IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - bool IsBuilt() const { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... + IMGUI_API void BuildGrowTexture(); + IMGUI_API void BuildCompactTexture(); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - void SetTexID(ImTextureID id){ TexID._Atlas = this; TexID._TexID = id; } // FIXME-NEWATLAS: Called by legacy backends. - void SetTexID(ImTextureRef id) { TexID = id; } // FIXME-NEWATLAS: Called by legacy backends. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { TexRef._TexData = NULL; TexRef._TexID = id; } // Called by legacy backends. + void SetTexID(ImTextureRef id) { TexRef = id; } // Called by legacy backends. #endif - + bool IsBuilt() const { return Fonts.Size > 0 && TexIsBuilt; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... //------------------------------------------- // Glyph Ranges @@ -3567,10 +3661,15 @@ struct ImFontAtlas //------------------------------------------- // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. - // - After calling Build(), you can query the rectangle position and render your pixels. - // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. - // - You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), - // so you can render e.g. custom colorful icons and use them as regular glyphs. + // You can request your rectangles to be mapped as font glyph (given a font + Unicode point), + // so you can render e.g. custom colorful icons and use them as regular glyphs. + // - If your backend supports ImGuiBackendFlags_RendererHasTextures (since 1.92.X): + // - Packing is done immediately. Returns >= on success. Return <0 on error. + // - You can render your pixels into the texture right after calling the AddCustomRectXXX functions. + // - Texture may be resized, so you cannot cache UV coordinates. // FIXME-NEWATLAS-V1: How to handle that smoothly? + // - If your backend does NOT supports ImGuiBackendFlags_RendererHasTextures (older than 1.92.X): + // - After calling Build(), you can query the rectangle position and render your pixels. + // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. IMGUI_API int AddCustomRectRegular(int width, int height); @@ -3586,34 +3685,38 @@ struct ImFontAtlas // Input ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) - ImTextureRef TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + ImTextureRef TexRef; // User data to refer to the latest texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + ImTextureFormat TexDesiredFormat; // Desired texture format (default to ImTextureFormat_RGBA32 but may be changed to ImTextureFormat_Alpha8). int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). + // Output + ImTextureData* TexData; // Current texture + ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). + // [Internal] - // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. - bool TexReady; // Set when texture was built matching current font input - bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format. - unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight - unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 - int TexWidth; // Texture width calculated during Build(). - int TexHeight; // Texture height calculated during Build(). - ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) + bool TexIsBuilt; // Set when texture was built matching current font input + bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. + ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight) ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + int TexNextUniqueID; // Next value to be stored in TexData->UniqueID + ImDrawListSharedData* DrawListSharedData; // In principle this could become an array (e.g. multiple contexts using same atlas) // [Internal] Font builder - const ImFontBuilderIO* FontBuilderIO; // Opaque interface to a font builder (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). - unsigned int FontBuilderFlags; // Shared flags (for all fonts) for custom font builder. THIS IS BUILD IMPLEMENTATION DEPENDENT. Per-font override is also available in ImFontConfig. - - // [Internal] Packing data - int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors - int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines + ImFontAtlasBuilder* Builder; // Opaque interface to our data that doesn't need to be public + const ImFontBackendIO* FontBackendIO; // Font backend opaque interface (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). Don't set directly! + const char* FontBackendName; // Font backend name (for display e.g. in About box) + void* FontBackendData; // Font backend opaque storage + unsigned int FontBuilderFlags; // (FIXME: Should be called FontBackendFlags) Shared flags (for all fonts) for custom font builder. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. . Per-font override is also available in ImFontConfig. + int _PackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. + int _PackedRects; // Number of packed rectangles. + float _PackNodesFactor = 1.0f; // [Obsolete] //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ @@ -3626,13 +3729,13 @@ struct ImFont { // [Internal] Members: Hot ~20/24 bytes (for CalcTextSize) ImVector IndexAdvanceX; // 12-16 // out // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). - float FallbackAdvanceX; // 4 // out // = FallbackGlyph->AdvanceX + float FallbackAdvanceX; // 4 // out // FindGlyph(FallbackChar)->AdvanceX float FontSize; // 4 // in // Height of characters/line, set during loading (don't change after loading) // [Internal] Members: Hot ~28/40 bytes (for RenderText loop) ImVector IndexLookup; // 12-16 // out // Sparse. Index glyphs by Unicode code-point. ImVector Glyphs; // 12-16 // out // All glyphs. - ImFontGlyph* FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) + int FallbackGlyphIndex; // 4 // out // Index of FontFallbackChar // [Internal] Members: Cold ~32/40 bytes // Conceptually Sources[] is the list of font sources merged to create this font. @@ -3644,18 +3747,21 @@ struct ImFont ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') float EllipsisWidth; // 4 // out // Total ellipsis Width float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 - float Scale; // 4 // in // Base font scale (1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() + float Scale; // 4 // in // Base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) int MetricsTotalSurface;// 4 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) - bool DirtyLookupTables; // 1 // out // ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + bool LockDisableLoading; + ImFontConfig* LockSingleSrcConfig; // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); - IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); - float GetCharAdvance(ImWchar c) { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); // Return fallback glyph if requested glyph doesn't exists. + IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); // Return NULL if glyph doesn't exist + IMGUI_API float GetCharAdvance(ImWchar c); + IMGUI_API bool IsGlyphLoaded(ImWchar c); + IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } const char* GetDebugName() const { return Sources ? Sources->Name : ""; } @@ -3668,14 +3774,24 @@ 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); // [Internal] Don't use! - IMGUI_API void BuildLookupTable(); IMGUI_API void ClearOutputData(); - IMGUI_API void GrowIndex(int new_size); - IMGUI_API void AddGlyph(const ImFontConfig* src_cfg, ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); + IMGUI_API ImFontGlyph* BuildLoadGlyph(ImWchar c); + IMGUI_API void BuildRegisterGlyph(ImFontConfig* src, const ImFontGlyph* glyph); + IMGUI_API void BuildGrowIndex(int new_size); + IMGUI_API void BuildClearGlyphs(); }; +// FIXME-NEWATLAS: Added indirection to avoid patching ImDrawCmd after texture updates. +inline ImTextureID ImDrawCmd::GetTexID() const +{ + ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; + if (TexRef._TexData != NULL) + IM_ASSERT(tex_id && "ImDrawCmd is referring to Atlas texture that wasn't uploaded to graphics system."); + return tex_id; +} + //----------------------------------------------------------------------------- // [SECTION] Viewports //----------------------------------------------------------------------------- @@ -3827,6 +3943,10 @@ struct ImGuiPlatformIO // Input - Interface with Renderer Backend //------------------------------------------------------------------ + // Optional: Maximum texture size supported by renderer (used to adjust how we size textures). 0 if not known. + int Renderer_TextureMaxWidth; + int Renderer_TextureMaxHeight; + // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. void* Renderer_RenderState; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 3932957f2..2a96f884a 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -622,6 +622,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::CheckboxFlags("io.BackendFlags: PlatformHasViewports", &io.BackendFlags, ImGuiBackendFlags_PlatformHasViewports); ImGui::CheckboxFlags("io.BackendFlags: HasMouseHoveredViewport",&io.BackendFlags, ImGuiBackendFlags_HasMouseHoveredViewport); ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", &io.BackendFlags, ImGuiBackendFlags_RendererHasVtxOffset); + ImGui::CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); ImGui::CheckboxFlags("io.BackendFlags: RendererHasViewports", &io.BackendFlags, ImGuiBackendFlags_RendererHasViewports); ImGui::EndDisabled(); @@ -1809,9 +1810,9 @@ static void DemoWindowWidgetsImages() // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples - ImTextureRef my_tex_id = io.Fonts->TexID; - float my_tex_w = (float)io.Fonts->TexWidth; - float my_tex_h = (float)io.Fonts->TexHeight; + ImTextureRef my_tex_id = io.Fonts->TexRef; + float my_tex_w = (float)io.Fonts->TexData->Width; + float my_tex_h = (float)io.Fonts->TexData->Height; { ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); ImVec2 pos = ImGui::GetCursorScreenPos(); @@ -8038,7 +8039,7 @@ void ImGui::ShowAboutWindow(bool* p_open) if (copy_to_clipboard) { ImGui::LogToClipboard(); - ImGui::LogText("```\n"); // Back quotes will make text appears without formatting when pasting on GitHub + ImGui::LogText("```cpp\n"); // Back quotes will make text appears without formatting when pasting on GitHub } ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); @@ -8154,9 +8155,11 @@ void ImGui::ShowAboutWindow(bool* p_open) if (io.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) ImGui::Text(" PlatformHasViewports"); if (io.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)ImGui::Text(" HasMouseHoveredViewport"); if (io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) ImGui::Text(" RendererHasVtxOffset"); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) ImGui::Text(" RendererHasTextures"); if (io.BackendFlags & ImGuiBackendFlags_RendererHasViewports) ImGui::Text(" RendererHasViewports"); ImGui::Separator(); - ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexWidth, io.Fonts->TexHeight); + ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexData->Width, io.Fonts->TexData->Height); + ImGui::Text("io.Fonts->FontBackendName: \"%s\"", io.Fonts->FontBackendName ? io.Fonts->FontBackendName : "NULL"); ImGui::Text("io.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y); ImGui::Text("io.DisplayFramebufferScale: %.2f,%.2f", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); ImGui::Separator(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2197c17d9..79dafa939 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -13,7 +13,8 @@ Index of this file: // [SECTION] ImDrawData // [SECTION] Helpers ShadeVertsXXX functions // [SECTION] ImFontConfig -// [SECTION] ImFontAtlas +// [SECTION] ImFontAtlas, ImFontAtlasBuilder +// [SECTION] ImFontAtlas: backend for stb_truetype // [SECTION] ImFontAtlas: glyph ranges helpers // [SECTION] ImFontGlyphRangesBuilder // [SECTION] ImFont @@ -392,6 +393,12 @@ ImDrawListSharedData::ImDrawListSharedData() ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); } ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); + RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. +} + +ImDrawListSharedData::~ImDrawListSharedData() +{ + IM_ASSERT(DrawLists.Size == 0); } void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) @@ -412,12 +419,22 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) ImDrawList::ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); - _Data = shared_data; + _SetDrawListSharedData(shared_data); } ImDrawList::~ImDrawList() { _ClearFreeMemory(); + _SetDrawListSharedData(NULL); +} + +void ImDrawList::_SetDrawListSharedData(ImDrawListSharedData* data) +{ + if (_Data != NULL) + _Data->DrawLists.find_erase_unsorted(this); + _Data = data; + if (_Data != NULL) + _Data->DrawLists.push_back(this); } // Initialize before use in a new frame. We always have a command ready in the buffer. @@ -576,10 +593,6 @@ void ImDrawList::_OnChangedClipRect() curr_cmd->ClipRect = _CmdHeader.ClipRect; } -// Operators for easy compare -static inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._Atlas == rhs._Atlas; } -static inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._Atlas != rhs._Atlas; } - void ImDrawList::_OnChangedTextureID() { // If current command is used with different settings we need to add a new command @@ -1693,8 +1706,6 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 if (font_size == 0.0f) font_size = _Data->FontSize; - IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TexRef); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. - ImVec4 clip_rect = _CmdHeader.ClipRect; if (cpu_fine_clip_rect) { @@ -2399,15 +2410,64 @@ ImFontConfig::ImFontConfig() } //----------------------------------------------------------------------------- -// [SECTION] ImFontAtlas +// [SECTION] ImTextureData +//----------------------------------------------------------------------------- +// - ImTextureData::Create() +// - ImTextureData::DestroyPixels() +//----------------------------------------------------------------------------- + +static int GetTextureFormatBytesPerPixel(ImTextureFormat format) +{ + switch (format) + { + case ImTextureFormat_Alpha8: return 1; + case ImTextureFormat_RGBA32: return 4; + } + IM_ASSERT(0); + return 0; +} + +void ImTextureData::Create(ImTextureFormat format, int w, int h) +{ + DestroyPixels(); + Format = format; + Width = w; + Height = h; + BytesPerPixel = GetTextureFormatBytesPerPixel(format); + UseColors = false; + Pixels = (unsigned char*)IM_ALLOC(Width * Height * BytesPerPixel); + IM_ASSERT(Pixels != NULL); + memset(Pixels, 0, Width * Height * BytesPerPixel); +} + +void ImTextureData::DestroyPixels() +{ + if (Pixels) + IM_FREE(Pixels); + Pixels = NULL; + UseColors = false; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas, ImFontAtlasBuilder //----------------------------------------------------------------------------- // - Default texture data encoded in ASCII +// - ImFontAtlasBuilder // - ImFontAtlas::ClearInputData() // - ImFontAtlas::ClearTexData() // - ImFontAtlas::ClearFonts() // - ImFontAtlas::Clear() -// - ImFontAtlas::GetTexDataAsAlpha8() -// - ImFontAtlas::GetTexDataAsRGBA32() +// - ImFontAtlas::ClearCache() +// - ImFontAtlas::BuildGrowTexture() +// - ImFontAtlas::BuildCompactTexture() +// - ImFontAtlasUpdateTextures() +// - ImFontAtlasTextureBlockConvertAndPostProcess() +// - ImFontAtlasTextureBlockConvert() +// - ImFontAtlasTextureBlockPostProcessMultiply() +// - ImFontAtlasTextureBlockCopy() +// - ImFontAtlasTextureBlockQueueUpload() +// - ImFontAtlas::GetTexDataAsAlpha8() [legacy] +// - ImFontAtlas::GetTexDataAsRGBA32() [legacy] // - ImFontAtlas::AddFont() // - ImFontAtlas::AddFontDefault() // - ImFontAtlas::AddFontFromFileTTF() @@ -2419,19 +2479,34 @@ ImFontConfig::ImFontConfig() // - ImFontAtlas::CalcCustomRectUV() // - ImFontAtlasGetMouseCursorTexData() // - ImFontAtlas::Build() -// - ImFontAtlasBuildMultiplyCalcLookupTable() -// - ImFontAtlasBuildMultiplyRectAlpha8() -// - ImFontAtlasBuildWithStbTruetype() -// - ImFontAtlasGetBuilderForStbTruetype() -// - ImFontAtlasUpdateSourcesPointers() -// - ImFontAtlasBuildSetupFont() -// - ImFontAtlasBuildPackCustomRects() -// - ImFontAtlasBuildRender8bppRectFromString() -// - ImFontAtlasBuildRender32bppRectFromString() -// - ImFontAtlasBuildRenderDefaultTexData() -// - ImFontAtlasBuildRenderLinesTexData() +// - ImFontAtlasBuildSetupFontBackendIO() +// - ImFontAtlasBuildPreloadAllGlyphRanges() +// - ImFontAtlasBuildUpdatePointers() +// - ImFontAtlasBuildRenderBitmapFromString() +// - ImFontAtlasBuildUpdateBasicTexData() +// - ImFontAtlasBuildUpdateLinesTexData() +// - ImFontAtlasBuildAddFont() +// - ImFontAtlasBuildSetupFontSpecialGlyphs() +// - ImFontAtlasBuildReloadFont() +// - ImFontAtlasAddDrawListSharedData() +// - ImFontAtlasRemoveDrawListSharedData() +// - ImFontAtlasUpdateDrawListsTextures() +// - ImFontAtlasUpdateDrawListsSharedData() +// - ImFontAtlasBuildSetTexture() +// - ImFontAtlasBuildAddTexture() +// - ImFontAtlasBuildRepackTexture() +// - ImFontAtlasBuildGrowTexture() +// - ImFontAtlasBuildCompactTexture() // - ImFontAtlasBuildInit() -// - ImFontAtlasBuildFinish() +// - ImFontAtlasBuildDestroy() +// - ImFontAtlasPackInit() +// - ImFontAtlasPackAddRect() +// - ImFontAtlasPackGetRect() +// - ImFont::BuildLoadGlyph() +// - ImFont::BuildClearGlyphs() +// - ImFontAtlasDebugLogTextureRequests() +//----------------------------------------------------------------------------- +// - ImFontAtlasGetBackendIOForStbTruetype() //----------------------------------------------------------------------------- // A work of art lies ahead! (. = white layer, X = black layer, others are blank) @@ -2486,23 +2561,29 @@ static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3 { ImVec2(109,0),ImVec2(13,15), ImVec2( 6, 7) }, // ImGuiMouseCursor_NotAllowed }; +#define IM_FONTGLYPH_INDEX_UNUSED ((ImU16)-1) // 0xFFFF +#define IM_FONTGLYPH_INDEX_NOT_FOUND ((ImU16)-2) // 0xFFFE + ImFontAtlas::ImFontAtlas() { memset(this, 0, sizeof(*this)); + TexDesiredFormat = ImTextureFormat_RGBA32; TexGlyphPadding = 1; - TexID._Atlas = this; - PackIdMouseCursors = PackIdLines = -1; + TexRef._TexData = NULL;// this; + TexNextUniqueID = 1; + _PackNodesFactor = 1.0f; + Builder = NULL; } ImFontAtlas::~ImFontAtlas() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); Clear(); } -void ImFontAtlas::ClearInputData() +void ImFontAtlas::ClearInputData() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); for (ImFontConfig& font_cfg : Sources) if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas) { @@ -2519,77 +2600,282 @@ void ImFontAtlas::ClearInputData() } Sources.clear(); CustomRects.clear(); - PackIdMouseCursors = PackIdLines = -1; // Important: we leave TexReady untouched } -void ImFontAtlas::ClearTexData() +void ImFontAtlas::ClearTexData() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - if (TexPixelsAlpha8) - IM_FREE(TexPixelsAlpha8); - if (TexPixelsRGBA32) - IM_FREE(TexPixelsRGBA32); - TexPixelsAlpha8 = NULL; - TexPixelsRGBA32 = NULL; - TexPixelsUseColors = false; - // Important: we leave TexReady untouched + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + TexList.clear(); + IM_DELETE(TexData); + TexData = NULL; + // TexData.Destroy(); + //IM_ASSERT(0); + // Important: we leave TexReady untouched } -void ImFontAtlas::ClearFonts() +void ImFontAtlas::ClearFonts() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + // FIXME-NEWATLAS: Illegal to remove currently bound font. + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); ClearInputData(); Fonts.clear_delete(); - TexReady = false; + TexIsBuilt = false; + DrawListSharedData->Font = NULL; + DrawListSharedData->FontScale = DrawListSharedData->FontSize = 0.0f; } -void ImFontAtlas::Clear() +void ImFontAtlas::Clear() { + //IM_DELETE(Builder); // FIXME-NEW-ATLAS: ClearXXX functions + const ImFontBackendIO* font_backend_io = FontBackendIO; + ImFontAtlasBuildSetupFontBackendIO(this, NULL); ClearInputData(); ClearTexData(); ClearFonts(); + ImFontAtlasBuildSetupFontBackendIO(this, font_backend_io); } -void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +void ImFontAtlas::ClearCache() { - // Build atlas on demand - if (TexPixelsAlpha8 == NULL) - Build(); - - *out_pixels = TexPixelsAlpha8; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; + int tex_w = (TexData && TexData->Status != ImTextureStatus_WantDestroy) ? TexData->Width : 0; + int tex_h = (TexData && TexData->Status != ImTextureStatus_WantDestroy) ? TexData->Height : 0; + ImFontAtlasBuildDestroy(this); + if (tex_w != 0 && tex_h != 0) + ImFontAtlasBuildAddTexture(this, tex_w, tex_h); + ImFontAtlasBuildInit(this); } -void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +void ImFontAtlas::BuildGrowTexture() { - // Convert to RGBA32 format on demand - // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp - if (!TexPixelsRGBA32) + ImFontAtlasBuildGrowTexture(this, TexData->Width, TexData->Height); +} + +void ImFontAtlas::BuildCompactTexture() +{ + ImFontAtlasBuildCompactTexture(this); +} + +static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* atlas) +{ + // [LEGACY] Copy back the ImGuiBackendFlags_RendererHasTextures flag from ImGui context. + // - This is the 1% exceptional case where that dependency if useful, to bypass an issue where otherwise at the + // time of an early call to Build(), it would be impossible for us to tell if the backend supports texture update. + // - Without this hack, we would have quite a pitfall as many legacy codebases have an early call to Build(). + // Whereas conversely, the portion of people using ImDrawList without ImGui is expected to be pathologically rare. + if (atlas->DrawListSharedData) + if (ImGuiContext* imgui_ctx = atlas->DrawListSharedData->Context) + atlas->DrawListSharedData->RendererHasTextures = (imgui_ctx->IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; +} + +// Called by NewFrame() +void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas) +{ + if (atlas->TexIsBuilt && atlas->Builder->PreloadedAllGlyphsRanges) { - unsigned char* pixels = NULL; - GetTexDataAsAlpha8(&pixels, NULL, NULL); - if (pixels) - { - TexPixelsRGBA32 = (unsigned int*)IM_ALLOC((size_t)TexWidth * (size_t)TexHeight * 4); - const unsigned char* src = pixels; - unsigned int* dst = TexPixelsRGBA32; - for (int n = TexWidth * TexHeight; n > 0; n--) - *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); - } + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); + IM_ASSERT_USER_ERROR(atlas->DrawListSharedData->RendererHasTextures == false, + "Called ImFontAtlas::Build() before ImGuiBackendFlags_RendererHasTextures got set! With new backends: you don't need to call Build()."); } - *out_pixels = (unsigned char*)TexPixelsRGBA32; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + ImTextureData* tex = atlas->TexList[tex_n]; + bool remove_from_list = false; + tex->Updates.resize(0); + tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; + tex->UpdateRect.w = tex->UpdateRect.h = 0; + + if (tex->Status == ImTextureStatus_Destroyed) + { + IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == NULL); + if (tex->WantDestroyNextFrame) + remove_from_list = true; // Destroy was scheduled by us + else + tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend (e.g. freed resources mid-run) + } + else if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_WantDestroy) + { + // Request destroy. Keep bool as it allows us to keep track of things. + IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); + tex->Status = ImTextureStatus_WantDestroy; + tex->DestroyPixels(); + } + else if (tex->Status == ImTextureStatus_WantDestroy) + { + // The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering. + // We allow the texture staying in _WantDestroy state and increment a counter which the backend can use to take its decision. + tex->UnusedFrames++; + } + + // If a texture has never reached the backend, they don't need to know about it. + if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == 0 && tex->BackendUserData == NULL) + remove_from_list = true; + + // Remove + if (remove_from_list) + { + IM_DELETE(tex); + atlas->TexList.erase(atlas->TexList.begin() + tex_n); + tex_n--; + } + } } +// Source buffer may be written to (used for in-place mods). +// Post-process hooks may eventually be added here. +void ImFontAtlasTextureBlockConvertAndPostProcess(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) +{ + IM_UNUSED(atlas); + IM_UNUSED(font); + IM_UNUSED(glyph); + + // Multiply operator (legacy) + if (src->RasterizerMultiply != 1.0f) + ImFontAtlasTextureBlockPostProcessMultiply(atlas, font, src, glyph, src_pixels, src_fmt, w, h, src_pitch, src->RasterizerMultiply); + + ImFontAtlasTextureBlockConvert(src_pixels, src_fmt, src_pitch, dst, dst_fmt, dst_pitch, w, h); +} + +void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) +{ + IM_ASSERT(src_pixels != NULL && dst_pixels != NULL); + if (src_fmt == dst_fmt) + { + int line_sz = w * GetTextureFormatBytesPerPixel(src_fmt); + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + memcpy(dst_pixels, src_pixels, line_sz); + } + else if (src_fmt == ImTextureFormat_Alpha8 && dst_fmt == ImTextureFormat_RGBA32) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU8* src_p = (const ImU8*)src_pixels; + ImU32* dst_p = (ImU32*)(void*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = IM_COL32(255, 255, 255, (unsigned int)(*src_p++)); + } + } + else if (src_fmt == ImTextureFormat_RGBA32 && dst_fmt == ImTextureFormat_Alpha8) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU32* src_p = (const ImU32*)(void*)src_pixels; + ImU8* dst_p = (ImU8*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = ((*src_p++) >> IM_COL32_A_SHIFT) & 0xFF; + } + } + else + { + IM_ASSERT(0); + } +} + +void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* pixels, ImTextureFormat format, int w, int h, int pitch, float in_multiply_factor) +{ + IM_UNUSED(atlas); + IM_UNUSED(font); + IM_UNUSED(src); + IM_UNUSED(glyph); + IM_ASSERT(in_multiply_factor >= 0.0f); + IM_ASSERT_PARANOID(w <= pitch); + if (format == ImTextureFormat_Alpha8) + { + for (int ny = h; ny > 0; ny--, pixels += pitch) + { + ImU8* p = (ImU8*)pixels; + for (int nx = w; nx > 0; nx--, p++) + { + unsigned int v = ImMin((unsigned int)(*p * in_multiply_factor), (unsigned int)255); + *p = (unsigned char)v; + } + } + } + else if (format == ImTextureFormat_RGBA32) + { + for (int ny = h; ny > 0; ny--, pixels += pitch) + { + ImU32* p = (ImU32*)(void*)pixels; + for (int nx = w; nx > 0; nx--, p++) + { + unsigned int a = ImMin((unsigned int)(((*p >> IM_COL32_A_SHIFT) & 0xFF) * in_multiply_factor), (unsigned int)255); + *p = IM_COL32((*p >> IM_COL32_R_SHIFT) & 0xFF, (*p >> IM_COL32_G_SHIFT) & 0xFF, (*p >> IM_COL32_B_SHIFT) & 0xFF, a); + } + } + } + else + { + IM_ASSERT(0); + } +} + +// Convert block from one texture to another +void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h) +{ + IM_ASSERT(src_tex != dst_tex); + IM_ASSERT(src_tex->Pixels != NULL && dst_tex->Pixels != NULL); + IM_ASSERT(src_tex->Format == dst_tex->Format); + IM_ASSERT(src_x >= 0 && src_x + w <= src_tex->Width); + IM_ASSERT(src_y >= 0 && src_y + h <= src_tex->Height); + IM_ASSERT(dst_x >= 0 && dst_x + w <= dst_tex->Width); + IM_ASSERT(dst_y >= 0 && dst_y + h <= dst_tex->Height); + for (int y = 0; y < h; y++) + memcpy(dst_tex->GetPixelsAt(dst_x, dst_y + y), src_tex->GetPixelsAt(src_x, src_y + y), w * dst_tex->BytesPerPixel); +} + +// Queue texture block update for renderer backend +void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h) +{ + // Queue texture update (no need to queue if status is _WantCreate) + IM_ASSERT(atlas); + if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates) + { + IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000); + ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h }; + tex->Status = ImTextureStatus_WantUpdates; + tex->Updates.push_back(req); + int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w); + int new_y1 = ImMax(tex->UpdateRect.h == 0 ? 0 : tex->UpdateRect.y + tex->UpdateRect.h, req.y + req.h); + IM_ASSERT(new_x1 < 0x8000); + IM_ASSERT(new_y1 < 0x8000); + tex->UpdateRect.x = ImMin(tex->UpdateRect.x, req.x); + tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y); + tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x); + tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y); + } +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +static void GetTexDataAsFormat(ImFontAtlas* atlas, ImTextureFormat format, unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + ImTextureData* tex = atlas->TexData; + if (!atlas->TexIsBuilt || tex == NULL || tex->Pixels == NULL || atlas->TexDesiredFormat != format) + { + atlas->TexDesiredFormat = format; + atlas->Build(); + tex = atlas->TexData; + } + if (out_pixels) { *out_pixels = (unsigned char*)tex->Pixels; }; + if (out_width) { *out_width = tex->Width; }; + if (out_height) { *out_height = tex->Height; }; + if (out_bytes_per_pixel) { *out_bytes_per_pixel = tex->BytesPerPixel; } +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_Alpha8, out_pixels, out_width, out_height, out_bytes_per_pixel); +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_RGBA32, out_pixels, out_width, out_height, out_bytes_per_pixel); +} +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); IM_ASSERT(font_cfg->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); @@ -2617,12 +2903,15 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) // - We may support it better later and remove this rounding. new_font_cfg.SizePixels = ImTrunc(new_font_cfg.SizePixels); - // Pointers to Sources data are otherwise dangling - ImFontAtlasUpdateSourcesPointers(this); + // Pointers to Sources are otherwise dangling + ImFontAtlasBuildUpdatePointers(this); + + if (Builder != NULL) + ImFontAtlasBuildAddFont(this, &new_font_cfg); // Invalidate texture - TexReady = false; - ClearTexData(); + //TexReady = false; + //ClearTexData(); return new_font_cfg.DstFont; } @@ -2675,7 +2964,7 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); size_t data_size = 0; void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); if (!data) @@ -2697,7 +2986,7 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, // NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); IM_ASSERT(font_cfg.FontData == NULL); IM_ASSERT(font_data_size > 100 && "Incorrect value for font_data_size!"); // Heuristic to prevent accidentally passing a wrong value to font_data_size. @@ -2731,6 +3020,25 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed return font; } +// FIXME-NEWATLAS-V1: Feature is broken for now. +/* + // Register custom rectangle glyphs + for (int i = 0; i < atlas->CustomRects.Size; i++) + { + const ImFontAtlasCustomRect* r = &atlas->CustomRects[i]; + if (r->Font == NULL || r->GlyphID == 0) + continue; + + // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, PixelSnapH + IM_ASSERT(r->Font->ContainerAtlas == atlas); + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(r, &uv0, &uv1); + r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); + if (r->GlyphColored) + r->Font->Glyphs.back().Colored = 1; + } +*/ + int ImFontAtlas::AddCustomRectRegular(int width, int height) { IM_ASSERT(width > 0 && width <= 0xFFFF); @@ -2764,7 +3072,7 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const { - IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates + IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); @@ -2777,9 +3085,8 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) return false; - IM_ASSERT(atlas->PackIdMouseCursors != -1); - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); - ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->X, (float)r->Y); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, atlas->Builder->PackIdMouseCursors); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->x, (float)r->y); ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; *out_size = size; *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; @@ -2791,407 +3098,104 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso return true; } -bool ImFontAtlas::Build() +bool ImFontAtlas::Build() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); // Default font is none are specified if (Sources.Size == 0) AddFontDefault(); // Select builder - // - Note that we do not reassign to atlas->FontBuilderIO, since it is likely to point to static data which + // - Note that we do not reassign to atlas->FontBackendIO, since it is likely to point to static data which // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are - // using a hot-reloading scheme that messes up static data, store your own instance of ImFontBuilderIO somewhere - // and point to it instead of pointing directly to return value of the GetBuilderXXX functions. - const ImFontBuilderIO* builder_io = FontBuilderIO; - if (builder_io == NULL) + // using a hot-reloading scheme that messes up static data, store your own instance of ImFontBackendIO somewhere + // and point to it instead of pointing directly to return value of the GetBackendIOXXX functions. + if (FontBackendIO == NULL) { #ifdef IMGUI_ENABLE_FREETYPE - builder_io = ImGuiFreeType::GetBuilderForFreeType(); + ImFontAtlasBuildSetupFontBackendIO(this, ImGuiFreeType::GetBackendIOForFreeType()); #elif defined(IMGUI_ENABLE_STB_TRUETYPE) - builder_io = ImFontAtlasGetBuilderForStbTruetype(); + ImFontAtlasBuildSetupFontBackendIO(this, ImFontAtlasGetBackendIOForStbTruetype()); #else IM_ASSERT(0); // Invalid Build function #endif } - // Build - return builder_io->FontBuilder_Build(this); + // Create initial texture size + ImFontAtlasBuildAddTexture(this, 512, 128); + ImFontAtlasBuildInit(this); + + // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(this); + if (DrawListSharedData->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures + ImFontAtlasBuildPreloadAllGlyphRanges(this); + TexIsBuilt = true; + + return true; } -void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) -{ - for (unsigned int i = 0; i < 256; i++) - { - unsigned int value = (unsigned int)(i * in_brighten_factor); - out_table[i] = value > 255 ? 255 : (value & 0xFF); - } -} - -void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) -{ - IM_ASSERT_PARANOID(w <= stride); - unsigned char* data = pixels + x + y * stride; - for (int j = h; j > 0; j--, data += stride - w) - for (int i = w; i > 0; i--, data++) - *data = table[*data]; -} - -void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v) +void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v) { // Automatically disable horizontal oversampling over size 36 *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (src->SizePixels * src->RasterizerDensity > 36.0f || src->PixelSnapH) ? 1 : 2; *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1; } -#ifdef IMGUI_ENABLE_STB_TRUETYPE -// Temporary data for one source font (multiple source fonts can be merged into one destination ImFont) -// (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.) -struct ImFontBuildSrcData +void ImFontAtlasBuildSetupFontBackendIO(ImFontAtlas* atlas, const ImFontBackendIO* font_backend_io) { - stbtt_fontinfo FontInfo; - stbtt_pack_range PackRange; // Hold the list of codepoints to pack (essentially points to Codepoints.Data) - stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position. - stbtt_packedchar* PackedChars; // Output glyphs - const ImWchar* SrcRanges; // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF) - int DstIndex; // Index into atlas->Fonts[] and dst_tmp_array[] - int GlyphsHighest; // Highest requested codepoint - int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font) - ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB) - ImVector GlyphsList; // Glyph codepoints list (flattened version of GlyphsSet) -}; + if (atlas->FontBackendIO == font_backend_io) + return; + IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); -// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont) -struct ImFontBuildDstData -{ - int SrcCount; // Number of source fonts targeting this destination font. - int GlyphsHighest; - int GlyphsCount; - ImBitVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font. -}; - -static void UnpackBitVectorToFlatIndexList(const ImBitVector* in, ImVector* out) -{ - IM_ASSERT(sizeof(in->Storage.Data[0]) == sizeof(int)); - const ImU32* it_begin = in->Storage.begin(); - const ImU32* it_end = in->Storage.end(); - for (const ImU32* it = it_begin; it < it_end; it++) - if (ImU32 entries_32 = *it) - for (ImU32 bit_n = 0; bit_n < 32; bit_n++) - if (entries_32 & ((ImU32)1 << bit_n)) - out->push_back((int)(((it - it_begin) << 5) + bit_n)); + ImFontAtlasBuildDestroy(atlas); + if (atlas->FontBackendIO && atlas->FontBackendIO->FontBackend_BackendShutdown) + { + atlas->FontBackendIO->FontBackend_BackendShutdown(atlas); + IM_ASSERT(atlas->FontBackendData == NULL); + } + atlas->FontBackendIO = font_backend_io; + atlas->FontBackendName = font_backend_io ? font_backend_io->FontBackend_Name : "NULL"; + if (atlas->FontBackendIO && atlas->FontBackendIO->FontBackend_BackendInit) + atlas->FontBackendIO->FontBackend_BackendInit(atlas); + if (atlas->Builder && font_backend_io != NULL) + atlas->ClearCache(); } -static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) +// Preload all glyph ranges for legacy backends. +// This may lead to multiple texture creation which might be a little slower than before. +void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas) { - IM_ASSERT(atlas->Sources.Size > 0); - - ImFontAtlasBuildInit(atlas); - - // Clear atlas - atlas->TexID._TexID = 0; - atlas->TexWidth = atlas->TexHeight = 0; - atlas->TexUvScale = ImVec2(0.0f, 0.0f); - atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); - atlas->ClearTexData(); - - // Temporary storage for building - ImVector src_tmp_array; - ImVector dst_tmp_array; - src_tmp_array.resize(atlas->Sources.Size); - dst_tmp_array.resize(atlas->Fonts.Size); - memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes()); - memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes()); - - // 1. Initialize font loading structure, check font data validity - for (int src_i = 0; src_i < atlas->Sources.Size; src_i++) + atlas->Builder->PreloadedAllGlyphsRanges = true; + for (int src_n = 0; src_n < atlas->Sources.Size; src_n++) { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - IM_ASSERT(src.DstFont && (!src.DstFont->IsLoaded() || src.DstFont->ContainerAtlas == atlas)); - - // Find index from src.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices) - src_tmp.DstIndex = -1; - for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++) - if (src.DstFont == atlas->Fonts[output_i]) - src_tmp.DstIndex = output_i; - if (src_tmp.DstIndex == -1) - { - IM_ASSERT(src_tmp.DstIndex != -1); // src.DstFont not pointing within atlas->Fonts[] array? - return false; - } - // Initialize helper structure for font loading and verify that the TTF/OTF data is correct - const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src.FontData, src.FontNo); - IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); - if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)src.FontData, font_offset)) - { - IM_ASSERT(0 && "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); - return false; - } - - // Measure highest codepoints - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.SrcRanges = src.GlyphRanges ? src.GlyphRanges : atlas->GetGlyphRangesDefault(); - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - { - // Check for valid range. This may also help detect *some* dangling pointers, because a common - // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent, - // or to forget to zero-terminate the glyph range array. - IM_ASSERT(src_range[0] <= src_range[1] && "Invalid range: is your glyph range array persistent? it is zero-terminated?"); - src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]); - } - dst_tmp.SrcCount++; - dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest); + ImFontConfig* src = &atlas->Sources[src_n]; + const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); + IM_ASSERT(ranges != NULL); + for (; ranges[0]; ranges += 2) + for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 + src->DstFont->FindGlyphNoFallback((ImWchar)c); } - - // 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs. - int total_glyphs_count = 0; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.GlyphsSet.Create(src_tmp.GlyphsHighest + 1); - if (dst_tmp.GlyphsSet.Storage.empty()) - dst_tmp.GlyphsSet.Create(dst_tmp.GlyphsHighest + 1); - - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - for (unsigned int codepoint = src_range[0]; codepoint <= src_range[1]; codepoint++) - { - if (dst_tmp.GlyphsSet.TestBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option for MergeMode (e.g. MergeOverwrite==true) - continue; - if (!stbtt_FindGlyphIndex(&src_tmp.FontInfo, codepoint)) // It is actually in the font? - continue; - - // Add to avail set/counters - src_tmp.GlyphsCount++; - dst_tmp.GlyphsCount++; - src_tmp.GlyphsSet.SetBit(codepoint); - dst_tmp.GlyphsSet.SetBit(codepoint); - total_glyphs_count++; - } - } - - // 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another) - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount); - UnpackBitVectorToFlatIndexList(&src_tmp.GlyphsSet, &src_tmp.GlyphsList); - src_tmp.GlyphsSet.Clear(); - IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount); - } - for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++) - dst_tmp_array[dst_i].GlyphsSet.Clear(); - dst_tmp_array.clear(); - - // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) - // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity) - ImVector buf_rects; - ImVector buf_packedchars; - buf_rects.resize(total_glyphs_count); - buf_packedchars.resize(total_glyphs_count); - memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes()); - memset(buf_packedchars.Data, 0, (size_t)buf_packedchars.size_in_bytes()); - - // 4. Gather glyphs sizes so we can pack them in our virtual canvas. - int total_surface = 0; - int buf_rects_out_n = 0; - int buf_packedchars_out_n = 0; - const int pack_padding = atlas->TexGlyphPadding; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - - src_tmp.Rects = &buf_rects[buf_rects_out_n]; - src_tmp.PackedChars = &buf_packedchars[buf_packedchars_out_n]; - buf_rects_out_n += src_tmp.GlyphsCount; - buf_packedchars_out_n += src_tmp.GlyphsCount; - - // Automatic selection of oversampling parameters - ImFontConfig& src = atlas->Sources[src_i]; - int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(&src, &oversample_h, &oversample_v); - - // Convert our ranges in the format stb_truetype wants - src_tmp.PackRange.font_size = src.SizePixels * src.RasterizerDensity; - src_tmp.PackRange.first_unicode_codepoint_in_range = 0; - src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data; - src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size; - src_tmp.PackRange.chardata_for_range = src_tmp.PackedChars; - src_tmp.PackRange.h_oversample = (unsigned char)oversample_h; - src_tmp.PackRange.v_oversample = (unsigned char)oversample_v; - - // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) - const float scale = (src.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels * src.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -src.SizePixels * src.RasterizerDensity); - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) - { - int x0, y0, x1, y1; - const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]); - IM_ASSERT(glyph_index_in_font != 0); - stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * oversample_h, scale * oversample_v, 0, 0, &x0, &y0, &x1, &y1); - src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + pack_padding + oversample_h - 1); - src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + pack_padding + oversample_v - 1); - total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; - } - } - for (int i = 0; i < atlas->CustomRects.Size; i++) - total_surface += (atlas->CustomRects[i].Width + pack_padding) * (atlas->CustomRects[i].Height + pack_padding); - - // We need a width for the skyline algorithm, any width! - // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. - // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface. - const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1; - atlas->TexHeight = 0; - if (atlas->TexDesiredWidth > 0) - atlas->TexWidth = atlas->TexDesiredWidth; - else - atlas->TexWidth = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512; - - // 5. Start packing - // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). - const int TEX_HEIGHT_MAX = 1024 * 32; - stbtt_pack_context spc = {}; - stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, 0, NULL); - spc.padding = atlas->TexGlyphPadding; // Because we mixup stbtt_PackXXX and stbrp_PackXXX there's a bit of a hack here, not passing the value to stbtt_PackBegin() allows us to still pack a TexWidth-1 wide item. (#8107) - ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); - - // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - - stbrp_pack_rects((stbrp_context*)spc.pack_info, src_tmp.Rects, src_tmp.GlyphsCount); - - // Extend texture height and mark missing glyphs as non-packed so we won't render them. - // FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?) - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - if (src_tmp.Rects[glyph_i].was_packed) - atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h); - } - - // 7. Allocate texture - atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); - atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); - atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight); - memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); - spc.pixels = atlas->TexPixelsAlpha8; - spc.height = atlas->TexHeight; - - // 8. Render/rasterize font characters into the texture - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontConfig& src = atlas->Sources[src_i]; - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - - stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects); - - // Apply multiply operator - if (src.RasterizerMultiply != 1.0f) - { - unsigned char multiply_table[256]; - ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, src.RasterizerMultiply); - stbrp_rect* r = &src_tmp.Rects[0]; - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++, r++) - if (r->was_packed) - ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, atlas->TexPixelsAlpha8, r->x, r->y, r->w, r->h, atlas->TexWidth * 1); - } - src_tmp.Rects = NULL; - } - - // End packing - stbtt_PackEnd(&spc); - buf_rects.clear(); - - // 9. Setup ImFont and glyphs for runtime - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - // When merging fonts with MergeMode=true: - // - We can have multiple input fonts writing into a same destination font. - // - dst_font->Sources is != from src which is our source configuration. - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - ImFont* dst_font = src.DstFont; - - const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels); - int unscaled_ascent, unscaled_descent, unscaled_line_gap; - stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); - - const float ascent = ImCeil(unscaled_ascent * font_scale); - const float descent = ImFloor(unscaled_descent * font_scale); - ImFontAtlasBuildSetupFont(atlas, dst_font, &src, ascent, descent); - const float font_off_x = src.GlyphOffset.x; - const float font_off_y = src.GlyphOffset.y + IM_ROUND(dst_font->Ascent); - - const float inv_rasterization_scale = 1.0f / src.RasterizerDensity; - - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - { - // Register glyph - const int codepoint = src_tmp.GlyphsList[glyph_i]; - const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i]; - stbtt_aligned_quad q; - float unused_x = 0.0f, unused_y = 0.0f; - stbtt_GetPackedQuad(src_tmp.PackedChars, atlas->TexWidth, atlas->TexHeight, glyph_i, &unused_x, &unused_y, &q, 0); - float x0 = q.x0 * inv_rasterization_scale + font_off_x; - float y0 = q.y0 * inv_rasterization_scale + font_off_y; - float x1 = q.x1 * inv_rasterization_scale + font_off_x; - float y1 = q.y1 * inv_rasterization_scale + font_off_y; - dst_font->AddGlyph(&src, (ImWchar)codepoint, x0, y0, x1, y1, q.s0, q.t0, q.s1, q.t1, pc.xadvance * inv_rasterization_scale); - } - } - - // Cleanup - src_tmp_array.clear_destruct(); - - ImFontAtlasBuildFinish(atlas); - return true; } -const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype() +void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) { - static ImFontBuilderIO io; - io.FontBuilder_Build = ImFontAtlasBuildWithStbTruetype; - return &io; -} - -#endif // IMGUI_ENABLE_STB_TRUETYPE - -void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas) -{ - for (ImFontConfig& src : atlas->Sources) + for (int src_n = 0; src_n < atlas->Sources.Size; src_n++) { - ImFont* font = src.DstFont; - if (!src.MergeMode) + ImFontConfig* src = &atlas->Sources[src_n]; + ImFont* font = src->DstFont; + if (!src->MergeMode) { - font->Sources = &src; + font->Sources = src; font->SourcesCount = 0; } font->SourcesCount++; } } -void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) -{ - if (!font_config->MergeMode) - { - font->ClearOutputData(); - font->FontSize = font_config->SizePixels; - IM_ASSERT(font->Sources == font_config); - font->ContainerAtlas = atlas; - font->Ascent = ascent; - font->Descent = descent; - } -} - void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque) { + ImTextureData* tex = atlas->TexData; stbrp_context* pack_context = (stbrp_context*)stbrp_context_opaque; IM_ASSERT(pack_context != NULL); @@ -3216,175 +3220,828 @@ void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opa { user_rects[i].X = (unsigned short)pack_rects[i].x; user_rects[i].Y = (unsigned short)pack_rects[i].y; - IM_ASSERT(pack_rects[i].w == user_rects[i].Width + pack_padding && pack_rects[i].h == user_rects[i].Height + pack_padding); - atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + tex->Height = ImMax(tex->Height, pack_rects[i].y + pack_rects[i].h); } } -void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value) +// Render a white-colored bitmap encoded in a string +void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char) { - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned char* out_pixel = atlas->TexPixelsAlpha8 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : 0x00; -} + ImTextureData* tex = atlas->TexData; + IM_ASSERT(x >= 0 && x + w <= tex->Width); + IM_ASSERT(y >= 0 && y + h <= tex->Height); -void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value) -{ - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned int* out_pixel = atlas->TexPixelsRGBA32 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : IM_COL32_BLACK_TRANS; -} - -static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) -{ - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); - IM_ASSERT(r->IsPacked()); - - const int w = atlas->TexWidth; - if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) + switch (tex->Format) { - // White pixels only - IM_ASSERT(r->Width == 2 && r->Height == 2); - const int offset = (int)r->X + (int)r->Y * w; - if (atlas->TexPixelsAlpha8 != NULL) + case ImTextureFormat_Alpha8: + { + ImU8* out_p = tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? 0xFF : 0x00; + break; + } + case ImTextureFormat_RGBA32: + { + ImU32* out_p = (ImU32*)(void*)tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? IM_COL32_WHITE : IM_COL32_BLACK_TRANS; + break; + } + } +} + +static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas, bool add_and_draw) +{ + ImVec2i pack_size = (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) ? ImVec2i(2, 2) : ImVec2i(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + + // Pack and store identifier so we can refresh UV coordinates on texture resize. + // FIXME-NEWATLAS: User/custom rects where user code wants to store UV coordinates will need to do the same thing. + ImFontAtlasBuilder* builder = atlas->Builder; + + if (add_and_draw) + builder->PackIdMouseCursors = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdMouseCursors); + + // Draw to texture + if (add_and_draw) + { + if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) { - atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + // 2x2 white pixels + ImFontAtlasBuildRenderBitmapFromString(atlas, r->x, r->y, 2, 2, "XX" "XX", 'X'); } else { - atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; + // 2x2 white pixels + mouse cursors + const int x_for_white = r->x; + const int x_for_black = r->x + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_white, r->y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.'); + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r->y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); } } - else - { - // White pixels and mouse cursor - IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); - const int x_for_white = r->X; - const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; - if (atlas->TexPixelsAlpha8 != NULL) - { - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', 0xFF); - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', 0xFF); - } - else - { - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', IM_COL32_WHITE); - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE); - } - } - atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); + atlas->TexUvWhitePixel = ImVec2((r->x + 0.5f) * atlas->TexUvScale.x, (r->y + 0.5f) * atlas->TexUvScale.y); } -static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) +static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas, bool add_and_draw) { if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) return; + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + + // Pack and store identifier so we can refresh UV coordinates on texture resize. + ImFontAtlasBuilder* builder = atlas->Builder; + if (add_and_draw) + builder->PackIdLinesTexData = ImFontAtlasPackAddRect(atlas, pack_size.x, pack_size.y); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, builder->PackIdLinesTexData); + + // Register texture region for thick lines + // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines); - IM_ASSERT(r->IsPacked()); + ImTextureData* tex = atlas->TexData; for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle int y = n; int line_width = n; - int pad_left = (r->Width - line_width) / 2; - int pad_right = r->Width - (pad_left + line_width); + int pad_left = (r->w - line_width) / 2; + int pad_right = r->w - (pad_left + line_width); // Write each slice - IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels - if (atlas->TexPixelsAlpha8 != NULL) + IM_ASSERT(pad_left + line_width + pad_right == r->w && y < r->h); // Make sure we're inside the texture bounds before we start writing pixels + if (add_and_draw) { - unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = 0x00; + switch (tex->Format) + { + case ImTextureFormat_Alpha8: + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r->x, r->y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = 0x00; - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = 0xFF; + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = 0xFF; - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = 0x00; - } - else - { - unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = 0x00; + break; + } + case ImTextureFormat_RGBA32: + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r->x, r->y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = IM_COL32(255, 255, 255, 0); - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = IM_COL32_WHITE; + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = IM_COL32_WHITE; - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); + break; + } + } } // Calculate UVs for this line - ImVec2 uv0 = ImVec2((float)(r->X + pad_left - 1), (float)(r->Y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r->X + pad_left + line_width + 1), (float)(r->Y + y + 1)) * atlas->TexUvScale; + ImVec2 uv0 = ImVec2((float)(r->x + pad_left - 1), (float)(r->y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r->x + pad_left + line_width + 1), (float)(r->y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } } -// Note: this is called / shared by both the stb_truetype and the FreeType builder +//----------------------------------------------------------------------------------------------------------------------------- + +static const ImFontGlyph* LoadFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) +{ + for (int n = 0; n < candidate_chars_count; n++) + if (candidate_chars[n] != 0) + if (const ImFontGlyph* glyph = font->FindGlyphNoFallback(candidate_chars[n])) + return glyph; + return NULL; +} + +bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src) +{ + ImFont* font = src->DstFont; + if (src->MergeMode == false) + { + font->ClearOutputData(); + font->FontSize = src->SizePixels; + font->ContainerAtlas = atlas; + IM_ASSERT(font->Sources == src); + } + + const ImFontBackendIO* font_backend_io = atlas->FontBackendIO; + if (!font_backend_io->FontBackend_FontSrcInit(atlas, src)) + return false; // FIXME-NEWATLAS: error handling + + ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, src); + return true; +} + +// Load/identify special glyphs +// (note that this is called again for fonts with MergeMode) +void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src) +{ + ImFont* font = src->DstFont; + IM_UNUSED(atlas); + + // While manipulating glyphs during init we want to restrict all searches for one source font. + font->LockSingleSrcConfig = src; + + // Setup Fallback character + // FIXME-NEWATLAS: could we use a scheme where this is lazily loaded? + const ImWchar fallback_chars[] = { font->FallbackChar, (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; + if (font->FallbackGlyphIndex == -1) + if (const ImFontGlyph* glyph = LoadFirstExistingGlyph(font, fallback_chars, IM_ARRAYSIZE(fallback_chars))) + { + font->FallbackChar = (ImWchar)glyph->Codepoint; + font->FallbackGlyphIndex = font->Glyphs.index_from_ptr(glyph); // Storing index avoid need to update pointer on growth. + font->FallbackAdvanceX = glyph->AdvanceX; + } + + // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) + ImFontGlyph* space_glyph = (ImFontGlyph*)(void*)font->FindGlyph((ImWchar)' '); + if (space_glyph != NULL) + space_glyph->Visible = false; + + // Setup Tab character. + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (font->FindGlyphNoFallback('\t') == NULL && space_glyph != NULL) + { + ImFontGlyph tab_glyph; + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX = space_glyph->AdvanceX * IM_TABSIZE; + font->BuildRegisterGlyph(font->Sources, &tab_glyph); + } + + // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). + // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. + // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. + const ImWchar ellipsis_chars[] = { src->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; + if (font->EllipsisChar == 0) + if (const ImFontGlyph* glyph = LoadFirstExistingGlyph(font, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars))) + { + font->EllipsisChar = (ImWchar)glyph->Codepoint; + font->EllipsisCharCount = 1; + font->EllipsisWidth = font->EllipsisCharStep = glyph->X1; + } + if (font->EllipsisChar == 0) + { + // FIXME-NEWATLAS-V2: We can now rasterize this into a regular character and register it! + const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; + if (const ImFontGlyph* dot_glyph = LoadFirstExistingGlyph(font, dots_chars, IM_ARRAYSIZE(dots_chars))) + { + font->EllipsisChar = (ImWchar)dot_glyph->Codepoint; + font->EllipsisCharCount = 3; + font->EllipsisCharStep = (float)(int)(dot_glyph->X1 - dot_glyph->X0) + 1.0f; + font->EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + font->EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. + } + } + + font->LockSingleSrcConfig = NULL; +} + +// Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* +void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(atlas->DrawListSharedData == NULL && data->FontAtlas == NULL); + atlas->DrawListSharedData = data; + data->FontAtlas = atlas; +} + +void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(atlas->DrawListSharedData == data && data->FontAtlas == atlas); + atlas->DrawListSharedData = data; + data->FontAtlas = NULL; +} + +// Update texture identifier in all active draw lists +void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex) +{ + ImDrawListSharedData* shared_data = atlas->DrawListSharedData; + for (ImDrawList* draw_list : shared_data->DrawLists) + { + // Replace in command-buffer + // (there is not need to replace in ImDrawListSplitter: current channel is in ImDrawList's CmdBuffer[], + // other channels will be on SetCurrentChannel() which already needs to compare CmdHeader anyhow) + if (draw_list->CmdBuffer.Size > 0 && draw_list->_CmdHeader.TexRef == old_tex) + draw_list->_SetTextureRef(new_tex); + + // Replace in stack + for (ImTextureRef& stacked_tex : draw_list->_TextureStack) + if (stacked_tex == old_tex) + stacked_tex = new_tex; + } +} + +// Update texture coordinates in all draw list shared context +void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) +{ + ImDrawListSharedData* shared_data = atlas->DrawListSharedData; + shared_data->FontAtlas = atlas; + shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; + shared_data->TexUvLines = atlas->TexUvLines; +} + +// Set current texture. This is mostly called from AddTexture() + to handle a failed resize. +static void ImFontAtlasBuildSetTexture(ImFontAtlas* atlas, ImTextureData* tex) +{ + ImTextureRef old_tex_ref = atlas->TexRef; + atlas->TexData = tex; + atlas->TexUvScale = ImVec2(1.0f / tex->Width, 1.0f / tex->Height); + atlas->TexRef._TexData = tex; + //atlas->TexID._TexID = tex->TexID; // <-- We intentionally don't do that and leave it 0, to allow late upload. + ImTextureRef new_tex_ref = atlas->TexRef; + ImFontAtlasUpdateDrawListsTextures(atlas, old_tex_ref, new_tex_ref); +} + +// Create a new texture, discard previous one +ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h) +{ + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex; + + // FIXME: Cannot reuse texture because old UV may have been used already (unless we remap UV). + /*if (old_tex != NULL && old_tex->Status == ImTextureStatus_WantCreate) + { + // Reuse texture not yet used by backend. + IM_ASSERT(old_tex->TexID == 0 && old_tex->BackendUserData == NULL); + old_tex->DestroyPixels(); + old_tex->Updates.clear(); + new_tex = old_tex; + old_tex = NULL; + } + else*/ + { + // Add new + new_tex = IM_NEW(ImTextureData)(); + new_tex->UniqueID = atlas->TexNextUniqueID++; + atlas->TexList.push_back(new_tex); + } + if (old_tex != NULL) + { + // Queue old as to destroy next frame + old_tex->WantDestroyNextFrame = true; + IM_ASSERT(old_tex->Status == ImTextureStatus_OK || old_tex->Status == ImTextureStatus_WantCreate || old_tex->Status == ImTextureStatus_WantUpdates); + } + + new_tex->Create(atlas->TexDesiredFormat, w, h); + new_tex->Status = ImTextureStatus_WantCreate; + + ImFontAtlasBuildSetTexture(atlas, new_tex); + + return new_tex; +} + +void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(g); // for IMGUI_DEBUG_LOG_FONT() + + ImFontAtlasBuilder* builder = atlas->Builder; + builder->LockDisableResize = true; + + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex = ImFontAtlasBuildAddTexture(atlas, w, h); + new_tex->UseColors = old_tex->UseColors; + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize+repack %dx%d => Texture #%03d: %dx%d\n", old_tex->UniqueID, old_tex->Width, old_tex->Height, new_tex->UniqueID, new_tex->Width, new_tex->Height); + + // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. + +#if 1 + // Repack + copy pixels + // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic. + ImFontAtlasPackInit(atlas); + ImVector old_rects; + old_rects.swap(builder->Rects); + for (ImFontAtlasRect& old_r : old_rects) + { + ImFontAtlasRectId new_r_id = ImFontAtlasPackAddRect(atlas, old_r.w, old_r.h); + if (new_r_id == -1) + { + // Undo, grow texture and try repacking again. + // FIXME-NEWATLAS-TESTS: This is a very rarely exercised path! It needs to be automatically tested properly. + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize failed. Will grow.\n", new_tex->UniqueID); + new_tex->WantDestroyNextFrame = true; + old_rects.swap(builder->Rects); + ImFontAtlasBuildSetTexture(atlas, old_tex); + ImFontAtlasBuildGrowTexture(atlas, w, h); + return; + } + ImFontAtlasRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); + ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h); + } + IM_ASSERT(old_rects.Size == builder->Rects.Size); + + // Patch glyphs UV + for (ImFont* font : atlas->Fonts) + for (ImFontGlyph& glyph : font->Glyphs) + if (glyph.PackId != -1) + { + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + } + + // Update other cached UV + ImFontAtlasBuildUpdateLinesTexData(atlas, false); + ImFontAtlasBuildUpdateBasicTexData(atlas, false); + +#else + // Copy previous pixels + ImFontAtlasTextureCopyBlock(atlas, old_tex, 0, 0, new_tex, 0, 0, ImMin(old_tex->Width, new_tex->Width), ImMin(old_tex->Height, new_tex->Height)); + + // Scale UV coordinates + // FIXME-NEWATLAS: Probably lossy? + ImVec2 uv_scale((float)old_tex->Width / new_tex->Width, (float)old_tex->Height / new_tex->Height); + for (ImFont* font : atlas->Fonts) + for (ImFontGlyph& glyph : font->Glyphs) + { + glyph.U0 *= uv_scale.x; + glyph.U1 *= uv_scale.x; + glyph.V0 *= uv_scale.y; + glyph.V1 *= uv_scale.y; + } + ImVec4 uv_scale4(uv_scale.x, uv_scale.y, uv_scale.x, uv_scale.y); + atlas->TexUvWhitePixel *= uv_scale; + for (ImVec4& uv : atlas->TexUvLines) + uv = uv * uv_scale4; +#endif + + builder->LockDisableResize = false; + ImFontAtlasUpdateDrawListsSharedData(atlas); +} + +void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_tex_w, int old_tex_h) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + if (old_tex_w == -1) + old_tex_w = atlas->TexData->Width; + if (old_tex_h == -1) + old_tex_h = atlas->TexData->Height; + + // FIXME-NEWATLAS-V1: Handle atlas->TexDesiredWidth from user? + // FIXME-NEWATLAS-V1: What to do when reaching limits exposed by backend? + // FIXME-NEWATLAS-V1: does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? + IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); + + // Grow texture so it follows roughly a square. + int new_tex_w = (old_tex_h < old_tex_w) ? old_tex_w : old_tex_w * 2; + int new_tex_h = (old_tex_h < old_tex_w) ? old_tex_h * 2 : old_tex_h; + + // Handle minimum size first (for pathologically large packed rects) + new_tex_w = ImMax(new_tex_w, ImUpperPowerOfTwo(builder->MaxRectSize.x + builder->PackPadding)); + new_tex_h = ImMax(new_tex_h, ImUpperPowerOfTwo(builder->MaxRectSize.y + builder->PackPadding)); + + ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); +} + +// You should not need to call this manually! +// If you think you do, let us know and we can advise about policies auto-compact. +void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + + ImTextureData* old_tex = atlas->TexData; + int old_tex_w = old_tex->Width; + int old_tex_h = old_tex->Height; + + // FIXME-NEWATLAS: Expose atlas->TexMinWidth etc. + const int min_w = ImMax(builder->MaxRectSize.x, 512); + const int min_h = builder->MaxRectSize.y; + const int surface_sqrt = (int)sqrtf((float)atlas->_PackedSurface); + + int new_tex_w; + int new_tex_h; + if (min_w >= min_h) + { + new_tex_w = ImMax(min_w, ImUpperPowerOfTwo(surface_sqrt)); + new_tex_h = ImMax(min_h, (int)(atlas->_PackedSurface / new_tex_w)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + } + else + { + new_tex_h = ImMax(min_h, ImUpperPowerOfTwo(surface_sqrt)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + new_tex_w = ImMax(min_w, (int)(atlas->_PackedSurface / new_tex_h)); + } + + if (new_tex_w == old_tex_w && new_tex_h == old_tex_h) + return; + + ImFontAtlasBuildRepackTexture(atlas, new_tex_w, new_tex_h); +} + +// Start packing over current empty texture void ImFontAtlasBuildInit(ImFontAtlas* atlas) { - // Register texture region for mouse cursors or standard white pixels - if (atlas->PackIdMouseCursors < 0) + ImFontAtlasBuilder* builder = atlas->Builder; + + const bool builder_is_new = (builder == NULL); + if (builder_is_new) { - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); - else - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(2, 2); + builder = atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); + builder->PackPadding = 1; } - // Register texture region for thick lines - // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - if (atlas->PackIdLines < 0) + ImFontAtlasPackInit(atlas); + + // Add required texture data + ImFontAtlasBuildUpdateLinesTexData(atlas, true); + ImFontAtlasBuildUpdateBasicTexData(atlas, true); + + // Register fonts + if (builder_is_new) { - if (!(atlas->Flags & ImFontAtlasFlags_NoBakedLines)) - atlas->PackIdLines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + ImFontAtlasBuildUpdatePointers(atlas); + for (ImFontConfig& cfg : atlas->Sources) + ImFontAtlasBuildAddFont(atlas, &cfg); } + + // Update UV coordinates etc. stored in bound ImDrawListSharedData instance + ImFontAtlasUpdateDrawListsSharedData(atlas); + + //atlas->TexIsBuilt = true; } -// This is called/shared by both the stb_truetype and the FreeType builder. -void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +// Destroy builder and all cached glyphs. Do not destroy actual fonts. +void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) { - // Render into our custom data blocks - IM_ASSERT(atlas->TexPixelsAlpha8 != NULL || atlas->TexPixelsRGBA32 != NULL); - ImFontAtlasBuildRenderDefaultTexData(atlas); - ImFontAtlasBuildRenderLinesTexData(atlas); + for (ImFont* font : atlas->Fonts) + font->BuildClearGlyphs(); + if (atlas->FontBackendIO && atlas->FontBackendIO->FontBackend_FontSrcDestroy != NULL) + for (ImFontConfig& font_cfg : atlas->Sources) + atlas->FontBackendIO->FontBackend_FontSrcDestroy(atlas, &font_cfg); - // Register custom rectangle glyphs - for (int i = 0; i < atlas->CustomRects.Size; i++) + IM_DELETE(atlas->Builder); + atlas->Builder = NULL; +} + +void ImFontAtlasPackInit(ImFontAtlas* atlas) +{ + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; + + // FIXME-NEWATLAS-V2: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 + // FIXME-NEWATLAS-V2: Experiment with number of nodes. 2024-11-05: Seems to be quite fine to reduce this. + int pack_node_count = tex->Width - builder->PackPadding; + //pack_node_count *= atlas->_PackNodesFactor; + builder->PackNodes.resize(pack_node_count); + IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); + stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width - builder->PackPadding, tex->Height - builder->PackPadding, builder->PackNodes.Data, builder->PackNodes.Size); + atlas->_PackedSurface = atlas->_PackedRects = 0; + builder->MaxRectSize = ImVec2i(0, 0); + builder->MaxRectBounds = ImVec2i(0, 0); +} + +// Important: Calling this may recreate a new texture and therefore change atlas->TexData +ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h) +{ + IM_ASSERT(w > 0 && w <= 0xFFFF); + IM_ASSERT(h > 0 && h <= 0xFFFF); + + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + builder->MaxRectSize.x = ImMax(builder->MaxRectSize.x, w); + builder->MaxRectSize.y = ImMax(builder->MaxRectSize.y, h); + + // Pack + ImFontAtlasRect r = { 0, 0, (unsigned short)w, (unsigned short)h }; + for (int attempts_remaining = 3; attempts_remaining >= 0; attempts_remaining--) { - const ImFontAtlasCustomRect* r = &atlas->CustomRects[i]; - if (r->Font == NULL || r->GlyphID == 0) - continue; + // Try packing + stbrp_rect pack_r = {}; + pack_r.w = r.w + builder->PackPadding; + pack_r.h = r.h + builder->PackPadding; + stbrp_pack_rects((stbrp_context*)(void*)&builder->PackContext, &pack_r, 1); + r.x = (unsigned short)(pack_r.x + builder->PackPadding); + r.y = (unsigned short)(pack_r.y + builder->PackPadding); + if (pack_r.was_packed) + break; - // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, PixelSnapH - IM_ASSERT(r->Font->ContainerAtlas == atlas); - ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(r, &uv0, &uv1); - r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); - if (r->GlyphColored) - r->Font->Glyphs.back().Colored = 1; + // If we ran out of attempts, return fallback + if (attempts_remaining == 0 || builder->LockDisableResize) + { + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + IMGUI_DEBUG_LOG_FONT("[font] Failed packing %dx%d rectangle. Returning fallback.\n", w, h); + return -1; + } + + // Resize atlas! (this should be a rare event) + ImFontAtlasBuildGrowTexture(atlas); } - // Build all fonts lookup tables - for (ImFont* font : atlas->Fonts) - if (font->DirtyLookupTables) - font->BuildLookupTable(); + builder->MaxRectBounds.x = ImMax(builder->MaxRectBounds.x, r.x + r.w); + builder->MaxRectBounds.y = ImMax(builder->MaxRectBounds.y, r.y + r.h); + atlas->_PackedSurface += w * h; + atlas->_PackedRects++; - atlas->TexReady = true; + builder->Rects.push_back(r); + return builder->Rects.Size - 1; } +ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + return &builder->Rects[id]; +} + +ImFontGlyph* ImFont::BuildLoadGlyph(ImWchar codepoint) +{ + if (LockDisableLoading) + return NULL; + + //char utf8_buf[5]; + //IMGUI_DEBUG_LOG("[font] BuildAddGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); + + ImFontAtlas* atlas = ContainerAtlas; + const ImFontBackendIO* font_backend_io = atlas->FontBackendIO; + if (!font_backend_io->FontBackend_FontAddGlyph(atlas, this, codepoint)) + { + // Mark index as not found, so we don't attempt the search twice + BuildGrowIndex(codepoint + 1); + IndexAdvanceX[codepoint] = (float)IM_FONTGLYPH_INDEX_NOT_FOUND; + IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; + return NULL; + } + + // FIXME: Add hooks for e.g. #7962 + ImFontGlyph* glyph = &Glyphs.back(); + return glyph; +} + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) +{ + // [DEBUG] Log texture update requests + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + for (ImTextureData* tex : atlas->TexList) + { + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + IM_ASSERT(tex->Updates.Size == 0); + if (tex->Status == ImTextureStatus_WantCreate) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: create %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + else if (tex->Status == ImTextureStatus_WantDestroy) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: destroy %dx%d, texid=0x%" IM_PRIX64 ", backend_data=%p\n", tex->UniqueID, tex->Width, tex->Height, tex->TexID, tex->BackendUserData); + else if (tex->Status == ImTextureStatus_WantUpdates) + { + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update %d regions, texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, tex->Updates.Size, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); + for (const ImTextureRect& r : tex->Updates) + { + IM_ASSERT(r.x >= 0 && r.y >= 0); + IM_ASSERT(r.x + r.w < tex->Width && r.y + r.h < tex->Height); + //IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update (% 4d..%-4d)->(% 4d..%-4d), texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, r.x, r.y, r.x + r.w, r.y + r.h, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); + } + } + } +} +#endif + +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas: backend for stb_truetype +//------------------------------------------------------------------------- +// (imstb_truetype.h in included near the top of this file, when IMGUI_ENABLE_STB_TRUETYPE is set) +//------------------------------------------------------------------------- + +#ifdef IMGUI_ENABLE_STB_TRUETYPE + +// One for each ConfigData +struct ImGui_ImplStbTrueType_FontSrcData +{ + stbtt_fontinfo FontInfo; + float ScaleForRasterX; // Factor in RasterizationDensity * OversampleH + float ScaleForRasterY; // Factor in RasterizationDensity * OversampleV + float ScaleForLayout; +}; + +static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplStbTrueType_FontSrcData); + IM_ASSERT(src->FontBackendData == NULL); + + // Initialize helper structure for font loading and verify that the TTF/OTF data is correct + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src->FontData, src->FontNo); + IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); // FIXME-NEWATLAS: error handling + if (!stbtt_InitFont(&bd_font_data->FontInfo, (unsigned char*)src->FontData, font_offset)) + { + IM_ASSERT(0 && "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); + return false; + } + src->FontBackendData = bd_font_data; + + // FIXME-NEWATLAS-V2: reevaluate sizing metrics + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); + if (src->SizePixels > 0.0f) + { + bd_font_data->ScaleForRasterX = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels * src->RasterizerDensity) * oversample_h; + bd_font_data->ScaleForRasterY = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels * src->RasterizerDensity) * oversample_v; + bd_font_data->ScaleForLayout = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, src->SizePixels); + } + else + { + bd_font_data->ScaleForRasterX = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels * src->RasterizerDensity) * oversample_h; + bd_font_data->ScaleForRasterY = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels * src->RasterizerDensity) * oversample_v; + bd_font_data->ScaleForLayout = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, -src->SizePixels); + } + + // FIXME-NEWATLAS-V2: make use of line gap value + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + + if (src->MergeMode == false) + { + ImFont* font = src->DstFont; + font->Ascent = ImCeil(unscaled_ascent * bd_font_data->ScaleForLayout); + font->Descent = ImFloor(unscaled_descent * bd_font_data->ScaleForLayout); + } + + return true; +} + +static void ImGui_ImplStbTrueType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontBackendData; + IM_DELETE(bd_font_data); + src->FontBackendData = NULL; +} + +static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) +{ + IM_UNUSED(atlas); + + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontBackendData; + IM_ASSERT(bd_font_data != NULL); + + int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + return glyph_index != 0; +} + +static bool ImGui_ImplStbTrueType_FontAddGlyph(ImFontAtlas* atlas, ImFont* font, ImWchar codepoint) +{ + // Search for first font which has the glyph + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = NULL; + ImFontConfig* src = NULL; + int glyph_index = 0; + int scan_count = (font->LockSingleSrcConfig != NULL) ? 1 : font->SourcesCount; + for (int src_n = 0; src_n < scan_count; src_n++, bd_font_data++) + { + src = font->LockSingleSrcConfig ? font->LockSingleSrcConfig : &font->Sources[src_n]; + bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontBackendData; + glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + if (glyph_index != 0) + break; + } + if (glyph_index == 0) + return false; // Not found + + // FIXME-NEWATLAS: Handling of atlas->TexGlyphPadding? + const float scale_for_layout = bd_font_data->ScaleForLayout; // ~ (font units to pixels) + const float scale_for_raster_x = bd_font_data->ScaleForRasterX; // ~ (font units to pixels) * RasterizationDensity * OversampleH + const float scale_for_raster_y = bd_font_data->ScaleForRasterY; // ~ (font units to pixels) * RasterizationDensity * OversampleV + + // Obtain size and advance + int x0, y0, x1, y1; + int advance, lsb; + stbtt_GetGlyphBitmapBoxSubpixel(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, 0, 0, &x0, &y0, &x1, &y1); + stbtt_GetGlyphHMetrics(&bd_font_data->FontInfo, glyph_index, &advance, &lsb); + const bool is_visible = (x0 != x1 && y0 != y1); + + // Prepare glyph + ImFontGlyph glyph; + glyph.Codepoint = codepoint; + glyph.AdvanceX = advance * scale_for_layout; + + // Pack and retrieve position inside texture atlas + // (generally based on stbtt_PackFontRangesRenderIntoRects) + if (is_visible) + { + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); + const int w = (x1 - x0 + oversample_h - 1); + const int h = (y1 - y0 + oversample_v - 1); + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); + ImFontAtlasRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + font->MetricsTotalSurface += w * h; + + // Render + stbtt_GetGlyphBitmapBox(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, &x0, &y0, &x1, &y1); + ImFontAtlasBuilder* builder = atlas->Builder; + builder->TempBuffer.resize(w * h * 1); + unsigned char* bitmap_pixels = builder->TempBuffer.Data; + memset(bitmap_pixels, 0, w * h * 1); + stbtt_MakeGlyphBitmapSubpixel(&bd_font_data->FontInfo, bitmap_pixels, r->w - oversample_h + 1, r->h - oversample_v + 1, w, + scale_for_raster_x, scale_for_raster_y, 0, 0, glyph_index); + + // Oversampling + // (those functions conveniently assert if pixels are not cleared, which is another safety layer) + if (oversample_h > 1) + stbtt__h_prefilter(bitmap_pixels, r->w, r->h, r->w, oversample_h); + if (oversample_v > 1) + stbtt__v_prefilter(bitmap_pixels, r->w, r->h, r->w, oversample_v); + + float font_off_x = src->GlyphOffset.x + stbtt__oversample_shift(oversample_h); + float font_off_y = src->GlyphOffset.y + stbtt__oversample_shift(oversample_v) + IM_ROUND(font->Ascent); + float recip_h = 1.0f / (oversample_h * src->RasterizerDensity); + float recip_v = 1.0f / (oversample_v * src->RasterizerDensity); + + // Register glyph + // r->x r->y are coordinates inside texture (in pixels) + // glyph.X0, glyph.Y0 are drawing coordinates from base text position, and accounting for oversampling. + glyph.X0 = x0 * recip_h + font_off_x; + glyph.Y0 = y0 * recip_v + font_off_y; + glyph.X1 = (x0 + (int)r->w) * recip_h + font_off_x; + glyph.Y1 = (y0 + (int)r->h) * recip_v + font_off_y; + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + glyph.Visible = true; + glyph.PackId = pack_id; + font->BuildRegisterGlyph(src, &glyph); + + // Copy to texture, post-process and queue update for backend + ImTextureData* tex = atlas->TexData; + IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); + ImFontAtlasTextureBlockConvertAndPostProcess(atlas, font, src, &font->Glyphs.back(), + bitmap_pixels, ImTextureFormat_Alpha8, w, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), w, h); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); + } + else + { + font->BuildRegisterGlyph(src, &glyph); + } + + return true; +} + +const ImFontBackendIO* ImFontAtlasGetBackendIOForStbTruetype() +{ + static ImFontBackendIO io; + io.FontBackend_Name = "stb_truetype"; + io.FontBackend_FontSrcInit = ImGui_ImplStbTrueType_FontSrcInit; + io.FontBackend_FontSrcDestroy = ImGui_ImplStbTrueType_FontSrcDestroy; + io.FontBackend_FontSrcContainsGlyph = ImGui_ImplStbTrueType_FontSrcContainsGlyph; + io.FontBackend_FontAddGlyph = ImGui_ImplStbTrueType_FontAddGlyph; + return &io; +} + +#endif // IMGUI_ENABLE_STB_TRUETYPE + //------------------------------------------------------------------------- // [SECTION] ImFontAtlas: glyph ranges helpers //------------------------------------------------------------------------- @@ -3713,113 +4370,30 @@ ImFont::~ImFont() ClearOutputData(); } -void ImFont::ClearOutputData() +void ImFont::ClearOutputData() { FontSize = 0.0f; FallbackAdvanceX = 0.0f; Glyphs.clear(); IndexAdvanceX.clear(); IndexLookup.clear(); - FallbackGlyph = NULL; + FallbackGlyphIndex = -1; ContainerAtlas = NULL; - DirtyLookupTables = true; Ascent = Descent = 0.0f; MetricsTotalSurface = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); } -static ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) +void ImFont::BuildClearGlyphs() { - for (int n = 0; n < candidate_chars_count; n++) - if (font->FindGlyphNoFallback(candidate_chars[n]) != NULL) - return candidate_chars[n]; - return 0; -} - -void ImFont::BuildLookupTable() -{ - int max_codepoint = 0; - for (int i = 0; i != Glyphs.Size; i++) - max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); - - // Build lookup table - IM_ASSERT(Glyphs.Size > 0 && "Font has not loaded glyph!"); - IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved + FallbackAdvanceX = 0.0f; + Glyphs.clear(); IndexAdvanceX.clear(); IndexLookup.clear(); - DirtyLookupTables = false; + FallbackGlyphIndex = 0; + MetricsTotalSurface = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); - GrowIndex(max_codepoint + 1); - for (int i = 0; i < Glyphs.Size; i++) - { - int codepoint = (int)Glyphs[i].Codepoint; - IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; - IndexLookup[codepoint] = (ImU16)i; - - // Mark 4K page as used - const int page_n = codepoint / 8192; - Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); - } - - // Create a glyph to handle TAB - // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) - if (FindGlyph((ImWchar)' ')) - { - if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times (FIXME: Flaky) - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& tab_glyph = Glyphs.back(); - tab_glyph = *FindGlyph((ImWchar)' '); - tab_glyph.Codepoint = '\t'; - tab_glyph.AdvanceX *= IM_TABSIZE; - IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; - IndexLookup[(int)tab_glyph.Codepoint] = (ImU16)(Glyphs.Size - 1); - } - - // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons) - if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)' ')) - glyph->Visible = false; - if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)'\t')) - glyph->Visible = false; - - // Setup Fallback character - const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) - { - FallbackChar = FindFirstExistingGlyph(this, fallback_chars, IM_ARRAYSIZE(fallback_chars)); - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) - { - FallbackGlyph = &Glyphs.back(); - FallbackChar = (ImWchar)FallbackGlyph->Codepoint; - } - } - FallbackAdvanceX = FallbackGlyph->AdvanceX; - for (int i = 0; i < max_codepoint + 1; i++) - if (IndexAdvanceX[i] < 0.0f) - IndexAdvanceX[i] = FallbackAdvanceX; - - // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). - // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. - // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. - const ImWchar ellipsis_chars[] = { Sources->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; - const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; - if (EllipsisChar == 0) - EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars)); - const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); - if (EllipsisChar != 0) - { - EllipsisCharCount = 1; - EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1; - } - else if (dot_char != 0) - { - const ImFontGlyph* dot_glyph = FindGlyph(dot_char); - EllipsisChar = dot_char; - EllipsisCharCount = 3; - EllipsisCharStep = (float)(int)(dot_glyph->X1 - dot_glyph->X0) + 1.0f; - EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. - } + // Don't clear BuilderData } // API is designed this way to avoid exposing the 8K page size @@ -3835,7 +4409,7 @@ bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) return true; } -void ImFont::GrowIndex(int new_size) +void ImFont::BuildGrowIndex(int new_size) { IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); if (new_size <= IndexLookup.Size) @@ -3847,50 +4421,43 @@ void ImFont::GrowIndex(int new_size) // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. // Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). // 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. -void ImFont::AddGlyph(const ImFontConfig* src, ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +void ImFont::BuildRegisterGlyph(ImFontConfig* src, const ImFontGlyph* in_glyph) { + int glyph_idx = Glyphs.Size; + Glyphs.push_back(*in_glyph); + ImFontGlyph& glyph = Glyphs[glyph_idx]; + IM_ASSERT(Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. + if (src != NULL) { // Clamp & recenter if needed - const float advance_x_original = advance_x; - advance_x = ImClamp(advance_x, src->GlyphMinAdvanceX, src->GlyphMaxAdvanceX); - if (advance_x != advance_x_original) + float advance_x = ImClamp(glyph.AdvanceX, src->GlyphMinAdvanceX, src->GlyphMaxAdvanceX); + if (advance_x != glyph.AdvanceX) { - float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f; - x0 += char_off_x; - x1 += char_off_x; + float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - glyph.AdvanceX) * 0.5f) : (advance_x - glyph.AdvanceX) * 0.5f; + glyph.X0 += char_off_x; + glyph.X1 += char_off_x; } // Snap to pixel if (src->PixelSnapH) advance_x = IM_ROUND(advance_x); - // Bake extra spacing - advance_x += src->GlyphExtraAdvanceX; + // Bake spacing + glyph.AdvanceX = advance_x + src->GlyphExtraAdvanceX; } + if (glyph.Colored) + ContainerAtlas->TexPixelsUseColors = ContainerAtlas->TexData->UseColors = true; - int glyph_idx = Glyphs.Size; - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& glyph = Glyphs[glyph_idx]; - glyph.Codepoint = (unsigned int)codepoint; - glyph.Visible = (x0 != x1) && (y0 != y1); - glyph.Colored = false; - glyph.X0 = x0; - glyph.Y0 = y0; - glyph.X1 = x1; - glyph.Y1 = y1; - glyph.U0 = u0; - glyph.V0 = v0; - glyph.U1 = u1; - glyph.V1 = v1; - glyph.AdvanceX = advance_x; - IM_ASSERT(Glyphs.Size < 0xFFFF); // IndexLookup[] hold 16-bit values and -1 is reserved. + // Update lookup tables + int codepoint = glyph.Codepoint; + BuildGrowIndex(codepoint + 1); + IndexAdvanceX[codepoint] = glyph.AdvanceX; + IndexLookup[codepoint] = (ImU16)glyph_idx; - // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) - // We use (U1-U0)*TexWidth instead of X1-X0 to account for oversampling. - float pad = ContainerAtlas->TexGlyphPadding + 0.99f; - DirtyLookupTables = true; - MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + pad) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + pad); + // Mark 4K page as used + const int page_n = codepoint / 8192; + Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); } void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) @@ -3903,30 +4470,76 @@ void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op return; - GrowIndex(dst + 1); + BuildGrowIndex(dst + 1); IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (ImU16)-1; IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; } -// Find glyph, return fallback if missing +// Find glyph, load if necessary, return fallback if missing ImFontGlyph* ImFont::FindGlyph(ImWchar c) { - if (c >= (size_t)IndexLookup.Size) - return FallbackGlyph; - const ImU16 i = IndexLookup.Data[c]; - if (i == (ImU16)-1) - return FallbackGlyph; - return &Glyphs.Data[i]; + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return &Glyphs.Data[FallbackGlyphIndex]; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + ImFontGlyph* glyph = BuildLoadGlyph(c); + return glyph ? glyph : &Glyphs.Data[FallbackGlyphIndex]; } +// Attempt to load but when missing, return NULL instead of FallbackGlyph ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) { - if (c >= (size_t)IndexLookup.Size) - return NULL; - const ImU16 i = IndexLookup.Data[c]; - if (i == (ImU16)-1) - return NULL; - return &Glyphs.Data[i]; + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return NULL; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + ImFontGlyph* glyph = BuildLoadGlyph(c); + return glyph; +} + +bool ImFont::IsGlyphLoaded(ImWchar c) +{ + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return false; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return true; + } + return false; +} + +// This is not fast query +bool ImFont::IsGlyphInFont(ImWchar c) +{ + ImFontAtlas* atlas = ContainerAtlas; + for (int src_n = 0; src_n < SourcesCount; src_n++) + if (atlas->FontBackendIO->FontBackend_FontSrcContainsGlyph(atlas, &Sources[src_n], c)) + return true; + return false; +} + +float ImFont::GetCharAdvance(ImWchar c) +{ + if (c < (size_t)IndexAdvanceX.Size) + { + const float x = IndexAdvanceX.Data[c]; + if (x >= 0.0f) + return x; + if (x == (float)IM_FONTGLYPH_INDEX_NOT_FOUND) // FIXME-NEWATLAS: could bake in index + return FallbackAdvanceX; + } + const ImFontGlyph* glyph = BuildLoadGlyph(c); + return glyph ? glyph->AdvanceX : FallbackAdvanceX; } // Trim trailing space and find beginning of next line @@ -3993,7 +4606,9 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c } } - const float char_width = ImFontGetCharAdvanceX(this, c); + // FIXME-NEWATLAS-V1: Measure perf, inline etc. + //const float char_width = ImFontGetCharAdvanceX(this, c); + const float char_width = GetCharAdvance((ImWchar)c); // ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX); if (ImCharIsBlankW(c)) { if (inside_word) @@ -4098,7 +4713,9 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons continue; } - const float char_width = ImFontGetCharAdvanceX(this, c) * scale; + // FIXME-NEWATLAS-V1: Measure perf, inline etc. + //const float char_width = ImFontGetCharAdvanceX(this, c) * scale; + const float char_width = GetCharAdvance((ImWchar)c) /* (int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX)*/ * scale; if (line_width + char_width >= max_width) { s = prev_s; diff --git a/imgui_internal.h b/imgui_internal.h index a74baebb7..9b31f27ad 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -37,6 +37,7 @@ Index of this file: // [SECTION] Tab bar, Tab item support // [SECTION] Table support // [SECTION] ImGui internal API +// [SECTION] ImFontBackendIO // [SECTION] ImFontAtlas internal API // [SECTION] Test Engine specific hooks (imgui_test_engine) @@ -140,6 +141,9 @@ struct ImGuiTextIndex; // Maintain a line index for a text buffer. // ImDrawList/ImFontAtlas struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImFontBackendIO; // Hooks and storage for a given font backend. +struct ImFontAtlasRect; // Packed rectangle (same as ImTextureRect) +struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas // ImGui struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) @@ -535,6 +539,14 @@ struct ImVec1 constexpr ImVec1(float _x) : x(_x) { } }; +// Helper: ImVec2i (2D vector, integer) +struct ImVec2i +{ + int x, y; + constexpr ImVec2i() : x(0), y(0) {} + constexpr ImVec2i(int _x, int _y) : x(_x), y(_y) {} +}; + // Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) struct ImVec2ih { @@ -794,8 +806,9 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag // You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. struct IMGUI_API ImDrawListSharedData { - ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) + ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current/default font (optional, for simplified AddText overload) float FontSize; // Current/default font size (optional, for simplified AddText overload) float FontScale; // Current/default font scale (== FontSize / Font->FontSize) @@ -805,13 +818,17 @@ struct IMGUI_API ImDrawListSharedData ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImVector TempBuffer; // Temporary write buffer + ImVector DrawLists; // All draw lists associated to this ImDrawListSharedData + ImGuiContext* Context; // [OPTIONAL] Link to Dear ImGui context. 99% of ImDrawList/ImFontAtlas can function without an ImGui context, but this facilitate handling one legacy edge case. // Lookup tables ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) + bool RendererHasTextures; // Copy of (GetIO().BackendFlags & ImGuiBackendFlags_RendererHasTextures). ImDrawListSharedData(); + ~ImDrawListSharedData(); void SetCircleTessellationMaxError(float max_error); }; @@ -2268,6 +2285,7 @@ struct ImGuiContext float FontScale; // == FontSize / Font->FontSize float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; + ImVectorTextures; double Time; int FrameCount; int FrameCountEnded; @@ -3846,6 +3864,7 @@ namespace ImGui IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); + IMGUI_API void DebugNodeTexture(ImTextureData* tex); IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); @@ -3880,30 +3899,102 @@ namespace ImGui //----------------------------------------------------------------------------- -// [SECTION] ImFontAtlas internal API +// [SECTION] ImFontBackendIO //----------------------------------------------------------------------------- +// Hooks and storage for a given font backend. // This structure is likely to evolve as we add support for incremental atlas updates. // Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public. -struct ImFontBuilderIO +struct ImFontBackendIO { - bool (*FontBuilder_Build)(ImFontAtlas* atlas); + const char* FontBackend_Name; + bool (*FontBackend_BackendInit)(ImFontAtlas* atlas); + void (*FontBackend_BackendShutdown)(ImFontAtlas* atlas); + bool (*FontBackend_FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); + void (*FontBackend_FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); + bool (*FontBackend_FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); + bool (*FontBackend_FontAddGlyph)(ImFontAtlas* atlas, ImFont* font, ImWchar codepoint); + + ImFontBackendIO() { memset(this, 0, sizeof(*this)); } }; // Helper for font builder #ifdef IMGUI_ENABLE_STB_TRUETYPE -IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype(); +IMGUI_API const ImFontBackendIO* ImFontAtlasGetBackendIOForStbTruetype(); +#endif + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas internal API +//----------------------------------------------------------------------------- + +// Packed rectangle (same as ImTextureRect) +struct ImFontAtlasRect +{ + unsigned short x, y; + unsigned short w, h; +}; +typedef int ImFontAtlasRectId; // <0 when invalid + +// Internal storage for incrementally packing and building a ImFontAtlas +struct stbrp_context_opaque { char data[80]; }; +struct stbrp_node; +struct ImFontAtlasBuilder +{ + stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file. + ImVector PackNodes; + int PackPadding; // Generally 1 to avoid bilinear filtering issues. + ImVector Rects; + ImVector TempBuffer; // Misc scratch buffer + ImVec2i MaxRectSize; // Largest rectangle to pack (defacto used as a "minimum texture size") + ImVec2i MaxRectBounds; // Bottom-right most used pixels + bool LockDisableResize; // Disable resizing texture + bool PreloadedAllGlyphsRanges; // Set when missing ImGuiBackendFlags_RendererHasTextures features forces atlas to preload everything. + + // Custom rectangle identifiers + ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. + ImFontAtlasRectId PackIdLinesTexData; + + ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); } +}; + +// FIXME-NEWATLAS: Cleanup +IMGUI_API void ImFontAtlasBuildSetupFontBackendIO(ImFontAtlas* atlas, const ImFontBackendIO* font_backend_io); +IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char); + +IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas); + +IMGUI_API ImTextureData* ImFontAtlasBuildAddTexture(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasBuildGrowTexture(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); +IMGUI_API void ImFontAtlasBuildCompactTexture(ImFontAtlas* atlas); + +IMGUI_API bool ImFontAtlasBuildAddFont(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy +IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); + +IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); +IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h); +IMGUI_API ImFontAtlasRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); + +IMGUI_API void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex); +IMGUI_API void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasTextureBlockConvertAndPostProcess(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, ImFontGlyph* glyph, unsigned char* pixels, ImTextureFormat format, int w, int h, int pitch, float in_multiply_factor); +IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +IMGUI_API void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas); #endif -IMGUI_API void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, float ascent, float descent); -IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque); -IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); -IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); -IMGUI_API void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); IMGUI_API bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 598d44f6c..8e794ab94 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3949,7 +3949,8 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c if (c == '\r') continue; - const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale; + // FIXME-NEWATLAS-V1: Measure perf, inline etc. + const float char_width = font->GetCharAdvance((ImWchar)c) * scale;// ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX)* scale; line_width += char_width; } @@ -4287,7 +4288,7 @@ void ImGui::PushPasswordFont() out_font->Ascent = in_font->Ascent; out_font->Descent = in_font->Descent; out_font->ContainerAtlas = in_font->ContainerAtlas; - out_font->FallbackGlyph = glyph; + out_font->FallbackGlyphIndex = in_font->Glyphs.index_from_ptr(glyph); // FIXME: broken out_font->FallbackAdvanceX = glyph->AdvanceX; IM_ASSERT(out_font->Glyphs.Size == 0 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0); PushFont(out_font); diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 1cb5ce28e..10d15b120 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -166,7 +166,7 @@ namespace // NB: No ctor/dtor, explicitly call Init()/Shutdown() struct FreeTypeFont { - bool InitFont(FT_Library ft_library, const ImFontConfig& src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. + bool InitFont(FT_Library ft_library, ImFontConfig& src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime. void CloseFont(); void SetPixelHeight(int pixel_height); // Change font pixel size. All following calls to RasterizeGlyph() will use this size const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint); @@ -185,7 +185,7 @@ namespace float InvRasterizationDensity; }; - bool FreeTypeFont::InitFont(FT_Library ft_library, const ImFontConfig& src, unsigned int extra_font_builder_flags) + bool FreeTypeFont::InitFont(FT_Library ft_library, ImFontConfig& src, unsigned int extra_font_builder_flags) { FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)src.FontData, (uint32_t)src.FontDataSize, (uint32_t)src.FontNo, &Face); if (error != 0)