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

No redraw during OS window resize in demo #3672

Closed
Flamaros opened this issue Dec 27, 2020 · 9 comments
Closed

No redraw during OS window resize in demo #3672

Flamaros opened this issue Dec 27, 2020 · 9 comments
Labels

Comments

@Flamaros
Copy link

Version: 1.79

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_glfw.cpp + imgui_impl_opengl3.cpp
Compiler: Visual Studio 2019
Operating System: Windows 10

When resizing the OS window there is no redraw in the demo applications, I think that it will be nice if this case was handled because I saw this issue in some other applications made using Dear ImGUI like tracy profiler or ImHex.

As end user when I see an application that stop rendering during the window resize I fell like if there is a performance issue here.

@Folling
Copy link

Folling commented Dec 27, 2020

That has nothing to do with Dear ImGui and fully depends on your backend.
It's present in SDL and GLFW, but not if you use Qt for example.

Note:
There is a branch (which might already have been merged) for GLFW which "fixes" it for windows.

@ocornut
Copy link
Owner

ocornut commented Apr 7, 2021

As answered this is generally the "default" behavior under Windows as resizing via OS borders/decoration puts Windows into a modal/blocking loop, so it's not really specific to Dear ImGui. Any GLFW+GL application will give you same result by default.

(Note that in multi-viewports mode resizing Dear ImGui windows via the Dear ImGui borders/decorations doesn't exhibit the problem, since it doesn't go through Windows modal/blocking loop).

There are variety of solutions available elsewhere, at low-level I think generally it seems to involve reacting on WM_TIMER, e.g.
https://stackoverflow.com/questions/28406919/redraw-window-using-directx-during-move-resize
Please note the linked discussion is not specific to DirectX11, but it is also best to do a wider search and not rely on this single link

At GLFW level it seems like you are supposed to call glfwSetWindowRefreshCallback() and in your callback you can perform a render + swap:
glfw/glfw#408
https://stackoverflow.com/questions/45880238/how-to-draw-while-resizing-glfw-window

It's a little outside the scope of our examples to do work (ideally we would do but in practice what we would do in the examples application may not match what you would/can do in your real app).

@ocornut
Copy link
Owner

ocornut commented Jan 24, 2024

Since this is frequently being asked practically everywhere (a little bit here but also much more in SDL/GLFW/Gamedev communities), I would ideally like us to make our examples apps demonstrate the dynamic resizing.
But I am pretty sure this would requite the example main loop to be refactored into standalone function, and last time I've tried doing that (for emscripten purpose) I backtracked since it made experimentation and example reading much less convenient (suddenly have to move variables to another place etc.). That, AND, getting this right everywhere for every use case is complicated. See for example #6374 some drivers being not happy with constant resizes from inside the modal loop, presumably because they hook on something to cleanup (but likely that was because lack of SwapBuffer).

I think step 1 is to do it for major examples (at least 1 example for each of Win32, SDL and GLFW) and then either we decide this is mergeable, either we write a definitive wiki page with instructions of how to change your app to support this.

slouken commented Dec 6, 2023
This is fixed for the SDL 2.30 release, in 509c70c
libsdl-org/SDL#1059 (comment)

Extra references:

@Folling
Copy link

Folling commented Jan 24, 2024

There are other ways to achieve this at least on X11 and Wayland (not sure about Windows), but are even less trivial. I guess a macro that captures the function defined by the user and assigns it to some function object could be a compromise between readability while still yielding a reusable function:

#define IMGUI_MAIN_FUNC(...) imgui_main_func /* some global variable for a function object */ = [&]() { __VA_ARGS__ };

// ...

int main() {
   setup();
   ImGuiIO& io = ImGui::GetIO();
   static float f = 0.0f;
   static int counter = 0;

   while (!windowShouldClose()) {
       preFrame();
       IMGUI_MAIN_FUNC(
            ImGui::Begin("Hello, world!");                          // Create a window called "Hello, world!" and append into it.

            ImGui::Text("This is some useful text.");               // Display some text (you can use a format strings too)

            ImGui::SliderFloat("float", &f, 0.0f, 1.0f);            // Edit 1 float using a slider from 0.0f to 1.0f
            ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color

            if (ImGui::Button("Button"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
                counter++;
            ImGui::SameLine();
            ImGui::Text("counter = %d", counter);

            ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
            ImGui::End();
       )
       postFrame();
   }
}

At least for examples. Afterall this would interfere with C interoperability if the function needs to capture state. But this gives you a function object that could be used in whatever dynamic resizing mechanism you use while keeping the code and state local to the main function.

@ocornut
Copy link
Owner

ocornut commented Jan 24, 2024

Hi @Folling long time no see :)

That's the trick we use for our Emscripten wrapper:
https://github.com/ocornut/imgui/blob/master/examples/libs/emscripten/emscripten_mainloop_stub.h
But it is intently used only under #ifdef EMSCRIPTEN blocks.

Basically I think the core problem is actually too complex to solve perfectly-for-every-use-case and simply in a way that we would dare adding them to our examples, but I think we should nevertheless gather the info and provide it in a centralized place, so at least people know can know what to copy if they want it, and can report improvements.

(PS: Updated my comment above with many links.)

@Folling
Copy link

Folling commented Jan 24, 2024

Indeed it has been :)

FWIW: The alternative approach on X11 that I used a while back involves multithreading and conditional variables where you have one thread listen to events and notify the main thread that a new render is required whenever an adequate event is posted by the X window manager.
Again, I think this is even more cumbersome than asking people to provide a separate function, but it does work and does allow you to keep the main function local, with the exception of something akin to my_cond_var.wait(lock, []() { return should_render; }); sitting atop it.
To achieve the immediateness I used a manual timer unlocking lock every 1/framerate seconds in yet another thread.

@ocornut
Copy link
Owner

ocornut commented Jan 24, 2024

If we provide a tutorial for implementing this, I have zero issue making the tutorial use a separate function for the frame. Question was only about providing this in our example, which I think may be too complicated for other reasons

@sschoener
Copy link

For anyone else that is stumbling into this issue with this problem and was hoping for a quick fix, I'll leave these notes on what I did. (It was not a quick fix.) I am using pure Win32, not GLFW. I don't have a fully updated example unfortunately because my use-case is only roughly inspired by the example.

One solution to this problem is to decouple your rendering from the windows message pump. Here is the general roadmap for this approach:

  • Stop using your message pump thread for your rendering/application logic.
  • Replace the PeekMessage loop in the message pump with a GetMessage, which will now block that main thread instead of letting it spin.
  • Create a new thread for your rendering/application logic and a message queue of your choosing so you can communicate between the message pump thread and your application logic thread.
  • Within ImGui_ImplWin32_WndProcHandler from the DearImGui Win32 backend, go through all places that touch the DearImGui I/O struct or any of the fields in the backend that are touched by both that WndProc and the backend's ImGui_ImplWin32_NewFrame function. These two functions may run concurrently, so those places need to be fixed up to instead emit a message for your message queue instead of a direct update.
    • I ended up duplicating the MouseTrackedArea field because it is read from both threads.
    • The WM_SETCURSOR message is the most problematic because it reads from the I/O struct. This is a data race. My choice was to ignore it, which so far has been fine. Naively I thought it would be enough to set the cursor in the next NewFrame call but apparently this message comes in very frequently and overrides whatever you set in NewFrame. Not setting a cursor here breaks cursor handling in general.
  • Also ensure that your top-level WndProc does not directly react to WM_SIZE events by resizing the swap chain. That will lead to deadlocks, since DX12 calls may sometimes call SendMessage. Instead, put another message into your message queue to handle resizing in your application logic thread.

I've put the moral equivalent of my changes to the backend implementation file here. Note that the std::vector<DearImGuiIOEvent> is only for illustrative purposes; you should use some SPSC queue for actually communicating between your threads. I have tested this solution for a few days and have not yet found the subtle issues I must surely have introduced.

If you are using SDL, there is also a solution available now (at least for Windows), as documented in this GitHub issue conversation.

@MirrasHue
Copy link

What worked for me in GLFW is only having glfwPollEvents() on the main thread, and the update and rendering in another thread (without polling events). The drawback is that multiple viewports don't work (they don't receive events, unless I add another glfwPollEvents(), but this breaks other things, also not recommended by GLFW). I think it's worth the compromise of not using viewports in my case, I can still use the docking features, and it is so beautiful to see the window being redrawn while resizing hahah.

I hope this will work in the long run, or at least until the PR @ocornut mentioned for GLFW is merged (it's been more than 4 years, yikes)

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

No branches or pull requests

5 participants