I am using GLFW and ImGui for a project that involves opening multiple windows. So far I have set this up so that each time a new window must be opened I spawn a thread that creates its own GLFW window and OpenGL context. The thread function looks something like this:
window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
// Check for creation error...
glfwMakeContextCurrent(window);
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); // Is this supposed to be done per-thread?
// Calling specific impl-specific ImGui setup methods for GLFW & OpenGL3...
// Set up OpenGL stuff ...
while (!glfwWindowShouldClose(window))
{
// Some heavy-duty processing happens here...
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// ImGui code is here...
// Rendering some stuff in the window here...
// Render ImGui last...
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
}
// Calling impl-specific ImGui shutdown here...
glfwDestroyWindow(window);
I know that GLFW requires you to poll events from the main thread (the one that called glfwInit()
), so I have a loop on my main thread that does that:
while (!appMustExit)
{
glfwWaitEvents();
}
// appMustExit is set from another thread that waits for console input
So the issue I am having is that my ImGui controls don't respond to any sort of input and glfwWindowShouldClose()
never returns true if I click on the Close button. It seems like the input state is only available on the thread that calls glfwPollEvents()
, which leads me to believe that you can't combine ImGui & GLFW while still using a separate thread for rendering!
How can I fix this to allow ImGui & these windows to respond to GLFW events?
My previous attempt used a single thread to iterate over each window and update/render it, but I am hoping to use threads to help the application scale better with many windows open.
Update: I would like to clarify that this application involves processing complex machine vision in real-time, and the ImGui code section is heavily integrated with controlling and responding to this machine vision code. Therefore I would like to be able to call the ImGui functions on the same thread as this processing, which also means this thread must be able to respond to glfw input.
I was able to find a way to change Dear ImGui to a (hopefully) thread-safe library with liberal use of the thead_local
specifier.
In imconfig.h
I had to create a new thread-local ImGuiContext pointer:
struct ImGuiContext;
extern thread_local ImGuiContext* threadCTX;
#define GImGui threadCTX
In imgui_impl_glfw.cpp
I had to change all of the local/static variables into thread_local
versions:
thread_local GLFWwindow* g_Window = NULL; // Per-Thread Main window
thread_local GlfwClientApi g_ClientApi = GlfwClientApi_Unknown;
thread_local double g_Time = 0.0;
thread_local bool g_MouseJustPressed[5] = { false, false, false, false, false };
thread_local GLFWcursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = { 0 };
// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
static thread_local GLFWmousebuttonfun g_PrevUserCallbackMousebutton = NULL;
static thread_local GLFWscrollfun g_PrevUserCallbackScroll = NULL;
static thread_local GLFWkeyfun g_PrevUserCallbackKey = NULL;
static thread_local GLFWcharfun g_PrevUserCallbackChar = NULL;
Likewise, in imgui_impl_opengl3.h
I did the same for the OpenGL object handles:
static thread_local char g_GlslVersionString[32] = "";
static thread_local GLuint g_FontTexture = 0;
static thread_local GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0;
static thread_local int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location
static thread_local int g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location
static thread_local unsigned int g_VboHandle = 0, g_ElementsHandle = 0;
With these few changes, I am now able to create a GLFW window & OpenGL context, initialize Dear ImGui, and call glfwPollEvents
on each thread without them affecting each other at all. Essentially each thread that creates a GLFW window can be used as if it were the 'main' thread.
This solution probably has a few setbacks, but it appears to work fine for my use-case where each window runs its event loop in its own thread, has its own OpenGL and ImGui contexts, and the windows do not interact with each other or share resources.