Swap buffers in another thread

I want to make render from a “main” thread, and have another thread call glfwSwapBuffers at regular intervals (or on demand from the “main” thread).

I have a MWE based on the example at https://www.glfw.org/documentation.html#example-code . Modification:

  1. Add rendering of a color changing rectangle
  2. Put glfwSwpBuffers in a seperate thread
  3. Register a callback function for window resize events

Unfortunately, it looks like something strange is happening to the “context”. When glfwSwapBuffers is called from a separate thread the rectangle does not stay centeret, and parts of the window are not updated (when enlarging the window).

Here is my program

// File: main.c
// Compile: cc -o main main.c -lglfw -lOpenGL
// Run: ./main

#include <GLFW/glfw3.h>
#include <stdio.h>
#include <pthread.h>
#include <time.h>

// Define a value for 'test'
// 1: Swap buffers in a different thread
// 2: Single-threaded program
#define test 1

float red = 0.5;
float rincr = 0.01;

uint8_t more = 1;

int width;
int height;
GLFWwindow* window;

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
    printf( "Update %i x %i\n", width, height );
}

void* cyclic(void* )
{
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 50000000;
    
    while(more) 
    {	   
        /* Swap front and back buffers */
//		glfwMakeContextCurrent(window);  // Don't uncomment, causes crash
        glfwSwapBuffers(window);
        
        /* Sleep a while */
        nanosleep( &ts, NULL);
    }
}

int main(void)
{
    /* Initialize the library */
    if (!glfwInit())
        return -1;
	
    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    
    /* Set callback function for window resize */
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

	/* Start another thread */
#if test == 1
	pthread_t thread1;								// Swapping buffers in a another thread
	pthread_create( &thread1, NULL, cyclic, NULL);  // has a strange effect on context (?)	
#endif

    /* Loop until the user closes the window */
    while( more )
    {
    	more = !glfwWindowShouldClose(window);
    	
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        red += rincr;
        if( red < 0.05 || 0.95 < red ) rincr = -rincr;
        
		glMatrixMode( GL_MODELVIEW );
   		glLoadIdentity();
     
        glColor3f(red, 0.2, 0.1);
        glBegin(GL_POLYGON);
        glVertex2f(-0.5f, -0.5f);
        glVertex2f( 0.5f, -0.5f);
        glVertex2f( 0.5f,  0.5f);
        glVertex2f(-0.5f,  0.5f);
        glEnd();
        
#if test == 2
        glfwSwapBuffers(window); // Works nicely
#endif    
        /* Poll for and process events */
        glfwPollEvents();        
    }

    glfwTerminate();
    return 0;
} 

Am I doing something wrong, or is my design not possible with GLFW?

Swapping buffers on one thread and rendering on another without some form of synchronization likely won’t play well with many drivers / OSes.

Good point.

Unfortunately, I did not see any change after a mutex.
The modified code is here (mutex stuff highlighted by /////////)

// File: main.c
// Compile: cc -o main main.c -lglfw -lOpenGL
// Run: ./main

#include <GLFW/glfw3.h>
#include <stdio.h>
#include <pthread.h>
#include <time.h>

// Define a value for 'test'
// 1: Swap buffers in a different thread
// 2: Single-threaded program
#define test 1

float red = 0.5;
float rincr = 0.01;

uint8_t more = 1;

int width;
int height;
GLFWwindow* window;

pthread_mutex_t lock; /////////////////

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
    printf( "Update %i x %i\n", width, height );
}

void* cyclic(void* )
{
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 50000000;
    
    while(more) 
    {	   
        /* Swap front and back buffers */
		pthread_mutex_lock( &lock ); /////////////////
        glfwSwapBuffers(window);
		pthread_mutex_unlock( &lock ); /////////////////
		        
        /* Sleep a while */
        nanosleep( &ts, NULL);
    }
}

int main(void)
{
    /* Initialize the library */
    if (!glfwInit())
        return -1;
	
    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    
    /* Set callback function for window resize */
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

	/* Start another thread */
#if test == 1
	pthread_mutex_init( &lock, NULL ); /////////////////
	pthread_t thread1;								// Swapping buffers in a another thread
	pthread_create( &thread1, NULL, cyclic, NULL);  // has a strange effect on context (?)	
#endif

    /* Loop until the user closes the window */
    while( more )
    {
		pthread_mutex_lock( &lock ); /////////////////
    	more = !glfwWindowShouldClose(window);
    	
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        red += rincr;
        if( red < 0.05 || 0.95 < red ) rincr = -rincr;
        
		glMatrixMode( GL_MODELVIEW );
   		glLoadIdentity();
     
        glColor3f(red, 0.2, 0.1);
        glBegin(GL_POLYGON);
        glVertex2f(-0.5f, -0.5f);
        glVertex2f( 0.5f, -0.5f);
        glVertex2f( 0.5f,  0.5f);
        glVertex2f(-0.5f,  0.5f);
        glEnd();
        
#if test == 2
        glfwSwapBuffers(window); // Works nicely
#endif    
        /* Poll for and process events */
        glfwPollEvents();        
		pthread_mutex_unlock( &lock ); /////////////////
    }

    glfwTerminate();
    return 0;
} 

As you can see I tried to guard as much as possible (except the body of “framebuffer_size_callback”, since “framebuffer_size_callback” is actually called during “glfwPollEvents”).

What could be wrong?

I think the first thing I would do is ensure that you render once, then swapbuffers and repeat. Using a lock simply ensures that swapping the buffers does not occur at the same time as rendering.

You could move to a semaphore for this, but once you do so you are essentially locking the two threads into being sequential.

So the simplest approach would be to go back to swapping buffers on one thread, or move all rendering to a secondary thread and in the framebuffer_size_callback set a global width and height variable (with a lock) and in the render loop use those variables to set the viewport every frame (with a lock).

Thanks for your suggestions. I might actually end up with a different organization for rendering and swapping buffers.

In the meantime I post a snapshot of the wrong behavior observed with separate threads for rendering and swapping: The rectangle has moved up (should have moved down), the rectangle also moves to right which is correct. Finally part of the window is not updated.

It might be a slightly academic problem, but I wonder what is going on here?

I found out what was wrong: I did not release the “context” in the main thread, which is why I had a crash trying to make the “context” current on the other thread (see comment “// Don’t uncomment, …” in my first post).

Below I have posted a working code. With #define test 1 the program is single threaded, with #define test 2 the program uses 2 threads. I have highlighted the important changes with ///////////.

With 2 threads everything seems a bit jerky, however, that can be adjusted by decreasing the amount of sleeping (make ts.tv_nsec smaller).

I found the solution by looking in the excellent documentation ! :slight_smile:

https://www.glfw.org/docs/3.3/group__context.html

// File: main.c
// Compile: cc -o main main.c -lglfw -lOpenGL
// Run: ./main

#include <GLFW/glfw3.h>
#include <stdio.h>
#include <pthread.h>
#include <time.h>

// Define a value for 'test'
// 1: Swap buffers in a different thread
// 2: Single-threaded program
#define test 1

float red = 0.5;
float rincr = 0.01;

uint8_t more = 1;

int width;
int height;
GLFWwindow* window;

pthread_mutex_t lock;

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
    printf( "Update %i x %i\n", width, height );
}

void* cyclic(void* )
{
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 50000000;
    
    while(more) 
    {	   
        /* Swap front and back buffers */
		pthread_mutex_lock( &lock );
		glfwMakeContextCurrent(window); /////////////////
        glfwSwapBuffers(window);
        glfwMakeContextCurrent(NULL); /////////////////
		pthread_mutex_unlock( &lock );
		        
        /* Sleep a while */
        nanosleep( &ts, NULL);
    }
}

int main(void)
{
    /* Initialize the library */
    if (!glfwInit())
        return -1;
	
    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    
    /* Set callback function for window resize */
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

	/* Start another thread */
#if test == 1
	printf( "Init mutex %i\n", pthread_mutex_init( &lock, NULL ) );
	pthread_t thread1;
	pthread_create( &thread1, NULL, cyclic, NULL);
#endif

    /* Loop until the user closes the window */
    while( more )
    {
		pthread_mutex_lock( &lock );
		glfwMakeContextCurrent(window); /////////////////
    	more = !glfwWindowShouldClose(window);
    	
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        red += rincr;
        if( red < 0.05 || 0.95 < red ) rincr = -rincr;
        
		glMatrixMode( GL_MODELVIEW );
   		glLoadIdentity();
     
        glColor3f(red, 0.2, 0.1);
        glBegin(GL_POLYGON);
        glVertex2f(-0.5f, -0.5f);
        glVertex2f( 0.5f, -0.5f);
        glVertex2f( 0.5f,  0.5f);
        glVertex2f(-0.5f,  0.5f);
        glEnd();
        
#if test == 2
        glfwSwapBuffers(window);
#endif    
        /* Poll for and process events */
        glfwPollEvents();        
        glfwMakeContextCurrent(NULL); /////////////////
		pthread_mutex_unlock( &lock );
    }

    glfwTerminate();
    return 0;
}