Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scale to window maintaining aspect ratio, but Viewport won't center #7590

Closed
hippyau opened this issue May 15, 2024 · 2 comments
Closed

Scale to window maintaining aspect ratio, but Viewport won't center #7590

hippyau opened this issue May 15, 2024 · 2 comments

Comments

@hippyau
Copy link

hippyau commented May 15, 2024

Version/Branch of Dear ImGui:

Version 1.90.7

Back-ends:

imgui_impl_sdl2.cpp + imgui_impl_opengl3.cpp

Compiler, OS:

GCC 10.5.0, Linux Ubuntu

Full config/build information:

Dear ImGui 1.90.7 WIP (19062)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=201103
define: __linux__
define: __GNUC__=10
--------------------------------
io.BackendPlatformName: imgui_impl_sdl2
io.BackendRendererName: imgui_impl_opengl3
io.ConfigFlags: 0x00000003
 NavEnableKeyboard
 NavEnableGamepad
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: 1920.00,1080.00
io.DisplayFramebufferScale: 0.94,0.94
--------------------------------
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:

My Issue/Question:

I am using Dear ImGui as the primary UI for an application, not drawing or rendering anything with SDL2 or OpenGL other than Dear ImGui. It's a system designed to run at full-screen on a 1920x1080 LCD panel, as a kiosk sort of thing.

So as far as Dear ImGui should be concerned, the UI logical display size should be always 1920x1080.
That is all working fine...

Now, when using on a PC with window manager and task bar etc, we have a window content area which is smaller than 1920x1080.

Rather than lose part of the "logical display area" due to the window title bar or window resizing, I want the display scaled, maintaining aspect, and being centered in the window (even if there is letter boxing).

I have provided an MCVE which almost works... but 2 things:

  • It's not centered in the window, like the viewportX / Y offset is not working, not centering the ImGui
  • The mouse cursor position correction is not working, partly I think because the above.

Screenshots/Video:

The areas with the red squiggly lines should not be there, rather display should be centered and black letter-boxing, like a wide screen video would on a 4:3 display...

image

image

Minimal, Complete and Verifiable Example code:

#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <SDL.h>
#include <SDL_opengl.h>

int main(int, char**)
{
    // Setup SDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0)
    {
        printf("Error: %s\n", SDL_GetError());
        return -1;
    }

    // Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
    // GL ES 2.0 + GLSL 100
    const char* glsl_version = "#version 100";
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#elif defined(__APPLE__)
    // GL 3.2 Core + GLSL 150
    const char* glsl_version = "#version 150";
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
#else
    // GL 3.0 + GLSL 130
    const char* glsl_version = "#version 130";
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#endif

    // Create window with graphics context
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
    SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
    SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1920, 1080, window_flags);
    if (window == nullptr)
    {
        printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError());
        return -1;
    }

    SDL_GLContext gl_context = SDL_GL_CreateContext(window);
    SDL_GL_MakeCurrent(window, gl_context);
    SDL_GL_SetSwapInterval(1); // Enable vsync

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls

    ImGui::StyleColorsDark();

    ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
    ImGui_ImplOpenGL3_Init(glsl_version);

    bool show_demo_window = false;    
    ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);

    // Main loop
    bool done = false;
    while (!done)
    {
        int windowWidth, windowHeight;
        SDL_GetWindowSize(window, &windowWidth, &windowHeight);
        
        // Calculate scale to fit 1920x1080 into the current window size
        float scaleX = windowWidth / 1920.0f;
        float scaleY = windowHeight / 1080.0f;        
        float scale = scaleX < scaleY ? scaleX : scaleY;
        int viewportWidth = (int)(1920 * scale);
        int viewportHeight = (int)(1080 * scale);
        int viewportX = (windowWidth - viewportWidth) / 2;
        int viewportY = (windowHeight - viewportHeight) / 2;


        SDL_Event event;
        while (SDL_PollEvent(&event))
        {
            ImGui_ImplSDL2_ProcessEvent(&event);            
            if (event.type == SDL_QUIT || (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)))
                done = true;
        }

        // Start the Dear ImGui frame
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplSDL2_NewFrame();
        // ----
        io.DisplaySize = ImVec2(1920, 1080); // < ---- Force the ImGui display size to 1920x1080
        io.DisplayFramebufferScale = ImVec2(scale, scale);
    
        int mouseX, mouseY;
        SDL_GetMouseState(&mouseX, &mouseY);
        io.MousePos = ImVec2((mouseX - viewportX) / scale, (mouseY - viewportY) / scale);
        // ----
        ImGui::NewFrame();
        // ----
       
        ImVec2 center = ImGui::GetMainViewport()->GetCenter();
        ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
        ImGui::Begin("Window Scaling Debug",nullptr,ImGuiWindowFlags_AlwaysAutoResize);
        ImGui::Text("io.DisplaySize: %d x %d", (int)io.DisplaySize.x, (int)io.DisplaySize.y);
        ImGui::Text("Window size: %d x %d", windowWidth, windowHeight);
        ImGui::Text("Viewport size: %d x %d", viewportWidth, viewportHeight);
        ImGui::Text("Viewport position: %d x %d", viewportX, viewportY);
        ImGui::Text("Scale: %.2f", scale);
        ImGui::Text("Mouse: %d x %d", (int)io.MousePos.x, (int)io.MousePos.y);        
        ImGui::Text("SDL Mouse: %d x %d", mouseX, mouseY);        
        ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
        ImGui::Checkbox("Demo Window", &show_demo_window);
        ImGui::End();

        // make a window in each corner, marking out the 1920x1080 display space
        for (int i = 0; i < 4; i++)
        {
            ImGui::SetNextWindowPos(ImVec2(i % 2 ? 0 : 1920 - 320, i / 2 ? 0 : 1080 - 240));
            ImGui::SetNextWindowSize(ImVec2(320, 240));  
            char windowName[32];
            sprintf(windowName, "Window %d", i);          
            ImGui::Begin(windowName, nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
            ImGui::Text("Window %d", i);
            ImGui::End();            
        }
    
        if (show_demo_window) { ImGui::ShowDemoWindow(&show_demo_window); }

        ImGui::Render();
        glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
        glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
        SDL_GL_SwapWindow(window);
    }

    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplSDL2_Shutdown();
    ImGui::DestroyContext();
    SDL_GL_DeleteContext(gl_context);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}
@hippyau
Copy link
Author

hippyau commented May 16, 2024

Okay, I worked it out.... had to very slightly modify the backend

example-2024-05-16_16.49.55.mp4

Backend

Modify static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object):

  //GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height));  // <------  Commented out by me

Modify void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data, ImVec2 offset):
(note added ImVec2 'offset' argument passed from example)

 // Apply scissor/clipping rectangle (Y is inverted in OpenGL)                
 GL_CALL(glScissor((int)clip_min.x+offset.x, (int)((float)fb_height - clip_max.y+offset.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)));

Example

#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <SDL.h>
#include <SDL_opengl.h>

int main(int, char**)
{
    // Setup SDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0)
    {
        printf("Error: %s\n", SDL_GetError());
        return -1;
    }

    // Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
    // GL ES 2.0 + GLSL 100
    const char* glsl_version = "#version 100";
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#elif defined(__APPLE__)
    // GL 3.2 Core + GLSL 150
    const char* glsl_version = "#version 150";
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
#else
    // GL 3.0 + GLSL 130
    const char* glsl_version = "#version 130";
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#endif

    // Create window with graphics context
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
    SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
    SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1920, 1080, window_flags);
    if (window == nullptr)
    {
        printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError());
        return -1;
    }

    SDL_GLContext gl_context = SDL_GL_CreateContext(window);
    SDL_GL_MakeCurrent(window, gl_context);
    SDL_GL_SetSwapInterval(1); // Enable vsync

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls

    ImGui::StyleColorsDark();

    ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
    ImGui_ImplOpenGL3_Init(glsl_version);

    bool show_demo_window = false;    
    ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);

    // Main loop
    bool done = false;
    while (!done)
    {

        SDL_Event event;
        while (SDL_PollEvent(&event))
        {
            ImGui_ImplSDL2_ProcessEvent(&event);            
            if (event.type == SDL_QUIT || (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)))
                done = true;
        }
        // --- Start new backend frame
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplSDL2_NewFrame();

        // Calculations to fit logical display into the current window
        // while maintaining aspect ratio and centering the display
        constexpr float logicalDisplayX = 1920.0f;
        constexpr float logicalDisplayY = 1080.0f;

        int windowWidth, windowHeight;
        SDL_GetWindowSize(window, &windowWidth, &windowHeight);
        float scaleX = windowWidth / logicalDisplayX;
        float scaleY = windowHeight / logicalDisplayY;        
        float scale = scaleX < scaleY ? scaleX : scaleY;
        int viewportWidth = (int)(logicalDisplayX * scale);
        int viewportHeight = (int)(logicalDisplayY * scale);
        int viewportX = (windowWidth - viewportWidth) / 2;
        int viewportY = (windowHeight - viewportHeight) / 2;

        io.DisplaySize = ImVec2(logicalDisplayX, logicalDisplayY);
        io.DisplayFramebufferScale = ImVec2(scale, scale);

        // scale the mouse position
        int mouseX, mouseY;
        SDL_GetMouseState(&mouseX, &mouseY);        
        io.AddMousePosEvent((mouseX - viewportX) / scale, (mouseY - viewportY) / scale);
        // ---- Start the Dear ImGui frame
        ImGui::NewFrame();


        // make a window that represents the "desktop" space, must always be at the back
        ImGui::SetNextWindowPos(ImVec2(0, 0));
        ImGui::SetNextWindowSize(ImVec2(logicalDisplayX, logicalDisplayY));
        ImGui::PushStyleColor(ImGuiCol_WindowBg, IM_COL32(115, 140, 153, 255));
        ImGui::Begin("Desktop Space", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus);        
        ImGui::End();
        ImGui::PopStyleColor();
        
      
        ImVec2 center = ImGui::GetMainViewport()->GetCenter();
        ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
        ImGui::Begin("Window Scaling Debug",nullptr,ImGuiWindowFlags_AlwaysAutoResize);
        ImGui::Text("io.DisplaySize: %d x %d", (int)io.DisplaySize.x, (int)io.DisplaySize.y);
        ImGui::Text("Window size: %d x %d", windowWidth, windowHeight);
        ImGui::Text("Viewport size: %d x %d", viewportWidth, viewportHeight);
        ImGui::Text("Viewport position: %d x %d", viewportX, viewportY);
        ImGui::Text("Scale: %.2f", scale);
        ImGui::Text("Mouse: %d x %d", (int)io.MousePos.x, (int)io.MousePos.y);        
        ImGui::Text("SDL Mouse: %d x %d", mouseX, mouseY);        
        ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
        ImGui::Checkbox("Demo Window", &show_demo_window);
        ImGui::End();

        // make a window in each corner, marking out the 1920x1080 display space
        for (int i = 0; i < 4; i++)
        {
            ImGui::SetNextWindowPos(ImVec2(i % 2 ? 0 : logicalDisplayX - 320, i / 2 ? 0 : logicalDisplayY - 240));
            ImGui::SetNextWindowSize(ImVec2(320, 240));  
            char windowName[32];
            sprintf(windowName, "Window %d", i);          
            ImGui::Begin(windowName, nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
            ImGui::Text("Corner Window %d", i);
            ImGui::End();            
        }
    
        if (show_demo_window) { ImGui::ShowDemoWindow(&show_demo_window); }
        ImGui::Render();
        glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
        glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
        glClear(GL_COLOR_BUFFER_BIT);

        // modified back end to accept the view port offsets
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData(), ImVec2(viewportX, viewportY));
        SDL_GL_SwapWindow(window);
    }

    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplSDL2_Shutdown();
    ImGui::DestroyContext();
    SDL_GL_DeleteContext(gl_context);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

@ocornut
Copy link
Owner

ocornut commented May 17, 2024

  io.AddMousePosEvent((mouseX - viewportX) / scale, (mouseY - viewportY) / scale);

Adding a Mouse Pos Event may conflicts with the one added by the backend.

Ideally you would parse the event list g.InputEventsQueue[], by reading old Size, calling ImGui_ImplSDL2_ProcessEvent()+ImGui_ImplSDL2_NewFrame(), reading new Size, and modifying the events.

It has been a recurrent topic that we should eventually allow backend to "transform" inputs and also work within a subsection of the main host window. Some forms of DPI scaling (for Mac) may be facilitated by it. And also see stuff like #6942, #6714, #6064, #3972 (yours!), #3814, this is why those issues are kept open.

In addition it is possible that the ideal solution for #2176 will be that we offset all viewports and our coordinate system to ensure everything use positive coordinates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants