Qt Weekly #19: QOpenGLWidget

After Qt 5.3's introduction of QQuickWidget, Qt 5.4 adds QOpenGLWidget, the long-awaited replacement for the legacy QGLWidget. This widget retains the familiar initializeGL/resizeGL/paintGL API, while enabling full interoperability with other widgets in complex user interfaces.

Internally QOpenGLWidget is built on the same technology as QQuickWidget. Unlike QGLWidget, it is not a native window and will not turn any siblings or ancestors into native either. This avoids stacking, clipping, focus and performance issues and is expected to behave identically across all supported platforms.

Benefits for Embedded

QOpenGLWidget also brings the possiblity of having QWidget-based interfaces and one or more OpenGL widgets on embedded hardware without a windowing system, using the eglfs platform plugin. The dreaded EGLFS: OpenGL windows cannot be mixed with others error is now a thing of the past. It also helps Wayland. At the moment the Wayland platform plugin does not have subsurface support and so attempting to embed a QGLWidget or QWindow into a widget layout is futile: the result is always a standalone, separate window. With QOpenGLWidget this is not the case anymore.

Below is a photo of the qopenglwidget example running on an i.MX6 SABRE Lite board with Embedded Linux and Qt Enterprise Embedded:

qopenglwidget_imx6

The application might look familiar to some. This is in fact an enhanced port of the old hellogl_es2 example, which has never actually worked in Qt 5 with the eglfs platform plugin, due to using QGLWidget as a child widget, which leads to creating multiple native windows. By simply changing the base class from QGLWidget to QOpenGLWidget, the problem disappears, and the application behaves identically across all desktop, mobile and embedded devices.

Benefits for Desktop

Of course, it is not just for OpenGL ES. On desktop platforms, OpenGL 3.x and 4.x, including core profiles, are fully supported. Unlike QGLWidget, which forced the usage of legacy and often incomplete utility classes like QGLFormat and QGLContext, QOpenGLWidget uses the modern equivalents from the QtGui module: QSurfaceFormat, QOpenGLContext and the other, modern QOpenGL classes.

The basic API is same as in QGLWidget. For example, a simple subclass could look like the following:

class Widget : public QOpenGLWidget, protected QOpenGLFunctions
{
public:
    Widget(QWidget *parent = 0);
    void initializeGL();
    void resizeGL(int w, int h);
    void paintGL();

private: QMatrix4x4 m_projection; };

Widget::Widget(QWidget *parent) : QOpenGLWidget(parent) { QSurfaceFormat format; format.setDepthBufferSize(24); setFormat(format); }

void Widget::initializeGL() { initializeOpenGLFunctions(); }

void Widget::resizeGL(int w, int h) { m_projection.setToIdentity(); m_projection.perspective(60.0f, w / float(h), 0.01f, 1000.0f); }

void Widget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); ... }

This should look quite familiar to those who have used QGLWidget before.

The usage of QOpenGLFunctions is not mandatory on most platforms, but it is highly recommended for applications intended to be cross-platform and future-proof. Those who find the inheritance-based approach annoying, can always get a current context's ready-to-be-used QOpenGLFunctions instance via QOpenGLContext::currentContext()->functions().

Advanced Topics

OpenGL 3.0+ and Core Profiles

Applications that are interested in modern OpenGL 3.x and 4.x features, can request a suitable context by setting the version, profile and possibly other settings in the QSurfaceFormat, just like they would do for a QWindow or QQuickView:

format.setVersion(4, 3);
format.setProfile(QSurfaceFormat::CoreProfile);

However, with Qt 5.4 there is a better way: The newly introduced setDefaultFormat() member function of QSurfaceFormat. This static function can be called at a suitable early stage during application initialization. It will then apply to all contexts (QOpenGLContext) and OpenGL windows and widgets (QWindow, QOpenGLWindow, QQuickWindow, QOpenGLWidget, ...) that are created afterwards. For example the following snippet ensures that all contexts request an OpenGL 4.3 core profile compatible context, and all windows request a depth and stencil buffer. The need for individual setFormat() calls for each and every context or window is thus eliminated:

    int main(int argc, char **argv)
    {
        QApplication app(argc, argv);
        QSurfaceFormat format;
        format.setVersion(4, 3);
        format.setProfile(QSurfaceFormat::CoreProfile);
        format.setDepthBufferSize(24);
        format.setStencilBufferSize(8);
        QSurfaceFormat::setDefaultFormat(format);
        ...
    }

Context Sharing

Advanced scenarios that require multiple contexts, potentially for use on multiple threads, are simplified too. For example, to generate or upload textures on a worker thread, we can simply do something like:

    QOpenGLContext *context = new QOpenGLContext;
    QOpenGLContext *shareContext = openglWidget->context();
    context->setFormat(shareContext->format());
    context->setShareContext(shareContext);
    context->create();
    context->moveToThread(workerThread);

With this initialization done on the gui thread, our new context can be made current on the worker thread and the two contexts can access each others textures.

Desktop applications that use multiple OpenGL widgets, possibly inside multiple top-level windows, will often wish to share resources like textures between these widgets. This is made easy with QOpenGLWidget:

  • For QOpenGLWidget instances that are placed into the same top-level window, sharing is implicit and always enabled.
  • For QOpenGLWidget instances that are placed into different top-level windows, the new application attribute Qt::AA_ShareOpenGLContexts is provided. Setting this attribute at the beginning of the application will make the contexts of all QOpenGLWidget and QQuickWidget instances sharing with each other without any further steps. No more manual setting up of share hierarchies, as was the case with QGLWidget.

Summary

With a replacement for QGLWidget, the QtOpenGL module, that is the good old QGL classes, can finally be avoided in all types of Qt applications. Therefore, while QGLWidget and friends are not going to disappear overnight, they will be deprecated in Qt 5.4 and it is strongly recommended to avoid using them in new applications. Instead, go for QOpenGLWidget.

P.S. As mentioned in the QQuickWidget post, there is a limitation regarding semi-transparency when using QQuickWidget or QOpenGLWidget as child widgets. For applications that absolutely need this, Qt 5.4 offers a workaround: the newly introduced Qt::WA_AlwaysStackOnTop widget attribute. This, at the expense of breaking the stacking order for other types of layouts, makes it possible to have a semi-transparent QQuickWidget or QOpenGLWidget with other widgets visible underneath. Of course, if the intention is only to make other applications on the desktop visible underneath, then the Qt::WA_TranslucentBackground attribute is sufficient.


Blog Topics:

Comments