Threaded OpenGL in 4.8

Published Friday June 3rd, 2011
17 Comments on Threaded OpenGL in 4.8
Posted in OpenGL, Painting, Threads

If you have used the OpenGL module in Qt then at one point or another you may have found yourself  wanting to execute some of that OpenGL in a separate thread. The underlying OpenGL implementation itself is (mostly) re-entrant so there was really nothing stopping you. In fact, waaay back in the 6th edition of Qt Quarterly we even had an article describing how you could do this. This was fine for people spending most of their time writing pure OpenGL, but what about if you wanted to use some of the Qt convenience classes in a separate thread? Unfortunately that wasn’t possible because those Qt classes were not thread-safe. With Qt 4.8, this has changed and we have now added support for a few of the most common scenarios. To use the new functionality on X11 you need to enable the Qt::AA_X11InitThreads application attribute to ensure that the underlying calls to GLX are thread-safe, but for Windows and Mac OS X it should work out of the box.

 

Buffer swapping thread

Depending on your driver and GPU, the call to swapBuffers() can sometimes be an expensive function call (especially on embedded processors). In many cases, this is the function that tells the GPU to take all of your render commands and execute them to render the current frame. If this operation is synchronous then your main thread is blocked while the GPU is doing its thing. That’s unfortunate because your current thread has much better things to do than wait for the GPU. For example, it could return to the event loop and process some user input, network traffic or advance the next scene of an animation. The solution to this in 4.8 is to have a separate thread whose sole purpose in life is to wait for the GPU by calling swapBuffers. In practice you would render everything as normal in your main thread, but instead of calling swapBuffers(), you would call doneCurrent() on the current GL context. You would then notify the swapping thread that you have finished rendering and it would call makeCurrent() to make the context active and then call swapBuffers(). The swapping thread should then call doneCurrent() and notify the main thread that it has finished. Note, there is a context switch here and this overhead may actually be slower than if the main thread had just waited for the GPU to finish so it’s important that you test this to see if you actually gain anything.

 

Texture uploading thread

Uploading many (or large) textures is typically an expensive operation because of the amount of data being pushed to the GPU. Again, this is one of those operations that can unnecessarily block your main thread. In 4.8 you can solve this problem by creating a pair of shared QGLWidgets. One of the widgets is made current in a separate thread, but is never made visible on screen. The main thread informs the uploading thread which images to upload and the uploading thread simply calls bindTexture() on each of these images and then notifies the main thread when each one has finished so it can be drawn to screen.

 

QPainter thread

In 4.8 it is now possible to use QPainter in a separate thread to render to a QGLWidget, QGLPixelBuffer and QGLFrameBufferObject assuming you are using the OpenGL [ES] 2.0 paint engine. It’s important to note that QGLWidget does not get moved to a secondary thread (it’s a QWidget afterall). However, since its context will be used elsewhere, it is necessary to call doneCurrent() on the QGLWidget to release the context from the main thread. Next you should create a separate QObject subclass that can be moved to the secondary painter thread. This QObject will make the QGLWidget’s context current in the painter thread and can then open a QPainter on the target and start painting. If you are using QGLWidget as the target, then you additionally need to set the “Qt::WA_PaintOutsidePaintEvent” attribute on the QGLWidget. Additionally, you will need to subclass and reimplement the resizeEvent() and paintEvent() functions of QGLWidget. The default implementation of these will try to make the QGLWidget’s context current and since these functions are called from the main thread (where the widget lives) we don’t want that to happen since we’re using the context in the painter thread. In your reimplementation of these functions, you should notify the QObject subclass that does the rendering so it can go and resize the viewport or repaint the scene if needed. The result of all this can be seen below in a new demo called glhypnotizer which runs several MDI subwindows containing a QGLWidget rendering from a separate thread.

 

Screenshot of demos/glhypnotizer in Qt 4.8

 

Do you like this? Share it
Share on LinkedInGoogle+Share on FacebookTweet about this on Twitter

Posted in OpenGL, Painting, Threads

17 comments

This is great. We’ve been wanting to do threaded OpenGL in Avogadro from the beginning and this will make it much easier. I definitely appreciate the Qt::AA_X11InitThreads flag, since it will remove some #ifdef’s in our code.

That’s cool. I’d also love to see an example involving GV with a QGLWidget viewport.

DavidB says:

Jason. I did not find the mentioned example in the git repository.

Jason Barron says:

@DavidB: It’s still working it’s way through the CI gates. Apologies for that, but it should be merged in soon.

Robin Lobel says:

Nice update, it would have been great to have this when I coded my multiple glcontext engine with streaming.
While you’re updating GL, please include the other glext like multitexturing, multiple render target, float textutes, etc.. It would be handy to have this right into Qt rather than playing with the GL headers/declarations/function calls.

Gunnar Sletta says:

Robin: something like this http://doc.qt.nokia.com/4.8-snapshot/qglfunctions.html ?

Robin Lobel says:

That’s a good step forward 🙂

Tegra3 says:

I think it will be very good for multicore CPU,such as tegra2

Jobs says:

To avoid context switch, the jobs should be done inside kernel(GPU driver). Just like Windows and Mac OS X did. Do we need this for wayland??

SumWon says:

Looks like this example needs some protection against (child) window resizes while the thread is busy painting. Causes PowerVR driver crashes if you try to resize the children on iOS…

Lodorot says:

What about threaded OpenGL in 4.7? Can you use shared QGLContextes within different threads at the same time (means makeCurrent() and currentContext()) if the creation and deleting is done whin the gui thread?

Does threaded OpenGL in 4.8 also mean you can create and delete shared QGLContexts within different threads? The whole QGLContextGroup stuff adding, removing and cleaning up e.g. in ~QGLContext doesn’t seems to be very thread safe.

Gunnar Sletta says:

Lodorot: In 4.7 and earlier versions of Qt 4.x it is possible to create QGLWidgets on the GUI thread, do doneCurrent() there, then makeCurrent() them in a background thread. Sharing between the QGLContexts of these QGLWidgets will work as long as you stick to non-Qt OpenGL API’s. Cleanup must be done on the GUI thread. What was added in 4.8 was fixes to Qt’s OpenGL module to make the Qt aspects of OpenGL more thread safe.

NuShrike says:

Hm.. duplicates all of my Qt work in RedCineX. Some minor progress in Qt finally.

Lodorot says:

@Jason Barron is the thread safe Qt OpenGL module already available in the 4.8 branch at gitorious?

Luca says:

And what about this in Qt for Linux Embedded?

Jason Barron says:

@Luca: I believe this should also work for Qt for Embedded Linux, but again this is heavily dependent on if your driver supports it or not.

przemo_li says:

Small off-topic.

There are few helper classes inside Qt (eg. for shaders) that seam to be for OpenGL 3.2 (eg. there is not “tesselation controll” type).
Also Qt Creator have helpers for Vertex and Fragment shader files, but not others.

Will it be fixed? updated? unified?

PS Do I still have to use some 3rd party lib to load OpenGL function entry points from .dll, or there is something inside Qt for it now?

And as for threaded Qt-OpenGL:

Great work!

Commenting closed.

Get started today with Qt Download now