c++ GLFW & ImGui:从主线程以外的线程创建ImGui控件

vc6uscn9  于 2023-04-01  发布在  其他
关注(0)|答案(3)|浏览(470)

我正在使用GLFW和ImGui进行一个涉及打开多个窗口的项目。到目前为止,我已经设置了这个,以便每次必须打开一个新窗口时,我都会生成一个线程,该线程创建自己的GLFW窗口和OpenGL上下文。线程函数看起来像这样:

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);

我知道GLFW要求你从主线程(调用glfwInit()的那个线程)轮询事件,所以我在主线程上有一个循环来做这件事:

while (!appMustExit)
{
    glfwWaitEvents();
}
// appMustExit is set from another thread that waits for console input

所以我遇到的问题是,我的ImGui控件不响应任何类型的输入,如果我点击关闭按钮,glfwWindowShouldClose()永远不会返回true。似乎输入状态只在调用glfwPollEvents()的线程上可用,这让我相信你不能在使用单独的线程进行渲染的同时将ImGui和GLFW结合起来!

如何修复此问题以允许ImGui和这些窗口响应GLFW事件?

我以前尝试使用单个线程来迭代每个窗口并更新/呈现它,但我希望使用线程来帮助应用程序在打开许多窗口时更好地扩展。

**更新:**我想澄清一下,这个应用程序涉及到实时处理复杂的机器视觉,而ImGui代码部分与控制和响应这个机器视觉代码高度集成,因此我希望能够在处理这个过程的同一个线程上调用ImGui函数,这也意味着这个线程必须能够响应glfw输入。

bqujaahr

bqujaahr1#

我能够找到一种方法,将Dear ImGui更改为(希望如此)线程安全库,并自由使用thead_local说明符。
imconfig.h中,我必须创建一个新的线程本地ImGuiContext指针:

struct ImGuiContext;
extern thread_local ImGuiContext* threadCTX;
#define GImGui threadCTX

imgui_impl_glfw.cpp中,我必须将所有的局部/静态变量都更改为thread_local版本:

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;

同样,在imgui_impl_opengl3.h中,我对OpenGL对象句柄做了同样的操作:

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;

有了这几个变化,我现在可以创建一个GLFW窗口和OpenGL上下文,初始化Dear ImGui,并在每个线程上调用glfwPollEvents,而不会相互影响。本质上,每个创建GLFW窗口的线程都可以像“主”线程一样使用。
这个解决方案可能有一些挫折,但它似乎对我的用例工作得很好,其中每个窗口在自己的线程中运行其事件循环,有自己的OpenGL和ImGui上下文,并且窗口不相互交互或共享资源。

ylamdve6

ylamdve62#

为什么你首先要创建多个线程?你可以完美地创建多个GLFW窗口和多个Dear ImGui上下文,并在同一个线程中管理所有内容。从不同的线程工作只会使一切更加难以处理。
在Dear ImGui的特定情况下,您可以使用'对接'分支中的多视口功能,该功能原生地支持提取主视口之外的任何dear imgui窗口,并为您创建/管理GLFW窗口。所有这些都由单个dear imgui上下文处理,因此您可以从一个窗口拖放到另一个窗口。

13z8s7eq

13z8s7eq3#

正如你所发现的,thread_local使事情变得更快。它通过创建ImGuiContext* threadCTX的单独示例并将它们传递给执行多线程的各个线程来实现这一魔力。因此每个核心不必竞争访问公共资源否则将是单个ImGuiContext* threadCTX

线程争用共享资源会影响性能

ImGui代码部分与控制和响应此机器视觉代码高度集成。
正如你所提到的,ImGui试图多次跨多个线程访问单个资源;那么每个核心都会竞争(最新/正确)状态,因为它现在是共享资源。由于这是共享的,它可能不会被加载该高速缓存中,并且每次线程试图访问该示例时,它都会使缓存内存无效。(快速内存)可能是瓶颈。CPU正在浪费时间获取共享资源。
最好不要将你的代码与ImGui或任何其他共享示例集成在一起。话虽如此,我有一些建议......以下是一些你可以做的建议,但没有实现/测试。

建议?

这取决于您的实现。
1.如果可以使用atomics将线程用作标志来同步线程,则可以提高性能。例如,仅在epoch或完整训练循环完成后调用每个线程中的clear/read/write,同时保持特定状态。但是,您希望在线程之间共享的内容应该非常具体。不要在每个线程之间共享示例,也不要在每个线程的每次传递时调用它们。
并且窗口不相互交互或共享资源。
1.在主线程中创建一个 Package 器函数,该函数选择一个渲染上下文,该渲染上下文打开调用共享示例的ImGui循环,临时将glfw回调绑定到特定的渲染上下文。
当处理边缘情况时,这两个都是一个麻烦的IMHO。如果有人有更好的想法,我想他们的建议。
ML特定建议:
1.为了共享ML资源,明确哪些资源需要共享。在一个时期完成时,从每个线程进行写入文件并关闭文件,然后向每个线程分派第n个线程已经完成任务,或者让每个线程决定何时检查主线程上的共享/存储/静态状态(也许在这里实现互斥锁是个好主意)。这个调用无论如何都需要该高速缓存无效,因为它需要将新数据加载到检查点文件或从检查点文件加载新数据。

相关问题