- Qt 6.12
- Qt Canvas Painter
- Qt Canvas Painter Examples and Tutorials
- Qt Canvas Painter - Compact Health Example
- Qt Canvas Painter | Commercial or GPLv3

# Qt Canvas Painter - Compact Health Example

Demonstrates the use of QCanvasPainter in a QWindow.

This example demonstrates using Qt Canvas Painter in a pure QWindow application. The Qt Quick and Qt Widgets stacks are not used.

The application renders all content through QRhi backends with QCanvasPainter. Rendering and QCanvasPainter's integration with QRhi are managed via QCanvasRhiPaintDriver. This means hardware acceleration is used where available for the selected backend.

Note: QRhi is part of Qt Gui Private. As such, it offers limited compatibility guarantees. There are no source or binary compatibility guarantees for these classes, meaning the API is only guaranteed to work with the Qt version the application was developed against.

#### What This Example Demonstrates

- Rendering a full custom UI in a QWindow.
- Creating and managing QRhi, a swapchain, and depth-stencil resources.
- Recording draw commands through QCanvasPainter and QCanvasRhiPaintDriver.
- Driving continuous animation with QWindow::requestUpdate().

#### Walkthrough

The application chooses a rendering API based on the platform by default. You can also request a specific backend with command-line arguments:

- `--null`
- `--opengl`
- `--vulkan`
- `--d3d11`
- `--d3d12`
- `--metal`

If backend creation fails, the example attempts to fall back to OpenGL ES 2 when available.

     int main(int argc, char *argv[])
     {
         QGuiApplication app(argc, argv);
         app.setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents);

         QRhi::Implementation graphicsApi;
     #if defined(Q_OS_WIN)
         graphicsApi = QRhi::D3D11;
     #elif QT_CONFIG(metal)
         graphicsApi = QRhi::Metal;
     #elif QT_CONFIG(vulkan)
         graphicsApi = QRhi::Vulkan;
     #else
         graphicsApi = QRhi::OpenGLES2;
     #endif

         QCommandLineParser cmdLineParser;
         cmdLineParser.addHelpOption();
         QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null"));
         cmdLineParser.addOption(nullOption);
         QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL"));
         cmdLineParser.addOption(glOption);
         QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan"));
         cmdLineParser.addOption(vkOption);
         QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
         cmdLineParser.addOption(d3d11Option);
         QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12"));
         cmdLineParser.addOption(d3d12Option);
         QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal"));
         cmdLineParser.addOption(mtlOption);

         cmdLineParser.process(app);

         if (cmdLineParser.isSet(nullOption))
             graphicsApi = QRhi::Null;
         if (cmdLineParser.isSet(glOption))
             graphicsApi = QRhi::OpenGLES2;
         if (cmdLineParser.isSet(vkOption))
             graphicsApi = QRhi::Vulkan;
         if (cmdLineParser.isSet(d3d11Option))
             graphicsApi = QRhi::D3D11;
         if (cmdLineParser.isSet(d3d12Option))
             graphicsApi = QRhi::D3D12;
         if (cmdLineParser.isSet(mtlOption))
             graphicsApi = QRhi::Metal;

     #if QT_CONFIG(opengl)
         QSurfaceFormat fmt;
         fmt.setDepthBufferSize(24);
         fmt.setStencilBufferSize(8);
     #  ifdef Q_OS_MACOS
         fmt.setVersion(4, 1);
         fmt.setProfile(QSurfaceFormat::CoreProfile);
     #  endif
         QSurfaceFormat::setDefaultFormat(fmt);
     #endif

         MainWindow window(graphicsApi);
         window.resize(1920 / 2, 1080 / 2);
         window.show();

         return app.exec();
     }

`MainWindow` is a subclass of `PainterWindow`, which is a QWindow. `PainterWindow` implements a window that shows content rendered via QRhi, Qt's graphics abstraction. It initializes a QRhi instance when the window is exposed, manages a swapchain and depth-stencil buffer, handles resize events, and invokes a virtual `paint()` function implemented by `MainWindow`.

For a better understanding of the `PainterWindow` implementation, it is recommended to check out the RHI Window Example, since in essence it is very similar to the `RhiWindow` class in that example.

One QCanvasPainter-specific step is to retrieve a QCanvasPainterFactory instance after QRhi has been initialized:

     if (!m_factory) {
         m_factory = new QCanvasPainterFactory;
         m_factory->create(m_rhi.get());
     }

Rendering a new frame happens in the `render()` function, which is invoked when the window is shown, resized, and in response to update requests scheduled via requestUpdate().

Unlike in the RHI Window Example, we will not create vertex and uniform buffers or graphics pipelines via the QRhi API, and issue draw calls directly. Instead, QCanvasRhiPaintDriver is used to record a render pass with the QCanvasPainter-generated rendering.

     void PainterWindow::render()
     {
         if (!m_factory || !m_factory->isValid() || !m_rhi || !m_sc)
             return;

         if (!m_hasSwapChain || m_notExposed)
             return;

         if (m_sc->currentPixelSize() != m_sc->surfacePixelSize() || m_newlyExposed) {
             resizeSwapChain();
             if (!m_hasSwapChain)
                 return;
             m_newlyExposed = false;
         }

         QRhi::FrameOpResult r = m_rhi->beginFrame(m_sc.get());
         if (r == QRhi::FrameOpSwapChainOutOfDate) {
             resizeSwapChain();
             if (!m_hasSwapChain)
                 return;
             r = m_rhi->beginFrame(m_sc.get());
         }
         if (r != QRhi::FrameOpSuccess) {
             qDebug("beginFrame failed with %d, retry", r);
             requestUpdate();
             return;
         }

         QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
         QRhiRenderTarget *rt = m_sc->currentFrameRenderTarget();

         QCanvasRhiPaintDriver *pd = m_factory->paintDriver();
         QCanvasPainter *painter = m_factory->painter();

         pd->resetForNewFrame();
         pd->beginPaint(cb, rt, m_fillColor, size(), float(devicePixelRatio()));
         paint(painter);
         pd->endPaint();

         m_rhi->endFrame(m_sc.get());
     }

MainWindow's `paint()` implementation uses the QCanvasPainter API and does not need to handle low-level QRhi details directly.

First, it loads and registers images if not yet done:

     void MainWindow::paint(QCanvasPainter *p)
     {
         if (!m_initialized) {
             auto flags = QCanvasPainter::ImageFlag::GenerateMipmaps;
             m_b1ImageLight = p->addImage(QImage(":/images/icon_random_light.png"), flags);
             m_b1ImageDark = p->addImage(QImage(":/images/icon_random_dark.png"), flags);
             m_b2ImageLight = p->addImage(QImage(":/images/icon_theme_light.png"), flags);
             m_b2ImageDark = p->addImage(QImage(":/images/icon_theme_dark.png"), flags);
             m_b3ImageLight = p->addImage(QImage(":/images/icon_settings_light.png"), flags);
             m_b3ImageDark = p->addImage(QImage(":/images/icon_settings_dark.png"), flags);
             m_sImageLight = p->addImage(QImage(":/images/icon_run_light.png"), flags);
             m_sImageDark = p->addImage(QImage(":/images/icon_run_dark.png"), flags);
             m_initialized = true;
         }

This is followed by the actual drawing, involving the filling and stroking of paths, rendering text, and drawing images. For example:

     // Highlight pressed button
     if (m_selectedButton) {
         p->beginPath();
         p->roundRect(m_views[m_selectedButton].rect, viewRadius);
         p->setLineWidth(2.0f * m_px);
         p->setStrokeStyle(m_theme.highlight());
         p->stroke();
     }

QCanvasPainter drawing commands prepare geometry, state, and QRhi draw work internally. The command stream is flushed when endPaint() is called in `PainterWindow::render()`. That is where a render pass is recorded on the associated QRhiCommandBuffer.

Before returning, `MainWindow::paint()` calls requestUpdate(), a QWindow function that requests a new frame synchronized with the display's presentation rate. This keeps the content continuously updating for animation.

See the full example source code for implementation details.

Example project @ code.qt.io