Laszlo Agocs

Qt Weekly #19: QOpenGLWidget

Published Wednesday September 10th, 2014
16 Comments on Qt Weekly #19: QOpenGLWidget
Posted in Graphics, OpenGL, Qt

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.

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

Posted in Graphics, OpenGL, Qt

16 comments

dslsynth says:

Really stupid novice question: Should the Q_OBJECT macro be specified in the Widget class example above?

Thanks a lot for a well written blog! ๐Ÿ™‚

Laszlo Agocs Laszlo Agocs says:

If you use features like signals/slots in the class, then you will have to add the Q_OBJECT macro. If you don’t, it is not neccessary.

Dyami Caliri says:

Very cool stuff Laszlo!

Yuriy says:

Why a new class? This post makes it sound like QOpenGLWidget fixes bugs in QGLWidget and provides the same API. Why not keep the name? Is there any reason one would want to continue to use QGLWidget instead of QOpenGLWidget?
The names are confusing because they mean exactly the same thing. Without looking at the documentation to find that one is “deprecated” (is it?) it’s impossible to tell in code why one class was used or the other. I have similar gripes about some of the other new classes in Qt5 like “QWindow”.

Laszlo Agocs Laszlo Agocs says:

It does not provide the same API. The main pattern of initializeGL/resizeGL/paintGL is the same but the rest is different.

Some of the utilities in QGLWidget are now provided by other classes (QOpenGLTexture) while some are just outdated and there will be no equivalent.

The names are confusing, no doubt about that. At least with 5.4 the message is finally clear: Avoid everything starting with QGL and go for QOpenGL.

Robin Lobel says:

Great job and thanks for the detailed blog post !

-So, If I want to disable VSync, I should start my application with:
QSurfaceFormat format;
format.setSwapInterval(0);
QSurfaceFormat::setDefaultFormat(format);
and that’s it, right ?

-About context sharing: if all my QOpenGLWidget (within the same window) use the same shaders, I only need to initialize thoses once and store their pointer to some static MyOpenGLWidget variables shared across all MyOpenGLWidget instances ?

-I used to use QOpenGLPaintDevice to do some extra painting. To my understanding, this is not required anymore, meaning I can now draw full-antialiased drawings on top of my OpenGL, right ?

It’s good that all the renering is now done under the hood, thoses GL surfaces have always been a problem accross platforms…

thanks!

Laszlo Agocs Laszlo Agocs says:

1. Yes. (As long as you don’t manually call setFormat() on some context afterwards. If you do, you will have to be careful to set the swap interval to 0 also in that QSurfaceFormat.)

2. Yes, because program objects are sharable.

3. QOpenGLPaintDevice is not neccessary anymore. The end result is the same, though. Under the hood it’s the exact same paint engine that performs the rendering.
Don’t forget the enable multisampling by setting the sample size to e.g. 4.

HGH says:

Can we use these with custom OpenGL wrappers like GLEW, glbinding? What about custom created contexts instead of QSurfaceFormat, QOpenGLContext ?
I wish for examples in the documentation too.

Laszlo Agocs Laszlo Agocs says:

The OpenGL wrappers are not really neccessary, just use the versioned variants of QOpenGLFunctions: http://qt-project.org/doc/qt-5/qopenglcontext.html#versionFunctions-2

GLEW and such may still work, though.

Adopting existing native contexts (EGLContext, GLXContext, HGLRC, NSOpenGLContext) is supported starting from Qt 5.4. See http://doc-snapshot.qt-project.org/qt5-5.4/qwglnativecontext.html and the similarly named platform-specific classes.

HGH says:

What I mean here is that I’d like to see examples how to integrate third party renderers/libraries/games/engines in Qt/Widgets/Qml that might use such wrapper or some other way to load the OpenGL functions.

cgcostume says:

on github, the group hpicgs provides/develops solutions for exactly this.
a) glbinding already includes a qt integration example using the latest QOpenGL classes.
b) the project gloperate (currently under volatile development, first release expected soon) further shows how to use a binding/rendering (in this case glbinding and globjects) with other rendering/windowing frameworks like qt, glfw, and openscenegraph (osg). It creates a leaky rendering layer on top of arbitrary rendering processes. Until the QOpenGLWidget is released with Qt 5.4, the wrapped QOpenGLWindow is used.

Simon Taylor says:

Hi Laszlo!

This sounds very cool. I want to have an OpenGL widget underneath a QWebView and hope this will provide the solution. (I know QWebView is being phased out, but not sure WebEngine is quite suitable yet for us).

I’ve had a read of the QQuickWidget post too but am just wondering about some internals:

1) Does using an QOpenGLWidget/QQuickWidget switch to using a GL QPainter for all the other widgets? If so, are there any known compatibility/performance problems with that?

2) The WebView contains a lot of stuff but is rarely updated, whereas I want the GL widget updated at 30-60 FPS. If the QWebView hasn’t requested a redraw, I assume it doesn’t get rendered again but is just re-blended over the top of the updated QOpenGLWidget?

Thanks

Simon

Laszlo Agocs Laszlo Agocs says:

1) No, it has no effect on other widgets. Or, to be correct, using a QOpenGLWidget and QQuickWidget has a lot of impact on how the window content is put together, but the other widgets will still be rendered using the raster paint engine regardless. They will therefore look exactly the same as before.

2) Having QOpenGLWidget or QQuickWidget under other widgets is no problem. Updating should be fine too: when only the QOpenGLWidget changes and the rest of the backingstore (an image containing all the other widgets) is clean, the other widgets will not get repainted. Only the updating of the QOpenGLWidget’s framebuffer and the final composition is done in this case.

Simon Taylor says:

Awesome, thanks a lot! Looking forward to trying it out when the 5.4 beta comes out.

Rich says:

“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.”

If you just want a window on which to render OpenGL, does using a QWindow backed by a QOpenGLSurface (as in the current opengl example) carry the same advantages?

Obviously this new class makes life simpler, I’m just wondering if it has performance benefits over a simple QWindow as well.

Laszlo Agocs Laszlo Agocs says:

If all you need is a window to render OpenGL, then definitely go for QWindow or its new-in-5.4 convenience wrapper QOpenGLWindow.

This is potentially faster and much more lightweight than using QOpenGLWidget and pulling in the various bits and pieces of the QWidget stack.

Commenting closed.

Get started today with Qt Download now