6.0.0-rc4Source

Using Nt OpenGL

How to render with OpenGL in a window or offscreen, paint with QPainter through OpenGL, and control vsync.

Nt renders with OpenGL through the same classes as Qt. The contexts and their functions are in Nt GUI (QOpenGLContext, QOpenGLFunctions, QSurfaceFormat, QOffscreenSurface); the window, the QPainter device and framebuffer objects are in Nt OpenGL (QOpenGLWindow, QOpenGLPaintDevice, QOpenGLFramebufferObject):

find_package(Nt6 REQUIRED)
add_executable(viewer main.cpp)
target_link_libraries(viewer PRIVATE Nt::OpenGL)

Nt does not link with an OpenGL library. It loads EGL (libEGL.so.1) and the OpenGL library when the first context is created, so programs that never use OpenGL run where there is none, and building Nt needs no OpenGL headers: <QtGui/qopengl.h> declares the types and constants.

A Window That Renders With OpenGL

Derive from QOpenGLWindow and reimplement initializeGL(), resizeGL() and paintGL(). The window creates its context the first time it is shown, makes it current before calling them, and swaps after paintGL(). A QPainter on the window draws with OpenGL too:

class Window : public QOpenGLWindow, protected QOpenGLFunctions {
public:
    Window() {
        connect(this, &QOpenGLWindow::frameSwapped, this, [this] {
            ++m_frames;
            update(); // the next frame
        });
    }

protected:
    void initializeGL() override {
        initializeOpenGLFunctions();
        m_clock.start();
    }

    void paintGL() override {
        const float t = static_cast<float>(m_clock.elapsed()) / 1000.0f;
        glClearColor(0.1f, 0.12f, 0.2f + 0.1f * std::sin(t), 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.translate(width() / 2.0, height() / 2.0);
        painter.rotate(static_cast<double>(t) * 45.0);
        painter.setPen(QPen(QColor(250, 210, 60), 6, Qt::SolidLine, Qt::RoundCap));
        painter.setBrush(QColor(60, 140, 220, 160));
        painter.drawRoundedRect(QRectF(-80, -50, 160, 100), 16, 16);
    }

Each frame ends with QOpenGLWindow::frameSwapped(); asking for the next frame from there renders continuously.

Frame Rate and Vsync

The swap interval of the format decides whether swapping waits for the display. With 1, the default, frames come at the refresh rate of the display, which suits animation. With 0, swapping does not wait, so a benchmark measures the rendering rather than the display:

QSurfaceFormat format;
format.setSwapInterval(0); // no vsync
format.setSamples(4);      // antialiasing for QPainter
window.setFormat(format);  // or QSurfaceFormat::setDefaultFormat(format) before any window is made

The glwindow example prints its frame rate: glwindow runs at the display's rate, and glwindow --no-vsync as fast as it can. A compositor can still hold back windows that do not wait for vsync; QOpenGLWindow::frameSwapped() tells when each frame was handed over.

QPainter on OpenGL

QOpenGLPaintDevice lets a QPainter draw into the framebuffer that is bound: the window, or a QOpenGLFramebufferObject. Everything QPainter draws on a QImage works: paths with both fill rules, pens with caps, joins and dashes, linear, radial and conical gradients, transformations, images, text and clipping. Paths are flattened and stroked by the same code as in the raster engine, so the pictures match.

QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
format.setSamples(4);
QOpenGLFramebufferObject fbo(QSize(800, 600), format);
fbo.bind();

QOpenGLPaintDevice device(fbo.size());
QPainter painter(&device);
painter.setRenderHint(QPainter::Antialiasing);
painter.drawPath(path);
painter.end();

QImage picture = fbo.toImage();

Two things differ from painting on an image:

Software and OpenGL Compared

painting --bench draws the scenes of the QPainter documentation 800 by 600 pixels large, in software into a QImage and with OpenGL into a framebuffer object with four samples (waiting for each frame to finish), and prints the time of each. On a laptop with an NVIDIA GeForce RTX 5070 Ti:

painting --gl shows the scenes in a QOpenGLWindow that does not wait for vsync, with the frame rate in its title; painting shows them with the raster engine.

OpenGL Calls

QOpenGLContext::functions() gives the OpenGL ES 2.0 API, which desktop OpenGL has too. Derive from QOpenGLFunctions and call initializeOpenGLFunctions() in initializeGL() to call the functions by their names. Other functions, such as those of OpenGL 3, come from QOpenGLContext::getProcAddress().

Without a format that asks otherwise, contexts are desktop OpenGL in a compatibility profile where EGL offers it, and OpenGL ES otherwise. Ask for a version and profile, or for OpenGL ES, with QSurfaceFormat::setVersion(), QSurfaceFormat::setProfile() and QSurfaceFormat::setRenderableType().

Without a Window

A QOffscreenSurface lets a context be current without a window, to render into framebuffer objects.

QOffscreenSurface surface;
surface.create();
QOpenGLContext context;
context.create();
context.makeCurrent(&surface);

On the offscreen platform, which has no window system, windows render into a framebuffer object that QOpenGLContext::defaultFramebufferObject() returns while the context is current on the window, so QOpenGLWindow and QOpenGLWindow::grabFramebuffer() work in tests and on servers without a display. That platform uses the surfaceless EGL platform of Mesa, or the first EGL device.

What Is Not There Yet

See also QOpenGLWindow, QOpenGLContext, QOpenGLPaintDevice and Using Nt GUI.