场景图 - 自定义 QSGRenderNode
展示如何使用 QSGRenderNode 在 Qt Quick 场景图中实现自定义渲染。
自定义渲染节点示例展示如何实现一个 QQuickItem 子类,后面有一个由 QSGRenderNode 派生的场景图节点支撑,并且提供它自己的基于 QRhi-的渲染。
注意:此示例演示了高级、低级别的功能,执行便携式、跨平台的 3D 渲染,并依赖于从 Qt Gui 模块提供有限兼容性保证的 API。要使用 QRhi API,应用程序需要链接到 Qt::GuiPrivate
并包含 <rhi/qrhi.h>
。
QSGRenderNode 允许直接访问场景图中的渲染硬件接口 (RHI)。此示例演示了如何创建基于 QSGRenderNode 的渲染节点,并通过自定义项目来管理它。渲染节点创建了一个 RHI 管道,更新顶点和统一缓冲区,并将渲染到 RHI 命令缓冲区。
在实践中,这是一种便携式、跨平台的自定义渲染方法,与场景图本身的渲染并行执行,而无需求助于原生 3D API(如 OpenGL、Metal 或 Vulkan)。相反,应用程序使用 Qt 的图形和着色器抽象层。
QSGRenderNode 是将自定义 2D/3D 渲染集成到 Qt Quick 场景的三种途径之一。其他两种选择是在 Qt Quick 场景的渲染之前或之后执行渲染,或者生成一个针对专用渲染目标(一个纹理)的整个单独的渲染通道,然后场景中的项目显示该纹理。基于 QSGRenderNode 的方法与前者类似,因为不涉及额外的渲染通道或渲染目标,并允许将自定义渲染命令“内联”注入到 Qt Quick 场景的渲染中。
请参考以下示例了解这三种方法
- 场景图 - RHI 在 QML 下 - 演示了一个基于 QQuickWindow::beforeRendering() 信号的下层方法。不需要额外的渲染通道和资源,但是与其他 Qt Quick 场景的合成和混合相当有限。在 Qt Quick 场景下或上渲染是简单的方法。
- 场景图 - RHI 纹理项目 - 演示创建一个自定义 QQuickItem,它将渲染到一个纹理中,并通过绘制带有生成内容的四边形来显示。这非常灵活,允许完全混合和组合最终 2D 图像与其他 Qt Quick 场景。这需要额外的渲染通道和渲染目标。
- 此示例 - 展示了“内联”方法,其中Qt Quick场景图在主绘制过程中调用自定义项和节点实现。这种方法在性能方面可能非常好(没有额外的绘制过程、纹理和混合操作),但可能存在潜在问题,是最复杂的方法。
自定义项从QQuickItem派生。最重要的是,它重新实现了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; };
构造函数将ItemHasContents标志设置为指示这是一个可视项。
CustomRender::CustomRender(QQuickItem *parent) : QQuickItem(parent) { setFlag(ItemHasContents, true); connect(this, &CustomRender::verticesChanged, this, &CustomRender::update); }
updatePaintNode()实现创建自定义场景图节点的实例,如果尚未完成。该项的备用QSGNode树由单个节点组成,即QSGRenderNode类的实例。当Qt Quick的线程渲染模型在使用中时,此函数在绘制线程中被调用,而主线程被阻塞。这就是为什么可以安全地访问主线程数据(如存储在QQuickItems中的数据)。该节点,即QSGRenderNode子类的实例,“存活”在绘制线程上。
QSGNode *CustomRender::updatePaintNode(QSGNode *old, UpdatePaintNodeData *) { CustomRenderNode *node = static_cast<CustomRenderNode *>(old); if (!node) node = new CustomRenderNode(window()); node->setVertices(m_vertices); return node; }
CustomRenderNode
类从QSGRenderNode派生,重新实现了多个虚拟函数。为了管理QRhi资源(缓冲区、管线等),在这种情况下,智能指针非常有用,因为节点与绘制线程上的其余场景(如果有)一起由场景图销毁,而QRhi仍可用,因此通过析构函数或通过智能指针释放资源是合法和安全的。
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; };
行为良好的QSGRenderNode子类还重新实现了releaseResources(),在这种情况下可以是一系列reset()调用。
void CustomRenderNode::releaseResources() { m_vertexBuffer.reset(); m_uniformBuffer.reset(); m_pipeline.reset(); m_resourceBindings.reset(); }
此QSGRenderNode通过QRhi API(而不是直接通过OpenGL、Vulkan、Metal等)执行渲染,并且考虑了项变换(因为它实际上只进行2D渲染)。因此,指定适当的标志可能会带来一点点性能提升。
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; }
每次Qt Quick场景渲染时都会调用prepare()和render()函数。第一个在准备渲染过程时被调用(但尚未记录)。这通常创建了资源,例如缓冲区、纹理和图形管线,如果尚未创建,并将数据上传到这些资源的队列中。
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()); }
在记录绘制过程时调用render()函数,目标是QQuickWindow的交换链,或纹理(如果是分层项或处于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()); }
另请参阅QSGRenderNode、QRhi、场景图 - QML下的RHI、场景图 - RHI纹理项和Qt Quick场景图。
© 2024 The Qt Company Ltd. 本文档中的文档贡献的版权属于其各自的所有者。提供的文档受GNU自由文档许可证版本1.3的条款约束,由自由软件基金会发布。Qt和相关标志是The Qt Company Ltd在芬兰和其他国家/地区的商标。所有其他商标均为其各自所有者的财产。