问题描述
我正在使用OpenGL创建图形用户界面应用程序,其中可以有任意数量的窗口-多文档界面"样式.
I am creating a graphical user interface application using OpenGL in which there can be any number of windows - "multi-document interface" style.
如果有一个窗口,则主循环可能如下所示:
If there were one window, the main loop could look like this:
- 处理事件
- draw()
- 交换缓冲区(vsync导致此阻塞直到垂直监视器刷新为止)
但是,当有3个窗口时,请考虑主循环:
However consider the main loop when there are 3 windows:
- 每个窗口处理事件
- 每个窗口draw()
- 窗口1交换缓冲区(一直阻塞到vsync)
- (一段时间后)窗口2个交换缓冲区(直到vsync才阻塞)
- (稍后再)窗口3个交换缓冲区(直到vsync才阻塞)
糟糕,现在正在以适当帧速率的1/3渲染应用程序的一帧.
Oops... now rendering one frame of the application is happening at 1/3 of the proper framerate.
一种解决方法是仅打开一个启用了vsync的窗口,而将其余的所有窗口均关闭vsync.首先在vsync窗口上调用swapBuffers()并绘制该窗口,然后在其余窗口上分别绘制其余窗口和swapBuffers().
One workaround is to have only one of the windows with vsync turned on, and the rest of them with vsync turned off. Call swapBuffers() on the vsync window first and draw that one, then draw the rest of the windows and swapBuffers() on each one.
此解决方法在大多数情况下看起来都不错,但这并非没有问题:
This workaround will probably look fine most of the time, but it's not without issues:
- 只有一个窗口很特别
- 比赛条件仍然可能导致屏幕撕裂
- 某些平台会忽略vsync设置并强制将其启用
- 我读到切换绑定了OpenGL上下文是一项昂贵的操作,应该避免.
由于每个线程可以绑定一个OpenGL上下文,所以答案可能是每个窗口只有一个线程.
Since there can be one OpenGL context bound per thread, perhaps the answer is to have one thread per window.
不过,我仍然希望GUI是单线程的,因此3窗口情况的主循环如下所示:
I still want the GUI to be single threaded, however, so the main loop for a 3-window situation would look like this:
(对于每个窗口)
- 锁定全局互斥锁
- 处理事件
- draw()
- 解锁全局互斥锁
- swapBuffers()
这项工作有效吗?此其他问题表示不会:
Will this work? This other question indicates that it will not:
为了调查这一说法,我创建了一个简单的测试程序.该程序创建N个窗口和N个线程,每个线程绑定一个窗口,请求每个窗口启用vsync,然后报告帧速率.到目前为止,结果如下:
To investigate this claim I created a simple test program. This program creates N windows and N threads, binds one window per thread, requests each window to have vsync on, and then reports the frame rate. So far the results are as follows:
- Linux,X11、4.4.0 NVIDIA 346.47(2015-04-13)
- 无论打开多少窗口,帧速率均为60fps.
- Linux, X11, 4.4.0 NVIDIA 346.47 (2015-04-13)
- frame rate is 60fps no matter how many windows are open.
- 帧速率未设置上限;交换缓冲区没有阻塞.
我想到的另一个想法是:只有一个OpenGL上下文,一个大的帧缓冲区,所有窗口的大小放在一起.
Another idea I thought of: have only one OpenGL context, and one big framebuffer, the size of all the windows put together.
每个帧,每个窗口在绘制之前都调用
glViewport
设置帧缓冲区的各自矩形.Each frame, each window calls
glViewport
to set their respective rectangle of the framebuffer before drawing.完成所有绘制后,在唯一的OpenGL上下文中使用swapBuffers().
After all drawing is complete, swapBuffers() on the only OpenGL context.
我将调查此替代方法是否有效.我有一些问题:
I'm about to investigate whether this workaround will work or not. Some questions I have are:
- 拥有这么大的帧缓冲区是否可以?
- 每帧多次调用
glViewport
可以吗? - 我正在使用的窗口库API甚至允许我创建独立于Windows的OpenGL上下文吗?
- 如果窗口大小各不相同,则会浪费帧缓冲区中的空间吗?
- Will it be OK to have such a big framebuffer?
- Is it OK to call
glViewport
multiple times every frame? - Will the windowing library API that I am using even allow me to create OpenGL contexts independent of windows?
- Wasted space in the framebuffer if the windows are all different sizes?
Camilla Berglund rel ="noreferrer"> GLFW 说:
Camilla Berglund, maintainer of GLFW says:
解决方法:只有一个上下文
此问题表示该问题算法可能会起作用:
Workaround: Only One Context
This question indicates that this algorithm might work:
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
根据提问者问题,
看来他们只是在Microsoft Windows上测试了这一点,尚不清楚该解决方案是否可以在任何地方都可以使用.
It looks like they only tested this on Microsoft Windows and it's not clear that this solution will work everywhere.
很多消息再次告诉我,makeContextCurrent()太慢,无法包含在draw()例程中.
Also once again many sources tell me that makeContextCurrent() is too slow to have in the draw() routine.
看起来这也不符合EGL.为了允许另一个线程进入
eglSwapBuffers()
,您必须先eglMakeCurrent(NULL)
,这意味着您的eglSwapBuffers
现在应该返回EGL_BAD_CONTEXT
.It also looks like this is not spec conformant with EGL. In order to allow another thread to
eglSwapBuffers()
, you have toeglMakeCurrent(NULL)
which means youreglSwapBuffers
now is supposed to returnEGL_BAD_CONTEXT
.所以,我的问题是:解决启用vsync的多窗口应用程序的最佳方法是什么?这似乎是一个常见问题,但我尚未为其找到令人满意的解决方案.
So, my question is: what's the best way to solve the problem of having a multi-windowed application with vsync on? This seems like a common problem but I have not yet read a satisfying solution for it.
类似于此问题:将多个OpenGL窗口同步到vsync ,但我想要一个与平台无关的解决方案-或至少为每个平台提供一个解决方案.
Similar to this question: Synchronizing multiple OpenGL windows to vsync but I want a platform-agnostic solution - or at least a solution for each platform.
这个问题:使用SwapBuffers( )具有多个OpenGL画布和垂直同步?,但实际上这个问题与Python无关.
And this question: Using SwapBuffers() with multiple OpenGL canvases and vertical sync? but really this problem has nothing to do with Python.
推荐答案
不,它不会阻塞.缓冲区交换调用 可能立即返回,而 not 阻止.但是,它所做的是插入一个同步点,以使更改后备缓冲区的命令的执行被延迟,直到发生缓冲区交换为止. OpenGL命令队列的长度有限.因此,一旦命令队列已满,进一步的OpenGL调用将阻塞程序,直到可以将其他命令推入队列.
No, it doesn't block. The buffer swap call may return immediately and not block. What it does however is inserting a synchronization point so that execution of commands altering the back buffer is delayed until the buffer swap happened. The OpenGL command queue is of limited length. Thus once the command queue is full, futher OpenGL calls will block the program until further commands can be pushes into the queue.
此外,缓冲区交换不是 OpenGL操作.这是图形/窗口系统级别的操作,独立于OpenGL上下文而发生.只需看一下缓冲区交换函数:它们采用的唯一参数是drawable(= window)的句柄.实际上,即使您在单个可绘制对象上运行多个OpenGL上下文,您也只能交换一次缓冲区.并且您可以完全不用在可绘制对象上使用OpenGL上下文.
Also the buffer swap is not an OpenGL operation. It's a graphics / windowing system level operation and happens independent of the OpenGL context. Just look at the buffer swap functions: The only parameter they take are a handle to the drawable (=window). In fact even if you have multiple OpenGL contexts operating on a single drawable, you swap the buffer only once; and you can do it without a OpenGL context being current on the drawable at all.
所以通常的方法是:
' 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绘图命令可能会停顿.
Since the buffer swap does not block, you can issue all the buffer swaps for all the windows, without getting delayed by V-Sync. However the next OpenGL drawing command that addresses a back buffer issued for swapping will likely stall.
一种解决方法是使用发生实际绘制的FBO,并将其与在交换缓冲区循环之前将FBO blit到后缓冲区的循环相结合:
A workaround for that is using an FBO into which the actual drawing happens and combine this with a loop doing the FBO blit to the back buffer before the swap buffer loop:
' 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()
这篇关于多个OpenGL上下文,多个窗口,多线程和垂直同步的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!