多个OpenGL上下文,多个窗口,多线程和vsync

时间:2015-04-14 00:44:17

标签: multithreading opengl

我正在使用OpenGL创建一个图形用户界面应用程序,其中可以有任意数量的窗口 - "多文档界面"风格。

如果有一个窗口,主循环可能如下所示:

  1. 处理事件
  2. 拉伸()
  3. 交换缓冲区(vsync导致此阻塞直到垂直监视器刷新)
  4. 但是当有3个窗口时考虑主循环:

    1. 每个窗口处理事件
    2. 每个窗口绘制()
    3. 窗口1交换缓冲区(阻止直到vsync)
    4. (一段时间之后)窗口2交换缓冲区(阻止直到vsync)
    5. (一段时间之后)窗口3交换缓冲区(阻止直到vsync)
    6. 糟糕...现在呈现应用程序的一个帧正好以适当的帧速率的1/3发生。

      解决方法:实用程序窗口

      一种解决方法是只打开一个带有vsync的窗口,其余的带有vsync的窗口关闭。首先在vsync窗口上调用swapBuffers()并绘制一个,然后在每个窗口上绘制其余的窗口和swapBuffers()。

      这种解决方法在大多数情况下可能看起来很好,但它并非没有问题:

      • 让一个窗口变得特别
      • 是不优雅的
      • 竞争条件仍可导致屏幕撕裂
      • 某些平台会忽略vsync设置并强制它在
      • 我读到切换哪个OpenGL上下文绑定是一项昂贵的操作,应该避免使用。

      解决方法:每个窗口一个线程

      由于每个线程可以绑定一个OpenGL上下文,因此答案可能是每个窗口有一个线程。

      我仍然希望GUI是单线程的,所以3窗口情况的主循环看起来像这样:

      (对于每个窗口)

      1. 锁定全球互斥
      2. 处理事件
      3. 拉伸()
      4. 解锁全局互斥
      5. swapBuffers()
      6. 这会有用吗?这个other question表示它不会:

          

        事实证明,窗户正在战斗。彼此:看起来像   甚至,SwapBuffers调用是同步的并且彼此等待   虽然他们是分开的线程。我正在测量帧到帧   每个窗口的时间和两个窗口,这下降到30帧/秒   3到20 fps等。

        为了调查此声明,我创建了simple test program。该程序创建N个窗口和N个线程,每个线程绑定一个窗口,请求每个窗口启用vsync,然后报告帧速率。到目前为止,结果如下:

        • Linux,X11,4.4.0 NVIDIA 346.47(2015-04-13)
          • 无论打开多少个窗口,帧速率都是60fps。
        • OSX 10.9.5(2015-04-13)
          • 帧率没有上限;交换缓冲区没有阻塞。

        解决方法:仅一个上下文,一个大帧缓冲区

        我想到的另一个想法是:只有一个OpenGL上下文和一个大的帧缓冲区,所有窗口的大小放在一起。

        每个帧,每个窗口调用glViewport以在绘制之前设置它们各自的帧缓冲矩形。

        完成所有绘图后,在唯一的OpenGL上下文中使用swapBuffers()。

        我即将调查此解决方法是否有效。我有一些问题是:

        • 有这么大的帧缓冲区可以吗?
        • 每帧多次调用glViewport是否可以?
        • 我使用的窗口库API是否允许我创建独立于窗口的OpenGL上下文?
        • 如果窗户的尺寸各不相同,框架缓冲区会浪费空间吗?
        Camilla Berglund的维护者

        GLFW说:

          

        这不是glViewport的工作方式。不是   缓冲区交换如何工作。每个窗口都有一个   帧缓冲区。你不能让他们分享一个。缓冲区交换是   每个窗口帧缓冲区和上下文只能绑定到一个   窗口一次。这是在操作系统级别而不是限制   GLFW。

        解决方法:仅一个上下文

        This question表示此算法可能有效:

        Activate OpenGL context on window 1  
        Draw scene in to window 1
        
        Activate OpenGL context on window 2  
        Draw scene in to window 2
        
        Activate OpenGL context on window 3  
        Draw scene in to window 3
        
        For all Windows
        SwapBuffers
        

        问题提问者,

          

        启用V-Sync后,SwapBuffers将同步到最慢的监视器和   更快的显示器上的窗口会变慢。

        看起来他们只在Microsoft Windows上对此进行了测试,并且不清楚此解决方案是否适用于所有地方。

        还有许多消息来源告诉我makeContextCurrent()在draw()例程中太慢了。

        看起来这也不符合EGL的规范。为了允许其他帖子eglSwapBuffers(),您必须eglMakeCurrent(NULL),这意味着您的eglSwapBuffers现在应该返回EGL_BAD_CONTEXT

        问题

        所以,我的问题是:什么是解决使用vsync的多窗口应用程序问题的最佳方法?这似乎是一个常见的问题,但我还没有找到令人满意的解决方案。

        类似问题

        与此问题类似:Synchronizing multiple OpenGL windows to vsync但我想要一个与平台无关的解决方案 - 或者至少是每个平台的解决方案。

        这个问题:Using SwapBuffers() with multiple OpenGL canvases and vertical sync?但实际上这个问题与Python无关。

2 个答案:

答案 0 :(得分:8)

  

交换缓冲区(vsync导致阻塞,直到垂直监视器刷新)

不,它没有阻止。缓冲区交换调用可能立即返回,块。然而它的作用是插入一个同步点,以便延迟执行改变后台缓冲区的命令,直到缓冲区交换发生。 OpenGL命令队列的长度有限。因此,一旦命令队列已满,进一步的OpenGL调用将阻止该程序,直到可以将更多命令推送到队列中。

缓冲交换也不是 OpenGL操作。它是一个图形/窗口系统级操作,独立于OpenGL上下文。只需看看缓冲交换函数:它们采用的唯一参数是drawable(= window)的句柄。实际上,即使您在单个drawable上运行多个OpenGL上下文,也只需将缓冲区交换一次;并且你可以在没有OpenGL上下文的情况下完成当前的绘制。

通常的做法是:

' first do all the drawing operations
foreach w in windows:
    foreach ctx in w.contexts:
        ctx.make_current(w)
        do_opengl_stuff()
        glFlush()

' with all the drawing commands issued
' loop over all the windows and issue
' the buffer swaps.
foreach w in windows:
    w.swap_buffers()

由于缓冲区交换没有阻塞,您可以为所有窗口发出所有缓冲区交换,而不会因V-Sync而延迟。但是,下一个OpenGL绘图命令可以解决为交换而发出的后台缓冲区的问题。

解决方法是使用FBO进行实际绘图,并将其与在交换缓冲区循环之前将FBO blit连接到后台缓冲区的循环结合使用:

' first do all the drawing operations
foreach w in windows:
    foreach ctx in w.contexts:
        ctx.make_current(w)
        glBindFramebuffer(GL_DRAW_BUFFER, ctx.master_fbo)
        do_opengl_stuff()
        glFlush()

' blit the FBOs' renderbuffers to the main back buffer
foreach w in windows:
    foreach ctx in w.contexts:
        ctx.make_current(w)
        glBindFramebuffer(GL_DRAW_BUFFER, 0)
        blit_renderbuffer_to_backbuffer(ctx.master_renderbuffer)
        glFlush()

' with all the drawing commands issued
' loop over all the windows and issue
' the buffer swaps.
foreach w in windows:
    w.swap_buffers()

答案 1 :(得分:0)

感谢@andrewrk所有论文的研究,我个人喜欢这样:

使用双缓冲区创建第一个窗口和他的opengl上下文。 此窗口上的活动vsync(swapinterval 1)

创建其他窗口并使用双缓冲区附加第一个上下文。 在其他窗口上禁用vsync(swapinterval 0)

对于每一帧 用于反转每个窗口(最后一个带有vsync的窗口)。 wglMakeCurrent(hdc,commonContext);
画。 SwapBuffer

以这种方式,我实现了vsync,所有窗口都基于同样的vsync。

但我在没有航空的情况下遇到了问题:撕裂...