如何使用 SDL2 为每个线程设置一个共享的 OpenGL 上下文?

lve*_*lla 5 c c++ opengl multithreading sdl-2

当我在我的 OpenGL 游戏中实现程序对象的并行初始化和更新时,我必须使用共享对象创建多个 OpenGL 上下文,并为每个线程绑定一个,以便我可以并行创建/更新 VBO。

我从这篇博客文章中得到了如何做到这一点的想法,并像这样进行了我的第一个实现(在 C++ 中,但这个问题也与 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
});

/* ... */
Run Code Online (Sandbox Code Playgroud)

这段代码在 Linux 下开发过程中的几个月都非常出色,直到我将游戏移植到 Windows。然后事情出错了:在 Intel 和 AMD GPU 上,总是在启动时崩溃。在 Nvidia 上,它大部分时间都可以工作,但是运行了几次,它也会在同一个地方崩溃:其中一个池线程发出的第一个 OpenGL 调用是glGenBuffers().

最终我们发现在有问题的线程上wglGetCurrentContext()返回0,导致我们发现SDL_GL_MakeCurrent()某些线程失败并出现以下错误:

  • wglMakeCurrent(): 句柄无效
  • wglMakeCurrent():不支持请求的转换操作

问题:如何使用 SDL2 在不同线程上正确设置具有共享对象的多个 OpenGL 上下文?

lve*_*lla 7

我不知道我们找到的解决方案是否适用于 SDL 支持的每个平台,但它至少适用于 Windows 和 Linux/X11。

\n

问题是它SDL_GL_MakeCurrent()通常不是线程安全的(它在 Linux/X11 上似乎是线程安全的,但这是偶然的,因为 SDL 是一个多平台库(问题确实是wglMakeCurrent(),而不是 SDL,因为旧代码也在 Wine 下工作))。

\n

所以我们只需使用互斥锁来保护调用。在我们的 C++ 代码中,它看起来像:

\n
/* ... */\nSDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);\nSDL_Window* window = SDL_CreateWindow("Title",\n    SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,\n    Options::width, Options::height, video_flags);\n\n// Create one SDL context per thread\nstd::vector<SDL_GLContext> threads_glcontexts(globalThreadPool.get_num_threads());  \nfor(auto& t_ctx: threads_glcontexts) {\n    t_ctx = SDL_GL_CreateContext(window);\n}\n\n// Main thread context\nSDL_GLContext main_glcontext = SDL_GL_CreateContext(window);\n\n// Setup one context per thread\n//\n// This function only returns when all threads\n// in the pool have executed the given function.\nstd::mutex mtx;\nglobalThreadPool.run_in_every_pool_thread([&](unsigned thread_idx) {\n    std::lock_guard<std::mutex> lock(mtx); // \xe2\x86\x90 \xe2\x86\x93 WORKING CODE\n    SDL_GL_MakeCurrent(window, threads_glcontexts[thread_idx]);\n});\n\n/* ... */\n
Run Code Online (Sandbox Code Playgroud)\n