2

I want to load some textures and meshes in a separate thread while the main program is showing a loading screen because it takes a few seconds to load all resources. I'm using OpenGL and GLFW. I tried to accomplish this with the following code:

    void *status;

    if(pthread_create(&loader, NULL, &loader_func, NULL))
    {
        fprintf(stderr, "Error creating loader thread\n");
        return 1;
    }

    while(_flags & FLAG_LOADING)
    {
        vec3 color = { 0.1, 0.3, 1.0 };
        if(glfwWindowShouldClose(window))
        {
            resource_destroy();
            glfwTerminate();
            return 0;
        }

        GL_CHECK(glClearColor(0.1, 0.1, 0.1, 1.0));
        GL_CHECK(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));

        font_renderer_activate();
        render_string(&_font_menu, "Loading...", _width / 2, _height / 2, 64,
                color, ALIGN_V_CENTER | ALIGN_H_CENTER);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    if(pthread_join(loader, &status))
    {
        fprintf(stderr, "Error joining loader and main thread\n");
        return 1;
    }

    if(*(int *)status)
    {
        fprintf(stderr, "Error loading resources\n");
        return 1;
    }

loader_func() is not rendering to the screen, and only uses OpenGL functions for creating VAOs, VBOs etc. and loading data into them.

The problem is that after the loading text shows up on screen and loading has finished, nothing shows up on screen (EDIT: except the textual HUD) and I'm getting a lot of debug error messages in my log (I'm wrapping all OpenGL calls in a macro that checks for errors with glGetError):

main.c:588
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap_texture);
GL_Error 0x502: GL_INVALID_OPERATION
main.c:589
glDrawArrays(GL_TRIANGLES, OFFSET_SKYBOX, VERTICES_SKYBOX);
GL_Error 0x502: GL_INVALID_OPERATION
main.c:629
glDrawArrays(GL_TRIANGLES, OFFSET_SELECTOR, VERTICES_SELECTOR);
GL_Error 0x502: GL_INVALID_OPERATION

When I call loader_func directly, there are no errors and the main render loop works correctly.

I read that to use OpenGL functions in another thread it is required to call glfwMakeContextCurrent but that wouldn't work in my case, because then the loading screen wouldn't be rendered. My only idea was to utilize a second library like SDL to create a window while loading, then destroy it and create a new window with GLFW for use with OpenGL. Is that what I want to achieve possible with just OpenGL?

whitwhoa
  • 2,389
  • 4
  • 30
  • 61
anton-tchekov
  • 1,028
  • 8
  • 20
  • I take it your OpenGL code works fine if you do everything in the main thread (except it's unresponsive for a few seconds)? – user2740650 Feb 16 '20 at 02:10
  • Yes, that is exactly what happens. – anton-tchekov Feb 16 '20 at 02:18
  • 2
    There a few related articles showing up in the margin for me, like https://stackoverflow.com/q/16203224/2740650 and https://stackoverflow.com/q/6172020/2740650. I think the gist of it is that you **DO** need to need to use `glfwMakeContextCurrent` when using multiple threads. Trouble is, you can't have two threads owning the window. Not sure why your main loop as shown has to busy-wait though, since it really just renders a message. Does it need to use OpenGL at all to render a message? If so, try using thread locking along with `glfwMakeContextCurrent` (switch back and forth). – user2740650 Feb 16 '20 at 02:29
  • To my knowledge glfw only supports rendering through OpenGL. I think I'll use the thread locking approach since I want to draw a progress bar so that the main thread will be informed of progress updates. – anton-tchekov Feb 16 '20 at 02:47

1 Answers1

4

The easiest way to handle this is to have the main thread create and manage all of the OpenGL objects, while the loading thread does the File IO (easily the slowest part of the loading). Once the loading thread is finished with loading a particular asset, it can deliver the loaded data to the main thread via <insert your favorite thread-safe mechanism here>, which can do the final OpenGL uploading part.

After all, it's not like rendering a loading screen is a huge performance drain or something, so the cost of uploading on the main thread will be minimal. This also permits you to do that loading bar thing, since your main thread will frequently be getting the results of the loading process, so it knows at any time how much of the loading is finished.

If you absolutely must have two threads both making OpenGL calls for some reason, then you should also have two OpenGL contexts, each being current in a different thread, and the two contexts sharing objects with each other. GLFW is perfectly happy to provide this if you ask it nicely. Create your main window as normal, then set the GLFW_VISIBLE hint to GLFW_FALSE, and create a second window (with an arbitrary resolution). You should pass the main window as the last parameter to glfwCreateWindow, so that the two contexts can share objects. You can then set each window current in different contexts and you're fine.

One word of caution. Contexts that share objects between them only share certain objects. Objects which reference other objects cannot be shared (also query objects are unsharable for some reason). VAOs reference buffer objects, so they can't be shared. So there's no point in trying to create them on the off-screen context.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982