Threaded OpenGL in 4.8

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.

 

 


Blog Topics:

Comments