Rendering from another thread (GLFW3)

realh wrote on Saturday, September 28, 2013:

I want to use separate threads for rendering and input. glfwWaitEvents can only be called on the main thread, but it looked as if it would be OK to render on a separate thread as long as I called glfwMakeContextCurrent at the start of it. This works fine in Linux, but not in Windows. I’ve written a test program which is supposed to draw a blue triangle on a white background; attached below. In Linux it does what it’s supposed to, but in Windows it stays black with no reported errors. I’ve also tried moving the call to glewInit into the thread, but in Windows that gives the error “Missing GL version”. In Linux it doesn’t seem to matter whether glewInit is called in the main thread or not. If I rewrite this test without using threads, it works correctly in Windows.

I’m using the precompiled version of GLFW 3.0.3 with MinGW (Linux version is 3.0.1) and pthreads-win32. Could there be some incompatibility between GLFW (or something else) and pthreads-win32?

// 00glfw.cpp: Very basic GLFW/OpenGL test without using framework
#define GLFW_DLL

#include <cstdlib>
#include <cstring>
#include <iostream>

#include <pthread.h>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

class MyException : public std::exception {
private:
    const char *desc;
public:
    MyException(const char *s) throw() : desc(s)
    {}

    const char *what() const throw()
    {
        return desc;
    }
};

static void GlfwErrorHandler(int code, const char *message)
{
    (void) code;
    throw MyException(message);
}

static GLFWwindow *init_glfw_window()
{
    glfwSetErrorCallback(GlfwErrorHandler);
    glfwInit();
    glfwWindowHint(GLFW_RED_BITS, 8);
    glfwWindowHint(GLFW_GREEN_BITS, 8);
    glfwWindowHint(GLFW_BLUE_BITS, 8);
    glfwWindowHint(GLFW_DEPTH_BITS, 16);
    GLFWwindow *win = glfwCreateWindow(1280, 720, "GLFW test", 0, 0);
    if (win)
        glfwMakeContextCurrent(win);
    else
        throw MyException("Failed to create GLFW window");
    return win;
}

static void init_glew()
{
    GLenum result = glewInit();
    if (result != GLEW_OK)
    {
        std::string s("GLEW init error: ");
        s += (const char *) glewGetErrorString(result);
        throw MyException(s.c_str());
    }
    std::cout << "OpenGL " << glGetString(GL_VERSION)
           << ", GLSL " <<  glGetString(GL_SHADING_LANGUAGE_VERSION)
           << ", GLEW " << glewGetString(GLEW_VERSION)
           << std::endl;
}

static void check_shader_build(GLuint shader, GLenum status_pname,
        bool link, const char *msg)
{
    GLint status = GL_TRUE;
    if (link)
        glGetProgramiv(shader, status_pname, &status);
    else
        glGetShaderiv(shader, status_pname, &status);
    if (status == GL_FALSE)
    {
        GLint len;
        if (link)
            glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &len);
        else
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
        std::size_t l = std::strlen(msg);
        char *logstr = new char[l + len + 1];
        std::strcpy(logstr, msg);
        if (link)
            glGetProgramInfoLog(shader, len, 0, (GLchar *) logstr + l);
        else
            glGetShaderInfoLog(shader, len, 0, (GLchar *) logstr + l);
        throw MyException(logstr);
    }
}

static GLuint compile_shader(GLenum stype, const char *src)
{
    GLuint shader = glCreateShader(stype);
    glShaderSource(shader, 1, &src, 0);
    glCompileShader(shader);
    check_shader_build(shader, GL_COMPILE_STATUS, false,
            (stype == GL_VERTEX_SHADER)
                    ? "Error compiling vertex shader: "
                    : "Error compiling fragment shader: ");
    return shader;
}

GLuint link_shaders(GLuint vert, GLuint frag)
{
    GLuint prog = glCreateProgram();
    glAttachShader(prog, vert);
    glAttachShader(prog, frag);
    glLinkProgram(prog);
    check_shader_build(prog, GL_LINK_STATUS, true,
            "Error linking shaders: ");
    glDetachShader(prog, vert);
    glDeleteShader(vert);
    glDetachShader(prog, frag);
    glDeleteShader(frag);
    return prog;
}

// Returns shader prog
static GLuint load_shaders(GLint *coord2d_tag)
{
    GLuint vert = compile_shader(GL_VERTEX_SHADER,
        "#version 120\n"
        "attribute vec2 coord2d;\n"
        "void main() {\n"
        "  gl_Position = vec4(coord2d, 0.0, 1.0);\n"
        "}\n");
    GLuint frag = compile_shader(GL_FRAGMENT_SHADER,
        "#version 120\n"
        "void main(void) {\n"
        "  gl_FragColor[0] = 0.0;\n"
        "  gl_FragColor[1] = 0.0;\n"
        "  gl_FragColor[2] = 1.0;\n"
        "}\n");
    GLuint prog = link_shaders(vert, frag);
    *coord2d_tag = glGetAttribLocation(prog, "coord2d");
    if (*coord2d_tag == -1)
        throw MyException("Failed to find coord2d attribute in shader prog");
    return prog;
}

struct AppData {
    GLFWwindow *win;
    volatile bool should_close;
    pthread_cond_t cond;
    pthread_mutex_t mutex;
};

static void *render_thread_loop(void *handle)
{
    static const GLfloat verts[] = {
             0.0f,  0.8f,
            -0.8f, -0.8f,
             0.8f, -0.8f,
    };
    AppData *data = (AppData *) handle;
    try
    {
        GLFWwindow *win = data->win;
        glfwMakeContextCurrent(win);

        std::cout << "Setting up shaders and buffers" << std::endl;
        GLint coord2d;
        GLuint prog = load_shaders(&coord2d);
        GLuint vbo;
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
        glClearColor(1, 1, 1, 1);
        glfwSwapBuffers(win);
        glfwSwapBuffers(win);

        std::cout << "Rendering" << std::endl;
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(prog);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glEnableVertexAttribArray(coord2d);
        glVertexAttribPointer(coord2d, 2, GL_FLOAT, GL_FALSE, 0, 0);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glDisableVertexAttribArray(coord2d);
        glfwSwapBuffers(win);

        std::cout << "Waiting for signal from main thread" << std::endl;
        pthread_mutex_lock(&data->mutex);
        while (!data->should_close)
            pthread_cond_wait(&data->cond, &data->mutex);
        pthread_mutex_unlock(&data->mutex);
    }
    catch (std::exception &x)
    {
        std::cerr << "Exception in rendering thread: " << x.what() << std::endl;
    }
    return 0;
}

static void check_err(int code, const char *msg)
{
    if (code)
    {
        std::string s(msg);
        s += std::strerror(code);
        throw MyException(s.c_str());
    }
}

int main(int argc, char **argv)
{
    (void) argc;
    (void) argv;

    try
    {
        AppData data;
        data.win = init_glfw_window();
        init_glew();
        data.should_close = false;
        pthread_cond_init(&data.cond, 0);
        pthread_mutex_init(&data.mutex, 0);

        pthread_t rthread;
        check_err(pthread_create(&rthread, 0, render_thread_loop, &data),
                "Error creating thread: ");

        std::cout << "Waiting for window to close" << std::endl;
        while (!glfwWindowShouldClose(data.win))
            glfwWaitEvents();

        data.should_close = true;
        std::cout << "Waiting for rendering thread to exit" << std::endl;
        pthread_mutex_lock(&data.mutex);
        pthread_cond_broadcast(&data.cond);
        pthread_mutex_unlock(&data.mutex);
        check_err(pthread_join(rthread, 0),
                "Error joining thread: ");
    }
    catch (std::exception &x)
    {
        std::cerr << "Exception in main thread: " << x.what() << std::endl;
        std::abort();
    }

    return 0;
}