ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
60.94k stars 10.29k forks source link

High DPI scaling on Mac OS and glfw #5081

Open jokteur opened 2 years ago

jokteur commented 2 years ago

Version/Branch of Dear ImGui:

Version: 1.87, 1.88 WIP Branch: docking & viewport

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_glfw.cpp + imgui_impl_opengl3.cpp Compiler: clang Operating System: Mac OS Monterey, Mac Mini M1

My Issue/Question:

Hello,

I know the questions about high DPI scaling have been asked a lot around here, however I do not think that I found a solution in the github issues.

My goal is to have an application that correctly DPI scaling. So I am following the instructions shown here. In the main loop, I checking if the scaling of the main window changed and I rebuild the fonts accordingly:

    [...] // In main.cpp of example_glfw_opengl3
    float prev_scale = 0.f;
    while (!glfwWindowShouldClose(window))
    {
        [...] // Event processing

        float xscale, yscale;
        glfwGetWindowContentScale(window, &xscale, &yscale);
        if (xscale != prev_scale) {
            prev_scale = xscale;
            io.Fonts->Clear();

            io.Fonts->AddFontFromFileTTF("Roboto-Regular.ttf", xscale * 16.0f);

            io.Fonts->Build();
            ImGui_ImplOpenGL3_DestroyFontsTexture();
            ImGui_ImplOpenGL3_CreateFontsTexture();

            // ImGui::GetStyle().ScaleAllSizes(xscale);
        }

        // Start the Dear ImGui frame
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();

       [...] // Rest of the code of main.cpp of example_glfw_opengl3
    }

So, this solution works fine on windows, and my fonts are not blurry and are correctly scaled.

However, on Mac OS the window of glfw is by default already scaled, so if I scale the fonts, then I get this:

Capture d’écran 2022-03-06 à 16 54 05

One quick solution would be to not scale the font at all, but then I obtain text that is at the correct scale, but blurry:

Capture d’écran 2022-03-06 à 17 20 51

My first initial guess was to look at the glfw window hints, like GLFW_SCALE_TO_MONITOR and GLFW_COCOA_RETINA_FRAMEBUFFER. The first hint does not seem to change anything, and the second hint does not help either, because it is GLFW_TRUE by default.

With GLFW_COCOA_RETINA_FRAMEBUFFER set to GLFW_TRUE, the framebuffer size of the main window

int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);

returns the actual number of pixels the window takes, not virtual pixels. This means on my 4K screen, if I fullscreen the main window, display_w will have 3860 pixels.

Searching further, I found that #3757 exposes a similar problem to mine, but with the SDL. The author of the issue provided a temporary fix. I tried to implement the fix in imgui_impl_glfw.cpp like this:

void ImGui_ImplGlfw_NewFrame()
{
    ImGuiIO& io = ImGui::GetIO();
    [...]
    if (bd->WantUpdateMonitors)
        ImGui_ImplGlfw_UpdateMonitors();

    // Fix
#if defined(__APPLE__)
    // On Apple, The window size is reported in Low DPI, even when running in high DPI mode
    ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
    if (!platform_io.Monitors.empty() && platform_io.Monitors[0].DpiScale > 1.0f && display_h != h)
    {
        io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
        io.DisplaySize = ImVec2((float)display_w, (float)display_h);
    }
#endif
    [...] // rest of the function
}

This does, along with the scaled font, produces the result I want:

Capture d’écran 2022-03-06 à 17 27 27

However, the mouse interaction does not work, because ImGui thinks that the mouse coordinates are in virtual pixels (I use glfw terms), but the UI is rendered in true pixels. I looked at

void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y)
{
    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
    if (bd->PrevUserCallbackCursorPos != NULL && window == bd->Window)
        bd->PrevUserCallbackCursorPos(window, x, y);

    ImGuiIO& io = ImGui::GetIO();
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
    {
        int window_x, window_y;
        glfwGetWindowPos(window, &window_x, &window_y);
        x += window_x;
        y += window_y;
    }
    io.AddMousePosEvent((float)x, (float)y);
    bd->LastValidMousePos = ImVec2((float)x, (float)y);
}

but I failed to provide a valid fix, because glfw provides virtual pixels positions (for both the callback and glfwGetWindowPos). This means if I have a 3680x2160px screen and a scaling of 2, with a mouse position of 400x200 the callback will return 200x100.

Multiplying the mouse position by the scale like in #3757 does not work, and this is because glfw always provides virtual pixel coordinates.

Do you know how I could resolve this ? I found a way to have the mouse coordinates match the window like this (with the viewport flags):

int window_x, window_y;
glfwGetWindowPos(window, &window_x, &window_y);
x += window_x;
y += window_y;

float xscale, yscale;
glfwGetWindowContentScale(window, &xscale, &yscale);
x *= xscale;
y *= yscale;

x -= window_x;
y -= window_y;

but this is obviously wrong because even if I can interact correctly with the widgets inside the main window, it means that I can also interact with widget when the mouse is outside the window...

Thank you in advance

DickyQi commented 2 years ago

My solution is change draw_data->FramebufferScale at function SetupViewportDrawData original code is: draw_data->FramebufferScale = io.DisplayFramebufferScale; modified code as: draw_data->FramebufferScale = ImVec2(floor(viewport->DpiScale), floor(viewport->DpiScale)); This is only workround, and I test sdl/glfw at Mac and Windows.

wolfpld commented 2 years ago

Some related discussion: https://bitbucket.org/wolfpld/tracy/issues/42/cannot-scroll-down-capture-on-macos

rokups commented 2 years ago

5301 also a relevant issue with a workaround.