Trying to get ImGui to work in high dpi mode with sdl3 renderer backend but its not actually drawing in high dpi.
Version/Branch of Dear ImGui:
master
Back-ends:
imgui_impl_sdl3 + imgui_impl_sdlrenderer3
Compiler, OS:
MacOS + cmake
Full config/build information:
Dear ImGui 1.91.0 WIP (19093)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=201703
define: __APPLE__
define: __GNUC__=4
define: __clang_version__=15.0.0 (clang-1500.3.9.4)
--------------------------------
io.BackendPlatformName: imgui_impl_sdl3
io.BackendRendererName: imgui_impl_sdlrenderer3
io.ConfigFlags: 0x00000000
io.ConfigMacOSXBehaviors
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x0000000E
HasMouseCursors
HasSetMousePos
RendererHasVtxOffset
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,64
io.DisplaySize: 800.00,600.00
io.DisplayFramebufferScale: 2.00,2.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00
Details:
I'm trying to setup ImGui to run in high dpi mode with the sdl3 renderer backend. I'm trying 2 different ways to get it correct, but either way it's just drawing ½ the needed size.
The test program set up SDL and ImGUI and tries to draw a 2 rect, 1 solid blue rect that is the full size of the application window and one that is green at (100, 100, 200, 200). This works in normal mode. In both high dpi modes it display incorrectly at ½ scale.
Screenshots/Video:
Normal mode:
$ ./test
Renderer Name: metal
SDL Window Scale: 1
SDL Window Point Size: 800x600
SDL Window Pixel Size: 800x600
ImGui Display Size: 800x600
ImGui Display Scale: 1x1
Running ImGui Display Size: 800x600
Running ImGui Display Scale: 1x1
--hidpiA mode:
$ ./test --hidpiA SDL_Window set: SDL_WINDOW_HIGH_PIXEL_DENSITY Renderer Name: metal ImGUI DisplaySize set pixels SDL Window Scale: 2 SDL Window Point Size: 800x600 SDL Window Pixel Size: 1600x1200 ImGui Display Size: 1600x1200 ImGui Display Scale: 2x2 Running ImGui Display Size: 800x600 Running ImGui Display Scale: 2x2
- Just a note here, while running the ImGui display size is somehow reset to 800x600. This doesn't seem correct to me as I set it to the pixel size of 1600x1200. The scale stays as expected
--hidpiB mode:
$ ./test --hidpiB SDL_Window set: SDL_WINDOW_HIGH_PIXEL_DENSITY Renderer Name: metal ImGUI DisplaySize set points SDL Window Scale: 2 SDL Window Point Size: 800x600 SDL Window Pixel Size: 1600x1200 ImGui Display Size: 800x600 ImGui Display Scale: 2x2 Running ImGui Display Size: 800x600 Running ImGui Display Scale: 2x2
- This is the one I thought would work. Everything is setup to look like 800x600 regardless, but it still draws in the wrong scale even though its set to 2.
Minimal, Complete and Verifiable Example code:
Run in normal mode: ./test
Run in high dpi mode setting the ImGui screen size in real pixels: ./test --hidpiA
Run in high dpi mode setting the ImGui screen size in points: ./test --hidpiB
#include <iostream>
#include <SDL3/SDL.h>
#include <imgui.h>
#include "backends/imgui_impl_sdl3.h"
#include "backends/imgui_impl_sdlrenderer3.h"
int WINDOW_WIDTH_REQESTED = 800;
int WINDOW_HEIGHT_REQUESTED = 600;
int main(int argc, char* args[]) {
int isHidpi = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(args[i], "--hidpiA") == 0) {
isHidpi = 1;
} else if (strcmp(args[i], "--hidpiB") == 0) {
isHidpi = 2;
}
}
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
return 1;
}
SDL_Window *window = nullptr;
if (isHidpi != 0) {
window = SDL_CreateWindow("SDL3 App", WINDOW_WIDTH_REQESTED, WINDOW_HEIGHT_REQUESTED, SDL_WINDOW_HIGH_PIXEL_DENSITY);
std::cout << "SDL_Window set: SDL_WINDOW_HIGH_PIXEL_DENSITY" << std::endl;
} else {
window = SDL_CreateWindow("SDL3 App", WINDOW_WIDTH_REQESTED, WINDOW_HEIGHT_REQUESTED, 0);
}
if (!window) {
std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl;
return 1;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, NULL);
if (!renderer) {
std::cerr << "Renderer could not be created! SDL_Error: " << SDL_GetError() << std::endl;
return 1;
}
std::cout << "Renderer Name: " << SDL_GetRendererName(renderer) << std::endl;
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
// Setup Platform/Renderer backends
ImGui_ImplSDL3_InitForSDLRenderer(window, renderer);
ImGui_ImplSDLRenderer3_Init(renderer);
ImGuiIO& io = ImGui::GetIO(); (void)io;
float displayScale = SDL_GetWindowDisplayScale(window);
int pixWidth, pixHeight, winWidth, winHeight;
SDL_GetWindowSizeInPixels(window, &pixWidth, &pixHeight);
SDL_GetWindowSize(window, &winWidth, &winHeight);
if (isHidpi) {
if (isHidpi == 1) {
io.DisplaySize = ImVec2(static_cast<float>(pixWidth), static_cast<float>(pixHeight));
std::cout << "ImGUI DisplaySize set pixels" << std::endl;
} else if (isHidpi == 2) {
io.DisplaySize = ImVec2(static_cast<float>(winWidth), static_cast<float>(winHeight));
std::cout << "ImGUI DisplaySize set points" << std::endl;
}
io.DisplayFramebufferScale = ImVec2(displayScale, displayScale);
io.FontGlobalScale = displayScale;
ImGui_ImplSDLRenderer3_DestroyDeviceObjects();
ImGui_ImplSDLRenderer3_CreateDeviceObjects();
} else {
io.DisplaySize = ImVec2(static_cast<float>(pixWidth), static_cast<float>(pixHeight));
}
std::cout << "SDL Window Scale: " << displayScale << std::endl;
std::cout << "SDL Window Point Size: " << winWidth << "x" << winHeight << std::endl;
std::cout << "SDL Window Pixel Size: " << pixWidth << "x" << pixHeight << std::endl;
std::cout << "ImGui Display Size: " << io.DisplaySize.x << "x" << io.DisplaySize.y << std::endl;
std::cout << "ImGui Display Scale: " << io.DisplayFramebufferScale.x << "x" << io.DisplayFramebufferScale.y << std::endl;
bool appRunning = true;
while (appRunning) {
SDL_Event e;
ImGuiIO& newIo = ImGui::GetIO(); (void)newIo;
while (SDL_PollEvent(&e) != 0) {
ImGui_ImplSDL3_ProcessEvent(&e);
switch (e.type) {
case SDL_EVENT_QUIT:
appRunning = false;
break;
case SDL_EVENT_KEY_DOWN:
if (e.key.key == SDLK_SPACE) {
std::cout << "Running ImGui Display Size: " << newIo.DisplaySize.x << "x" << newIo.DisplaySize.y << std::endl;
std::cout << "Running ImGui Display Scale: " << newIo.DisplayFramebufferScale.x << "x" << newIo.DisplayFramebufferScale.y << std::endl;
}
break;
default:
break;
}
}
// Clear the screen
SDL_SetRenderDrawColorFloat(renderer, 0.45f, 0.55f, 0.60f, 1.00f);
SDL_RenderClear(renderer);
// New frame
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
// Draw GUI
ImGui::GetForegroundDrawList()->AddRectFilled(ImVec2(0, 0), ImVec2(io.DisplaySize.x, io.DisplaySize.y), IM_COL32(0, 0, 255, 255));
ImGui::GetForegroundDrawList()->AddRectFilled(ImVec2(100, 100), ImVec2(200, 200), IM_COL32(0, 255, 0, 255));
// Display frame
ImGui::Render();
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer);
SDL_RenderPresent(renderer);
}
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
if (renderer) {
SDL_DestroyRenderer(renderer);
}
if (window) {
SDL_DestroyWindow(window);
}
SDL_Quit();
return 0;
}
I can confirm the same using the Wayland SDL backend on Linux, also with SDL3 Renderer. The behavior of point size, pixel size, and scale is the same on this platform too, it seems. And if we try to scale the style and fonts, leaving everything else the same, it is indeed super broken:
If we look into the implementation of ImGui_ImplSDL3_NewFrame in imgui_impl_sdl3.cpp, it is clear that ImGui is setting the display size every frame. Sure, this makes it handle window resizing quite transparently. I looked into changing it but it looks as intended to me. At least, the intention seems to be that ImGUI will work in "point" coordinates internally. This has the advantage that we don't need to do anything to input events, they work as intended. So we can try working with that assumption instead.
Setting global font scale as in the example seems to scale up the UI but SDL_Renderer does not seem to particularly care about the scaling, and renders stuff in native pixel coordinates. Since we cannot convince the current implementation to not reset logical window size to point size every frame, this breaks. Instead, we can scale up our rendering with SDL_SetRenderScale(...), something like this:
// Display frame
ImGui::Render();
SDL_SetRenderScale(renderer, displayScale, displayScale);
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer);
SDL_SetRenderScale(renderer, 1.0f, 1.0f);
SDL_RenderPresent(renderer);
This gives the following result:
Yeah, I can live with that.
One downside is that the default font, and any font where the texture is not hi-res, will look blurry. I will try to get this working later with a TTF font, when time allows. I could swear I've seen an example somewhere where the font is regenerated with a higher-res texture but keeps the logical metrics the same. I just can't find it anymore...
EDIT: another downside of this approach, is that all rendering will be snapped to logical pixel coordinates, which may look quite off when not using integer scale ratios. It could be better for ImGui to work with physical device pixels to prevent that.
The solution in this case seems to be changing the code in ImGui_ImplSDL3_NewFrame to always set internal window size to physical pixels, and scale the style and fonts in the application. This way we can guarantee that rendering will be pixel-perfect on every scale ratio. One may need to do something with the mouse coordinates though, that much I don't know. Maybe io.DisplayFramebufferScale is taking care of coordinate transformation, and this could be why the input works out-of-the-box with the above scaling solution. This needs more testing in any case.
I want to keep my project maintenance as simple as possible for now, so I will keep all the ImGui code unmodified, and will keep using the above solution as-is.
Good high DPI support is still a work in progress with Dear ImGui, but I'd expect it to be a bit less janky that what you're seeing here.
Can you check if example_sdl2_sdlrenderer2 has the same issue? I'm noticing the SDL2 example specifies SDL_WINDOW_ALLOW_HIGHDPI but the SDL3 one doesn't specify it or anything similar. It might be that the SDL3 Renderer backend hasn't ever been tested with a high DPI display and is bugged as it's still relatively new (ditto on the SDL3 platform backend.)
Also considering you first raised this issue back in July, have you updated since then?
One downside is that the default font, and any font where the texture is not hi-res, will look blurry. I will try to get this working later with a TTF font, when time allows. I could swear I've seen an example somewhere where the font is regenerated with a higher-res texture but keeps the logical metrics the same. I just can't find it anymore...
You may have remembered seeing it in the 10 years of Dear ImGui post (see section 6 here), both font rendering and high DPI support are listed here.
The SDF fonts fork also accomplishes decent scalable font rendering.
There's also various workarounds scattered about for reloading fonts when the DPI changes.
Edit: The font rendering improvements mentioned above are now available for public testing: https://github.com/ocornut/imgui/issues/8465
// Display frame ImGui::Render(); SDL_SetRenderScale(renderer, displayScale, displayScale); ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer); SDL_SetRenderScale(renderer, 1.0f, 1.0f); SDL_RenderPresent(renderer);
Thank you for this snippet of code. I've been pulling my hair out for days trying to get macOS HighDPI working and just this simple example give me the clarity I needed. Still have to work on the scrollbar sizes and some other matters but at least now the mouse tracks properly and the draw area fills out the window.
Thank you again!
I'm glad this was useful to you! As of the latest release it seems that DPI handling is being reworked, and we may get a more general and robust scaling system soon. Dynamic fonts are indeed already released. When the new scaling system drops, I'll give it a go as well :)
@zaun Do you believe this is solved for you? Or should this ticket remain open?
@lucaspcamargo - Yes this solves the issue. Thanks!