场景图 - 金属纹理导入
演示如何使用直接用 Metal 创建的纹理。
金属纹理导入示例展示了应用程序如何在Qt Quick 场景中导入和使用MTLTexture。在集成本地 Metal 渲染时,这为底层或覆盖方法提供了另一种选择。在许多情况下,首先通过纹理 "扁平化 "三维内容,是将自定义三维内容与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 }
该应用程序公开了一个自定义QQuickItem 子类,名称为 CustomTextureItem。该子类在 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 Quicks 场景图的命令缓冲区上,针对纹理编码一个完整的渲染传递,从而更新金属纹理的内容。beforeRendering() 是正确的位置,因为信号是在Qt Quick 开始编码自己的渲染命令之前发出的。在此示例中,选择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() 创建一个包装(而非拥有)该 MTLTexture 的QSGTexture 。最后,通过调用基类的 setTexture() 函数将QSGTexture 与底层材质关联起来。
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()是与 beforeRendering() 相连的槽,它使用在 sync() 中创建的缓冲区和管道状态对象对渲染命令进行编码。
© 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.