如何使用 SDL2 为每个线程设置一个共享的 OpenGL 上下文?
How to setup one shared OpenGL contexts per thread with SDL2?
当我在我的 OpenGL 游戏中实现程序对象的并行初始化和更新时,我必须创建多个具有共享对象的 OpenGL 上下文,并为每个线程绑定一个,这样我就可以 create/update VBO 并行。
我从 this blog post 得到了如何做到这一点的想法,并像这样做了我的第一个实现(在 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
});
/* ... */
这段代码在 Linux 下开发的几个月里都运行良好,直到我将游戏移植到 Windows。然后出了问题:在 Intel 和 AMD GPU 上,总是在启动时崩溃。在 Nvidia 上,它大部分时间都能正常工作,但 运行 有几次它还在同一个地方崩溃:第一个由池线程之一发出的 OpenGL 调用,即 glGenBuffers()
。
最终我们发现 wglGetCurrentContext()
在有问题的线程上返回了 0
,这导致我们发现 SDL_GL_MakeCurrent()
某些线程失败并出现如下错误:
- wglMakeCurrent(): 句柄无效
- wglMakeCurrent(): 不支持请求的转换操作
问题:如何使用 SDL2 在不同线程上使用共享对象正确设置多个 OpenGL 上下文?
我不知道我们找到的解决方案是否适用于 SDL 支持的每个平台,但它至少适用于 Windows 和 Linux/X11。
问题是 SDL_GL_MakeCurrent()
通常不是 thread-safe(在 Linux/X11 上似乎是线程安全的,但这是偶然的,因为 SDL 是一个多平台库(问题确实是 wglMakeCurrent()
,而不是 SDL,因为旧代码也可以在 Wine 下工作)。
所以我们只需要使用互斥体来保护调用。在我们的 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.
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]);
});
/* ... */
当我在我的 OpenGL 游戏中实现程序对象的并行初始化和更新时,我必须创建多个具有共享对象的 OpenGL 上下文,并为每个线程绑定一个,这样我就可以 create/update VBO 并行。
我从 this blog post 得到了如何做到这一点的想法,并像这样做了我的第一个实现(在 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
});
/* ... */
这段代码在 Linux 下开发的几个月里都运行良好,直到我将游戏移植到 Windows。然后出了问题:在 Intel 和 AMD GPU 上,总是在启动时崩溃。在 Nvidia 上,它大部分时间都能正常工作,但 运行 有几次它还在同一个地方崩溃:第一个由池线程之一发出的 OpenGL 调用,即 glGenBuffers()
。
最终我们发现 wglGetCurrentContext()
在有问题的线程上返回了 0
,这导致我们发现 SDL_GL_MakeCurrent()
某些线程失败并出现如下错误:
- wglMakeCurrent(): 句柄无效
- wglMakeCurrent(): 不支持请求的转换操作
问题:如何使用 SDL2 在不同线程上使用共享对象正确设置多个 OpenGL 上下文?
我不知道我们找到的解决方案是否适用于 SDL 支持的每个平台,但它至少适用于 Windows 和 Linux/X11。
问题是 SDL_GL_MakeCurrent()
通常不是 thread-safe(在 Linux/X11 上似乎是线程安全的,但这是偶然的,因为 SDL 是一个多平台库(问题确实是 wglMakeCurrent()
,而不是 SDL,因为旧代码也可以在 Wine 下工作)。
所以我们只需要使用互斥体来保护调用。在我们的 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.
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]);
});
/* ... */