辅助线程中的 OpenGL 渲染

Posted

技术标签:

【中文标题】辅助线程中的 OpenGL 渲染【英文标题】:OpenGL Rendering in a secondary thread 【发布时间】:2011-09-04 13:08:30 【问题描述】:

我正在编写一个 3D 模型查看器应用程序作为一个爱好项目,同时也是一个测试平台来尝试不同的渲染技术。我使用 SDL 处理窗口管理和事件,使用 OpenGL 进行 3D 渲染。我的程序的第一次迭代是单线程的,并且运行良好。但是,我注意到单线程程序导致系统变得非常缓慢/滞后。我的解决方案是将所有渲染代码移动到不同的线程中,从而释放主线程来处理事件并防止应用程序变得无响应。

这个解决方案间歇性地工作,由于主要来自 X 窗口系统的一组不断变化的(在我看来很奇怪)错误,程序经常崩溃。这让我质疑我最初的假设,即只要我所有的 OpenGL 调用都发生在创建上下文的线程中,一切都应该正常。在花了一天的大部分时间在互联网上寻找答案之后,我彻底被难住了。

更简洁:是否可以在主线程以外的线程中使用 OpenGL 执行 3D 渲染?我仍然可以在此配置中使用跨平台窗口库,例如 SDL 或 GLFW 吗?有没有更好的方法来做我想做的事情?

到目前为止,我一直在使用 C++ 在 Linux (Ubuntu 11.04) 上进行开发,但如果有更适合这些语言的解决方案,我也对 Java 和 Python 感到满意。

更新:根据要求,一些澄清:

当我说“系统变得迟缓”时,我的意思是与桌面交互(拖动窗口、与面板交互等)变得比平时慢得多。移动我的应用程序的窗口需要几秒钟的时间,而其他交互也慢到令人讨厌。 至于对合成窗口管理器的干扰...我正在使用 Ubuntu 11.04 附带的 GNOME shell(暂时远离 Unity...),我找不到任何禁用桌面效果的选项,例如在以前的发行版中有。我认为这意味着我没有使用合成窗口管理器......尽管我可能错了。 我相信“X 错误”是由于我在终端收到的错误消息导致的服务器错误。更多详情如下。

我的应用程序的多线程版本遇到的错误:

XIO:X 服务器“:0.0”上的致命 IO 错误 11(资源暂时不可用) 在 73 个请求(73 个已知已处理)之后,剩余 0 个事件。

失败请求的 X 错误:BadColor(无效的颜色图参数) 失败请求的主要操作码:79(X_FreeColormap) 失败请求中的资源 ID:0x4600001 失败请求序列号:72 输出流中的当前序列号:73

游戏:../../src/xcb_io.c:140:dequeue_pending_request:断言 `req == dpy->xcb->pending_requests' 失败。 中止

我总是得到上述三个错误之一,我得到的一个错误显然是随机的,这(在我看来)似乎证实了我的问题确实源于我对线程的使用。请记住,我是边学边学的,所以很有可能在我的无知中我有一些相当愚蠢的事情。

解决方案: 对于遇到类似问题的任何人,我解决了我的问题,方法是将我对 SDL_Init(SDL_INIT_VIDEO) 的调用移至渲染线程,并使用互斥锁锁定上下文初始化。这确保了上下文是在将要使用它的线程中创建的,并且它可以防止主循环在初始化任务完成之前启动。启动过程的简化大纲:

1) 主线程初始化struct,它将在两个线程之间共享,并且包含一个互斥锁。 2) 主线程产生渲染线程并休眠一小段时间(1-5ms),给渲染线程时间来锁定互斥锁。在此暂停之后,主线程在尝试锁定互斥锁时阻塞。 3) 渲染线程锁定互斥体,初始化SDL的视频子系统并创建OpenGL上下文。 4) 渲染线程解锁互斥锁并进入其“渲染循环”。 5)主线程不再被阻塞,所以它在完成初始化步骤之前锁定和解锁互斥锁。

请务必阅读答案和 cmets,那里有很多有用的信息。

【问题讨论】:

不确定 X,但在 Win32 上这是允许的。上下文只能在一个线程上处于活动状态,但听起来您正在遵守该规则。 这是我从研究中得到的印象。理想情况下,我真的很想让我的应用程序是跨平台的......如果没有其他原因,我只想尽可能少地处理 Windows API =P 这不能怪你……尽管我发现 Win32 API(事件分派)的这些方面要优越得多。我希望 Linux 有一个 MsgWaitForMultipleObjectsEx 等价物(可能名称更短)。与 poll 类似,但能够等待文件、子进程、计时器、线程、互斥体和 UI 消息。 您应该在解决方案的第 1 步中小心。 “短暂时间”可能不够长,因此您应该使用一个标志来指示渲染线程是否已初始化。在主线程中,检查此标志,如果未设置,则线程应等待来自渲染线程的信号/广播。 @BenVoigt 我知道这是一个旧线程,但你不应该比较 linux 和 win32 api。你应该比较win32 api和x11 【参考方案1】:

我在类似情况下所做的是将我的 OpenGL 调用保留在主线程中,但将顶点数组准备移动到单独的线程(或多个线程)。

基本上,如果您设法将 CPU 密集型的东西从 OpenGL 调用中分离出来,您就不必担心令人遗憾的可疑 OpenGL 多线程。

对我来说效果很好。

【讨论】:

这是一个很好的建议,如果我不能让事情按照我的设想工作,这似乎就是要走的路。谢谢!【参考方案2】:

只要一次只从一个线程访问 OpenGL 上下文,就不会遇到任何问题。你说即使是你的单线程程序也会让你的系统变得迟缓。这是否意味着整个系统或只是您自己的应用程序?在单线程 OpenGL 程序中应该发生的最糟糕的情况是,处理该程序的用户输入变得迟缓,但系统的其余部分不受影响。

如果您使用某些合成窗口管理器(Compiz、KDE4 kwin),请尝试禁用所有合成效果会发生什么。

当您说 X 错误 时,您是指客户端错误,还是 X 服务器日志中报告的错误?后一种情况不应该发生,因为 X 服务器必须能够处理任何类型的格式错误的 X 命令流,并且最多发出警告。如果它(X 服务器)崩溃,这是一个错误,应该报告给 X.org。

如果你的程序崩溃了,那么它与 X 的交互就有问题;在这种情况下,请向我们提供其变体中的错误输出。

【讨论】:

@rjacks:这些是客户端错误,X 服务器错误会显示在 /var/log/Xorg..log 上。如果可用,GNOME3 shell 也会使用合成,它会检测到兼容的配置。禁用它可能是通过一些晦涩的 gconf 设置来实现的。不过我建议安装一个轻量级的窗口管理器(Fluxbox 或类似的)并使用它进行测试。 @rjecks:AFAIK SDL 仍在使用 Xlib,在您的情况下,它是 xcb 之上的 Xlib。众所周知,Xlib 有几个无法修复的问题,看起来您确实遇到了其中一些问题。不幸的是 GLX,特别是关于直接渲染的部分是针对 Xlib 编写的。如果您可以在没有直接渲染的情况下生存 - 如果您因此使用缓冲区对象当然可以 - 那么纯间接渲染不会带来任何缺点,并且只能使用 xcb 来完成。不幸的是,没有易于使用的库。这是我的 TODO 管道中的一个项目:github.com/datenwolf/XcbIGL【参考方案3】:

我收到了您的一个错误:

../../src/xcb_io.c:140: dequeue_pending_request: Assertion `req == 
    dpy->xcb->pending_requests' failed. Aborted

还有很多不同的。原来 SDL_PollEvent 需要一个带有初始化内存的指针。所以这失败了:

SDL_Event *event;
SDL_PollEvent(event);

虽然这有效:

SDL_Event event;
SDL_PollEvent(&event);

以防其他人从 google 遇到此问题。

【讨论】:

【参考方案4】:

以防万一 - X-Server 有自己的同步子系统。 在绘图时尝试以下操作: man XInitThreads - 用于初始化man XLockDisplay/XUnlockDisplay - 用于绘图(不确定用于事件处理);

【讨论】:

【参考方案5】:

这是一半答案,一半问题。

可以在单独的线程中在 SDL 中进行渲染。它通常适用于任何操作系统。您需要做的是,确保在渲染线程接管时使 GL 上下文处于当前状态。同时,在你这样做之前,你需要从主线程中释放它,例如:

从主线程调用:

void Renderer::Init()

#ifdef _WIN32
    m_CurrentContext = wglGetCurrentContext();
    m_CurrentDC      = wglGetCurrentDC();
    // release current context
    wglMakeCurrent( nullptr, nullptr );
#endif
#ifdef __linux__
    if (!XInitThreads())
    
        THROW( "XLib is not thread safe." );
    
    SDL_SysWMinfo wm_info;
    SDL_VERSION( &wm_info.version );
    if ( SDL_GetWMInfo( &wm_info ) ) 
        Display *display = wm_info.info.x11.gfxdisplay;
        m_CurrentContext = glXGetCurrentContext();
        ASSERT( m_CurrentContext, "Error! No current GL context!" );
        glXMakeCurrent( display, None, nullptr );
        XSync( display, false );
    
#endif

从渲染线程调用:

void Renderer::InitGL()

    // This is important! Our renderer runs its own render thread
    // All
#ifdef _WIN32
    wglMakeCurrent(m_CurrentDC,m_CurrentContext);
#endif
#ifdef __linux__
    SDL_SysWMinfo wm_info;
    SDL_VERSION( &wm_info.version );
    if ( SDL_GetWMInfo( &wm_info ) ) 
        Display *display = wm_info.info.x11.gfxdisplay;
        Window   window  = wm_info.info.x11.window;
        glXMakeCurrent( display, window, m_CurrentContext );
        XSync( display, false );
    
#endif
    // Init GLEW - we need this to use OGL extensions (e.g. for VBOs)
    GLenum err = glewInit();
    ASSERT( GLEW_OK == err, "Error: %s\n", glewGetErrorString(err) );

不幸的是,这里的风险是 SDL 没有本机 MakeCurrent() 函数。因此,我们必须对 SDL 内部进行一些探讨(1.2、1.3 可能现在已经解决了这个问题)。

还有一个问题,即由于某种原因,我在 SDL 关闭时遇到了问题。也许有人可以告诉我如何在线程终止时安全地释放上下文。

【讨论】:

【参考方案6】:
    C++、SDL、OpenGL:::
      在主线程上:SDL_CreateWindow( ); SDL_CreateSemaphore( ); SDL_SemWait( ); 在 renderThread 上:SDL_CreateThread(run, "rendererThread", (void*)this) SDL_GL_CreateContext() “初始化 openGl 和 glew 的其余部分” SDL_SemPost( ) //解锁之前创建的信号量 P.S: SDL_CreateThread( ) 只将函数作为它的第一个参数而不是方法,如果需要一个方法而不是通过将其设为友元函数来模拟类中的方法/函数。这样,它将具有方法特征,同时仍可用作 SDL_CreateThread() 的函子。 PSS:在为线程创建的“run(void* data)”内部,“(void*)”这很重要,为了在函数内部重新获得“this”,需要这一行“ ClassName* me = (ClassName*)data;"

【讨论】:

以上是关于辅助线程中的 OpenGL 渲染的主要内容,如果未能解决你的问题,请参考以下文章

ios的opengl究竟能不能在非主线程渲染

onDrawFrame、requestRender 和渲染线程? | OpenGL ES 2.0

QT opengl多线程实现原理

是否可以在 opengl 中从 2 个不同的线程渲染 2 个不同的帧缓冲区对象?

使用来自不同线程的 opengl-command

OpenGL 多线程共享纹理