Szenengraph – Importieren von Metal-Texturen

Zeigt, wie eine direkt mit Metal erstellte Textur verwendet wird.

„Squircle“ als Metal-Textur gerendert und in einem <span translate=Qt Quick-Element angezeigt" src="images/metaltextureimport-example.jpg" title="„Squircle“ als Metal-Textur gerendert und in einem Qt Quick-Element angezeigt"/>

Das Beispiel „Metal-Textur-Import“ zeigt, wie eine Anwendung eine MTLTexture in die „ Qt Quick “-Szene importieren und verwenden kann. Dies bietet eine Alternative zu den Underlay- oder Overlay-Ansätzen, wenn es um die Integration von nativem Metal-Rendering geht. In vielen Fällen ist die Verwendung einer Textur – und damit das vorherige „Abflachen“ der 3D-Inhalte – die beste Option, um benutzerdefinierte 3D-Inhalte in die von „ Qt Quick “ bereitgestellten 2D-UI-Elemente zu integrieren und mit diesen zu kombinieren.

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
    }

Die Anwendung stellt eine benutzerdefinierte Unterklasse von ` QQuickItem ` unter dem Namen `CustomTextureItem` bereit. Diese wird in QML instanziiert. Der Wert der Eigenschaft ` t ` wird ebenfalls animiert.

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;
};

Die Implementierung unseres benutzerdefinierten Elements umfasst das Überschreiben von ` QQuickItem::updatePaintNode()` sowie von Funktionen und Slots, die sich auf Geometrieänderungen und die Bereinigung beziehen.

class CustomTextureNode : public QSGTextureProvider, public QSGSimpleTextureNode
{
    Q_OBJECT

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

    QSGTexture *texture() const override;

    void sync();

Außerdem benötigen wir einen Scenegraph-Knoten. Anstatt direkt von ` QSGNode` abzuleiten, können wir ` QSGSimpleTextureNode ` verwenden, das uns einige der Funktionen bereits vorimplementiert zur Verfügung stellt.

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;
}

Die Funktion `updatePaintNode()` des Elements wird im Render-Thread (sofern vorhanden) aufgerufen, wobei der Haupt- (GUI-)Thread gesperrt ist. Hier erstellen wir einen neuen Knoten, falls noch keiner vorhanden ist, und aktualisieren ihn. Der Zugriff auf Qt-Objekte, die im Haupt-Thread liegen, ist hier sicher, sodass `sync()` die benötigten Werte aus ` QQuickItem ` oder ` QQuickWindow` berechnet und kopiert.

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();
    });

Der Knoten verlässt sich nicht nur auf die typische Aktualisierungssequenz „ QQuickItem – QSGNode “, sondern ist auch mit „ QQuickWindow::beforeRendering()“ verbunden. Dort wird der Inhalt der Metal-Textur aktualisiert, indem ein vollständiger Render-Durchlauf, der auf die Textur abzielt, im Befehlspuffer des Qt Quick-Szenengraphen kodiert wird. „beforeRendering()“ ist hierfür der richtige Ort, da das Signal ausgesendet wird, bevor „ Qt Quick “ mit der Kodierung seiner eigenen Renderbefehle beginnt. Die Wahl von „ QQuickWindow::beforeRenderPassRecording()“ wäre in diesem Beispiel ein Fehler.

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());

Nach dem Kopieren der benötigten Werte führt `sync()` auch einige Initialisierungen der Grafikressourcen durch. Das `MTLDevice` wird aus dem Szenengraphen abgefragt. Sobald eine `MTLTexture` verfügbar ist, wird über ` QNativeInterface::QSGOpenGLTexture::fromNative()` ein ` QSGTexture ` erstellt, das diese umschließt (ohne sie zu besitzen). Schließlich wird die ` QSGTexture ` durch Aufruf der Funktion `setTexture()` der Basisklasse mit den zugrunde liegenden Materialien verknüpft.

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];
}

render(), der mit beforeRendering() verbundene Slot, kodiert die Renderbefehle unter Verwendung der in sync() erstellten Puffer und Pipeline-Zustandsobjekte.

Beispielprojekt @ code.qt.io

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