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

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

このカスタムレンダリングノードの例では、QSGRenderNode を継承したシーングラフノードをバックエンドとして使用し、独自のQRhi ベースのレンダリングを提供するQQuickItem のサブクラスを実装する方法を示します。

「ダイレクト」および「レイヤー」レンダリングモードで表示された、長方形が重なったRGB三角形

注:この例は 、Qt GUIモジュールの互換性が限定的に保証されているAPIに依存しつつ、移植性のあるクロスプラットフォーム3Dレンダリングを実行する、高度で低レベルの機能を実演するものです。QRhi APIを使用するには、アプリケーションをQt::GuiPrivate にリンクし、<rhi/qrhi.h> をインクルードする必要があります。

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

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

QSGRenderNode これは、Qt Quick シーンにカスタム2D/3Dレンダリングを統合する3つの方法のうちの1つを実現するものです。他の2つの選択肢は、レンダリングをbefore またはafter でQt Quick シーン自身のレンダリングと並行して実行するか、専用のレンダリングターゲット(テクスチャ)を対象とした完全に独立したレンダリングパスを生成し、シーン内のアイテムでそのテクスチャを表示させる方法です。QSGRenderNode に基づくアプローチは、追加のレンダリングパスやレンダリングターゲットを必要としないという点で前者と似ており、Qt Quick シーン自身のレンダリングに「インライン」でカスタムレンダリングコマンドを挿入することができます。

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

  • シーングラフ - QML 下の RHI- `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 のスレッド化されたレンダリングモデルが使用されている場合、この関数はレンダリングスレッド上で呼び出され、メインスレッドはブロックされます。そのため、メインスレッドのデータ(QQuickItemに格納されているデータなど)にアクセスしても安全です。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 のスワップチェーン、またはテクスチャ(レイヤー化されたアイテムの場合、あるいは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。

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