Skip to content

Instantly share code, notes, and snippets.

@Carcons
Last active March 2, 2024 16:24
Show Gist options
  • Save Carcons/ae792a5165a680f9235a796e2efc56b0 to your computer and use it in GitHub Desktop.
Save Carcons/ae792a5165a680f9235a796e2efc56b0 to your computer and use it in GitHub Desktop.
Minimal C++17 example of using multithreading with Dear ImGui, OpenGL (GLEW) and GLFW
// C++17
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <iostream>
#include <cstdint>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
using namespace std::chrono_literals;
#pragma region Window
static GLFWwindow* g_pWindow = nullptr;
constexpr char* TITLE = "Multithreaded GLFW3, OpenGL & Dear ImGui";
constexpr std::uint32_t WIDTH = 1280u;
constexpr std::uint32_t HEIGHT = WIDTH * 9u / 16u;
constexpr std::uint16_t GL_VER_MAJ = 4u;
constexpr std::uint16_t GL_VER_MIN = 6u;
constexpr std::uint16_t VSYNC = 1u;
constexpr float BG_COLOR[] = { 0.5f, 0.f, 0.f, 1.f };
#pragma endregion
#pragma region Thread stuff
static std::thread g_rendererThread;
static std::condition_variable g_rendererCV;
static std::mutex g_rendererMtx;
static std::atomic<bool> g_shutdownRenderer = false;
static bool g_rendererInitialized = false;
static bool g_blockDrawing = false;
static auto g_onBlockSleepTime = 30ms;
#pragma endregion
#pragma region ImGui stuff
constexpr char* IMGUI_SHADER_VER = "#version 460 core";
static ImDrawData* g_pImguiBackBuffer = new ImDrawData(); // Where ImGui::Render() draws to
static ImDrawData* g_pImguiFrontBuffer = new ImDrawData(); // What ImGui_ImplOpenGL3_RenderDrawData render
#pragma endregion
static void ImGuiSwapBuffers()
{
std::scoped_lock lk(g_rendererMtx);
ImDrawData* const pBackBuff = g_pImguiBackBuffer;
g_pImguiBackBuffer = g_pImguiFrontBuffer;
g_pImguiFrontBuffer = pBackBuff;
}
static void CopyGUI()
{
ImDrawData* data = ImGui::GetDrawData();
if (!data)
return;
g_pImguiBackBuffer->CmdLists.clear_delete();
g_pImguiBackBuffer->Clear();
g_pImguiBackBuffer->DisplaySize = data->DisplaySize;
g_pImguiBackBuffer->OwnerViewport = data->OwnerViewport;
g_pImguiBackBuffer->FramebufferScale = data->FramebufferScale;
g_pImguiBackBuffer->CmdListsCount = data->CmdListsCount;
for (int32_t i = 0; i < data->CmdListsCount; i++)
g_pImguiBackBuffer->CmdLists.push_back(data->CmdLists[i]->CloneOutput());
}
static void RendererThread()
{
// Init
{
std::scoped_lock lk(g_rendererMtx);
std::cout << "Renderer thread ID: " << std::this_thread::get_id() << '\n';
glewExperimental = GL_TRUE;
glewInit();
glfwMakeContextCurrent(g_pWindow);
glfwSwapInterval(VSYNC);
//Initialize ImGui
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplGlfw_InitForOpenGL(g_pWindow, true);
ImGui_ImplOpenGL3_Init(IMGUI_SHADER_VER);
ImGui::StyleColorsDark();
// First time build fonts etc
ImGui_ImplOpenGL3_NewFrame();
g_rendererInitialized = true;
}
g_rendererCV.notify_one();
while (!g_shutdownRenderer)
{
/*
if (g_blockDrawing)
{
glfwSwapBuffers(g_pWindow);
std::this_thread::sleep_for(g_onBlockSleepTime);
continue;
}
*/
glClearColor(BG_COLOR[0], BG_COLOR[1], BG_COLOR[2], BG_COLOR[3]);
glClear(GL_COLOR_BUFFER_BIT);
// some heavy work
std::this_thread::sleep_for(4ms);
{
std::scoped_lock lk(g_rendererMtx);
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplOpenGL3_RenderDrawData(g_pImguiFrontBuffer);
}
glfwSwapBuffers(g_pWindow);
}
// All of this code happens while main thread is safely waiting for this function end
if (g_pImguiBackBuffer)
{
g_pImguiBackBuffer->CmdLists.clear_delete();
delete g_pImguiBackBuffer;
g_pImguiBackBuffer = nullptr;
}
if (g_pImguiFrontBuffer)
{
g_pImguiFrontBuffer->CmdLists.clear_delete();
delete g_pImguiFrontBuffer;
g_pImguiFrontBuffer = nullptr;
}
ImGui_ImplGlfw_Shutdown();
ImGui_ImplOpenGL3_Shutdown();
}
static void Update()
{
// some heavy work
std::this_thread::sleep_for(6ms);
// GUI logic and construction
{
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
{
ImGui::ShowDemoWindow();
// imgui metrics shows the fps of this thread (update): internally it uses glfwGetTime
ImGui::ShowMetricsWindow();
ImGui::Begin("Test");
ImGui::End();
}
ImGui::Render();
CopyGUI();
ImGuiSwapBuffers();
}
// Notice: to pass something to the renderer, use a concurrent queue with draw commands or similar
}
int main(int, char**)
{
std::cout << "Main/Update thread ID: " << std::this_thread::get_id() << '\n';
if (!glfwInit())
return -1;
g_pWindow = glfwCreateWindow(WIDTH, HEIGHT, TITLE, nullptr, nullptr);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VER_MAJ);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_VER_MIN);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// GLFW callbacks
{
// ...
}
g_rendererThread = std::thread(RendererThread);
std::cout << "Waiting Renderer initialization...\n";
{
std::unique_lock lk(g_rendererMtx);
g_rendererCV.wait(lk, [] { return g_rendererInitialized; });
}
std::cout << "Renderer initialized\n";
while (!glfwWindowShouldClose(g_pWindow))
{
//g_blockDrawing = true;
glfwPollEvents();
//g_blockDrawing = false;
Update();
}
g_shutdownRenderer = true;
g_rendererThread.join();
glfwDestroyWindow(g_pWindow);
g_pWindow = nullptr;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment