Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to setup one shared OpenGL contexts per thread with SDL2?

When I implemented parallel initialization and update of procedural objects in my OpenGL game, I had to create multiple OpenGL contexts with shared objects, and bind one per thread, so I could create/update the VBOs in parallel.

I got the idea of how to do that from this blog post, and made my first implementation like this (in C++, but this question is also relevant for C):

/* ... */

SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
SDL_Window* window = SDL_CreateWindow("Title",
    SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
    Options::width, Options::height, video_flags);

// Create one SDL context per thread
std::vector<SDL_GLContext> threads_glcontexts(globalThreadPool.get_num_threads());  
for(auto& t_ctx: threads_glcontexts) {
    t_ctx = SDL_GL_CreateContext(window);
}

// Main thread context
SDL_GLContext main_glcontext = SDL_GL_CreateContext(window);

// Setup one context per thread
//
// This function only returns when all threads
// in the pool have executed the given function.
globalThreadPool.run_in_every_pool_thread([&](unsigned thread_idx) {
    SDL_GL_MakeCurrent(window, threads_glcontexts[thread_idx]); // ← BROKEN CODE
});

/* ... */

This code worked wonderfully for various months while developing it under Linux, until I ported the game to Windows. Then things went wrong: on Intel and AMD GPUs, is always crashed at startup. On Nvidia, it worked most of the times, but run it a few times and it also crashed on the same place: the first OpenGL call issued by one of the pool threads, which was glGenBuffers().

Eventually we found that wglGetCurrentContext() returned 0 on the offending thread, what lead us to discover that SDL_GL_MakeCurrent() failed for some threads with errors like:

  • wglMakeCurrent(): The handle is invalid
  • wglMakeCurrent(): The requested transformation operation is not supported

The question: how to properly setup multiple OpenGL contexts with shared objects on different threads with SDL2?

like image 441
lvella Avatar asked Oct 22 '20 14:10

lvella


1 Answers

I don't know if the solution we found will work on every platform supported by SDL, but it at least works on both Windows and Linux/X11.

The issue was that SDL_GL_MakeCurrent() is not thread-safe in general (it seems to be thread safe on Linux/X11, but that is by chance, since SDL is a multiplatform library (and the problem really is wglMakeCurrent(), not SDL, because the old code also worked under Wine)).

So we simply had to protect the call with a mutex. In our C++ code, it looks like:

/* ... */
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
SDL_Window* window = SDL_CreateWindow("Title",
    SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
    Options::width, Options::height, video_flags);

// Create one SDL context per thread
std::vector<SDL_GLContext> threads_glcontexts(globalThreadPool.get_num_threads());  
for(auto& t_ctx: threads_glcontexts) {
    t_ctx = SDL_GL_CreateContext(window);
}

// Main thread context
SDL_GLContext main_glcontext = SDL_GL_CreateContext(window);

// Setup one context per thread
//
// This function only returns when all threads
// in the pool have executed the given function.
std::mutex mtx;
globalThreadPool.run_in_every_pool_thread([&](unsigned thread_idx) {
    std::lock_guard<std::mutex> lock(mtx); // ← ↓ WORKINING CODE
    SDL_GL_MakeCurrent(window, threads_glcontexts[thread_idx]);
});

/* ... */
like image 95
lvella Avatar answered Oct 27 '22 00:10

lvella