シーングラフ - カスタム QSGRenderNode

Qt Quick シーングラフでカスタムレンダリングを実装するためにQSGRenderNode を使用する方法を示します。

QRhiカスタムレンダーノードの例では、QSGRenderNode から派生したシーングラフノードによってバックされるQQuickItem のサブクラスを実装する方法を示しています。

注意: この例では、Qt Gui モジュールからの互換性保証が限定された API に依存しながら、ポータブルでクロスプラットフォームな 3D レンダリングを実行する、高度で低レベルの機能を示しています。QRhi API を使用できるように、アプリケーションはQt::GuiPrivate にリンクし、<rhi/qrhi.h> をインクルードしています。

QSGRenderNode は、シーングラフ内の Render Hardware Interface(RHI)への直接アクセスを可能にします。この例では、 ベースのレンダーノードを作成し、カスタムアイテムで管理する方法を示します。レンダーノードはRHIパイプラインを作成し、頂点バッファとユニフォームバッファを更新し、RHIコマンドバッファにレンダリングします。QSGRenderNode

実際には、これはOpenGL、Metal、Vulkanなどのネイティブ3D APIに頼ることなく、シーングラフ独自のレンダリングとインラインでカスタムレンダリングを実行する、移植性の高いクロスプラットフォームのアプローチです。むしろ、アプリケーションはQtのグラフィックスとシェーダーの抽象化レイヤーを使用します。

QSGRenderNode は、Qt Quick シーンにカスタム 2D/3D レンダリングを統合する 3 つの方法の 1 つです。他の2つのオプションは、 または Qt Quick シーン自身のレンダリングを実行するか、専用のレンダーターゲット(テクスチャ)をターゲットとする全く別のレンダーパスを生成し、シーン内のアイテムにテクスチャを表示させることです。 ベースのアプローチは、追加のレンダーパスやレンダーターゲットが関与しないという意味で、前者に似ており、Qt Quick シーン自身のレンダリングと「インライン」でカスタムレンダリングコマンドを注入することができます。before after QSGRenderNode

これら3つのアプローチについては、以下の例を参照してください:

  • Scene Graph - RHI Under QML-QQuickWindow::beforeRendering() シグナルに基づく "アンダーレイ" アプローチを示します。追加のレンダーパスやリソースは必要ありませんが、Qt Quick シーンの残りの部分との合成やブレンドはかなり制限されます。Qt Quickシーンの「下」または「上」にレンダリングするのが、最もシンプルなアプローチです。
  • Scene Graph - RHI Texture Item- テクスチャにレンダリングし、生成されたコンテンツでテクスチャ化された四角形を表示するカスタムQQuickItem を作成するデモです。これは非常に柔軟で、Qt Quick シーンの残りの部分と、結果の 2D イメージの完全なブレンドと合成が可能です。ただし、レンダーパスとレンダーターゲットが追加されます。
  • この例では、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-derived クラスのインスタンスである 1 つのノードで構成されます。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;
}

prepare() と render() 関数は、Qt Quick シーンがレンダリングするたびに呼び出されます。最初の関数は、レンダリングパスを準備する(まだ記録していない)ときに呼び出されます。これは通常、バッファ、テクスチャ、グラフィックスパイプラインなどのリソースを作成し、データがアップロードされるのを待ちます。

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's swapchain、またはテクスチャ(レイヤーアイテムの場合、または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());
}

プロジェクト例 @ code.qt.io

QSGRenderNode,QRhi,Scene Graph - RHI Under QML,Scene Graph - RHI Texture Item,Qt Quick Scene Graphも参照してください

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。