8
votes

I have started to get into the slightly overwhelming scene of OpenGL resource loading on a separate thread so the main thread can continue to render an object. When stepping in, I noticed that GLFW released an updated version a month back with easier context management.

However, with glfwMakeContextCurrent() I have been unable to make this possible. In the loading thread I use this function and after its completion I add it again so the main thread then receives the context for further use. This is not allowing me to create and compile shaders or any other OpenGL related creation.

UPDATED:

What needs to be done so I can use GLFW in this situation? As GLFW is portable, I would love to use code that includes it. I do not know the necessary steps to prepare a thread keeping the GLFW API in mind.

As this blog post states, I need to create two threads with an OpenGL context (not the same context ;D ) and then share information. However, the instructions shown are platform specific. How can I then make use of GLFW so the steps in the example are as platform independent as possible?

1
You can't make the same context current in two separate threads.Nicol Bolas

1 Answers

17
votes

Use the share parameter on glfwCreateWindow():

// g++ main.cpp -pthread `pkg-config --cflags --libs glfw3 glew`
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <chrono>
#include <thread>
#include <atomic>

// reload shared VBO with random data every second
void MyThread( GLFWwindow* win, GLuint vbo, std::atomic< bool >& running )
{
    glfwMakeContextCurrent( win );
    glewInit();
    while( running )
    {
        float temp[ 512 ];
        for( size_t i = 0; i < 512; i+=2 )
        {
            temp[ i+0 ] = static_cast< float >( rand() % 600 );
            temp[ i+1 ] = static_cast< float >( rand() % 600 );
        }

        glBindBuffer( GL_ARRAY_BUFFER, vbo );
        glBufferData( GL_ARRAY_BUFFER, sizeof( temp ), temp, GL_DYNAMIC_DRAW );
        glBindBuffer( GL_ARRAY_BUFFER, 0 );

        // See GL 3.3 spec, section D.3.1
        glFinish();

        std::this_thread::sleep_for( std::chrono::milliseconds( 1000 ) );
    }
}

int main( int argc, char** argv )
{
    if( !glfwInit() )
        return -1;

    glfwWindowHint( GLFW_VISIBLE, GL_FALSE );
    GLFWwindow* threadWin = glfwCreateWindow( 1, 1, "Thread Window", NULL, NULL );

    glfwWindowHint( GLFW_VISIBLE, GL_TRUE );
    GLFWwindow* window = glfwCreateWindow( 600, 600, "Hello World", NULL, threadWin );
    glfwMakeContextCurrent( window );
    glewInit();

    // load shared VBO with dummy data
    float temp[ 512 ] = { 0 };
    GLuint vbo;
    glGenBuffers( 1, &vbo );
    glBindBuffer( GL_ARRAY_BUFFER, vbo );
    glBufferData( GL_ARRAY_BUFFER, sizeof( float ) * 512, temp, GL_DYNAMIC_DRAW );
    glBindBuffer( GL_ARRAY_BUFFER, 0 );
    std::atomic< bool > running( true );
    std::thread aThread( MyThread, threadWin, vbo, std::ref( running ) );

    while( !glfwWindowShouldClose( window ) )
    {
        glfwPollEvents();

        glClear( GL_COLOR_BUFFER_BIT );

        int w, h;
        glfwGetFramebufferSize( window, &w, &h );
        glViewport( 0, 0, w, h );
        
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();
        glOrtho( 0, 600, 0, 600, -1, 1 );

        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
        
        // re-bind shared VBO to pick up potential content changes
        // See GL 3.3 spec, section D.3.3, Rule 4
        glBindBuffer( GL_ARRAY_BUFFER, vbo );

        glEnableClientState( GL_VERTEX_ARRAY );   
        glVertexPointer( 2, GL_FLOAT, 0, 0 );
        glColor3ub( 255, 0, 0 );
        glDrawArrays( GL_LINES, 0, 256 );

        std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
        glfwSwapBuffers( window );
    }

    running = false;
    aThread.join();

    glfwTerminate();
    return 0;
}