imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Multi-viewports with SRGB framebuffer

Open hls333555 opened this issue 2 years ago • 22 comments

Version/Branch of Dear ImGui:

Version: 1.87 Branch: docking

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_opengl3.cpp + imgui_impl_glfw.cpp Compiler: MSVC2022 Operating System: XXX

My Issue/Question: I have enabled SRGB framebuffer by calling glEnable(GL_FRAMEBUFFER_SRGB); when creating the default window context. But when I drag an imgui window out of my main viewport, a new framebuffer seems to get created, and my SRGB image is gone.

  1. Is there a chance I can initialize each opengl context (by calling some GL functions somewhere) when a new viewport window is created?
  2. Also, I have some input bindings bound to the default window, so I want to bind those to newly created viewport windows. How can I achieve this? Thanks a lot!

hls333555 avatar Jun 15 '22 05:06 hls333555

When window is detached from main viewport, new window is created by ImGui_ImplGlfw_CreateWindow(). You probably need to enable sRGB after glfwCreateWindow() call.

rokups avatar Jun 15 '22 07:06 rokups

glEnable(GL_FRAMEBUFFER_SRGB); is a global setting already, which applies during blending, so you have full control over it.

If you have problem I imagine it may be that OpenGL requires adjustment when creating the framebuffer format, see: https://community.khronos.org/t/setting-up-srgb/75216

ocornut avatar Jun 15 '22 11:06 ocornut

glEnable(GL_FRAMEBUFFER_SRGB); is a global setting already, which applies during blending, so you have full control over it.

If you have problem I imagine it may be that OpenGL requires adjustment when creating the framebuffer format, see: https://community.khronos.org/t/setting-up-srgb/75216

Sadly, it's not true. I have enabled SRGB after main window creation. But this does not work for me. You can see the following gif, the whole color changes(including my checkerboard sphere and imgui window background) after dragging out of the main window. You can also notice that the imgui window rounding at the top corner disappears after that. 1

hls333555 avatar Jun 16 '22 05:06 hls333555

I have enabled SRGB after main window creation

The link I provided suggest you also need to call glfwWindowHint(GL_FRAMEBUFFER_SRGB_EXT, GL_TRUE); prior to window creation.

ocornut avatar Jun 17 '22 12:06 ocornut

I have enabled SRGB after main window creation

The link I provided suggest you also need to call glfwWindowHint(GL_FRAMEBUFFER_SRGB_EXT, GL_TRUE); prior to window creation.

Thanks. But I have already enabled that before my main window creation. I have also tried glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE); which does not work either...

hls333555 avatar Jun 17 '22 12:06 hls333555

Whatever you do on your main viewport needs to be done on secondary viewport, try to find out what's the difference.. You may need to redirect platform_io.Platform_CreateWindow() or platform_io.Renderer_CreateWindow() to your own function to add extra code before/after window creation and then call the previous platform/renderer handler.

ocornut avatar Jun 17 '22 12:06 ocornut

Whatever you do on your main viewport needs to be done on secondary viewport, try to find out what's the difference.. You may need to redirect platform_io.Platform_CreateWindow() or platform_io.Renderer_CreateWindow() to your own function to add extra code before/after window creation and then call the previous platform/renderer handler.

So I have to modify imgui_impl_opengl3.cpp and imgui_impl_glfw.cpp right? Or could you please provide some callbacks so that I could add logic in my own code because I actually do not want to modify imgui code...

hls333555 avatar Jun 17 '22 12:06 hls333555

So I have to modify imgui_impl_opengl3.cpp and imgui_impl_glfw.cpp right?

No.

Or could you please provide some callbacks

The callbacks are platform_io.Platform_CreateWindow and platform_io.Renderer_CreateWindow etc you can modify/redirect them. Remember old function, change callback to yours, and have your function call the old one.

ocornut avatar Jun 17 '22 12:06 ocornut

So I have to modify imgui_impl_opengl3.cpp and imgui_impl_glfw.cpp right?

No.

Or could you please provide some callbacks

The callbacks are platform_io.Platform_CreateWindow and platform_io.Renderer_CreateWindow etc you can modify/redirect them. Remember old function, change callback to yours, and have your function call the old one.

Oh I did not realize that! I will have a try! Thanks

hls333555 avatar Jun 17 '22 12:06 hls333555

So I have to modify imgui_impl_opengl3.cpp and imgui_impl_glfw.cpp right?

No.

Or could you please provide some callbacks

The callbacks are platform_io.Platform_CreateWindow and platform_io.Renderer_CreateWindow etc you can modify/redirect them. Remember old function, change callback to yours, and have your function call the old one.

After digging into ImGui_ImplGlfw_CreateWindow() in imgui_impl_glfw.cpp, I find that I cannot easily copy the whole function into my own code as there are many things defined static in that cpp file. I want to copy those instead of creating my own because I do not want to modify that implementation, in fact, I just want to add some extra OpenGL init code and some window callbacks into that... What should I do? Thanks a lot!

hls333555 avatar Jun 18 '22 05:06 hls333555

I just want to add some extra OpenGL init code and some window callbacks into that...

Which exactly?

ocornut avatar Jun 18 '22 07:06 ocornut

I just want to add some extra OpenGL init code and some window callbacks into that... Which exactly?

static void ImGui_ImplGlfw_CreateWindow(ImGuiViewport* viewport)
{
    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
    ImGui_ImplGlfw_ViewportData* vd = IM_NEW(ImGui_ImplGlfw_ViewportData)();
    viewport->PlatformUserData = vd;

    // GLFW 3.2 unfortunately always set focus on glfwCreateWindow() if GLFW_VISIBLE is set, regardless of GLFW_FOCUSED
    // With GLFW 3.3, the hint GLFW_FOCUS_ON_SHOW fixes this problem
    glfwWindowHint(GLFW_VISIBLE, false);
    glfwWindowHint(GLFW_FOCUSED, false);
#if GLFW_HAS_FOCUS_ON_SHOW
    glfwWindowHint(GLFW_FOCUS_ON_SHOW, false);
 #endif
    glfwWindowHint(GLFW_DECORATED, (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? false : true);
#if GLFW_HAS_WINDOW_TOPMOST
    glfwWindowHint(GLFW_FLOATING, (viewport->Flags & ImGuiViewportFlags_TopMost) ? true : false);
#endif
    GLFWwindow* share_window = (bd->ClientApi == GlfwClientApi_OpenGL) ? bd->Window : NULL;
    vd->Window = glfwCreateWindow((int)viewport->Size.x, (int)viewport->Size.y, "No Title Yet", NULL, share_window);
    vd->WindowOwned = true;
    viewport->PlatformHandle = (void*)vd->Window;
#ifdef _WIN32
    viewport->PlatformHandleRaw = glfwGetWin32Window(vd->Window);
#endif
    glfwSetWindowPos(vd->Window, (int)viewport->Pos.x, (int)viewport->Pos.y);

    ///////////////////////////////////////////////////////////////////
    /// glEnable(GL_FRAMEBUFFER_SRGB);
    /// And bind my own glfw callbacks here
    //////////////////////////////////////////////////////////////////

    // Install GLFW callbacks for secondary viewports
    glfwSetWindowFocusCallback(vd->Window, ImGui_ImplGlfw_WindowFocusCallback);
    glfwSetCursorEnterCallback(vd->Window, ImGui_ImplGlfw_CursorEnterCallback);
    glfwSetCursorPosCallback(vd->Window, ImGui_ImplGlfw_CursorPosCallback);
    glfwSetMouseButtonCallback(vd->Window, ImGui_ImplGlfw_MouseButtonCallback);
    glfwSetScrollCallback(vd->Window, ImGui_ImplGlfw_ScrollCallback);
    glfwSetKeyCallback(vd->Window, ImGui_ImplGlfw_KeyCallback);
    glfwSetCharCallback(vd->Window, ImGui_ImplGlfw_CharCallback);
    glfwSetWindowCloseCallback(vd->Window, ImGui_ImplGlfw_WindowCloseCallback);
    glfwSetWindowPosCallback(vd->Window, ImGui_ImplGlfw_WindowPosCallback);
    glfwSetWindowSizeCallback(vd->Window, ImGui_ImplGlfw_WindowSizeCallback);
    if (bd->ClientApi == GlfwClientApi_OpenGL)
    {
        glfwMakeContextCurrent(vd->Window);
        glfwSwapInterval(0);
    }
}

I just want to keep the original CreateWindow implementation and insert my own code (see the above commented lines) into it. But when I copy the content, some symbols are defined in imgui_impl_glfw.cpp file which I cannot reference in my own code.

hls333555 avatar Jun 18 '22 07:06 hls333555

Omar isn't saying you should clone ImGui_ImplGlfw_CreateWindow entirely. You should add your own callback which chains to it. For example:

static void (*OldCreateWindow)(ImGuiViewport*);

static void Hls333555CreateWindowShim(ImGuiViewport* viewport)
{
    OldCreateWindow(viewport);

    GLFWwindow* window = (GLFWwindow*)viewport->PlatformHandle;

    // Your manipulations of the GLFW window go here.
}

int main(int, char**)
{
    // ... skipped most GLFW and Dear ImGui initialization ...

    // Setup Platform/Renderer backends
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init(glsl_version);

    ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
    OldCreateWindow = platform_io.Platform_CreateWindow;
    platform_io.Platform_CreateWindow = &Hls333555CreateWindowShim;

    // ... skipped everything else ...
}

If you need to replace callbacks that the GLFW backend is already using, you should chain them as well. (All glfwSet***Callback functions return the previous callback if there was one. The GLFW backend uses this to chain to existing user callbacks, look for references to PrevUserCallbackWindowFocus in imgui_impl_glfw.cpp if you for an example.)

PathogenDavid avatar Jun 18 '22 14:06 PathogenDavid

Omar isn't saying you should clone ImGui_ImplGlfw_CreateWindow entirely. You should add your own callback which chains to it. For example:

static void (*OldCreateWindow)(ImGuiViewport*);

static void Hls333555CreateWindowShim(ImGuiViewport* viewport)
{
    OldCreateWindow(viewport);

    GLFWwindow* window = (GLFWwindow*)viewport->PlatformHandle;

    // Your manipulations of the GLFW window go here.
}

int main(int, char**)
{
    // ... skipped most GLFW and Dear ImGui initialization ...

    // Setup Platform/Renderer backends
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init(glsl_version);

    ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
    OldCreateWindow = platform_io.Platform_CreateWindow;
    platform_io.Platform_CreateWindow = &Hls333555CreateWindowShim;

    // ... skipped everything else ...
}

If you need to replace callbacks that the GLFW backend is already using, you should chain them as well. (All glfwSet***Callback functions return the previous callback if there was one. The GLFW backend uses this to chain to existing user callbacks, look for references to PrevUserCallbackWindowFocus in imgui_impl_glfw.cpp if you for an example.)

Thansk for you reply! It's very helpful for me! However, I have found the last two glfw callbacks which cannot be chained and those callbacks are cpp-static:

glfwSetWindowCloseCallback(vd->Window, ImGui_ImplGlfw_WindowCloseCallback);
glfwSetWindowSizeCallback(vd->Window, ImGui_ImplGlfw_WindowSizeCallback);

How to solve this, thanks a lot!

hls333555 avatar Jun 19 '22 10:06 hls333555

First of all, it is unclear why you would need to chain those callbacks and how they have anything to do with srgb.

Secondly, if you actually needed to chain those (which I doubt so) you can call the glfw function to retrieve the current function pointer.

I suspect you misunderstood David’s answer, all you need are the few lines in their post: https://github.com/ocornut/imgui/issues/5397#issuecomment-1159477757

ocornut avatar Jun 19 '22 10:06 ocornut

First of all, it is unclear why you would need to chain those callbacks and how they have anything to do with srgb.

Secondly, if you actually needed to chain those (which I doubt so) you can call the glfw function to retrieve the current function pointer.

I suspect you misunderstood David’s answer, all you need are the few lines in their post: #5397 (comment)

OK, I may misunderstood what this mean:

All glfwSet***Callback functions return the previous callback if there was one.

I'll try it again.

hls333555 avatar Jun 19 '22 10:06 hls333555

Please read my previous message again. You should not read those field nor call static functions. You can obtain the callback value via the glfw set functions.

ocornut avatar Jun 19 '22 11:06 ocornut

Please read my previous message again. You should not read those field nor call static functions. You can obtain the callback value via the glfw set functions.

That works perfectly now! Thanks. However, there is some other issues related to multi-viewport:

  1. The window rounding will disappear when a new viewport window is created(you can see this in my previous gif, note the upper corner has a rounding size of 8). 1

  2. The mouse cursor cannot be disabled explicitly in the created viewport window (I want this feature as I will manipulate camera in that viewport window and cursor should be disabled in that situation): I see the code in imgui_impl_glfw.cpp that will force set input mode every frame for every viewport and I find that it will only return if main viewport's cursor is disabled but not the other viewports':

static void ImGui_ImplGlfw_UpdateMouseCursor()
{
    ImGuiIO& io = ImGui::GetIO();
    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
    if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(bd->Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED)
        return;

    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
    ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
    for (int n = 0; n < platform_io.Viewports.Size; n++)
    {
        GLFWwindow* window = (GLFWwindow*)platform_io.Viewports[n]->PlatformHandle;
        if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)
        {
            // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
            glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
        }
        else
        {
            // Show OS mouse cursor
            // FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here.
            glfwSetCursor(window, bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]);
            glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
        }
    }
}

I can fix this by adding if (glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) continue; into the for loop as a workaround.

hls333555 avatar Jun 20 '22 05:06 hls333555

The window rounding will disappear when a new viewport window is created

That's expected and is why the examples disable window rounding when viewports are enabled.

The mouse cursor cannot be disabled explicitly in the created viewport window

It'd probably be easier to disable the mouse cursor via ImGui::SetMouseCursor(ImGuiMouseCursor_None); so you don't have to fight it.

PathogenDavid avatar Jun 20 '22 12:06 PathogenDavid

The window rounding will disappear when a new viewport window is created

That's expected and is why the examples disable window rounding when viewports are enabled.

So this is impossible to have both rounding enabled right?

The mouse cursor cannot be disabled explicitly in the created viewport window

It'd probably be easier to disable the mouse cursor via ImGui::SetMouseCursor(ImGuiMouseCursor_None); so you don't have to fight it.

This method indeed hide the cursor, but it does not achieve the glfw way of "locking" the cursor to its position. And I in fact need to "disable" the mouse cursor. The glfw doc says:

If you wish to implement mouse motion based camera controls or other input schemes that require unlimited mouse movement, set the cursor mode to GLFW_CURSOR_DISABLED.

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

This will hide the cursor and lock it to the specified window. GLFW will then take care of all the details of cursor re-centering and offset calculation and providing the application with a virtual cursor position. This virtual position is provided normally via both the cursor position callback and through polling.

hls333555 avatar Jun 21 '22 04:06 hls333555

So this is impossible to have both rounding enabled right?

Correct, supporting window rounding on every platform for every backend is non-trivial.

This method indeed hide the cursor, but it does not achieve the glfw way of "locking" the cursor to its position.

Ah, sorry. I missed the distinction between GLFW_CURSOR_DISABLED and GLFW_CURSOR_HIDDEN.

In that case you should just set the ImGuiConfigFlags_NoMouseCursorChange flag in ImGuiIO::ConfigFlags when you have the cursor locked.

PathogenDavid avatar Jun 23 '22 05:06 PathogenDavid

This method indeed hide the cursor, but it does not achieve the glfw way of "locking" the cursor to its position.

Ah, sorry. I missed the distinction between GLFW_CURSOR_DISABLED and GLFW_CURSOR_HIDDEN.

In that case you should just set the ImGuiConfigFlags_NoMouseCursorChange flag in ImGuiIO::ConfigFlags when you have the cursor locked.

This solution works quite nice! Thanks!

hls333555 avatar Jun 23 '22 06:06 hls333555