Arborescence de scène - QSGRenderNode personnalisé

Ce document explique 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 implémenter une sous-classe de ` QQuickItem ` qui s'appuie sur un nœud du graphe de scène dérivé de ` QSGRenderNode`, fournissant son propre rendu basé sur ` QRhi`.

Triangle RVB avec des rectangles qui se chevauchent, affiché dans les modes de rendu « Direct » et « Layer »

Remarque : cet exemple illustre des fonctionnalités avancées de bas niveau permettant un rendu 3D portable et multiplateforme, tout en s’appuyant sur des API du module Qt GUI dont la compatibilité n’est pas pleinement garantie. Pour pouvoir utiliser les API d’ QRhi, l’application doit être liée à Qt::GuiPrivate et inclure <rhi/qrhi.h>.

QSGRenderNode permet un accès direct à l’interface matérielle de rendu (RHI) au sein de l’arbre de scène. Cet exemple montre comment créer un nœud de rendu basé sur QSGRenderNode et le gérer à l’aide d’un élément personnalisé. Le nœud de rendu crée un pipeline RHI, met à jour les tampons de sommets et d’uniformes, puis effectue le rendu dans le tampon de commandes RHI.

En pratique, il s’agit d’une approche portable et multiplateforme permettant d’effectuer un rendu personnalisé en parallèle du rendu propre au graphe de 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 shaders de Qt.

QSGRenderNode C’est l’élément clé de l’une des trois méthodes permettant d’intégrer un rendu 2D/3D personnalisé dans une scène d’ Qt Quick. Les deux autres options consistent soit à effectuer l’ before ou l’ after e du rendu propre à la scène d’ Qt Quick, soit à générer un passage de rendu entièrement distinct ciblant une cible de rendu dédiée (une texture), puis à faire en sorte qu’un élément de la scène affiche cette texture. L’approche basée sur ` QSGRenderNode` est similaire à la première, dans la mesure où elle n’implique ni passe de rendu ni cible de rendu supplémentaires, et permet d’injecter des commandes de rendu personnalisées « en ligne » avec le rendu propre à la scène ` Qt Quick `.

Reportez-vous aux exemples suivants pour ces trois approches :

  • Arborescence de scène - RHI sous QML - Illustre une approche « sous-jacente » basée sur le signal QQuickWindow::beforeRendering(). Aucun passage de rendu ni aucune ressource supplémentaires ne sont nécessaires, mais la composition et le mélange avec le reste de la scène Qt Quick sont assez limités. Le rendu « sous » ou « par-dessus » la scène Qt Quick constitue l’approche la plus simple.
  • Graphique de scène – Élément de texture RHI – Illustre la création d’un élément personnalisé QQuickItem qui effectue un rendu dans une texture et affiche un quadrilatère texturé avec le contenu généré. Cette approche est très flexible et permet une fusion et une composition complètes de l’image 2D résultante avec le reste de la scène Qt Quick. Cela implique toutefois un passage de rendu et une cible de rendu supplémentaires.
  • Cet exemple - Illustre l’approche « en ligne », dans laquelle le graphe de scène d’ Qt Quick fait appel à l’élément personnalisé et à l’implémentation du nœud lors du passage de rendu principal. Cette approche peut être très avantageuse en termes de performances (aucun passage de rendu supplémentaire, aucune texturation ni aucun mélange n’est nécessaire), 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 la méthode ` 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 active 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 la fonction `updatePaintNode()` crée une instance du nœud de graphe de scène personnalisé, si ce n’est pas déjà fait. L’arborescence ` QSGNode ` sous-jacente à cet élément se compose d’un seul nœud, une instance d’une classe dérivée de ` QSGRenderNode`. Lorsque le modèle de rendu multithread d’ 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 thread principal (telles que celles stockées dans des QQuickItems). Le nœud, instance de la sous-classe d’ QSGRenderNode, va « vivre » sur le thread 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 ` dérive de ` QSGRenderNode`, en réimplémentant un certain nombre de fonctions virtuelles. Pour gérer les ressources d’ QRhi s (tampons, pipelines, etc.), les pointeurs intelligents s’avèrent très utiles dans ce cas, car le nœud est détruit par le graphe de scène en même temps que le reste de la scène sur le thread de rendu (s’il y en a un), tandis que l’ QRhi est toujours disponible ; il est donc légal et sûr de libérer les ressources depuis le destructeur ou via des pointeurs intelligents.

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 d’ QSGRenderNode qui se comportent correctement réimplémentent également la méthode releaseResources(), ce qui, dans ce cas, peut se résumer à une simple série d’appels à reset().

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

Cet « QSGRenderNode » effectue son rendu via les API d’ QRhi (et non directement via OpenGL, Vulkan, Metal, etc.), et il prend en compte la transformation de l’élément (puisqu’il n’effectue en réalité qu’un rendu 2D). Il convient donc de spécifier les indicateurs appropriés, ce qui peut apporter une légère 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 de l’ Qt Quick effectue un rendu. La première est appelée lors de la préparation (mais pas encore de l’enregistrement) de la passe de rendu. Cela consiste généralement à créer des ressources, telles que des tampons, des textures et des pipelines graphiques, si ce n’est pas déjà fait, et à mettre en file d’attente le chargement des données vers celles-ci.

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 pendant l’enregistrement d’un passage de rendu, ciblant soit la « swapchain » de l’ QQuickWindow, soit une texture (dans le cas d’éléments superposés, ou lorsqu’on se trouve dans un ShaderEffectSource).

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 sur code.qt.io

Voir également QSGRenderNode, QRhi, Graphique de scène - RHI sous QML, Élément de texture RHI et Graphique de scèneQt Quick .

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