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

Multithreading. ImGui lags behind #6597

Closed
IceLuna opened this issue Jul 11, 2023 · 10 comments
Closed

Multithreading. ImGui lags behind #6597

IceLuna opened this issue Jul 11, 2023 · 10 comments

Comments

@IceLuna
Copy link

IceLuna commented Jul 11, 2023

Version/Branch of Dear ImGui:

Version: 1.89.4 WIP
Branch: docking

My Issue/Question:

Hi! I have a render thread that does all the rendering and my problem is that ImGui UI lags behind.

[22:53:39.481] [Main Thread] ImGui Begin Frame #726
[22:53:39.484] [Render Thread] ImGui RenderDrawData Frame #724

These frame numbers are just static local variables that increment each time it prints. So as you can see begin frame was called 726 times, and Render draw data - 724 times. I think that might be the problem.
If I wait for the render thread to finish before calling BeginFrame, everything seems fine

So, does the ImGui render previous frames that weren't rendered yet? If so, is there a way to make it render UI using the data from the most recent frame (the data that was collected during ImGui Begin Frame #726)?

I couldn't find any information about that and digging through the ImGui code didn't help yet.
Thanks!

Screenshots/Video
Here's the code that each thread executes for ImGui
image

@GamingMinds-DanielC
Copy link
Contributor

GamingMinds-DanielC commented Jul 12, 2023

ImGui doesn't lag behind in the way you think. Frames don't get queued up for rendering. If you loop through the main thread twice before getting a lock in the render thread, a frame gets skipped. If you loop through the render thread twice before getting a lock in the main thread, a frame is rendered twice.

ImGui just doesn't handle multithreading at all, proper synchronization would be your responsibility. You could do that f.e. with two semaphores. The main thread acquires a semaphore M with an initial count of 1, does its thing and releases a semaphore R with an initial count of 0. The render thread acquires R and once it is done with the render data releases M. You can accomplish the same with condition variables or waitable events, that's entirely up to you. But all of that is basically just serialized processing split into two threads.

@IceLuna
Copy link
Author

IceLuna commented Jul 12, 2023

Thanks for the clarification.

I thought one mutex is enough, since at any give point in time ImGui is either being rendered to the screen on RT, or being updated on MT (by ImGui calls)

@ocornut
Copy link
Owner

ocornut commented Jul 12, 2023

I believe none of this discussion is related to what Dear ImGui is doing or not doing. We provide you with ImDrawData. Whereas you render it immediately or later, and whether your graphic engine use a queue or any other mechanism and not really within the scope of Dear ImGui. Every example we provide calls a backend's RenderDrawData for every time ImGui::NewFrame() is called.

I'm closing as this doesn't have to do with Dear ImGui, but feel free to discuss this here if needed.

@ocornut
Copy link
Owner

ocornut commented Jul 12, 2023

Note that as the buffers returned by ImGui::GetDrawData() are owned by Dear ImGui, if you want to use them AFTER the next call to NewFrame() (e.g. for async rendering) you need to be copying/swapping them.

While we provide a ImDrawList::CloneOutput() at the ImDrawList level, it may make sense that we add an extra function to facilitate an efficient swapping instead of expecting people to do this themselves, as people are more likely to do a copy rather than a swap and waste resources on this. Right now one niggle is ImDrawData contains ImDrawList** CmdLists which is a pointer to internal storage, so the structure is not self contained and therefore ambiguous to swap (and easier to deep copy).

I'm going to work on that.

@IceLuna
Copy link
Author

IceLuna commented Jul 12, 2023

Ok, thanks for the help!

ocornut added a commit that referenced this issue Jul 12, 2023
…DrawData itself. Faclitate user-manipulation of the array (#6406, #4879, #1878) + deep swap. (#6597, #6475, #6167, #5776, #5109, #4763, #3515, #1860)

+ Metrics: avoid misleadingly iterating all layers of DrawDataBuilder as everything is flattened into Layers[0] at this point.

# Conflicts:
#	imgui.cpp
#	imgui_internal.h
@ocornut
Copy link
Owner

ocornut commented Jul 12, 2023

I pushed some changes (c649aca + 1a9ddd2 + dbeeeae) outlined here which should make it easier to perform a deep-swap (instead of a deep-copy) of a ImDrawData is desired for multi-threaded rendering:
#6406 (comment)

@1623275623
Copy link

@IceLuna how you implement multithread in imgui. i met many problems when i do this. if so,please show me your code.

@sakiodre
Copy link
Contributor

@1623275623 if you only needed to use ImDrawList from another thread, check out this solution

@IceLuna
Copy link
Author

IceLuna commented Jul 15, 2023

@1623275623 Basically, you need one mutex that will prevent running ImGui code concurrently (as shown on the attachment).
Main Thread does all the calls as usual except for the actual rendering (call to RenderDrawData).
Render Thread waits for the mutex to be available, locks it and just calls RenderDrawData.
The code is available in my repo github.com/iceluna/eagle/tree/v0.6 (v0.6 branch)
The relevant files:

  1. Eagle/src/Eagle/Renderer/RenderManager.cpp (EndFrame function)
  2. Eagle/src/Eagle/Core/Application.cpp (Run function)
  3. Eagle/src/Platform/Vulkan/VulkanImGuiLayer.cpp
  4. Eagle-editor/src/EditorLayer.cpp. EditorLayer::OnImGuiRender() calls ImGui functions

I'm not sure if it'll work if graphics API doesn't support multithreading.

@sakiodre I don't need something like that just to render ImGui in RT. At least if Vulkan is used :)

ocornut added a commit that referenced this issue Aug 6, 2023
@ocornut
Copy link
Owner

ocornut commented Feb 5, 2024

I have posted a ImDrawDataSnapshot class which allows efficiently taking a snapshot without full copy and with amortized allocations, you can find it there: (feedback welcome in that other thread)
#1860 (comment)

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

5 participants