Using SwapBuffers() with multiple OpenGL canvases and vertical sync?
Asked Answered
C

1

1

I have a GUI written using wxPython that contains two GLCanvases, a 'display' canvas and a 'preview' canvas onto which I am drawing some very simple geometry using PyOpenGL. The 'preview' and 'display' canvases display the contents of the same framebuffer:

enter image description here

I need both of these canvases to be updated synchronously at a consistent framerate with no tearing.

So far I have just been calling

self.SetCurrent()
# draw stuff...
self.SwapBuffers()

for both the preview and display canvases within my rendering loop. This works reasonably well provided that I disable vsync in my driver settings.

However, if I enable vsync my framerate drops to ~30Hz, half the vertical refresh rate of my display. Presumably this is because the execution of the rendering loop stalls at each SwapBuffers() call waiting for the next vertical refresh - if I comment out one of the two SwapBuffers() calls, my framerate goes back up to 60Hz, but then of course only one of my canvases gets updated.

What I'd really like is for both of my canvases to be updated synchronously, once per vsync interval. Is there any way that I can achieve this using the wx.glcanvas.GLCanvas class?

Crim answered 11/9, 2013 at 17:44 Comment(7)
I explained this in another similar question recently. You can do multi-threaded rendering with each canvas having its own thread, but this adds a lot of complexity. Alternatively, you can use VSYNC on one canvas, swap buffers and then immediately afterwards swap buffers on the other one (without VSYNC). The vast majority of the time you will not see any tearing on the other canvas, but it might have a 1 frame latency.Faucal
That sounds like it would work, but a big part of the problem that I'm having is that the SwapBuffers() method of a wx.glcanvas.GLCanvas object seems to implicitly block execution until the next vsync. I'm not currently aware of any way that I can call SwapBuffers without also waiting for the next vertical refresh, aside from just disabling vsync entirely in my driver settings.Crim
@ali_m: What Andon implies here is, that you use {wgl|glX}SwapInterval to set the swap interval for the first, synching SwapBuffers to 1, and then right after the synching SwapBuffers returns set the swap interval to 0 and do the other SwapBuffers.Concrescence
@AndonM.Coleman: I recently dealt with a similar problem, once again. As it turns out, according to the specification, the SwapBuffers call should never block on VSync. Instead the next (implicit) OpenGL synchronization point after a synched SwapBuffers shall be stalled until the VSync happened. In your typical rendering loop this means, that every, but the first SwapBuffers call may effectively stall. Here's a small experiment: Measure the actual time the execution of SwapBuffers takes. For bonus points measure the time from start of rendering to SwapBuffer return time.Concrescence
@AndonM.Coleman Unfortunately I can't find anything in the wxPython documentation relating to swap intervals. I'll look into whether it's possible to go down the multithreaded rendering route.Crim
@ali_m: I think wxPython is probably just a thin wrapper around OpenGL. If you know the underlying window system, you can use {wgl|glX}SwapInterval like datenwolf said. I just figured wx was managing your OpenGL context for you so it would have a more portable way of doing the same thing.Faucal
@Concrescence it took me a little while to figure out how to implement it in PyOpenGL, but your original suggestion to use glXSwapInterval to reset the swap interval on the fly works beautifully. If you write it up as an answer I'd be happy to accept.Crim
C
0

What Andon implied in his comment is, that you use {wgl|glX}SwapInterval to set the swap interval for the first, synching SwapBuffers to 1, and then right after the synching SwapBuffers returns set the swap interval to 0 and do the other SwapBuffers.

Concrescence answered 11/9, 2013 at 22:15 Comment(2)
For anyone else who might want to do this using PyOpenGL, the call is from OpenGL import GLX; GLX.glXSwapInterval{SGI|MESA}(0) (the SGI function worked for me with a GeForce GT 630)Crim
Just keep in mind that GLX is specific to X11/GLX. If you're running on Windows it's something along the line of from OpenGL import wgl; wgl.wglSwapInterval…Concrescence

© 2022 - 2024 — McMap. All rights reserved.