How ImGui Handles DPI Scaling for High-DPI Displays

ImGui handles DPI scaling by separating logical UI coordinates from physical pixel density through the ImGuiIO::DisplayFramebufferScale field, which backends populate by querying OS-specific DPI APIs to automatically scale fonts, geometry, and input coordinates.

The Dear ImGui library (ocornut/imgui) delegates high-DPI support to platform backends while providing the core infrastructure to scale the entire rendering pipeline. When the framebuffer scale is greater than 1.0, the library multiplies font atlas sizes, widget geometry, and divides mouse input to maintain consistent visual sizing across Retina displays and other high-density monitors.

Core DPI Scaling Architecture

The central mechanism lives in ImGuiIO::DisplayFramebufferScale (defined in imgui.h). This ImVec2 field tells ImGui the ratio between logical pixels and physical framebuffer pixels. When the scale exceeds 1.0 (e.g., 2.0 on a Retina display), ImGui automatically performs three transformations:

  1. Font Scaling – The font atlas is generated at a size multiplied by the scale factor, ensuring text remains crisp rather than bitmap-stretched.
  2. Geometry Scaling – All widget coordinates and sizes are multiplied by the scale during the rendering phase, keeping UI elements the same physical size regardless of pixel density.
  3. Input Adjustment – Mouse positions are divided by the scale factor so that the logical cursor position matches the scaled UI coordinates.

Official Backend Implementations

ImGui itself does not detect monitor DPI; instead, official platform backends query native OS APIs and populate io.DisplayFramebufferScale before NewFrame() is called.

Win32 Implementation

The Win32 backend (imgui_impl_win32.cpp) uses ImGui_ImplWin32_GetDpiScaleForMonitor() to detect DPI. According to the source code at lines 27-40, it calls GetDpiForMonitor on Windows 8.1 and later, falling back to GetDeviceCaps(LOGPIXELSX) on older systems. The result is stored in io.DisplayFramebufferScale via ImGui_ImplWin32_NewFrame().

SDL2 Implementation

The SDL2 backend (imgui_impl_sdl2.cpp) queries the display DPI using SDL_GetDisplayDPI(). As implemented at lines 75-81, if the returned value is less than 96 (the baseline DPI), it clamps to 96 to prevent division by zero or negative scales. It then calculates the scale as dpi / 96.0f and assigns it to the IO structure.

GLFW Implementation

The GLFW backend (imgui_impl_glfw.cpp) handles Windows-specific DPI through the legacy helper ImGui_ImplWin32_GetLegacyDpiScale(), which reads LOGPIXELSX from a device context (see lines 93-95). On other platforms, it typically uses glfwGetMonitorContentScale to obtain the necessary values.

Implementing DPI Scaling in Custom Backends

If you are writing a custom backend for a platform not covered by the official implementations, you must manually detect and set the DPI scale:

// 1. Detect native DPI using OS-specific APIs
//    Windows: GetDpiForMonitor or GetDeviceCaps
//    macOS:   [NSScreen backingScaleFactor]
//    SDL:     SDL_GetDisplayDPI
//    GLFW:    glfwGetMonitorContentScale

// 2. Calculate scale (96 DPI is the baseline)
float scale = dpi / 96.0f;

// 3. Assign to ImGuiIO
ImGuiIO& io = ImGui::GetIO();
io.DisplayFramebufferScale = ImVec2(scale, scale);

Additionally, you must transform incoming mouse coordinates by dividing them by scale before passing them to ImGui, unless you utilize the built-in input helpers that handle this automatically.

Handling DPI Changes and Multi-Viewport

DPI scaling is not a static initialization value. Applications must respond to monitor changes and per-window density variations.

Dynamic DPI Updates

Update the scale in the following scenarios:

  • On window creation – Initialize the scale once the OS window is associated with a monitor.
  • On monitor change – Listen for WM_DPICHANGED (Win32), SDL_EVENT_DISPLAY_ORIENTATION (SDL), or similar events and recompute DisplayFramebufferScale.
  • On window movement – When dragging a window between monitors with different densities, the backend must detect the change and update the IO structure accordingly.

Multi-Viewport Support

When using ImGuiConfigFlags_ViewportsEnable, each ImGuiViewport may exist on a monitor with a different DPI. According to the ocornut/imgui source, each viewport stores its own FramebufferScale field. The platform backend must set viewport->Flags |= ImGuiViewportFlags_IsPlatformWindow; and update viewport->FramebufferScale during the NewFrame processing. The official backends handle this automatically by querying the monitor associated with each platform window.

Summary

  • Backend-driven architecture – ImGui consumes DisplayFramebufferScale but relies on the platform backend to query native DPI via OS APIs.
  • Automatic scaling – When properly configured, ImGui scales fonts, geometry, and input coordinates automatically based on the framebuffer scale value.
  • Baseline calculation – Always compute scale as dpi / 96.0f, where 96 represents the standard 100% DPI baseline.
  • Dynamic updates – Handle WM_DPICHANGED and monitor change events to update scaling when windows move between displays.
  • Per-viewport scaling – In multi-viewport mode, each viewport maintains its own FramebufferScale to support mixed-DPI environments.

Frequently Asked Questions

How do I enable DPI scaling in my ImGui application?

You do not need to enable a specific flag in the core library. Instead, ensure your platform backend (Win32, SDL2, GLFW, etc.) is correctly detecting the monitor DPI and writing to ImGuiIO::DisplayFramebufferScale. The official backends do this automatically in their NewFrame() functions. If you are not using an official backend, implement the OS-specific DPI detection yourself and set the scale value before calling ImGui::NewFrame().

Why do my fonts appear blurry on high-DPI displays?

Blurry fonts indicate that DisplayFramebufferScale is set to 1.0 on a high-DPI monitor, causing the font atlas to be generated at logical resolution rather than physical pixel resolution. Verify that your backend is correctly detecting the DPI and that you are regenerating the font atlas after setting the scale. The font size should be specified in logical pixels; ImGui multiplies this by the framebuffer scale internally to create a high-resolution atlas.

What is the difference between DisplayFramebufferScale and DisplaySize?

DisplaySize (measured in logical pixels) represents the size of the display area available for the UI, while DisplayFramebufferScale is a multiplier that converts logical coordinates to physical framebuffer pixels. Prior to ImGui 1.87, some backends used DisplaySize to indirectly handle DPI by reporting the framebuffer size directly. Modern backends should keep DisplaySize as the logical size and use DisplayFramebufferScale to communicate the pixel density ratio.

How do I handle different DPIs for multiple monitors?

When using multi-viewport support, each ImGuiViewport structure contains its own FramebufferScale field. As the platform backend creates and manages OS windows, it must detect the DPI of the monitor hosting each window and update that viewport's FramebufferScale accordingly. The core library uses this per-viewport scale when generating the draw data for that specific window, ensuring correct scaling even when windows span monitors with different densities.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →