OpenGL in Qt 5.1 – Part 1

This blog is the first in a series that will show how to use OpenGL with Qt 5. In this article, we shall take a very quick look at Qt’s historical support for OpenGL and then go on to describe the first batch of new features coming in Qt 5.1 that will enhance Qt’s OpenGL support. Upcoming blogs in this series will describe more features and show some simple examples of how easy it is to use Qt for OpenGL development.

A (very) brief history of Qt and OpenGL

Qt has a long history of supporting drawing with OpenGL. Most Qt developers are aware of QGLWidget and maybe the various incarnations of the OpenGL-based paint engines. These allow drawing with raw OpenGL or with the convenience of the QPainter API respectively. In addition to these, Qt also offers some helpful wrappers around other OpenGL object types such as QGLShaderProgram, QGLFramebufferObject, QGLBuffer etc.

During the design of Qt 5, these QGL* classes were marked as “Done” and shiny new QOpenGL* replacements were introduced and placed directly into the QtGui library. The reason for these changes is that the new Qt Quick 2 renderer is based upon OpenGL and so is now a core part of Qt’s graphical offerings. Also, the new QOpenGL* classes can be used as direct replacements for the older QGL* classes. For new code, the QOpenGL* classes from QtGui are recommended.

Qt 5.0 exposed basically the same subset of OpenGL functionality as Qt 4.8 did, which is pretty much the intersection of OpenGL 2 and OpenGL ES 2. This also happens to be the functionality needed by Qt Quick 2. In addition to the Qt 4.8 functionality, Qt 5.0 also makes it very easy to create native windows and OpenGL contexts on any platform. No more messing around with the idiosyncrasies of various platforms to create a context that can support the OpenGL Core profile. Just use QOpenGLContext and save yourself from some grey hairs!

With Qt 5.1, we are beginning the adventure of exposing more and more OpenGL functionality so as to make using OpenGL with Qt simple, elegant and hopefully fun! To this end, KDAB has invested significant resources into pushing the boundaries with Qt and OpenGL.

Functions, functions everywhere!

OpenGL is, to put it bluntly, a bit of a pain to work with on some platforms. One of the major reasons for this pain is the need to resolve entry point addresses dynamically at runtime rather than the build time linker being able to do so. For example, on Microsoft Windows, the address of any function introduced since OpenGL 1.1 must be resolved at run-time. That is nearly all of functions used in a modern OpenGL application!

To assist with this Qt has provided a couple of helpful utilities: QOpenGLContext::getProcAddress() and QOpenGLFunctions. The former can be used to perform manual resolution of entry points, whilst the latter is a class that has member functions mapping to the common subset of functions in OpenGL 2 and OpenGL ES 2. These helpers are great, as far as they go. The issues are that QOpenGLFunctions is limited in what it exposes (the above mentioned subset of OpenGL 2 and ES 2) and manual resolving of entry points is extremely tedious and error-prone. Alternatively it is possible to use an external function resolver such as GLEW or GLee. However, it is often a pain to get these to play nicely with Qt in terms of header inclusion order etc.

Enter QOpenGLContext::versionFunctions()! This unassuming little function is your gateway to OpenGL entry point utopia 🙂 This function can be used to obtain a pointer to an object with member functions for every function in the requested OpenGL version and profile. Let’s take a look at a simple example. Say we have created a QWindow sub-class on which to render and we now wish to create an OpenGL 4.3 Core profile context and resolve every function. To do so is as simple as:

Window::Window( QScreen* screen ) : QWindow( screen ), { // Tell Qt we will use OpenGL for this window setSurfaceType( OpenGLSurface ); // Specify the format and create platform-specific surface QSurfaceFormat format; format.setDepthBufferSize( 24 ); format.setMajorVersion( 4 ); format.setMinorVersion( 3 ); format.setSamples( 4 ); format.setProfile( QSurfaceFormat::CoreProfile ); setFormat( format ); create(); // Create an OpenGL context m_context = new QOpenGLContext; m_context->setFormat( format ); m_context->create(); // Make the context current on this window m_context->makeCurrent( this ); // Obtain a functions object and resolve all entry points // m_funcs is declared as: QOpenGLFunctions_4_3_Core* m_funcs m_funcs = m_context->versionFunctions(); if ( !m_funcs ) { qWarning( "Could not obtain OpenGL versions object" ); exit( 1 ); } m_funcs->initializeOpenGLFunctions(); }

From that point on we can simply use member functions on the QOpenGLFunctions_4_3_Core object. For example:

// Set a vertex attribute divisor // Used with instanced rendering // Introduced in OpenGL 3.3 m_funcs->glVertexAttribDivisor( pointLocation, 1 ); // Dispatch processing via a compute shader // Introduced in OpenGL 4.3 m_funcs->glDispatchCompute( 512 / 16, 512 / 16, 1 );

As you can see, this easily puts all OpenGL functionality into your hands on any platform that supports it. Moreover, the QOpenGLContext, QOpenGLFunctions_4_3 and similar classes try hard to minimise the amout of work done in resolving functions by sharing backends containing the actual function pointers. In addition, this approach automatically takes care of context-specific function addresses (e.g. if using multiple threads and contexts or multiple GPUs). The code for these classes is generated automatically by a helper utility so it is easy to update as new OpenGL versions are released.

OpenGL Extensions

OpenGL also has a well-known extension mechanism that allows vendors to introduce new or experimental functionality and API to see if it is useful and well thought out. Unfortunately, if an extension introduces new functions then these too need to be resolved as for other OpenGL functions as described above.

There are two stages to being able to use an OpenGL extension:

Check if the implementation supports the extension If the extension introduces new API, resolve the entry points

Qt helps with both of these steps. To check if an extension is supported we can use QOpenGLContext::hasExtension(). Alternatively to get the complete list of supported extensions use OpenGLContext::extensions():

// Query extensions QList extensions = m_context->extensions().toList(); std::sort( extensions ); qDebug() << "Supported extensions (" << extensions.count() << ")"; foreach ( const QByteArray &extension, extensions ) qDebug() << " " << extension;

For the second part, we would need to use our old friend QOpenGLContext::getProcAddress(). New in Qt 5.1 though is the module QtOpenGLExtensions. This module builds as a static library and contains a class for every OpenGL extension in the Khronos registry that introduces new API. To use an OpenGL extension we can therefore use code similar to this:

// Check if extension is supported if ( !m_context->hasExtension( QByteArrayLiteral( "GL_ARB_instanced_arrays" ) ) qFatal( "GL_ARB_instanced_arrays is not supported" ); // Create instance of helper class and resolve functions QOpenGLExtension_ARB_instanced_arrays* m_instanceFuncs = new QOpenGLExtension_ARB_instanced_arrays(); m_instanceFuncs->initializeOpenGLFunctions(); // Call an extension function m_instanceFuncs->glVertexAttribDivisorARB( pointLocation, 1 );

As with the core OpenGL functions, the code for the extensions is generated for easy updates in the future.

Read Part 2…