シーングラフ - メタルテクスチャインポート

Metal で直接作成されたテクスチャを使用する方法を示します。

Metal Texture Import の例では、アプリケーションがQt Quick シーン内でMTLTexture をインポートして使用する方法を示しています。これは、Metal ネイティブレンダリングの統合に関して、アンダーレイアプローチやオーバーレイアプローチに代わる方法を提供します。多くの場合、Qt Quick が提供する 2D UI 要素とカスタム 3D コンテンツを統合して混在させるには、まずテクスチャを経由して 3D コンテンツを「平坦化」するのが最良の選択肢です。

import MetalTextureImport
CustomTextureItem {
    id: renderer
    anchors.fill: parent
    anchors.margins: 10

    SequentialAnimation on t {
        NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
        NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
        loops: Animation.Infinite
        running: true
    }

このアプリケーションは、CustomTextureItemという名前で、QQuickItem のカスタムサブクラスを公開しています。これはQMLでインスタンス化されます。t プロパティの値もアニメーションします。

class CustomTextureItem : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged)
    QML_ELEMENT

public:
    CustomTextureItem();

    qreal t() const { return m_t; }
    void setT(qreal t);

signals:
    void tChanged();

protected:
    QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;
    void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;

private slots:
    void invalidateSceneGraph();

private:
    void releaseResources() override;

    CustomTextureNode *m_node = nullptr;
    qreal m_t = 0;
};

カスタムアイテムの実装には、QQuickItem::updatePaintNode ()のオーバーライド、ジオメトリの変更とクリーンアップに関連する関数とスロットが含まれます。

class CustomTextureNode : public QSGTextureProvider, public QSGSimpleTextureNode
{
    Q_OBJECT

public:
    CustomTextureNode(QQuickItem *item);
    ~CustomTextureNode();

    QSGTexture *texture() const override;

    void sync();

また、シーングラフ・ノードも必要です。QSGNode から直接派生する代わりに、QSGSimpleTextureNode を使用することができます。

QSGNode *CustomTextureItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
    CustomTextureNode *n = static_cast<CustomTextureNode *>(node);

    if (!n && (width() <= 0 || height() <= 0))
        return nullptr;

    if (!n) {
        m_node = new CustomTextureNode(this);
        n = m_node;
    }

    m_node->sync();

    n->setTextureCoordinatesTransform(QSGSimpleTextureNode::NoTransform);
    n->setFiltering(QSGTexture::Linear);
    n->setRect(0, 0, width(), height());

    window()->update(); // ensure getting to beforeRendering() at some point

    return n;
}

アイテムのupdatePaintNode()関数は、メイン(GUI)スレッドがブロックされた状態で、レンダースレッド(ある場合)で呼び出されます。ここでは、まだノードがない場合は新しいノードを作成し、それを更新します。メインスレッドにある Qt オブジェクトへのアクセスは安全なので、sync() はQQuickItem またはQQuickWindow から必要な値を計算してコピーします。

CustomTextureNode::CustomTextureNode(QQuickItem *item)
    : m_item(item)
{
    m_window = m_item->window();
    connect(m_window, &QQuickWindow::beforeRendering, this, &CustomTextureNode::render);
    connect(m_window, &QQuickWindow::screenChanged, this, [this]() {
        if (m_window->effectiveDevicePixelRatio() != m_dpr)
            m_item->update();
    });

このノードは、典型的なQQuickItem -QSGNode の更新シーケンスに依存するだけでなく、QQuickWindow::beforeRendering ()にも接続します。このシグナルは、Qt Quick が自身のレンダリングコマンドをエンコードし始める前に発行されるため、beforeRendering() はこのために適切な場所です。この例では、代わりにQQuickWindow::beforeRenderPassRecording ()を選択するとエラーになります。

void CustomTextureNode::sync()
{
    m_dpr = m_window->effectiveDevicePixelRatio();
    const QSize newSize = m_window->size() * m_dpr;
    bool needsNew = false;

    if (!texture())
        needsNew = true;

    if (newSize != m_size) {
        needsNew = true;
        m_size = newSize;
    }

    if (needsNew) {
        delete texture();
        [m_texture release];

        QSGRendererInterface *rif = m_window->rendererInterface();
        m_device = (id<MTLDevice>) rif->getResource(m_window, QSGRendererInterface::DeviceResource);
        Q_ASSERT(m_device);

        MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
        desc.textureType = MTLTextureType2D;
        desc.pixelFormat = MTLPixelFormatRGBA8Unorm;
        desc.width = m_size.width();
        desc.height = m_size.height();
        desc.mipmapLevelCount = 1;
        desc.resourceOptions = MTLResourceStorageModePrivate;
        desc.storageMode = MTLStorageModePrivate;
        desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
        m_texture = [m_device newTextureWithDescriptor: desc];
        [desc release];

        QSGTexture *wrapper = QNativeInterface::QSGMetalTexture::fromNative(m_texture, m_window, m_size);

        qDebug() << "Got QSGTexture wrapper" << wrapper << "for an MTLTexture of size" << m_size;

        setTexture(wrapper);
    }
    m_t = float(static_cast<CustomTextureItem *>(m_item)->t());

必要な値をコピーした後、sync()はグラフィックス・リソースの初期化も行う。MTLDeviceがシーングラフから照会されます。MTLTextureが利用可能になると、QNativeInterface::QSGOpenGLTexture::fromNative ()を介して、それをラップする(所有しない)QSGTexture 。最後に、QSGTexture 、基底クラスの setTexture() 関数を呼び出すことで、基底マテリアルに関連付けられます。

void CustomTextureNode::render()
{
    if (!m_initialized)
        return;

    // Render to m_texture.
    MTLRenderPassDescriptor *renderpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
    MTLClearColor c = MTLClearColorMake(0, 0, 0, 1);
    renderpassdesc.colorAttachments[0].loadAction = MTLLoadActionClear;
    renderpassdesc.colorAttachments[0].storeAction = MTLStoreActionStore;
    renderpassdesc.colorAttachments[0].clearColor = c;
    renderpassdesc.colorAttachments[0].texture = m_texture;

    QSGRendererInterface *rif = m_window->rendererInterface();
    id<MTLCommandBuffer> cb = (id<MTLCommandBuffer>) rif->getResource(m_window, QSGRendererInterface::CommandListResource);
    Q_ASSERT(cb);
    id<MTLRenderCommandEncoder> encoder = [cb renderCommandEncoderWithDescriptor: renderpassdesc];

    const QQuickWindow::GraphicsStateInfo &stateInfo(m_window->graphicsStateInfo());
    void *p = [m_ubuf[stateInfo.currentFrameSlot] contents];
    memcpy(p, &m_t, 4);

    MTLViewport vp;
    vp.originX = 0;
    vp.originY = 0;
    vp.width = m_size.width();
    vp.height = m_size.height();
    vp.znear = 0;
    vp.zfar = 1;
    [encoder setViewport: vp];

    [encoder setFragmentBuffer: m_ubuf[stateInfo.currentFrameSlot] offset: 0 atIndex: 0];
    [encoder setVertexBuffer: m_vbuf offset: 0 atIndex: 1];
    [encoder setRenderPipelineState: m_pipeline];
    [encoder drawPrimitives: MTLPrimitiveTypeTriangleStrip vertexStart: 0 vertexCount: 4 instanceCount: 1 baseInstance: 0];

    [encoder endEncoding];
}

beforeRendering()に接続されたスロットであるrender()は、sync()で作成されたバッファとパイプライン状態オブジェクトを使用して、レンダリングコマンドをエンコードします。

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

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