Scene Graph - Importation d'une texture métallique
Montre comment utiliser une texture créée directement avec Metal.

L'exemple Metal Texture Import montre comment une application peut importer et utiliser une MTLTexture dans la scène Qt Quick. Il s'agit d'une alternative aux approches de sous-couche ou de superposition lorsqu'il s'agit d'intégrer le rendu natif de Metal. Dans de nombreux cas, passer par une texture, et donc "aplatir" d'abord les contenus 3D, est la meilleure option pour intégrer et mélanger des contenus 3D personnalisés avec les éléments d'interface utilisateur 2D fournis par Qt Quick.
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 }
L'application expose une sous-classe personnalisée de QQuickItem sous le nom de CustomTextureItem. Cette sous-classe est instanciée en QML. La valeur de la propriété t est également animée.
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; };
La mise en œuvre de notre élément personnalisé implique la surcharge de QQuickItem::updatePaintNode(), ainsi que des fonctions et des emplacements liés aux changements de géométrie et au nettoyage.
class CustomTextureNode : public QSGTextureProvider, public QSGSimpleTextureNode
{
Q_OBJECT
public:
CustomTextureNode(QQuickItem *item);
~CustomTextureNode();
QSGTexture *texture() const override;
void sync();Nous avons également besoin d'un nœud de graphe de scène. Au lieu de dériver directement de QSGNode, nous pouvons utiliser QSGSimpleTextureNode qui nous donne une partie de la fonctionnalité préimplémentée par commodité.
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;
}La fonction updatePaintNode() de l'élément est appelée sur le thread de rendu (s'il y en a un), le thread principal (GUI) étant bloqué. Ici, nous créons un nouveau nœud s'il n'y en a pas encore, et nous le mettons à jour. L'accès aux objets Qt XML vivant sur le thread principal est ici sans danger, de sorte que sync() calculera et copiera les valeurs dont il a besoin à partir de QQuickItem ou 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();
});Le nœud ne se contente pas de s'appuyer sur la séquence typique de mise à jour QQuickItem - QSGNode, il se connecte également à QQuickWindow::beforeRendering(). C'est là que le contenu de la texture Metal sera mis à jour en encodant une passe de rendu complète, ciblant la texture, sur le tampon de commande de Qt Quick. beforeRendering() est le bon endroit pour cela, parce que le signal est émis avant que Qt Quick ne commence à encoder ses propres commandes de rendu. Choisir QQuickWindow::beforeRenderPassRecording() à la place serait une erreur dans cet exemple.
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());Après avoir copié les valeurs dont nous avons besoin, sync() effectue également une initialisation des ressources graphiques. Le MTLDevice est interrogé à partir du scenegraph. Une fois qu'une MTLTexture est disponible, une QSGTexture l'enveloppant (sans la posséder) est créée via QNativeInterface::QSGOpenGLTexture::fromNative(). Enfin, QSGTexture est associé aux matériaux sous-jacents en appelant la fonction setTexture() de la classe de base.
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(), le slot connecté à beforeRendering(), encode les commandes de rendu en utilisant les tampons et les objets d'état du pipeline créés dans sync().
© 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.