GLFW & ImGui: Создание элементов управления ImGui из потока, отличного от основного

Я использую 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() никогда не возвращает истину, если я нажимаю кнопку Закрыть. Кажется, что состояние ввода доступно только в потоке, который вызывает glfwPollEvents(), что наводит меня на мысль, что вы не можете объединить ImGui & GLFW, все еще используя отдельный поток для рендеринга!

Как я могу исправить это, чтобы ImGui и эти окна могли реагировать на события GLFW?

Моя предыдущая попытка использовала один поток для итерации каждого окна и его обновления / рендеринга, но я надеюсь использовать потоки, чтобы помочь приложению лучше масштабироваться при многих открытых окнах.

Обновление: я хотел бы уточнить, что это приложение включает в себя обработку сложного машинного зрения в режиме реального времени, а раздел кода ImGui тесно интегрирован с управлением и реагированием на этот код машинного зрения. Поэтому я хотел бы иметь возможность вызывать функции ImGui в том же потоке, что и эта обработка, что также означает, что этот поток должен быть в состоянии отвечать на ввод glfw.

3 ответа

Решение

Мне удалось найти способ изменить 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, и окна не взаимодействуют друг с другом и не делятся друг с другом Ресурсы.

Как вы обнаружили,делает все быстрее. Он творит эту магию, создавая отдельные экземпляры и передавая их различным потокам, выполняющим многопоточность. Таким образом, каждому ядру не приходится конкурировать за доступ к общему ресурсу , который в противном случае был бы единым..

Потоки, конкурирующие за общий ресурс, вредны для производительности.

Раздел кода ImGui тесно интегрирован с контролем и реагированием на этот код машинного зрения.

Как вы упомянули, ImGui пытается получить доступ к одному ресурсу в нескольких потоках много раз; тогда каждое ядро ​​будет соревноваться за получение (последнего/правильного) состояния из основной памяти, поскольку теперь это общий ресурс. Поскольку это общий доступ, он, вероятно, не будет загружен в кеш, и каждый раз, когда поток пытается получить доступ к экземпляру, он аннулирует кэшированную память. Эта конкуренция между потоками и постоянное аннулирование кэшированной памяти (быстрой памяти), вероятно, является узким местом. ЦП тратит время на получение общего ресурса.

Лучше не интегрировать свой код с ImGui или любым другим общим экземпляром. При этом у меня есть несколько предложений... Ниже приведены некоторые предложения, которые вы могли бы сделать, но они не реализованы/не протестированы.

Предложения?

Зависит от вашей реализации.

  1. Если вы можете использоватьсинхронизация потоков с использованием их в качестве флагов может повысить производительность. Например, вызывайте операции очистки/чтения/записи в каждом потоке только после завершения эпохи или полного цикла обучения, сохраняя при этом определенное состояние. Однако то, что именно вы собираетесь разделить между потоками, должно быть очень конкретным. Не делитесь экземплярами и не вызывайте их при каждом проходе каждого потока.

и окна не взаимодействуют друг с другом и не используют общие ресурсы.

  1. Создайте функцию-оболочку в основном потоке, которая выбирает контекст рендеринга, который включает цикл ImGui, который вызывает общий экземпляр, временно привязывая обратные вызовы glfw к конкретному контексту рендеринга.

ИМХО, когда имеешь дело с крайними случаями, оба из них доставляют хлопоты. Если у кого-то есть идеи получше, я хотел бы услышать их предложения.

Рекомендации по машинному обучению:

  1. Для совместного использования ресурсов ML укажите, какие ресурсы необходимо использовать совместно. (Желательно только файлы контрольных точек для состояния ml). Выполните запись в файл и закройте файл из каждого потока после завершения эпохи, а затем либо отправьте каждому потоку сообщение о том, что n-й поток выполнил задачу, либо позвольте каждому потоку решать, когда он хочет проверить общий/сохраненный файл. /static состояние в основном потоке (возможно, реализация мьютекса здесь была бы хорошей идеей). Этот вызов в любом случае потребует аннулирования кэша, поскольку ему необходимо загрузить новые данные в/из файлов контрольных точек.

Почему вы создаете несколько потоков в первую очередь? Вы можете прекрасно создать несколько окон GLFW и несколько контекстов Dear ImGui и управлять всем в рамках одного потока. Работа с разными потоками только усложнит задачу.

В конкретном случае Dear ImGui вы можете использовать функции мультипорта в ветке 'docking', которые изначально поддерживают извлечение любых дорогих окон imgui за пределами основного окна просмотра и создают / управляют окнами GLFW для вас. Все это обрабатывается одним дорогим контекстом imgui, так что вы можете, например, перетаскивать из одного окна в другое.

Другие вопросы по тегам