Sur cette page

Qt Canvas Painter - Exemple de santé compacte

Démontre l'utilisation de QCanvasPainter dans une page QWindow.

Cet exemple démontre l'utilisation de Qt Canvas Painter dans une application purement QWindow. Qt Quick ou QWidget ne sont pas utilisés du tout, et il n'y a aucune dépendance sur les modules Qt autres que Core, Gui, et Canvas Painter.

L'application met en place un rendu 3D accéléré à l'aide de QRhi, et rend tout le contenu de la fenêtre à l'aide de QCanvasPainter. Le rendu et l'intégration de QCanvasPainter avec QRhi sont gérés par QCanvasRhiPaintDriver.

Visite guidée

Tout en choisissant une API 3D à utiliser en fonction de la plateforme, l'application offre également la possibilité d'en demander une à l'aide d'arguments de ligne de commande.

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 est une sous-classe de PainterWindow, qui est une fenêtre QWindow. PainterWindow implémente une fenêtre qui affiche un contenu rendu via QRhi, l'abstraction graphique 3D de Qt. Sa tâche consiste à initialiser une instance QRhi lors de la réception d'un événement d'exposition, c'est-à-dire lorsque la fenêtre est affichée, à gérer une chaîne d'échange et un tampon de profondeur-stencil, tout en se chargeant d'agir lorsque la fenêtre est redimensionnée, et à invoquer une fonction virtuelle paint() qui est implémentée dans MainWindow.

Pour une meilleure compréhension de l'implémentation de PainterWindow, il est recommandé de consulter l'exemple de fenêtre RHI, puisqu'elle est essentiellement très similaire à la classe RhiWindow de cet exemple.

Une étape spécifique à QCanvasPainter consiste à récupérer une instance de QCanvasPainterFactory une fois que QRhi a été initialisé avec succès :

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

Le rendu d'une nouvelle image se fait dans la fonction render(), qui est invoquée lorsque la fenêtre est affichée, redimensionnée et en réponse aux demandes de mise à jour programmées via requestUpdate().

Contrairement à l'exemple de la fenêtre RHI, nous ne créerons pas de tampons de sommets et d'uniformes ni de pipelines graphiques via l'API QRhi, et nous émettrons des appels de dessin directement. Au lieu de cela, QCanvasRhiPaintDriver est utilisé pour enregistrer une passe de rendu avec le rendu généré par QCanvasPainter.

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()) ; }

L'implémentation de paint() de MainWindow fonctionne avec l'API QCanvasPainter et n'a pas besoin de se préoccuper des détails de bas niveau tels que l'API QRhi.

Tout d'abord, elle charge et enregistre les images si cela n'a pas encore été fait :

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;
    }

Vient ensuite le dessin proprement dit, qui implique le remplissage et le traçage des chemins, le rendu du texte et le dessin des images. Par exemple :

    // 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();
    }

Les appels de remplissage et de tracé QCanvasPainter préparent en interne les vertex, l'index, les données uniformes et les appels de dessin basés sur QRhi. Le flux de commandes est vidé lorsque endPaint() est appelé dans PainterWindow::render(). C'est là qu'une passe de rendu est enregistrée sur le site QRhiCommandBuffer.

Avant de retourner, MainWindow::paint() appelle requestUpdate(), une fonction QWindow qui demande une nouvelle image, synchronisée avec le taux de présentation de l'écran. C'est ce qui garantit que le contenu de la fenêtre est continuellement mis à jour et qu'elle peut s'animer.

Pour plus de détails, consultez le code source de l'exemple complet ci-dessous.

Exemple de projet @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.