Szenendiagramm - Metall-Texturimport
Zeigt, wie eine direkt mit Metal erstellte Textur verwendet werden kann.
Das Beispiel Metal Texture Import zeigt, wie eine Anwendung eine MTLTextur in die Szene Qt Quick importieren und verwenden kann. Dies stellt eine Alternative zu den Underlay- oder Overlay-Ansätzen dar, wenn es um die Integration von nativem Metal-Rendering geht. In vielen Fällen ist der Weg über eine Textur und damit die "Verflachung" der 3D-Inhalte die beste Option, um benutzerdefinierte 3D-Inhalte mit den von Qt Quick bereitgestellten 2D-UI-Elementen zu integrieren und zu mischen.
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 QQuickItem Unterklasse mit dem Namen CustomTextureItem zur Verfügung. 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 die Überschreibung von QQuickItem::updatePaintNode() sowie Funktionen und Slots, die sich auf Geometrieänderungen und Bereinigungen 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 einen Teil der Funktionalität als Bequemlichkeit vorimplementiert.
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 auf dem Render-Thread (falls vorhanden) aufgerufen, während der Haupt-Thread (GUI) blockiert ist. Hier erstellen wir einen neuen Knoten, falls noch keiner vorhanden ist, und aktualisieren ihn. Der Zugriff auf Qt-Objekte, die auf dem Haupt-Thread leben, ist hier sicher, so dass sync() die benötigten Werte von 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 QQuickItem - QSGNode Aktualisierungssequenz, sondern stellt auch eine Verbindung zu QQuickWindow::beforeRendering() her. Dort wird der Inhalt der Metalltextur aktualisiert, indem ein vollständiger Rendering-Durchgang, der auf die Textur abzielt, in den Befehlspuffer des Qt Quicks-Szenegraphen kodiert wird. beforeRendering() ist der richtige Ort dafür, da das Signal ausgegeben wird, bevor Qt Quick beginnt, seine eigenen Rendering-Befehle zu kodieren. Stattdessen QQuickWindow::beforeRenderPassRecording() zu wählen, 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());
Nachdem die benötigten Werte kopiert wurden, führt sync() auch einige Initialisierungen der Grafikressourcen durch. Das MTLDevice wird aus dem Scenegraph abgefragt. Sobald eine MTLTexture verfügbar ist, wird mit QNativeInterface::QSGOpenGLTexture::fromNative() eine QSGTexture erstellt, die sie umhüllt (nicht besitzt). Schließlich wird die QSGTexture mit den zugrundeliegenden Materialien verbunden, indem die setTexture()-Funktion der Basisklasse aufgerufen wird.
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 Rendering-Befehle unter Verwendung der in sync() erstellten Puffer und Pipeline-Statusobjekte.
© 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.