Skip to content

How to multithreading? #8550

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

Open
MennoVink opened this issue Apr 5, 2025 · 6 comments
Open

How to multithreading? #8550

MennoVink opened this issue Apr 5, 2025 · 6 comments

Comments

@MennoVink
Copy link

Version/Branch of Dear ImGui:

docking

Back-ends:

imgui_impl_osx.cpp + imgui_impl_vulkan.cpp

Compiler, OS:

appleclang

Full config/build information:

No response

Details:

Where can i find information on how multithreading is expected to work?

I just updated to the latest docking branch, and am confused as to what the new way to do multithreading is now. The macos backend was changed so that applications no longer provide inputs, instead the backend does this on it's own. However, i dont see a way to make it exclusively acquire the context, or activate one for that matter.

imgui.cpp hints that the GImGui member should be thread_local, so i've done that.
On thread 2 (control) i call ImGui::CreateContext(), and then initialize vulkan resources+fonts off of any realtime threads. I need the context later on thread 3 (render/process) so i store the result.
On thread 3 (render) i call ImGui::SetCurrentContext and then ImGui::NewFrame etc.
On thread 1 (main/ui) the ImGuiObserver::onApplicationBecomeActive is called, it calls ImGui::GetIO, but there's no context active so it asserts and reads out of bounds.

Am i supposed to intercept os events and activate the context prior to dispatch it through NSApp sendEvent? If so, then how do i know which context to activate. I might have multiple ones active for multiple windows.

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

// Here's some code anyone can copy and paste to reproduce your issue
ImGui::Begin("Example Bug");
MoreCodeToExplainMyIssue();
ImGui::End();
@MennoVink MennoVink changed the title How do multithreading? How to multithreading? Apr 6, 2025
@ocornut
Copy link
Owner

ocornut commented Apr 6, 2025

I just updated to the latest docking branch, and am confused as to what the new way to do multithreading is now

In general terms (main lib) nothing ever changed. Dear ImGui function may not be run in parallel for the same context.

The macos backend was changed so that applications no longer provide inputs, instead the backend does this on it's own. However, i dont see a way to make it exclusively acquire the context, or activate one for that matter.

Specifically about the OSX backend: I personally don't know/understand how it works. So I encourage you to use git blame to find trace of why things were changed, and we can advise if there an improvement to make.

imgui.cpp hints that the GImGui member should be thread_local, so i've done that.

It should be thread_local if you want to create multiple contexts and access them from different thread (with 1 context per thread) but this doesn't seem like what you are describing below. Do you have multiple contexts? If you don't the regular global variable is enough.

As per your setup the correct answer depends on details you haven't provided. What do want to to do in each and at which point do they overlap aka call ImGui code for the same context simultaneously? It's hard to understand what your "Render context" mean if it calls NewFrame(), so we need to know what you want to do.

For the render thread if you need to render output for frame N while the context is already processing a subsequent frame, you may want to extract the ImDrawData as in #8532 but again I am not sure if that's part of your problem or not.

@MennoVink
Copy link
Author

Thank you for your response. I'll provide some more details which hopefully clears it up a bit.

I am not trying to share contexts for parallel usage on multiple threads. Instead, i'm trying to do as suggested, only access a context from a single thread. This is impossible with the macos backend because it contains a ImGuiObserver that is called from the event pump running on the main thread. The implementation inside ImGuiObserver accesses GImGui directly.

This changes things from Use one context per thread to You can only have a single context and everything must happen on the main thread. This is because at any point the main thread may require access to the context, so at no point another thread may have access to it.

Imagine this:

  1. The main thread is doing nothing other than the event runloop.
  2. Some secondary thread creates a context for itself intended to only be used on that thread.
  3. Some tertiary thread creates another context just for itself. Again this context is just for that thread.

This setup requires GImGui to be thread_local. Once i change that, no context was ever active on the main thread. The instance of GImGui is still null on the main thread. When the ImGuiObserver::onApplicationBecomeActive is executed, this runs on the main thread. There is no context there, so it crashes.

I have found this commit e66fc22 removed ImGui_ImplOSX_HandleEvent. Previously i was able to provide events manually while providing the NSView. Now how do i do this if the backend's ImGuiObserver handles events and just assumes there's a thread_local instance being set on the main thread?

My endgoal is to be able to run UI on all (lets say three) connected displays. These displays may have varying display resolutions as well as framebuffer scales. My current approach is to have one window per display and one ImGuiContext per window. I can then use varying io.DisplaySize and io.DisplayFramebufferScale per context/window/display as well as have each ui render in parallel to other UIs running in other windows with other contexts.

@ocornut
Copy link
Owner

ocornut commented Apr 7, 2025

This setup requires GImGui to be thread_local. Once i change that, no context was ever active on the main thread. The instance of GImGui is still null on the main thread. When the ImGuiObserver::onApplicationBecomeActive is executed, this runs on the main thread. There is no context there, so it crashes.
I have found this commit e66fc22 removed ImGui_ImplOSX_HandleEvent. Previously i was able to provide events manually while providing the NSView. Now how do i do this if the backend's ImGuiObserver handles events and just assumes there's a thread_local instance being set on the main thread?

We would need to investigate why I made this change (somehow the decision appears to be buried in #4821) and decide on another change to the OSX backend that allows your situation to work while fullfilling whatever reason this change was initially aimed at.
I don't know OSX enough to be able to design that.

My endgoal is to be able to run UI on all (lets say three) connected displays. These displays may have varying display resolutions as well as framebuffer scales.

You would be better off using a single context and multi-viewports. You'll probably need a few hacks for framebufferscale support (we have dozens of related open issues until this hopefully is solved in 1.92.x).

@MennoVink
Copy link
Author

I'm going back to a single (non thread_local) context on a single thread. Should i be able to call ImGui::NewFrame + ImGui::Render on one other thread still or how would i make use of this context on a single other thread?
Calling ImGui::Render ends up in the platform_io.Platform_SetImeDataFn lambda. This calls NSTextInputContext::activate. The documentation reads You should not call this method directly; it is invoked by the system. My application crashes (ui functions on macos must orignate from main thread) with this message:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[HIRunLoopSemaphore wait:] has been invoked on a secondary thread; the problem is likely in a much shallower frame in the backtrace'

@WenchaoHuang
Copy link

A naive approach to handling multithreading might be: ensure all ImGui method calls are wrapped within a std::lock_guardstd::mutex scope.

@ocornut
Copy link
Owner

ocornut commented Apr 10, 2025

Why going back? Could you investigate fixing the initial issue by reintroducing the event handler in backend?

Please open separate issues for the problem so we can track and fix separately.

I wasn’t aware of this IME thing. Does the language somehow magically allows queuing something for the main thread?

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

No branches or pull requests

3 participants