Gráfico de Escena - Custom QSGRenderNode

Muestra cómo usar QSGRenderNode para implementar un renderizado personalizado en el gráfico de escena Qt Quick.

El ejemplo de nodo de renderizado personalizado muestra cómo implementar una subclase de QQuickItem que está respaldada por un nodo de gráfico de escena derivado de QSGRenderNode, proporcionando su propio renderizado basado en QRhi.

Nota: Este ejemplo demuestra una funcionalidad avanzada de bajo nivel que realiza un renderizado 3D portable y multiplataforma, a la vez que depende de APIs con garantía de compatibilidad limitada del módulo Qt Gui. Para poder utilizar las APIs QRhi, la aplicación enlaza con Qt::GuiPrivate e incluye <rhi/qrhi.h>.

QSGRenderNode permite el acceso directo a la Render Hardware Interface (RHI) dentro del scenegraph. Este ejemplo demuestra cómo crear un nodo de render basado en QSGRenderNode y gestionarlo con un elemento personalizado. El nodo de render crea un pipeline RHI, actualiza los buffers de vértices y uniformes, y renderiza en el buffer de comandos RHI.

En la práctica, se trata de un enfoque portátil y multiplataforma para realizar renderizados personalizados en línea con el propio renderizado del scenegraph, sin recurrir a una API 3D nativa como OpenGL, Metal o Vulkan. En su lugar, la aplicación utiliza la capa de abstracción de gráficos y shaders de Qt.

QSGRenderNode es el habilitador de una de las tres formas de integrar el renderizado 2D/3D personalizado en una escena de Qt Quick. Las otras dos opciones son realizar el renderizado before o after el propio renderizado de la escena Qt Quick, o generar un pase de renderizado completamente separado dirigido a un objetivo de renderizado dedicado (una textura) y luego hacer que un elemento de la escena muestre la textura. El enfoque basado en QSGRenderNode es similar al anterior, en el sentido de que no hay pases de renderizado adicionales ni objetivos de renderizado, y permite inyectar comandos de renderizado personalizados "en línea" con el propio renderizado de la escena Qt Quick.

Consulte los siguientes ejemplos de estos tres enfoques:

  • Gráfico de escena - RHI bajo QML - Demuestra un enfoque "subyacente" basado en la señal QQuickWindow::beforeRendering(). No se necesitan recursos ni pases de renderizado adicionales, pero la composición y la mezcla con el resto de la escena Qt Quick son bastante limitadas. Renderizar "por debajo" o "por encima" de la escena Qt Quick es el enfoque más sencillo.
  • Scene Graph - RHI Texture Item - Demuestra la creación de un QQuickItem personalizado que renderiza en una textura y muestra un quad texturizado con el contenido generado. Esto es muy flexible y permite una completa mezcla y composición de la imagen 2D resultante con el resto de la escena Qt Quick. Eso viene a expensas de un pase de render y un objetivo de render adicionales.
  • Este ejemplo - Demuestra el enfoque "en línea", donde el gráfico de la escena Qt Quick llama a la implementación de elementos y nodos personalizados durante el pase de renderizado principal. Este enfoque puede ser muy bueno para el rendimiento (no hay pases de render adicionales, texturizado, y la mezcla están involucrados), pero tiene trampas potenciales y es el método más complicado.

El elemento personalizado deriva de QQuickItem. Y lo que es más importante, reimplementa 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;
};

El constructor establece la bandera ItemHasContents para indicar que se trata de un elemento visual.

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

La implementación de updatePaintNode() crea una instancia del nodo scenegraph personalizado, si aún no se ha hecho. El árbol de respaldo QSGNode para este elemento consiste en un único nodo, una instancia de una clase derivada de QSGRenderNode. Cuando se utiliza el modelo de renderizado por hilos de Qt Quick, esta función se ejecuta en el hilo de renderizado con el hilo principal bloqueado. Por eso es seguro acceder a los datos del hilo principal (como los datos almacenados en QQuickItems). El nodo, la instancia de la subclase QSGRenderNode, va a "vivir" en el hilo de renderizado.

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 clase CustomRenderNode deriva de QSGRenderNode, reimplementando una serie de funciones virtuales. Para gestionar los recursos de QRhi (buffers, pipelines, etc.), los punteros inteligentes son bastante útiles en este caso, ya que el nodo es destruido por el grafo de escena junto con el resto de la escena en el hilo de renderizado (si lo hay) mientras que QRhi sigue disponible, y por tanto liberar recursos desde el destructor o mediante punteros inteligentes es legal y seguro.

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

Las subclases de QSGRenderNode que se comportan bien también reimplementan releaseResources(), que en este caso puede ser un simple conjunto de llamadas a reset().

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

Este QSGRenderNode realiza su renderizado a través de las APIs de QRhi (y no directamente a través de OpenGL, Vulkan, Metal, etc.), y tiene en cuenta la transformación del ítem (ya que realmente sólo realiza renderizado 2D). De ahí que se especifiquen las banderas adecuadas, lo que puede suponer una pequeña mejora de rendimiento.

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

Las funciones prepare() y render() son llamadas cada vez que la escena Qt Quick se renderiza. La primera es llamada cuando se prepara (pero aún no se graba) el pase de render. Esto típicamente crea recursos, como buffers, texturas, y pipelines gráficos, si aún no se han hecho, y pone en cola la carga de datos en ellos.

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 función render() se ejecuta mientras está activa la grabación de un pase de renderizado, dirigido a la cadena de intercambio de QQuickWindow o a una textura (en el caso de elementos por capas o dentro de 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());
}

Proyecto de ejemplo @ code.qt.io

Véase también QSGRenderNode, QRhi, Gráfico de escena - RHI bajo QML, Gráfico de escena - Elemento de textura RHI, y Qt Quick Gráfico de escena.

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