From 5ee984555984979e39e0fd5584be6dc442686499 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 16 May 2025 18:04:44 +0200 Subject: [PATCH] Fonts: automatically set current rasterizer density to viewport density. Effectively should fix most things on macOS. # Conflicts: # imgui.cpp # imgui.h --- docs/FONTS.md | 2 +- imgui.cpp | 6 +++++- imgui.h | 13 +++++++------ imgui_draw.cpp | 45 +++++++++++++++++++++++++-------------------- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/docs/FONTS.md b/docs/FONTS.md index 452499059..baa53adde 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -130,7 +130,7 @@ ImGui::PopFont(); **For advanced options create a ImFontConfig structure and pass it to the AddFont() function (it will be copied internally):** ```cpp ImFontConfig config; -config.RasterizerDensity = 2.0f; +config.OversampleH = 1.0f; ImFont* font = io.Fonts->AddFontFromFileTTF("font.ttf", size_pixels, &config); ``` diff --git a/imgui.cpp b/imgui.cpp index 2a4001d65..58455df8d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4398,6 +4398,8 @@ static void SetCurrentWindow(ImGuiWindow* window) g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking if (window) { + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + g.FontRasterizerDensity = window->Viewport->FramebufferScale.x; // == SetFontRasterizerDensity() ImGui::UpdateCurrentFontSize(); ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } @@ -8699,7 +8701,8 @@ void ImGui::UpdateCurrentFontSize() g.DrawListSharedData.FontScale = g.FontScale; } -// FIXME-DPI: Not sure how to expose this. It may be automatically applied based on current viewport, if we had this information stored in viewport or monitor. +// Exposed in case user may want to override setting density. +// IMPORTANT: Begin()/End() is overriding density. Be considerate of this you change it. void ImGui::SetFontRasterizerDensity(float rasterizer_density) { ImGuiContext& g = *GImGui; @@ -15240,6 +15243,7 @@ static void ImGui::UpdateViewportsNewFrame() main_viewport->Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp; main_viewport->Pos = ImVec2(0.0f, 0.0f); main_viewport->Size = g.IO.DisplaySize; + main_viewport->FramebufferScale = g.IO.DisplayFramebufferScale; for (ImGuiViewportP* viewport : g.Viewports) { diff --git a/imgui.h b/imgui.h index 61056bb6f..0eb83cd9a 100644 --- a/imgui.h +++ b/imgui.h @@ -2322,7 +2322,8 @@ struct ImGuiIO ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Keyboard/Gamepad navigation options, etc. ImGuiBackendFlags BackendFlags; // = 0 // See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend. - ImVec2 DisplaySize; // // Main display size, in pixels (generally == GetMainViewport()->Size). May change every frame. + ImVec2 DisplaySize; // // Main display size, in pixels (== GetMainViewport()->Size). May change every frame. + ImVec2 DisplayFramebufferScale; // = (1, 1) // Main display density. For retina display where window coordinates are different from framebuffer coordinates. This will affect font density + will end up in ImDrawData::FramebufferScale. float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. May change every frame. float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. const char* IniFilename; // = "imgui.ini" // Path to .ini file (important: default "imgui.ini" is relative to current working dir!). Set NULL to disable automatic .ini loading/saving or if you want to manually call LoadIniSettingsXXX() / SaveIniSettingsXXX() functions. @@ -2334,7 +2335,6 @@ struct ImGuiIO float FontGlobalScale; // = 1.0f // Global scale all fonts bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. - ImVec2 DisplayFramebufferScale; // = (1, 1) // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale. // Keyboard/Gamepad Navigation options bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. @@ -3341,7 +3341,7 @@ struct ImDrawData ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. ImVec2 DisplayPos; // Top-left position of the viewport to render (== top-left of the orthogonal projection matrix to use) (== GetMainViewport()->Pos for the main viewport, == (0.0) in most single-viewport applications) ImVec2 DisplaySize; // Size of the viewport to render (== GetMainViewport()->Size for the main viewport, == io.DisplaySize in most single-viewport applications) - ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Based on io.DisplayFramebufferScale. Generally (1,1) on normal display, (2,2) on OSX with Retina display. + ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Copied from viewport->FramebufferScale (== io.DisplayFramebufferScale for main viewport). Generally (1,1) on normal display, (2,2) on OSX with Retina display. ImGuiViewport* OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). ImVector* Textures; // List of textures to update. Most of the times the list is shared by all ImDrawData, has only 1 texture and it doesn't need any update. This almost always points to ImGui::GetPlatformIO().Textures[]. May be overriden or set to NULL if you want to manually update textures. @@ -3734,8 +3734,8 @@ enum ImFontFlags_ ImFontFlags_None = 0, ImFontFlags_UseDefaultSize = 1 << 0, // Legacy compatibility: make PushFont() calls without explicit size use font->DefaultSize instead of current font size. ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. - ImFontFlags_NoLoadGlyphs = 1 << 2, // Disable loading new glyphs. - ImFontFlags_LockBakedSizes = 1 << 3, // Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. + ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs. + ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display. }; // Font runtime data and rendering @@ -3766,7 +3766,7 @@ struct ImFont // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API ImFontBaked* GetFontBaked(float font_size); // Get or create baked data for given size + IMGUI_API ImFontBaked* GetFontBaked(float font_size, float density = -1.0f); // Get or create baked data for given size IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } const char* GetDebugName() const { return Sources.Size ? Sources[0]->Name : ""; } // Fill ImFontConfig::Name. @@ -3832,6 +3832,7 @@ struct ImGuiViewport ImGuiViewportFlags Flags; // See ImGuiViewportFlags_ ImVec2 Pos; // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates) ImVec2 Size; // Main Area: Size of the viewport. + ImVec2 FramebufferScale; // Density of the viewport for Retina display (always 1,1 on Windows, may be 2,2 etc on macOS/iOS). This will affect font rasterizer density. ImVec2 WorkPos; // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos) ImVec2 WorkSize; // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 65485fb20..6306e9c75 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3786,25 +3786,28 @@ ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_si ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) { ImFontAtlasBuilder* builder = atlas->Builder; - ImFontBaked* closest_larger_match = NULL; - ImFontBaked* closest_smaller_match = NULL; - for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + for (int step_n = 0; step_n < 2; step_n++) { - ImFontBaked* baked = &builder->BakedPool[baked_n]; - if (baked->ContainerFont != font || baked->WantDestroy) - continue; - if (baked->RasterizerDensity != font_rasterizer_density) - continue; - if (baked->Size > font_size && (closest_larger_match == NULL || baked->Size < closest_larger_match->Size)) - closest_larger_match = baked; - if (baked->Size < font_size && (closest_smaller_match == NULL || baked->Size > closest_smaller_match->Size)) - closest_smaller_match = baked; + ImFontBaked* closest_larger_match = NULL; + ImFontBaked* closest_smaller_match = NULL; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->ContainerFont != font || baked->WantDestroy) + continue; + if (step_n == 0 && baked->RasterizerDensity != font_rasterizer_density) // First try with same density + continue; + if (baked->Size > font_size && (closest_larger_match == NULL || baked->Size < closest_larger_match->Size)) + closest_larger_match = baked; + if (baked->Size < font_size && (closest_smaller_match == NULL || baked->Size > closest_smaller_match->Size)) + closest_smaller_match = baked; + } + if (closest_larger_match) + if (closest_smaller_match == NULL || (closest_larger_match->Size >= font_size * 2.0f && closest_smaller_match->Size > font_size * 0.5f)) + return closest_larger_match; + if (closest_smaller_match) + return closest_smaller_match; } - if (closest_larger_match) - if (closest_smaller_match == NULL || (closest_larger_match->Size >= font_size * 2.0f && closest_smaller_match->Size > font_size * 0.5f)) - return closest_larger_match; - if (closest_smaller_match) - return closest_smaller_match; return NULL; } @@ -5195,7 +5198,7 @@ ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterize } // ImFontBaked pointers are valid for the entire frame but shall never be kept between frames. -ImFontBaked* ImFont::GetFontBaked(float size) +ImFontBaked* ImFont::GetFontBaked(float size, float density) { ImFontBaked* baked = LastBaked; @@ -5203,12 +5206,14 @@ ImFontBaked* ImFont::GetFontBaked(float size) // - ImGui::PushFontSize() will already round, but other paths calling GetFontBaked() directly also needs it (e.g. ImFontAtlasBuildPreloadAllGlyphRanges) size = ImGui::GetRoundedFontSize(size); - if (baked && baked->Size == size && baked->RasterizerDensity == CurrentRasterizerDensity) + if (density < 0.0f) + density = CurrentRasterizerDensity; + if (baked && baked->Size == size && baked->RasterizerDensity == density) return baked; ImFontAtlas* atlas = ContainerAtlas; ImFontAtlasBuilder* builder = atlas->Builder; - baked = ImFontAtlasBakedGetOrAdd(atlas, this, size, CurrentRasterizerDensity); + baked = ImFontAtlasBakedGetOrAdd(atlas, this, size, density); if (baked == NULL) return NULL; baked->LastUsedFrame = builder->FrameCount;