Graphique de scène - QSGRenderNode personnalisé

Montre comment utiliser QSGRenderNode pour implémenter un rendu personnalisé dans le graphe de scène Qt Quick.

L'exemple de nœud de rendu personnalisé montre comment mettre en œuvre une sous-classe de QQuickItem qui est soutenue par un nœud de graphe de scène dérivé de QSGRenderNode, fournissant son propre rendu basé sur QRhi.

Note : Cet exemple démontre une fonctionnalité avancée de bas niveau effectuant un rendu 3D portable et multiplateforme, tout en s'appuyant sur des APIs avec une garantie de compatibilité limitée du module Qt GUI. Pour pouvoir utiliser les API de QRhi, l'application est liée à Qt::GuiPrivate et inclut <rhi/qrhi.h>.

QSGRenderNode L'interface Render Hardware Interface (RHI) permet d'accéder directement à l'interface Render Hardware Interface (RHI) dans le graphe de scène. Cet exemple montre comment créer un nœud de rendu basé sur QSGRenderNode et le gérer avec un élément personnalisé. Le nœud de rendu crée un pipeline RHI, met à jour les tampons de sommets et d'uniformes et effectue le rendu dans le tampon de commande RHI.

En pratique, il s'agit d'une approche portable et multiplateforme permettant d'effectuer un rendu personnalisé en ligne avec le propre rendu de la scène, sans recourir à une API 3D native telle qu'OpenGL, Metal ou Vulkan. L'application utilise plutôt la couche d'abstraction graphique et de shader de Qt.

QSGRenderNode est l'outil permettant d'intégrer un rendu 2D/3D personnalisé dans une scène Qt Quick. Les deux autres options consistent à effectuer le rendu before ou after de la scène Qt Quick, ou à générer une passe de rendu distincte ciblant une cible de rendu dédiée (une texture), puis à faire en sorte qu'un élément de la scène affiche la texture. L'approche basée sur QSGRenderNode est similaire à la première, dans le sens où aucune passe de rendu ou cible de rendu supplémentaire n'est impliquée, et permet d'injecter des commandes de rendu personnalisées "en ligne" avec le propre rendu de la scène Qt Quick.

Les exemples suivants illustrent ces trois approches :

  • Graphique de scène - RHI sous QML - Démontre une approche "sous-couche" basée sur le signal QQuickWindow::beforeRendering(). Aucune passe de rendu ni ressource supplémentaire n'est nécessaire, mais la composition et le mélange avec le reste de la scène Qt Quick sont très limités. Le rendu "sous" ou "par-dessus" la scène Qt Quick est l'approche la plus simple.
  • Scene Graph - RHI Texture Item - Démonstration de la création d'un site QQuickItem personnalisé qui effectue le rendu dans une texture et affiche un quadruplet texturé avec le contenu généré. Cette méthode est très souple et permet de mélanger et de composer complètement l'image 2D résultante avec le reste de la scène Qt Quick. Cela se fait aux dépens d'une passe de rendu et d'une cible de rendu supplémentaires.
  • Cet exemple illustre l'approche "en ligne", dans laquelle le graphe de scène Qt Quick fait appel à l'implémentation de l'élément et du nœud personnalisés pendant la passe de rendu principale. Cette approche peut être très performante (pas de passes de rendu, de textures et de mélanges supplémentaires), mais elle comporte des pièges potentiels et constitue la méthode la plus compliquée.

L'élément personnalisé dérive de QQuickItem. Plus important encore, il réimplémente updatePaintNode().

class CustomRender : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(QList<QVector2D> vertices READ vertices WRITE setVertices NOTIFY verticesChanged)
    QML_ELEMENT

public:
    explicit CustomRender(QQuickItem *parent = nullptr);

    QList<QVector2D> vertices() const;
    void setVertices(const QList<QVector2D> &newVertices);

signals:
    void verticesChanged();

protected:
    QSGNode *updatePaintNode(QSGNode *old, UpdatePaintNodeData *) override;

private:
    QList<QVector2D> m_vertices;
};

Le constructeur définit le drapeau ItemHasContents pour indiquer qu'il s'agit d'un élément visuel.

CustomRender::CustomRender(QQuickItem *parent)
    : QQuickItem(parent)
{
    setFlag(ItemHasContents, true);
    connect(this, &CustomRender::verticesChanged, this, &CustomRender::update);
}

L'implémentation de updatePaintNode() crée une instance du nœud scenegraph personnalisé, si cela n'a pas encore été fait. L'arbre de soutien QSGNode pour cet élément consiste en un seul nœud, une instance d'une classe dérivée de QSGRenderNode. Lorsque le modèle de rendu threadé de Qt Quick est utilisé, cette fonction est appelée sur le thread de rendu, le thread principal étant bloqué. C'est pourquoi il est possible d'accéder en toute sécurité aux données du fil d'exécution principal (telles que les données stockées dans QQuickItems). Le nœud, l'instance de la sous-classe QSGRenderNode, va "vivre sur" le fil de rendu.

QSGNode *CustomRender::updatePaintNode(QSGNode *old, UpdatePaintNodeData *)
{
    CustomRenderNode *node = static_cast<CustomRenderNode *>(old);

    if (!node)
        node = new CustomRenderNode(window());

    node->setVertices(m_vertices);

    return node;
}

La classe CustomRenderNode est dérivée de QSGRenderNode et réimplémente un certain nombre de fonctions virtuelles. Pour gérer les ressources de QRhi (tampons, pipelines, etc.), les pointeurs intelligents sont très utiles dans ce cas, car le nœud est détruit par le graphe de scène avec le reste de la scène sur le thread de rendu (s'il y en a un) alors que QRhi est toujours disponible, et la libération des ressources à partir du destructeur ou via les pointeurs intelligents est donc légale et sûre.

class CustomRenderNode : public QSGRenderNode
{
public:
    CustomRenderNode(QQuickWindow *window);

    void setVertices(const QList<QVector2D> &vertices);

    void prepare() override;
    void render(const RenderState *state) override;
    void releaseResources() override;
    RenderingFlags flags() const override;
    QSGRenderNode::StateFlags changedStates() const override;

protected:
    QQuickWindow *m_window;
    std::unique_ptr<QRhiBuffer> m_vertexBuffer;
    std::unique_ptr<QRhiBuffer> m_uniformBuffer;
    std::unique_ptr<QRhiShaderResourceBindings> m_resourceBindings;
    std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
    QList<QRhiShaderStage> m_shaders;
    bool m_verticesDirty = true;
    QList<QVector2D> m_vertices;
};

Les sous-classes de QSGRenderNode qui se comportent bien réimplémentent également releaseResources(), qui dans ce cas peut être un simple ensemble d'appels reset().

void CustomRenderNode::releaseResources()
{
    m_vertexBuffer.reset();
    m_uniformBuffer.reset();
    m_pipeline.reset();
    m_resourceBindings.reset();
}

Cette QSGRenderNode effectue son rendu via les API QRhi (et non directement via OpenGL, Vulkan, Metal, etc.), et elle prend en compte la transformation de l'objet (car elle ne fait vraiment que du rendu 2D). D'où la spécification des drapeaux appropriés, ce qui peut apporter une petite amélioration des performances.

QSGRenderNode::RenderingFlags CustomRenderNode::flags() const
{
    // We are rendering 2D content directly into the scene graph using QRhi, no
    // direct usage of a 3D API. Hence NoExternalRendering. This is a minor
    // optimization.

    // Additionally, the node takes the item transform into account by relying
    // on projectionMatrix() and matrix() (see prepare()) and never rendering at
    // other Z coordinates. Hence DepthAwareRendering. This is a potentially
    // bigger optimization.

    return QSGRenderNode::NoExternalRendering | QSGRenderNode::DepthAwareRendering;
}

Les fonctions prepare() et render() sont appelées à chaque fois que la scène Qt Quick effectue un rendu. La première est appelée lors de la préparation (mais pas encore lors de l'enregistrement) de la passe de rendu. Elle crée généralement des ressources, telles que des tampons, des textures et des pipelines graphiques, si cela n'a pas encore été fait, et met en file d'attente le téléchargement des données vers ces ressources.

void CustomRenderNode::prepare()
{
    QRhi *rhi = m_window->rhi();
    QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();

    if (m_verticesDirty) {
        m_vertexBuffer.reset();
        m_verticesDirty = false;
    }

    if (!m_vertexBuffer) {
        m_vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer,
                                            m_vertices.count() * sizeof(QVector2D)));
        m_vertexBuffer->create();
        resourceUpdates->uploadStaticBuffer(m_vertexBuffer.get(), m_vertices.constData());
    }

La fonction render() est appelée lorsque l'enregistrement d'une passe de rendu, ciblant soit la chaîne d'échange de QQuickWindow, soit une texture (dans le cas d'éléments en couches, ou à l'intérieur d'une ShaderEffectSource), est actif.

void CustomRenderNode::render(const RenderState *)
{
    QRhiCommandBuffer *cb = commandBuffer();
    cb->setGraphicsPipeline(m_pipeline.get());
    QSize renderTargetSize = renderTarget()->pixelSize();
    cb->setViewport(QRhiViewport(0, 0, renderTargetSize.width(), renderTargetSize.height()));
    cb->setShaderResources();
    QRhiCommandBuffer::VertexInput vertexBindings[] = { { m_vertexBuffer.get(), 0 } };
    cb->setVertexInput(0, 1, vertexBindings);
    cb->draw(m_vertices.count());
}

Exemple de projet @ code.qt.io

Voir aussi QSGRenderNode, QRhi, Scene Graph - RHI Under QML, Scene Graph - RHI Texture Item, et Qt Quick Scene Graph.

© 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.