场景图 - Metal 纹理导入
展示如何使用使用 Metal 直接创建的纹理。

Metal 纹理导入示例展示了如何在一个 Qt Quick 场景中使用和导入一个 MTLTexture。当需要集成原生 Metal 渲染时,这为“底层”或“叠加”方法提供了一个替代方案。在许多情况下,在纹理上操作,并因此首先将 3D 内容“展平”,是将自定义 3D 内容与 Qt Quick 提供的 2D UI 元素集成和混合的最佳选项。
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)线程被阻塞。在这里,如果还未创建,我们会创建一个新的节点并更新它。访问 QA 快速对象是安全的,所以 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()。就在那里,Metal 纹理的内容将通过在 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。最后,通过调用基类的 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];
}连接到 beforeRendering() 的 render() 调用使用在 sync() 中创建的缓冲区和管道状态对象编码渲染命令。
© 2024 Qt公司有限公司。本文件中的文档贡献均为各自所有者的版权。提供的文档根据自由软件基金会的出版和GNU自由文档许可证(版本1.3)的条款进行许可,请访问GNU自由文档许可证版本1.3。Qt及其相应标识均为芬兰和/或其他国家的Qt公司有限公司的商标。商标。所有其他商标均为各自所有者的财产。