If you’re like me and kinda hop freely among the 3 main desktop platforms one of the most annoying things you’ll notice is that HiDPI and fractional display scaling works pretty much without any special consideration on MacOS and Windows… yet on Linux it can be a minefield full of bear traps and edge cases for different apps.

Wayland definitely improved the situation, and the mainstream desktop environments on Linux really have come a long way. As some distros move slower than others it might be a while before all the advances are uniformly available though.

One thing I consistently experience though is that applications using ImGUI frequently ignore my display scale. And indeed, once I had Dear ImGUI hooked up in my MoonWorks app I found that I couldn’t see anything because it wasn’t taking my display scale into account by default. (How could it? It’s my job to tell it that in the first place right?)

So this post was intended to be just a note to my future self regarding how I resolved this and got “proper” (aka readable for my middle-age eyesight).


Setup Link to heading

When initializing your ImGUI integration at startup, you’ll want to handle a couple of little details. Here is my initialization function for example:

public static System.IntPtr Initialize(Game game, string fontPath)
{
    var context = ImGui.CreateContext();
    ImGui.SetCurrentContext(context);

    var io = ImGui.GetIO();

    io.BackendFlags = ImGuiBackendFlags.None | ImGuiBackendFlags.RendererHasVtxOffset;
    io.ConfigFlags = ImGuiConfigFlags.DockingEnable | ImGuiConfigFlags.DpiEnableScaleFonts |
                     ImGuiConfigFlags.DpiEnableScaleViewports;

    var display      = SDL3.SDL.SDL_GetPrimaryDisplay();
    var displayScale = SDL3.SDL.SDL_GetDisplayContentScale(display);

    io.Fonts.AddFontFromFileTTF(fontPath, 32);
    io.FontGlobalScale = 0.5f;

    io.DisplaySize             = new Vector2(game.MainWindow.Width, game.MainWindow.Height);
    io.DisplayFramebufferScale = new Vector2(displayScale);

    ImGui.StyleColorsLight();

    return context;
}

Several things to notice:

First we get the “primary” display from SDL and query it’s display scale:

var display      = SDL3.SDL.SDL_GetPrimaryDisplay();
var displayScale = SDL3.SDL.SDL_GetDisplayContentScale(display);
Note
If you want to support multi-monitor arrangements and moving windows correctly from one monitor to another, then this is approach is insufficient. I haven’t yet had to deal with that so I can’t offer any solutions here at the moment.

The second thing to note is that I’m doing something kind weird with fonts:

io.Fonts.AddFontFromFileTTF(fontPath, 32);
io.FontGlobalScale = 0.5f;

This is creating the font for the GUI “extra large”, and then scaling it down to half the size. The purpose is to ensure that fonts are still crisp despite the display scaling.

Note
If I’m honest, I did a quick comparison and if I rendered the font at size 16 it looked about the same. So maybe the bigger issue is that your font selection matters more than this oversizing of the atlas.

Finally, you’ll want to be sure that ImGUI has both the actual game display size the framebuffer scale:

io.DisplaySize             = new Vector2(game.MainWindow.Width, game.MainWindow.Height);
io.DisplayFramebufferScale = new Vector2(displayScale);

Rendering Link to heading

At this point you’re basically done. The only thing you have to adjust is how you provide mouse coordinates to ImGUI at the start of a frame:

var mousePosX = Inputs.Mouse.X / io.DisplayFramebufferScale.X;
var mousePosY = Inputs.Mouse.Y / io.DisplayFramebufferScale.Y;

io.AddMousePosEvent(mousePosX, mousePosY);
Note
As stated previously, if you’re in the situation where you need to handle multi-monitor setups with varying display scales, then you’d also need to be properly updating io.DisplayFramebufferScale to reflect the current display.

Example Link to heading

First here is my game without display scaling:

no-scaling

And here it is with display scaling:

scaled