如果只有一个窗口,则主循环可能如下所示:
1. 处理事件 2. 绘制() 3. 交换缓冲区(垂直同步会导致此操作阻塞,直到垂直监视器刷新)
然而,考虑当有3个窗口时的主循环:
1. 每个窗口处理事件 2. 每个窗口绘制() 3. 窗口1交换缓冲区(阻塞直到垂直同步) 4. (稍后)窗口2交换缓冲区(阻塞直到垂直同步) 5. (稍后)窗口3交换缓冲区(阻塞直到垂直同步)
糟糕……现在应用程序的渲染每帧发生的次数只有正常帧率的1/3。
解决方法:实用程序窗口
一种解决方法是只有一个窗口开启垂直同步,其余窗口关闭垂直同步。先调用vsync窗口的swapBuffers()并绘制它,然后绘制其余窗口并在每个窗口上调用swapBuffers()。
这种解决方法大部分时间看起来都很好,但并非没有问题:
- 有一个窗口特殊是不优雅的
- 竞态条件仍可能导致屏幕撕裂
- 某些平台忽略垂直同步设置并强制其开启
- 我读到切换绑定的OpenGL上下文是一项昂贵的操作,应该避免使用。
解决方法:每个窗口一个线程
由于每个线程只能绑定一个OpenGL上下文,因此也许答案是每个窗口一个线程。
然而,我仍希望GUI是单线程的,因此3个窗口情况下的主循环如下:
(对于每个窗口)
- 锁定全局互斥量
- 处理事件
- 绘制()
- 解锁全局互斥量
- 交换缓冲区()
这样行吗?这个其他问题表明它不会:
原来这些窗口正在互相“斗争”:看起来SwapBuffers调用是同步的,即使它们在不同的线程中。我正在测量每个窗口的帧间时间,对于两个窗口,这会降至30fps,三个窗口则为20fps等等。为了调查这一说法,我创建了一个简单的测试程序(simple test program)。该程序创建N个窗口和N个线程,将每个窗口绑定到一个线程,请求每个窗口打开垂直同步,并报告帧速率。到目前为止,结果如下:
- Linux,X11,4.4.0 NVIDIA 346.47(2015-04-13)
- 无论打开多少个窗口,帧速率都为60fps。
- OSX 10.9.5(2015-04-13)
- 帧速率没有上限;SwapBuffers不会阻塞。
解决方法:只使用一个上下文,一个大的帧缓冲区
另一个我想到的想法是:只有一个OpenGL上下文和一个大的帧缓冲区,大小等于所有窗口的大小之和。每一帧,每个窗口在绘制前调用
glViewport
来设置它们各自的帧缓冲矩形。在所有绘制完成后,对唯一的OpenGL上下文进行swapBuffers()操作。
我即将调查这个解决方法是否可行。我有一些问题:
- 拥有如此大的帧缓冲区是否可行?
- 每帧多次调用
glViewport
是否可行? - 我使用的窗口库API是否允许我创建独立于窗口的OpenGL上下文?
- 如果窗口大小都不同,帧缓冲区会浪费空间吗?
glViewport的工作原理并非如此。缓冲区交换也不是这样工作的。每个窗口都有一个帧缓冲区,您无法使它们共享一个帧缓冲区。缓冲区交换是针对每个窗口的帧缓冲区进行的,并且上下文一次只能绑定到单个窗口。这是在操作系统级别上的限制,而不是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
根据问题提出者的说法,启用V-Sync后,SwapBuffers将会同步到最慢的显示器上,而在更快的显示器上的窗口会变慢。看起来他们只在Microsoft Windows上进行了测试,而且不清楚这个解决方案是否适用于其他平台。此外,再次有许多来源告诉我,在draw()例程中使用makeContextCurrent()速度太慢。另外,这似乎也不符合EGL的规范。为了允许另一个线程调用eglSwapBuffers(),您必须使用eglMakeCurrent(NULL),这意味着您的eglSwapBuffers现在应该返回EGL_BAD_CONTEXT。所以,我的问题是:有没有最佳方法来解决带有垂直同步的多窗口应用程序的问题?这似乎是一个常见的问题,但我还没有找到令人满意的解决方案。
与此问题类似:如何同步多个OpenGL窗口到垂直同步?,但我想要一个跨平台的解决方案 - 或者至少为每个平台提供一个解决方案。
以及这个问题:如何在多个OpenGL画布和垂直同步中使用SwapBuffers()?,但实际上这个问题与Python无关。