SwapBuffers() in another thread

I know, I know. This is a topic that has been discussed a lot. I’ve never seen a solution suggested like the one I’m trying to do though, so bare with me.

In the past I did all the GL calls and SwapBuffers() calls in a single thread and everything was great. However my windows were actually created in another thread so that all the windows event and the message pump is handled by that other thread.
When Kepler cards came along under certain conditions (certain Quadro profiles + Mosaic) my SwapBuffers() calls would hang. Nvidia told me this was because DeviceContexts are actually thread-affine, and GDI functions should only be called on them in the threads that created them. Quote from Nvidia “Calling GDI functions using an HDC from the non-hdc-affine thread has always been wrong (OpenGL is a GDI api and GDI objects like HDCs are thread-affine), but the failure cases are in general hard to repro.”

So I moved my SwapBuffers() to the window’s thread with it’s own GL context, and using GLsync objects and a renderbuffer I do all my rendering in the main thread still, and blit a framebuffer onto the DeviceContext before doing the SwapBuffers() in the window’s threads. This works, but seems to have a large performance impact.

My new idea is to actually only do the SwapBuffers() in the window’s thread, and not have a 2nd GL context at all. My theory is that since SwapBuffers() is actually a GDI function, and doesn’t care about GL at all.
So I’ve tried it out and it actually works. I now do all my rendering in my main thread with a single context, including blitting the final image to the DC, then I call SwapBuffers() from the window’s thread. No GL context is ever made current in the window’s thread. I had update issues to start, but as a test I used glFinish before I sent the message to the window’s thread telling it to swap the buffers, and the update issues went away, which makes sense.

The question is:

  1. Am I just lucky that this is working, and will it not work on other random drivers/GPUs?
  2. How do I properly synchronize this method, so SwapBuffers() happens after the blit is finished, and I don’t start blitting before the previous SwapBuffers() has finished? Since I don’t have a 2nd GL context in the window’s thread I can’t use GLsync objects there, although I can use them in the main thread. For the 2nd part, will attempting to blit to the DC from the main thread implicitly synchronize with the SwapBuffers()?

Thoughts? I’m also asking Nvidia this and will respond with anything they give me.

I don’t quite understand your original problem. I create all my windows in the main thread but create their OpenGL context in the render thread. I handle the GUI events in the main thread and the rendering in the render thread.

As for you questions, I don’t know the answer to the first and without a glFinish or GLsyn how you can know when to issue the swap.

I do something similar to you, but not quite. It works, but the performance is far worse than if 100% of the OpenGL calls occur in a single thread. (22 fps vs. 30 fps in one sample file I have)

All my calls are in 1 thread. In the render thread, each timestep, I loop through each window calling its render function. In fact we only render a window if some data has changed.

My pseudo code is like this


GUI thread

check for mouse event (or keyboard etc)

update camera or other data for that window
flag window as dirty

loop

Render Thread

Is is time to render? 

for each window
  has something changed?, yes render

loop


[QUOTE=MalcolmB;1252603]
2. How do I properly synchronize this method, so SwapBuffers() happens after the blit is finished, and I don’t start blitting before the previous SwapBuffers() has finished?[/QUOTE]
Use a synchronisation object such as an event object or a condition variable. After the rendering thread has sent the message to the main thread, it should wait upon the object. The main thread should signal the object after SwapBuffers() has completed.

[b]tonyo_au[/b] : Which thread does the swap buffers occur in for each window? If it’s in the Render Thread then that is technically illegal according to Nvidia, although I’ve only ever found one case where it doesn’t work. However since this particular case matters to me I need to have a solution that has the SwapBuffers() occur in your equivalent of the ‘GUI thread’.

[[b]GClements[/b]](http://www.opengl.org/discussion_boards/member.php/28966-GClements) 	 : Is it guaranteed that the buffer swap will have occurred when that function returns? Or is it possible it'll happen sometime later in the driver's thread? If it's guaranteed then yes your suggestion is the solution. I'll try it out and see if anomalies show up. Thanks

I suppose it depends upon what you mean by “occurred”.

It’s implicit that any rendering commands performed after SwapBuffers() will occur on a different buffer to those performed before it (assuming that the context isn’t single-buffered). If triple-buffering is enabled, the previous back buffer won’t necessarily be displayed immediately, but it shouldn’t be affected by subsequent rendering commands.

However: I suppose that it’s possible that the assignment of front/back buffers is per device context, in which case there’s no guarantee that calling SwapBuffers() for one device context will necessarily swap them for another device context, let alone synchronously.

Also, the comment about not using a device context from a thread other than the one which created it contradicts what Microsoft sayshere, which is that device contexts can be shared between threads so long as access is synchronised.

Yes, the requirement given from that Nvidia dev surprised me also, and does seem to contradict that article and other articles I’ve found. Trying to get reconfirmation of this from them, but I’m deferring to them for now.

So far my tests with this new solution have been positive. My performance is back up on par with the fully single threaded solution for the test cases I’ve tried, and I haven’t seen any visual artifacts yet. I’m doing SwapBuffers() in the window’s thread, with no GL context used at all. Still need to use a high speed camera to ensure all my frames are getting shown on the display correctly. Need to test this on more GPUs/driver versions also.

Which thread does the swap buffers occur in for each window?

I swap in the render thread. Are you saying on 600 series nVidia cards this will fail? I have been using this code for a couple of years on a range of ATI and nVidia cards but I don’t have any 600 cards.

What card is causing the problem?

If I remember correctly the issue was with a Quadro K5000 when using the Video Editing profile + Mosaic. It’s specific case where it stops working, it works in 99% of cases the way you are currently doing it. But I need it to work in this 1% case.

Another update: running at 120hz on a 120hz monitor, all the frames get correctly shown to the monitor using this method (testing by recording with a high speed camera).
So, interesting results…

Resuscitating this old thread “thanks” to Microsoft.

I’ve been doing for years what @MalcolmB describes at the beginning of their post, namely all GL calls and SwapBuffers() calls happening in a “rendering” thread, which is different than the “message pump/GUI” thread, in which the HDC for the OpenGL part of the user interface gets created. This has been working fine on Win 7, 8, and 10.

Now enter Windows 11. On Windows 11 I’ve noticed that even though the OpenGL/rendering/SwapBuffers sub-system still works perfectly, refreshes to other non-OpenGL HDCs in the same user interface stutter consistently. To explain this better, in my main frame I have a section managed by OpenGL (the “OpenGL canvas”, whose content is drawn by the “rendering thread” and swap-buffered as explained earlier), and another section drawn with “traditional” GDI APIs (e.g. OnPaint handlers drawing line primitives, etc.), happening in the “message pump/GUI” thread, obviously. Now, even though I’m re-painting those non-OpenGL HDCs exactly 64 times per-second, their visible content is only refreshed every 10-20 frames. I’ve confirmed that turning off SwapBuffers() calls from the rendering thread makes each re-paint immediately visible.

So somehow it smells like NVidia’s “failure cases that are in general hard to repro” became more concrete in Windows 11. It seems to me that invoking SwapBuffers() on the OpenGL canvas HDC from a separate thread somehow messes with refreshes to other HDC’s that are happening on the main thread.

Rather than blaming Microsoft for this, I’m inclined to say that this is the day of reckoning for having sinned for years with SwapBuffers on the different thread :slight_smile:

So it seems to me now that the only way out of this is to follow @MalcolmB’s advice of issuing the SwapBuffers() call from the “message pump/GUI” thread. I’m planning to use @GClements’ advice, namely use a synchronization object to make sure that the rendering thread waits for SwapBuffers() to be completed on the GUI thread.

@MalcolmB, are you still happy with the rendering thread synchronizing with the GUI thread on the SwapBuffers call? Any gotcha’s after 9 years? Anyone else doing it this way? (realizing I’m quite hopeful here, as the thread is 9 years old!)