【问题标题】:Multiple OpenGL contexts, multiple windows, multithreading, and vsync多个 OpenGL 上下文、多个窗口、多线程和垂直同步
【发布时间】:2015-06-19 11:23:15
【问题描述】:

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

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

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

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

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

糟糕...现在以正常帧速率的 1/3 呈现应用程序的一帧。

解决方法:实用程序窗口

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

这种解决方法在大多数情况下可能看起来不错,但也不是没有问题:

  • 一窗特别不雅
  • 竞争条件仍可能导致屏幕撕裂
  • 某些平台会忽略垂直同步设置并强制其开启
  • 我了解到,切换绑定的 OpenGL 上下文是一项昂贵的操作,应该避免。

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

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

但是,我仍然希望 GUI 是单线程的,因此 3 窗口情况的主循环如下所示:

(对于每个窗口)

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

这行得通吗?这个other question 表示它不会:

事实证明,窗户正在互相“战斗”:看起来像 SwapBuffers 调用是同步的并相互等待,甚至 尽管它们在不同的线程中。我正在逐帧测量 每个窗口的时间和两个窗口,这下降到 30 fps,与 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设置各自的framebuffer矩形。

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

我将调查此解决方法是否可行。我的一些问题是:

  • 有这么大的帧缓冲区可以吗?
  • 可以每帧多次调用glViewport吗?
  • 我使用的窗口库 API 是否允许我创建独立于窗口的 OpenGL 上下文?
  • 如果窗口大小不同,会浪费帧缓冲区中的空间吗?

Camilla BerglundGLFW 的维护者说:

这不是 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

根据提问者的说法,

启用垂直同步后,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 无关。

【问题讨论】:

  • 所以你的意思是,根据你的实验,每个线程(和窗口)一个上下文可以正常工作?

标签: multithreading opengl


【解决方案1】:

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

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

另外,缓冲区交换不是 OpenGL 操作。这是一个图形/窗口系统级操作,独立于 OpenGL 上下文发生。只需查看缓冲区交换函数:它们采用的唯一参数是可绘制对象 (=window) 的句柄。事实上,即使您在单个可绘制对象上运行多个 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()

【讨论】:

  • 感谢您提供这个非常有启发性的答案。相关问题:IRC 上的人一直告诉我ctx.make_current(w) 是“非常慢”,因此我应该在每个上下文中使用一个线程。这真的是个问题吗?
  • 我用它来测试你的伪代码:github.com/andrewrk/opengl-multi-window-test 在 OSX 和 Windows 上,按预期工作。在使用 NVIDIA 346.47 驱动程序的 Linux 上,我得到了60 / window_count fps。
  • 据一位在 IRC 上为 NVIDIA 工作的人说,该规范不要求 swap_buffers() 是异步的,获得所需行为的最可靠方法是每个窗口有一个线程。跨度>
  • @andrewrk:实际上规范可以用任何一种方式解释。确切的措辞是:The client must synchronize the threads that perform the swap and the rendering, using some means outside the scope of GLX, to insure that each new frame is completely rendered before it is made visibleSubsequent OpenGL commands can be issued immediately, but will not be executed until the buffer swapping has completed, typically during vertical retrace of the display monitor。除此之外,GLX 规范什么也没说。众所周知,NVidia 以有利于其实施的方式解释 OpenGL 规范。
  • @Maypeur:给我看一下明确说明的规范。相信我,当我发现规范没有对 SwapBuffers 时序行为做出明确声明时,我也很惊讶。我确实在上面测量了这些东西……不是用软件定时器,而是用示波器探测到监视器的 V-Sync 信号,并通过注入 SwapBuffer 代码的单个 I/O 指令切换 GPIO 引脚(可以允许用户空间程序执行直接 I/ O 写入特定地址,请参阅ioperm(2))。
【解决方案2】:

感谢@andrewrk 的所有这些研究,我个人就是这样做的:

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

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

对于每一帧 用于反转每个窗口(最后启用 vsync 的窗口)。 wglMakeCurrent(hdc, commonContext);
画。 交换缓冲区

通过这种方式,我实现了垂直同步,所有窗口都基于相同的垂直同步。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 1970-01-01
    • 2012-12-13
    • 1970-01-01
    • 2020-01-07
    • 2011-07-28
    • 2013-06-19
    相关资源
    最近更新 更多