Skip to content

Instantly share code, notes, and snippets.

@Madsy
Last active January 15, 2024 10:04
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save Madsy/6980061 to your computer and use it in GitHub Desktop.
Save Madsy/6980061 to your computer and use it in GitHub Desktop.
Working multi-threading two-context OpenGL example with GLFW 3.0.3 and GLEW 1.8
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <vector>
#include <cmath>
#include <cstdio>
#include <limits>
#include <chrono>
#include <thread>
#include <mutex>
//#define GL33
//#define FULLSCREEN
static const int width = 640;
static const int height = 360;
GLFWwindow* window;
GLFWwindow* window_slave;
GLuint fb[2] = {std::numeric_limits<GLuint>::max(), std::numeric_limits<GLuint>::max()}; //framebuffers
GLuint rb[2] = {std::numeric_limits<GLuint>::max(), std::numeric_limits<GLuint>::max()}; //renderbuffers, color and depth
bool threadShouldRun = true;
bool isFBOdirty = true; //true when last frame was displayed, false
//when just updated and not yet displayed
bool isFBOready = false; //set by worker thread when initialized
bool isFBOsetupOnce = false; //set by main thread when initialized
std::timed_mutex mutexGL;
static bool checkFrameBuffer(GLuint fbuffer)
{
bool isFB = glIsFramebuffer(fbuffer);
bool isCA = glIsRenderbuffer(rb[0]);
bool isDSA = glIsRenderbuffer(rb[1]);
bool isComplete = false;
if(isFB){
glBindFramebuffer(GL_FRAMEBUFFER, fbuffer);
isComplete = (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}
/*
printf("Is fb a framebuffer? %s\n", isFB ? "[yes]" : "[no]");
printf("Is rb[0] a color renderbuffer? %s\n", isCA ? "[yes]" : "[no]");
printf("Is rv[1] a depth stencil renderbuffer? %s\n", isDSA ? "[yes]" : "[no]");
printf("Is fb framebuffer-complete? %s\n", isComplete ? "[yes]" : "[no]");
*/
return isFB && isCA &&isDSA && isComplete;
}
/* worker thread creates its own FBO to render to, as well as two renderbuffers.
The renderbuffers are also used by a separate FBO in main()
fb[0] is owned by main, and fb[1] is owned by worker thread
*/
static bool createFrameBuffer() //for worker thread
{
bool ret;
glGenFramebuffers(1, &fb[1]);
glBindFramebuffer(GL_FRAMEBUFFER, fb[1]);
glGenRenderbuffers(2, rb);
glBindRenderbuffer(GL_RENDERBUFFER, rb[0]);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 2, GL_RGBA8, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, rb[1]);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 2, GL_DEPTH24_STENCIL8, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, rb[0]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb[0]);
glBindRenderbuffer(GL_RENDERBUFFER, rb[1]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rb[1]);
glFlush();
if(!(ret = checkFrameBuffer(fb[1]))){
glDeleteRenderbuffers(2, rb);
glDeleteFramebuffers(1, &fb[1]);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
return ret;
}
/* If worker thread is finished initializing renderbuffers, reuse them in a FBO for main.
fb[0] is owned by main, and fb[1] is owned by worker thread */
static void createFrameBufferMain()
{
while(!mutexGL.try_lock_for(std::chrono::seconds(1))){
return;
}
if(isFBOready){ //is other thread finished setting up FBO?
if(glIsRenderbuffer(rb[0]) && glIsRenderbuffer(rb[1])){
glBindFramebuffer(GL_FRAMEBUFFER, fb[0]);
glBindRenderbuffer(GL_RENDERBUFFER, rb[0]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb[0]);
glBindRenderbuffer(GL_RENDERBUFFER, rb[1]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rb[1]);
isFBOsetupOnce = true;
}
}
mutexGL.unlock();
}
/* Used in main to copy from fbo into the real framebuffer.
fb[0] FBO owned by main reuses renderbuffers from worker thread.
The contents of those renderbuffers are then copied into the
default framebuffer by this function. */
static void copyFrameBuffer(GLuint fbuffer)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, width, height,
0, 0, width, height,
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
static GLFWwindow* initWindow(GLFWwindow* shared, bool visible)
{
GLFWwindow* win;
#ifdef GL33
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
#endif
if(visible)
glfwWindowHint(GLFW_VISIBLE, GL_TRUE);
else
glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
#ifdef FULLSCREEN
GLFWmonitor* monitor = 0;
if(visible) //Don't create fullscreen window for offscreen contexts
monitor = glfwGetPrimaryMonitor();
win = glfwCreateWindow(width, height, "Optimus example", monitor, shared);
#else
win = glfwCreateWindow(width, height, "Optimus example", 0, shared);
#endif
return win;
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if ((key == GLFW_KEY_ESCAPE || key == GLFW_KEY_ENTER)
&& action == GLFW_PRESS){
glfwSetWindowShouldClose(window, GL_TRUE);
}
}
/********************************************** TEST *********************************/
static void worker_thread()
{
glfwMakeContextCurrent(window_slave);
//create new shared framebuffer object
mutexGL.lock();
createFrameBuffer();
isFBOready = true;
mutexGL.unlock();
for(;;){
mutexGL.lock();
if(!threadShouldRun){
mutexGL.unlock();
break;
}
if(isFBOdirty){
glBindFramebuffer(GL_FRAMEBUFFER, fb[1]);
float r = (float)rand() / (float)RAND_MAX;
glClearColor(r,r,r,1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glFlush();
isFBOdirty = false;
}
mutexGL.unlock();
}
printf("Exiting thread..\n");
return;
}
int main(int argc, char* argv[])
{
if(!glfwInit()){
printf("Failed to initialize glfw\n");
return 0;
}
//main window
window = initWindow(0, true);
//window used by second thread
window_slave = initWindow(window, false);
if(!window || !window_slave){
glfwTerminate();
printf("Failed to create glfw windows\n");
return 0;
}
glfwSetKeyCallback(window, key_callback);
glfwMakeContextCurrent(window);
if(glewInit()){
printf("Failed to init GLEW\n");
glfwDestroyWindow(window);
glfwDestroyWindow(window_slave);
glfwTerminate();
return 0;
}
std::thread gl_thread(worker_thread);
glGenFramebuffers(1, &fb[0]);
glViewport(0, 0, width, height);
while(!glfwWindowShouldClose(window)){
glfwPollEvents(); //get key input
if(!isFBOsetupOnce){
createFrameBufferMain(); //isFBOsetupOnce = true when FBO can be used
} else {
if(checkFrameBuffer(fb[0])){
if(!mutexGL.try_lock_for(std::chrono::seconds(1)))
continue;
if(!isFBOdirty){
copyFrameBuffer(fb[0]);
glfwSwapBuffers(window);
isFBOdirty = true;
//printf("Framebuffer OK\n");
} else {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
mutexGL.unlock();
} else {
printf("Framebuffer not ready!\n");
GLenum e = glGetError();
printf("OpenGL error: %X\n", e);
}
}
}
threadShouldRun = false; //other thread only reads this
gl_thread.join();
glfwDestroyWindow(window);
glfwDestroyWindow(window_slave);
glfwTerminate();
return 0;
}
@hermestrimegistus
Copy link

hermestrimegistus commented Jan 14, 2020

Why did you now create two different windows for two different context? Is this possible and does it make sound to do so?

My bad. I expanded window and can see two windows.

@UnrealKaraulov
Copy link

UnrealKaraulov commented Jan 15, 2024

Working multi-threading two-context OpenGL example with GLFW 3.0.3 and GLEW 1.8

WOW this is just a flickering window and nothing else.

You can give a real example of a multi-threaded renderer with depth saving, etc.?!!!!))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment