Laszlo Agocs

Qt Weekly #20: Completing the offering: QOpenGLWindow and QRasterWindow

Published Thursday November 20th, 2014
17 Comments on Qt Weekly #20: Completing the offering: QOpenGLWindow and QRasterWindow
Posted in Graphics, OpenGL, QPA, Qt

Together with the introduction of QOpenGLWidget, Qt 5.4 adds two more classes: QOpenGLWindow and QRasterWindow. Let us now look at the list of native window classes and OpenGL container widgets. The list may look long and confusing at first glance, but it is all quite logical so everything will fall into place quickly:

  • QWindow: Represents a native window in the windowing system. The fundamental window class in Qt 5. Every top-level window, be it widget or Quick based, will have a QWindow under the hood. Can also be used directly, without widgets or Quick, both for OpenGL and software rendered graphics. Has no dependencies to the traditional QWidget stack.
  • QRasterWindow: Convenience wrapper over QWindow for software rendered graphics.
  • QOpenGLWindow: Convenience wrapper over QWindow for OpenGL graphics. Optionally backed by a framebuffer object, but the default behavior (and thus performance) is equivalent to QWindow.
  • QOpenGLWidget: The modern replacement for Qt 4’s QGLWidget. A widget for showing OpenGL rendered content. Can be used like any other QWidget. Backed by a framebuffer object.
  • QQuickWindow: A QWindow subclass for displaying a Qt Quick 2 (QML) scene.
  • QQuickView: Convenience wrapper for QQuickWindow for easy setup of scenes loaded from QML files.
  • QQuickWidget: The equivalent of QQuickView in the QWidget world. Like QOpenGLWidget, it allows embedding a Qt Quick 2 scene into a traditional widget-based user interface. Backed by a framebuffer object.

For completeness sake, it is worth noting two additional APIs:

  • QQuickRenderControl: Allows rendering Qt Quick 2 scenes into framebuffer objects, instead of targeting an on-screen QQuickWindow.
  • QWidget::createWindowContainer(): In Qt 5.1 & 5.2 the only way to embed Qt Quick 2 content (or in fact any QWindow) into a widget-based UI was via this function. With the introduction of QQuickWidget and QOpenGLWidget this approach should be avoided as much as possible. Its usage should be restricted to cases where it is absolutely neccessary to have a real native window embedded into the widget-based interface and the framebuffer object-based, more robust alternatives are not acceptable, or where it is known in advance that the user interface layout is such that the embedded window will not cause issues (for example because the embedded window does not care about input, is not part of complex layouts that often get resized, etc.).

We will now take a look at no. 2 & 3, the QWindow convenience wrappers.

Ever since the introduction of the QPA architecture and QWindow, that is, since Qt 5.0, it has been possible to create windows based on QWindow that perform custom OpenGL drawing. Such windows do not use any QWidget-derived widgets, instead they render everything on their own. A game or a graphics intensive application with its own custom user interface is a good example.

This is the most lightweight and efficient way to perform native OpenGL rendering with Qt 5. It is free from the underlying complexities of the traditional widget stack and can operate with nothing but the QtCore and QtGui modules present. On space-constrained embedded devices this can be a big benefit (no need to deploy QtWidgets or any additional modules).

Power and efficiency comes at a cost: A raw QWindow does not hide contexts, surfaces and related settings, and it does not provide any standard mechanism for triggering updates or opening a QPainter (backed by the OpenGL 2.0 paint engine) targeting the window’s associated native window surface.

For example, a simple QWindow subclass that performs continous drawing (synchronized to the display’s vertical refresh by the blocking swapBuffers call) both via QPainter and directly via OpenGL could look like the following:

class MyWindow : public QWindow
{
public:
    MyWindow() : m_paintDevice(0) {
        setSurfaceType(QSurface::OpenGLSurface);

        QSurfaceFormat format;
        format.setDepthBufferSize(24);
        format.setStencilBufferSize(8);
        setFormat(format);

        m_context.setFormat(format);
        m_context.create();
    }

    ~MyWindow() { delete m_paintDevice; }

    void exposeEvent(QExposeEvent *) {
        if (isExposed())
            render();
    }

    void resizeEvent(QResizeEvent *) {
        ...
    }

    void render() {
        m_context.makeCurrent(this);

        if (!m_paintDevice)
            m_paintDevice = new QOpenGLPaintDevice;
        if (m_paintDevice->size() != size())
            m_paintDevice->setSize(size());

        QOpenGLFunctions *f = m_context.functions();
        f->glClear(GL_COLOR_BIT | GL_DEPTH_BUFFER_BIT);
        // issue some native OpenGL commands

        QPainter p(m_paintDevice);
        // draw using QPainter
        p.end();

        m_context.swapBuffers(this);

        // animate continuously: schedule an update
        QCoreApplication::postEvent(new QEvent(QEvent::UpdateRequest), this);
    }

    bool event(QEvent *e) {
        if (e->type() == QEvent::UpdateRequest) {
            render();
            return true;
        }
        return QWindow::event(e);
    }

private:
    QOpenGLContext m_context;
    QOpenGLPaintDevice *m_paintDevice;
};

Now compare the above code with the QOpenGLWindow-based equivalent:

class MyWindow : public QOpenGLWindow
{
public:
    MyWindow() {
        QSurfaceFormat format;
        format.setDepthBufferSize(24);
        format.setStencilBufferSize(8);
        setFormat(format);
    }

    void resizeGL(int w, int h) {
        ...
    }

    void paintGL() {
        QOpenGLFunctions *f = context()->functions();
        f->glClear(GL_COLOR_BIT | GL_DEPTH_BUFFER_BIT);
        // issue some native OpenGL commands

        QPainter p(this);
        // draw using QPainter

        // animate continuously: schedule an update
        update();
    }
};

That is a bit shorter, isn’t it. The API familiar from QOpenGLWidget (initializeGL/resizeGL/paintGL) is there, together with the update() function and the ability to easily open a painter on the window. While QOpenGLWindow, when used this way, does not remove or add anything compared to raw QWindow-based code, it makes it easier to get started, while leading to shorter, cleaner application code.

QRasterWindow follows the same concept. While everything it does can be achieved with QWindow and QBackingStore, like in the raster window example, it is definitely more convenient. With QRasterWindow, the example in question can be reduced to something like the following:

class RasterWindow : public QRasterWindow
{
    void paintEvent(QPaintEvent *) {
        QPainter painter(this);
        painter.fillRect(0, 0, width(), height(), Qt::white);
        painter->drawText(QRectF(0, 0, width(), height()), Qt::AlignCenter, QStringLiteral("QRasterWindow"));
    }
};

Painters opened on a QOpenGLWindow are always backed by the OpenGL paint engine, whereas painters opened on a QRasterWindow are always using the raster paint engine, regardless of having OpenGL support enabled or available at all. This means that QRasterWindow, just like the traditional widgets, is available also in -no-opengl builds or in environments where OpenGL support is missing.

Now, what about incremental rendering? In the QRasterWindow example above there is strictly speaking no need to clear the entire drawing area on each paint. Had the application wished so, it could have continued drawing over the existing, preserved backing store content in each invocation of paintEvent(), as long as the window did not get resized. With QGLWidget, QWindow or the QOpenGLWindow example shown above this is not possible, unless preserved swap is enabled via the underlying windowing system interface, since on each paintGL() call the color buffer contents is effectively undefined. QOpenGLWidget does not have this problem since it is backed by a framebuffer object instead of targeting the window surface directly. The same approach can be applied to QOpenGLWindow too. Hence the introduction of the different update behaviors that can be set on a QOpenGLWindow.

Take the following QWindow-based code:

class MyWindow : public QWindow
{
public:
    MyWindow() : m_paintDevice(0), m_fbo(0), m_iter(0) {
        ... // like in the first snippet above
    }

    ...

    void render() {
        m_context.makeCurrent(this);

        if (!m_fbo || m_fbo->size() != size()) {
            delete m_fbo;
            m_fbo = new QOpenGLFramebufferObject(size(), QOpenGLFramebufferObject::CombinedDepthStencilAttachment);
            m_iter = 0;
        }

        if (!m_paintDevice)
            m_paintDevice = new QOpenGLPaintDevice;
        if (m_paintDevice->size() != size())
            m_paintDevice->setSize(size());

        m_fbo->bind();
        QPainter p(m_paintDevice);

        // Draw incrementally using QPainter.
        if (!m_iter)
            p.fillRect(0, 0, width(), height(), Qt::white);

        p.drawText(QPointF(10, m_iter * 40), QString(QStringLiteral("Hello from repaint no. %1")).arg(m_iter));

        ++m_iter;

        p.end();
        m_fbo->release();

        // Now either blit the framebuffer onto the default one or draw a textured quad.
        ...

        m_context.swapBuffers(this);

        // animate continously: schedule an update
        QCoreApplication::postEvent(new QEvent(QEvent::UpdateRequest), this);
    }

private:
    QOpenGLContext m_context;
    QOpenGLPaintDevice *m_paintDevice;
    QOpenGLFramebufferObject *m_fbo;
    int m_iter;
};

For brevity the code for getting the framebuffer’s content onto the window surface is omitted. With QOpenGLWindow’s PartialUpdateBlit or PartialUpdateBlend the same can be achieved in a much more concise way. Note the parameter passed to the base class constructor.

class MyWindow : public QOpenGLWindow
{
public:
    Window() : QOpenGLWindow(PartialUpdateBlit), m_iter(0) {
        QSurfaceFormat format;
        format.setDepthBufferSize(24);
        format.setStencilBufferSize(8);
        setFormat(format);
    }

    void resizeGL(int, int) {
        m_iter = 0;
    }

    void paintGL() {
        QPainter p(this);

        // Draw incrementally using QPainter.
        if (!m_iter)
            p.fillRect(0, 0, width(), height(), Qt::white);

        p.drawText(QPointF(10, m_iter * 40), QString(QStringLiteral("Hello from repaint no. %1")).arg(m_iter));

        ++m_iter;

        update();
    }

private:
    int m_iter;
};

That’s it, and there is no code omitted in this case. Internally the two are approximately equivalent. With the QOpenGLWindow-based approach managing the framebuffer object is no longer the application’s responsibility, it is taken care by Qt. Simple and easy.

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

Posted in Graphics, OpenGL, QPA, Qt

17 comments

HGH says:

How do we do
● Adoption of existing native OpenGL contexts

Laszlo Agocs Laszlo Agocs says:

By using QGLXNativeContext, QEGLNativeContext, QWGLNativeContext and friends. See http://doc.qt.io/snapshots/qt5-5.4/qwglnativecontext.html

kangjoni7 says:

Hi, does QOpenGLWidget will work for es 2 context?or only exclusive native gl?

Laszlo Agocs Laszlo Agocs says:

Yes, it will. Just like QWindow does.

Pascal Henze says:

Can i use QOpenGLWindow as viewport of a QGraphicsView to enable openGL drawing in a QGraphicsScene ?

Laszlo Agocs Laszlo Agocs says:

No, you can only use QGLWidget or QOpenGLWidget as a viewport for widgets.

Pascal Henze says:

Okay thanks for that.

My other question belongs to the QT3D Framework. The main View(QGLView) is based on QWindow which does the openGL drawing.

How can i integrate this in a QWidget based application using QOpenGLWidget ? At the moment i’m using QWidget::createWindowContainer() for that.

Laszlo Agocs Laszlo Agocs says:

QGLView is a QWindow so the same applies as with integrating a QWindow or QQuickWindow or QQuickView into QWidget-based apps.

QWidget::createWindowContainer() is probably the only option for QGLView. For Quick Qt 5.4 introduced some alternatives (QQuickWidget) but AFAIK there is no such option for Qt3D (yet?).

Rubern Wong says:

I just tried using QOpenGLWidget as the viewport of my QGraphicsView with the latest Qt5.4.0 rc. It causes lots of flickering when I have a few QGraphicsWidget derived objects in the view.

Rubern Wong says:

I have a QGraphicsView containing lots of QGraphicsWidget objects. By simply replacing setViewport(new QGLWidget); with setViewport(new QOpenGLWidget)the performance gets a lot worse. When I scroll the view it becomes sluggish at times. Although I do love the fact that you can place semi-transparent QWidget on top of QOpenGLWidget. If only we can get the best of both worlds…

Rubern Wong says:

I seem to have found the cause of flickering. On my QGraphicsView I draw a couple of tiny icons from png files that have transparent background. If I remove those, the flickering is gone. Why is that?

Mehdi Benadel says:

Hi,

Good job on the OpenGL integration progress 🙂 !

A question : Can we integrate a QOpenGLWidget in a QMLView (or overlay the OpenGL Widget with QML content) ?

Laszlo Agocs Laszlo Agocs says:

You can use QOpenGLWidget in combination with QQuickWidget. It may however be more efficient to move your OpenGL code from the QOpenGLWidget into the QQuickView using one of the following approaches:

1. http://doc-snapshot.qt-project.org/qt5-5.4/qtquick-scenegraph-openglunderqml-example.html

2. http://doc-snapshot.qt-project.org/qt5-5.4/qquickframebufferobject.html

Jorge Diogo says:

Can QOpenGLWindow use an existing shared context?
If not this is quite a problem as there’s no way to create the context.

I’ve seen the Qt::AA_ShareOpenGLContexts flag but QOpenGLWindow is never mentioned?…

Laszlo Agocs Laszlo Agocs says:

QOpenGLWindow creates a context for you. If this is not desired you can just use QWindow as well.

AA_ShareOpenGLContexts has no effect for QOpenGLWindow, QWindow or generic QOpenGLContext usage. It only affects QOpenGLWidget and QQuickWidget.

Jorge Diogo says:

Shouldn’t the global sharing behavior (when using AA_ShareOpenGLContexts ) also apply to QOpenGLWindow? Or another way of allowing it to use a shared context?

The main reason to use QOpenGLWindow instead of QOpenGLWidget is to avoid the performance hit of the latter’s framebuffer for faster rendering.

Since most non-trivial apps need to share OpenGL resources between its views, I think QOpenGLWindow without a way to use shared GL resources is somewhat useless on itself…

Laszlo Agocs Laszlo Agocs says:

As a matter of fact that would be useful, yes. In the meantime you can use QWindow if you need full control over the contexts.

Commenting closed.

Get started today with Qt Download now