Last active
March 2, 2024 16:24
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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