Szenegraph - Benutzerdefinierter QSGRenderNode
Zeigt, wie man QSGRenderNode verwendet, um benutzerdefiniertes Rendering im Qt Quick Szenegraph zu implementieren.
Das Beispiel des benutzerdefinierten Renderknotens zeigt, wie eine QQuickItem Unterklasse implementiert wird, die von einem von QSGRenderNode abgeleiteten Szenegraphenknoten unterstützt wird und ihr eigenes QRhi-basiertes Rendering bereitstellt.
Hinweis: Dieses Beispiel demonstriert eine fortgeschrittene Low-Level-Funktionalität, die portables, plattformübergreifendes 3D-Rendering durchführt und dabei auf APIs mit begrenzter Kompatibilitätsgarantie des Qt Gui-Moduls zurückgreift. Um die QRhi APIs nutzen zu können, verlinkt die Anwendung auf Qt::GuiPrivate
und schließt <rhi/qrhi.h>
ein.
QSGRenderNode ermöglicht den direkten Zugriff auf das Render Hardware Interface (RHI) innerhalb des Scenegraphs. In diesem Beispiel wird gezeigt, 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 ist dies ein portabler, plattformübergreifender Ansatz, um benutzerdefiniertes Rendering inline mit dem szeneneigenen Rendering durchzuführen, ohne auf eine native 3D-API wie OpenGL, Metal oder Vulkan zurückgreifen zu müssen. Stattdessen nutzt die Anwendung die Grafik- und Shader-Abstraktionsschicht von Qt.
QSGRenderNode ist der Enabler für eine der drei Möglichkeiten, benutzerdefiniertes 2D/3D-Rendering in eine Qt Quick Szene zu integrieren. Die anderen beiden Möglichkeiten sind, das Rendering before
oder after
der Qt Quick Szene selbst durchzuführen oder einen separaten Rendering-Durchgang zu erzeugen, der auf ein spezielles Rendering-Ziel (eine Textur) abzielt und dann ein Element in der Szene die Textur anzeigen zu lassen. Der QSGRenderNode-basierte Ansatz ähnelt dem erstgenannten insofern, als dass keine zusätzlichen Rendering-Durchläufe oder Rendering-Ziele involviert sind, und ermöglicht das Einfügen von benutzerdefinierten Rendering-Befehlen "inline" mit dem eigenen Rendering der Qt Quick -Szene.
Die folgenden Beispiele zeigen diese drei Ansätze:
- Scene Graph - RHI Under QML - Demonstriert einen "Underlay"-Ansatz, der auf dem Signal QQuickWindow::beforeRendering() basiert. Es werden kein zusätzlicher Rendering-Durchgang und keine zusätzlichen Ressourcen benötigt, aber die Komposition und die Überblendung mit dem Rest der Qt Quick Szene ist ziemlich eingeschränkt. Das Rendern "unter" oder "über" der Qt Quick Szene ist der einfachste Ansatz.
- Scene Graph - RHI Texture Item - Demonstriert die Erstellung eines benutzerdefinierten QQuickItem, das in eine Textur gerendert wird und ein mit dem generierten Inhalt texturiertes Quadrat anzeigt. 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 Renderdurchgangs und Renderziels.
- Dieses Beispiel demonstriert den "Inline"-Ansatz, bei dem der Qt Quick -Szenengraph die benutzerdefinierte Element- und Knotenimplementierung während des Haupt-Rendervorgangs aufruft. Dieser Ansatz kann sehr leistungsfördernd sein (es sind keine zusätzlichen Renderdurchgänge, Texturen und Überblendungen erforderlich), birgt jedoch potenzielle Fallstricke und ist die komplizierteste Methode.
Das benutzerdefinierte Element leitet sich von QQuickItem ab. Am wichtigsten ist, dass es updatePaintNode() neu implementiert.
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 Kennzeichen ItemHasContents, um anzuzeigen, 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 updatePaintNode()-Implementierung erstellt eine Instanz des benutzerdefinierten Scenegraph-Knotens, falls dies noch nicht geschehen ist. Der unterstützende QSGNode Baum für dieses Element besteht aus einem einzelnen Knoten, einer Instanz einer QSGRenderNode-abgeleiteten Klasse. Wenn Qt Quick das threaded Rendering-Modell verwendet, wird diese Funktion auf dem Render-Thread aufgerufen, während der Haupt-Thread blockiert ist. Deshalb ist es sicher, auf die Daten des Haupt-Threads zuzugreifen (z. B. auf die in QQuickItems gespeicherten Daten). Der Knoten, die Instanz der Unterklasse QSGRenderNode, wird auf dem Render-Thread "leben".
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 reimplementiert eine Reihe von virtuellen Funktionen. Um die Ressourcen von QRhi (Puffer, Pipelines usw.) zu verwalten, sind Smart Pointer in diesem Fall sehr nützlich, da der Knoten vom Szenegraphen zusammen mit dem Rest der Szene auf dem Render-Thread (falls vorhanden) zerstört wird, während QRhi noch verfügbar ist, und daher die Freigabe von Ressourcen durch den Destruktor oder über Smart Pointer legal und sicher ist.
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; };
Gut funktionierende QSGRenderNode Unterklassen reimplementieren auch releaseResources(), was in diesem Fall ein einfacher Satz von reset() Aufrufen sein kann.
void CustomRenderNode::releaseResources() { m_vertexBuffer.reset(); m_uniformBuffer.reset(); m_pipeline.reset(); m_resourceBindings.reset(); }
Dieses QSGRenderNode führt sein Rendering über die QRhi APIs durch (und nicht direkt über OpenGL, Vulkan, Metal, etc.) und berücksichtigt die Elementtransformation (da es wirklich nur 2D-Rendering durchführt). Daher die Angabe der entsprechenden Flags, die eine kleine Leistungsverbesserung bringen 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 jedes Mal aufgerufen, wenn die Qt Quick Szene gerendert wird. Die erste wird aufgerufen, wenn der Rendervorgang vorbereitet (aber noch nicht aufgezeichnet) wird. Dadurch werden normalerweise Ressourcen wie Puffer, Texturen und Grafikpipelines erstellt, falls dies noch nicht geschehen ist, und das Hochladen von Daten in die Warteschlange eingeordnet.
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 Rendering-Durchlaufs aktiv ist, der entweder auf die Swapchain von QQuickWindow oder auf eine Textur (im Falle von geschichteten Elementen oder innerhalb einer ShaderEffectSource) abzielt.
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()); }
Siehe auch QSGRenderNode, QRhi, Scene Graph - RHI Under QML, Scene Graph - RHI Texture Item, und Qt Quick Scene Graph.
© 2025 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.