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

and*_*wrk 14 opengl multithreading

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

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

  1. 处理事件
  2. 画()
  3. 交换缓冲区(vsync导致此阻塞,直到垂直监视器刷新)

但是当有3个窗口时考虑主循环:

  1. 每个窗口处理事件
  2. 每个窗口绘制()
  3. 窗口1交换缓冲区(阻塞直到vsync)
  4. (一段时间之后)窗口2交换缓冲区(阻塞直到vsync)
  5. (一段时间之后)窗口3交换缓冲区(阻塞直到vsync)

糟糕...现在呈现应用程序的一个帧发生在适当帧率的1/3处.

解决方法:实用程序窗口

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

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

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

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

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

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

(对于每个窗口)

  1. 锁定全局互斥锁
  2. 处理事件
  3. 画()
  4. 解锁全局互斥锁
  5. swapBuffers()

这会有用吗?这个问题表明它不会:

事实证明,窗口彼此"战斗":看起来SwapBuffers调用是同步的并且彼此等待,即使它们在不同的线程中.我正在测量每个窗口的帧到帧时间,并且有两个窗口,这下降到30 fps,有3到20 fps等.

为了调查这个说法我创建了一个简单的测试程序.该程序创建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上下文?
  • 如果窗户的尺寸都不同,框架缓冲区会浪费空间吗?

卡米拉伯格伦德的维护者GLFW说:

这不是glViewport的工作原理.这不是缓冲区交换的工作方式.每个窗口都有一个帧缓冲区.你不能让他们分享一个.缓冲区交换是每个窗口帧缓冲区,上下文一次只能绑定到一个窗口.这是OS级别而不是GLFW的限制.

解决方法:只有一个上下文

这个问题表明该算法可能有效:

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
Run Code Online (Sandbox Code Playgroud)

问题提问者,

启用V-Sync后,SwapBuffers将同步到最慢的监视器,而速度更快的监视器上的窗口将变慢.

看起来他们只是在Microsoft Windows上对此进行了测试,并不清楚这个解决方案是否可以在任何地方使用.

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

它看起来也不符合EGL的规范.为了让另一个线程eglSwapBuffers(),你必须eglMakeCurrent(NULL),这意味着你eglSwapBuffers现在应该返回EGL_BAD_CONTEXT.

问题

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

类似的问题

与此问题类似:将多个OpenGL窗口同步到vsync,但我想要一个与平台无关的解决方案 - 或者至少是每个平台的解决方案.

这个问题:使用SwapBuffers()与多个OpenGL画布和垂直同步?但实际上这个问题与Python无关.

dat*_*olf 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()
Run Code Online (Sandbox Code Playgroud)

由于缓冲区交换没有阻塞,您可以为所有窗口发出所有缓冲区交换,而不会因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()
Run Code Online (Sandbox Code Playgroud)