Szenengraph – Benutzerdefinierter QSGRenderNode

Zeigt, wie man mit „ QSGRenderNode “ benutzerdefiniertes Rendering im Szenenbaum „ Qt Quick “ implementiert.

Das Beispiel für einen benutzerdefinierten Render-Knoten zeigt, wie eine Unterklasse von ` QQuickItem ` implementiert wird, die auf einem von ` QSGRenderNode` abgeleiteten Szenengraph-Knoten basiert und ein eigenes, auf ` QRhi` basierendes Rendering bereitstellt.

RGB-Dreieck mit überlappenden Rechtecken, dargestellt in den Rendermodi „Direct“ und „Layer“

Hinweis: Dieses Beispiel veranschaulicht fortgeschrittene, Low-Level-Funktionalität zur Durchführung von portablem, plattformübergreifendem 3D-Rendering, wobei es sich auf APIs aus dem Qt GUI-Modul stützt, für die nur eine begrenzte Kompatibilitätsgarantie besteht. Um die QRhi -APIs nutzen zu können, muss die Anwendung mit „ Qt::GuiPrivate “ verknüpft sein und „ <rhi/qrhi.h> “ einbinden.

QSGRenderNode ermöglicht den direkten Zugriff auf das Render Hardware Interface (RHI) innerhalb des Szenengraphen. Dieses Beispiel veranschaulicht, wie ein auf „ QSGRenderNode “ basierender Renderknoten erstellt und mit einem benutzerdefinierten Element verwaltet wird. Der Renderknoten erstellt eine RHI-Pipeline, aktualisiert Vertex- und Uniform-Puffer und rendert in den RHI-Befehlspuffer.

In der Praxis handelt es sich hierbei um einen portablen, plattformübergreifenden Ansatz, um benutzerdefiniertes Rendering inline mit dem eigenen Rendering des Szenengrafiks zu führen, ohne auf eine native 3D-API wie OpenGL, Metal oder Vulkan zurückzugreifen. Stattdessen nutzt die Anwendung die Grafik- und Shader-Abstraktionsschicht von Qt.

QSGRenderNode ist die Grundlage für eine der drei Möglichkeiten, benutzerdefiniertes 2D/3D-Rendering in eine „ Qt Quick “-Szene zu integrieren. Die beiden anderen Optionen bestehen darin, das Rendering „ before “ oder „ after “ des eigenen Renderings der „ Qt Quick “-Szene durchzuführen oder einen vollständig separaten Render-Durchlauf zu generieren, der auf ein dediziertes Render-Ziel (eine Textur) abzielt, und anschließend die Textur über ein Element in der Szene anzeigen zu lassen. Der auf „ QSGRenderNode “ basierende Ansatz ähnelt dem erstgenannten insofern, als keine zusätzlichen Render-Pässe oder Render-Ziele erforderlich sind und er das „Inline“-Einfügen benutzerdefinierter Rendering-Befehle in das eigene Rendering der „ Qt Quick “-Szene ermöglicht.

Beachten Sie die folgenden Beispiele für diese drei Ansätze:

  • Szenenbaum – RHI unter QML – Demonstriert einen „Underlay“-Ansatz auf Basis des Signals „ QQuickWindow::beforeRendering()“. Es sind keine zusätzlichen Render-Durchgänge und Ressourcen erforderlich, jedoch sind die Möglichkeiten der Komposition und Überblendung mit dem Rest der „ Qt Quick “-Szene recht begrenzt. Das Rendern „unter“ oder „über“ der „ Qt Quick “-Szene ist der einfachste Ansatz.
  • Szenengraph – RHI-Textur-Element – Veranschaulicht die Erstellung eines benutzerdefinierten „ QQuickItem “, das in eine Textur rendert und ein Quad anzeigt, das mit dem generierten Inhalt texturiert ist. Dies ist sehr flexibel und ermöglicht eine vollständige Überblendung und Komposition des resultierenden 2D-Bildes mit dem Rest der „ Qt Quick “-Szene. Dies geht jedoch auf Kosten eines zusätzlichen Render-Durchgangs und eines zusätzlichen Render-Ziels.
  • Dieses Beispiel – Veranschaulicht den „Inline“-Ansatz, bei dem der Qt Quick -Szenenbaum während des Hauptrenderdurchlaufs die benutzerdefinierte Item- und Knotenimplementierung aufruft. Dieser Ansatz kann sich hervorragend auf die Leistung auswirken (es sind keine zusätzlichen Renderdurchläufe, Texturierungen und Überblendungen erforderlich), birgt jedoch potenzielle Fallstricke und ist die komplizierteste Methode.

Das benutzerdefinierte Element leitet sich von ` QQuickItem` ab. Vor allem implementiert es die Methode ` updatePaintNode()` neu.

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

Der Konstruktor setzt das Flag „ ItemHasContents “, um anzugeben, dass es sich um ein visuelles Element handelt.

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

Die Implementierung von `updatePaintNode()` erstellt eine Instanz des benutzerdefinierten Szenengraf-Knotens, falls dies noch nicht geschehen ist. Der zugrunde liegende „ QSGNode “-Baum für dieses Element besteht aus einem einzigen Knoten, einer Instanz einer von ` QSGRenderNode` abgeleiteten Klasse. Wenn das Thread-basierte Rendering-Modell von „ Qt Quick “ verwendet wird, wird diese Funktion im Render-Thread aufgerufen, während der Hauptthread gesperrt ist. Aus diesem Grund ist der Zugriff auf Daten des Hauptthreads (wie beispielsweise in QQuickItems gespeicherte Daten) unbedenklich. Der Knoten, die Instanz der Unterklasse von „ QSGRenderNode “, wird im Render-Thread „weiterleben“.

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

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

    node->setVertices(m_vertices);

    return node;
}

Die Klasse „ CustomRenderNode “ leitet sich von „ QSGRenderNode “ ab und implementiert eine Reihe virtueller Funktionen neu. Zur Verwaltung von Ressourcen des „ QRhi “ (Puffer, Pipelines usw.) sind Smart-Pointer in diesem Fall sehr nützlich, da der Knoten vom Szenengraphen zusammen mit dem Rest der Szene im Render-Thread (sofern vorhanden) zerstört wird, während der „ QRhi “ noch verfügbar ist; daher ist die Freigabe von Ressourcen über den Destruktor oder mittels Smart-Pointer zulässig und sicher.

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

Sich korrekt verhaltende Unterklassen von ` QSGRenderNode ` implementieren zudem ` releaseResources()` neu, was in diesem Fall aus einer einfachen Abfolge von `reset()`-Aufrufen bestehen kann.

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

Diese „ QSGRenderNode “ führt ihr Rendering über die „ QRhi “-APIs durch (und nicht direkt über OpenGL, Vulkan, Metal usw.) und berücksichtigt dabei die Item-Transformation (da sie eigentlich nur 2D-Rendering durchführt). Daher sollten die entsprechenden Flags festgelegt werden, was zu einer leichten Leistungssteigerung führen kann.

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

Die Funktionen `prepare()` und `render()` werden bei jedem Rendern der „ Qt Quick “-Szene aufgerufen. Die erste wird bei der Vorbereitung (aber noch nicht bei der Aufzeichnung) des Render-Durchgangs aufgerufen. Dabei werden in der Regel Ressourcen wie Puffer, Texturen und Grafik-Pipelines erstellt, sofern dies noch nicht geschehen ist, und das Hochladen von Daten in diese Ressourcen wird in die Warteschlange gestellt.

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

Die Funktion `render()` wird aufgerufen, während die Aufzeichnung eines Render-Durchgangs aktiv ist, der entweder auf die Swapchain des „ QQuickWindow “ oder auf eine Textur abzielt (im Falle von mehrschichtigen Elementen oder innerhalb eines „ 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());
}

Beispielprojekt @ code.qt.io

Siehe auch „ QSGRenderNode“ , „ QRhi “, „Scene Graph – RHI unter QML“, „Scene Graph – RHI Texture Item“ und „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.