How to Handle DPI Scaling and High‑DPI Displays in ImGui Applications
Dear ImGui handles high‑DPI displays by separating logical UI coordinates from physical pixels via ImGuiIO::DisplayFramebufferScale, which back‑ends populate by querying the OS DPI and dividing by 96.
Dear ImGui (ocornut/imgui) renders crisp, resolution‑independent interfaces by decoupling the logical size of widgets from the physical pixel density of the monitor. The core mechanism relies on ImGuiIO::DisplayFramebufferScale, defined in imgui.h, which back‑ends set by detecting the native DPI and computing a scale factor relative to the standard 96 DPI baseline.
How ImGui Implements DPI Scaling Internally
The DisplayFramebufferScale Mechanism
The ImGuiIO::DisplayFramebufferScale field (located at line 2420 in imgui.h) tells the library how many physical pixels correspond to one logical UI unit. When this value is greater than 1.0—for example, 2.0 on a Retina display—the library automatically adjusts three critical subsystems.
Automatic Scaling Behaviors
- Font atlas generation: The font system multiplies the requested pixel size by
DisplayFramebufferScalebefore rasterizing glyphs, ensuring text remains sharp rather than pixel‑doubled. - Geometry submission: Vertex positions for all widgets are multiplied by the scale during the draw list phase, keeping buttons and windows the same visual size regardless of resolution.
- Mouse input normalization: Incoming cursor coordinates are divided by the scale factor so that
io.MousePosmatches the logical coordinate space used by the UI.
Platform Backend DPI Detection
ImGui does not query the OS directly; instead, the platform back‑end detects the monitor DPI and writes the result to io.DisplayFramebufferScale. The official back‑ends implement this as follows:
Win32 Implementation
In backends/imgui_impl_win32.cpp (lines 27‑40), the ImGui_ImplWin32_GetDpiScaleForMonitor() function uses GetDpiForMonitor on Windows 8.1 and later, falling back to GetDeviceCaps(LOGPIXELSX) for older systems. The resulting value is stored in io.DisplayFramebufferScale inside ImGui_ImplWin32_NewFrame().
SDL2 and SDL3 Handling
The SDL2 back‑end in backends/imgui_impl_sdl2.cpp (lines 75‑81) calls SDL_GetDisplayDPI() and clamps values below 96 to the baseline. It then computes scale = dpi / 96.0f and assigns it to the IO structure.
GLFW Support
For GLFW on Windows, imgui_impl_glfw.cpp (lines 93‑95) invokes ImGui_ImplWin32_GetLegacyDpiScale(), which reads LOGPIXELSX from a device context to determine the scale factor.
Implementing DPI Scaling in Your Application
Standard Integration Workflow
When using an official back‑end, DPI handling is automatic. Initialize ImGui, call the back‑end’s NewFrame() function, and the scale is applied before you build any UI:
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // optional
// Platform and renderer back‑ends populate io.DisplayFramebufferScale
ImGui_ImplWin32_NewFrame(); // or ImGui_ImplSDL2_NewFrame, etc.
ImGui_ImplDX11_NewFrame();
ImGui::NewFrame();
ImGui::ShowDemoWindow();
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
Handling Dynamic DPI Changes
Update the scale factor whenever the window moves to a different monitor:
- Win32: Listen for
WM_DPICHANGEDand recompute the scale. - SDL2: Watch for
SDL_EVENT_DISPLAY_ORIENTATIONor similar display events. - GLFW:
glfwGetMonitorContentScalereturns updated values afterglfwPollEvents.
Building Custom Back‑Ends for High‑DPI
If you are writing a custom platform layer, follow these steps:
- Detect native DPI using OS‑specific APIs (e.g.,
GetDpiForMonitoron Windows,SDL_GetDisplayDPIon SDL, orglfwGetMonitorContentScaleon GLFW). - Compute the scale as
dpi / 96.0f(96 DPI is the baseline). - Assign the scale to
io.DisplayFramebufferScale = ImVec2(scale, scale);. - Transform mouse coordinates by dividing incoming positions by
scaleso thatio.MousePosaligns with the logical UI space.
Multi‑Viewport DPI Considerations
When ImGuiConfigFlags_ViewportsEnable is active, each OS window may reside on a monitor with different pixel density. ImGui creates a separate ImGuiViewport for each platform window, storing an independent FramebufferScale per viewport.
The platform back‑end must set viewport->Flags |= ImGuiViewportFlags_IsPlatformWindow; and update viewport->FramebufferScale during its NewFrame processing. The official back‑ends handle this automatically, ensuring that a viewport dragged from a 96 DPI monitor to a 200 DPI Retina display updates its scale dynamically.
Summary
- DPI handling is back‑end driven: The core library consumes
io.DisplayFramebufferScalebut never queries the OS. - Official back‑ends are ready: Win32, SDL2/3, GLFW, and macOS back‑ends already implement robust DPI detection in
imgui_impl_*.cpp. - Scale calculation: Always divide the native DPI by 96.0f to obtain the correct framebuffer scale.
- Dynamic updates: Recompute the scale on monitor changes or
WM_DPICHANGEDevents to support mixed‑DPI setups. - Multi‑viewport support: Each
ImGuiViewportmaintains its ownFramebufferScalefor per‑monitor DPI awareness.
Frequently Asked Questions
How does ImGui handle font scaling on high‑DPI displays?
ImGui multiplies the font size by DisplayFramebufferScale before rasterizing the glyph atlas. This means a 16px font at 2.0x DPI is rendered at 32 physical pixels, producing crisp text rather than upscaled bitmaps.
What is the baseline DPI value used for scaling calculations?
The baseline is 96 DPI, the standard Windows logical resolution. Back‑ends compute the scale factor as scale = native_dpi / 96.0f, which is why SDL2 clamps values below 96 to prevent incorrect scaling on legacy displays.
How do I update DPI scaling when a window moves between monitors?
Listen for the platform‑specific DPI change event (e.g., WM_DPICHANGED on Win32) inside your window message loop. Recompute io.DisplayFramebufferScale using the new monitor’s DPI, and call ImGui::GetIO().Fonts->Build() to regenerate the font atlas if the scale changes significantly.
Why are my mouse coordinates incorrect on a high‑DPI display?
If you are bypassing the official back‑end input helpers, you must divide incoming mouse coordinates by io.DisplayFramebufferScale before writing them to io.MousePos. The official back‑ends perform this normalization automatically in their NewFrame implementations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →