OpenGL在辅助线程中渲染

时间:2011-05-30 03:24:52

标签: multithreading opengl sdl

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

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

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

到目前为止,我一直在使用C ++在Linux(Ubuntu 11.04)上进行开发,尽管如果有一种解决方案在这些语言中效果更好,我对Java和Python也很满意。

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

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

我的应用程序的多线程版本出现的错误:

  

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

     

X请求失败的错误:BadColor(无效的Colormap参数)     失败请求的主要操作码: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)主线程不再被阻塞,因此在完成初始化步骤之前锁定和解锁互斥锁。

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

6 个答案:

答案 0 :(得分:7)

只要一次只触摸一个线程的OpenGL上下文,就不应该遇到任何问题。你说甚至你的单线程程序使你的系统运行缓慢。这是指整个系统还是仅适用于您自己的应用程序?在单线程OpenGL程序中应该发生的最糟糕的情况是,处理该一个程序的用户输入会变得滞后,但系统的其余部分不会受到影响。

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

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

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

答案 1 :(得分:3)

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

基本上,如果你设法将cpu密集的东西与OpenGL调用分开,你不必担心不幸的可疑OpenGL多线程。

对我来说很漂亮。

答案 2 :(得分:3)

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

答案 3 :(得分:1)

我收到了你的一个错误:

../../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);

如果其他人从谷歌中遇到此事。

答案 4 :(得分:1)

这是半个答案,也是半个问题。

可以在单独的线程中在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关机时遇到了问题。也许有人可以告诉我如何在线程终止时安全地释放上下文。

答案 5 :(得分:1)

  1. C ++,SDL,OpenGl :::
      主线程上的
    1. :SDL_CreateWindow();
    2. SDL_CreateSemaphore();
    3. SDL_SemWait();
    4. on renderThread:SDL_CreateThread(run," rendererThread",(void *)this)
    5. SDL_GL_CreateContext()
    6. "初始化openGl和glew"
    7. 的其余部分
    8. SDL_SemPost()//解锁以前创建的信号量
    9. P.S:SDL_CreateThread()只将函数作为第一个参数而不是方法,如果需要一个方法,那么通过使它成为友元函数来模拟类中的方法/函数。这样它将具有方法特征,同时仍然可以用作SDL_CreateThread()的函子。
    10. P.S.S:" run(void * data)"为线程创建,"(void *)"这很重要,为了重新获得这个"在函数内部需要这一行" ClassName * me =(ClassName *)data;"