diff options
author | Laszlo Agocs <laszlo.agocs@qt.io> | 2019-08-16 14:21:15 +0200 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@qt.io> | 2019-08-22 16:10:24 +0200 |
commit | 7dcdf3b7ae27cd26b84731941f5b1f380c5b3316 (patch) | |
tree | 90f5641dc9d4951c7318328d0a205a30a500f775 /examples/quick/scenegraph | |
parent | ef2715251e1785e273873e4000ed08fd99962ab7 (diff) |
rendernode example: Add support for Metal
Plus clarify QQuickWindow::begin/endExternalCommands() in combination
with QSGRenderNode in the docs. As the example demonstrates, calling
these functions is not necessary within render() of a render node.
Also fix an issue with resetting the scissor in the renderer after
calling render() of a QSGRenderNode.
Change-Id: If8c2dab38d62aa444266d37901f062a51e767f68
Reviewed-by: Christian Strømme <christian.stromme@qt.io>
Diffstat (limited to 'examples/quick/scenegraph')
9 files changed, 513 insertions, 1 deletions
diff --git a/examples/quick/scenegraph/rendernode/customrenderitem.cpp b/examples/quick/scenegraph/rendernode/customrenderitem.cpp index 7ba9286afa..67a9cccfc6 100644 --- a/examples/quick/scenegraph/rendernode/customrenderitem.cpp +++ b/examples/quick/scenegraph/rendernode/customrenderitem.cpp @@ -53,6 +53,7 @@ #include <QSGRendererInterface> #include "openglrenderer.h" +#include "metalrenderer.h" #include "d3d12renderer.h" #include "softwarerenderer.h" @@ -82,6 +83,18 @@ QSGNode *CustomRenderItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) #endif break; + case QSGRendererInterface::MetalRhi: +#ifdef Q_OS_DARWIN + { + MetalRenderNode *metalNode = new MetalRenderNode(this); + n = metalNode; + metalNode->resourceBuilder()->setWindow(window()); + QObject::connect(window(), &QQuickWindow::beforeRendering, + metalNode->resourceBuilder(), &MetalRenderNodeResourceBuilder::build); + } +#endif + break; + case QSGRendererInterface::Direct3D12: // ### Qt 6: remove #if QT_CONFIG(d3d12) n = new D3D12RenderNode(this); @@ -102,3 +115,6 @@ QSGNode *CustomRenderItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) return n; } //! [2] + +// This item does not support being moved between windows. If that is desired, +// itemChange() should be reimplemented as well. diff --git a/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc b/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc index e7864387cc..ba6551fddf 100644 --- a/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc +++ b/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc @@ -38,7 +38,9 @@ Metal, Direct 3D, or OpenGL). This example demonstrates implementing a custom QQuickItem backed by a QSGRenderNode implementation, where the node renders a triangle directly via the graphics API. The rest of the scene - (background, text, rectangles) are standard Qt Quick items. + (background, text, rectangles) are standard Qt Quick items. The example has + full support for OpenGL and Metal, as well as the software backend of Qt + Quick. The custom item behaves like any other Qt Quick item, meaning it participates and stacking and clipping as usual, which is a big difference diff --git a/examples/quick/scenegraph/rendernode/main.qml b/examples/quick/scenegraph/rendernode/main.qml index c89d6136c7..153a71e097 100644 --- a/examples/quick/scenegraph/rendernode/main.qml +++ b/examples/quick/scenegraph/rendernode/main.qml @@ -146,6 +146,7 @@ Item { case GraphicsInfo.Direct3D12: apiStr = "Direct3D 12 (direct)"; break; case GraphicsInfo.Software: apiStr = "Software (QPainter)"; break; case GraphicsInfo.OpenGLRhi: apiStr = "OpenGL (RHI)"; break; + case GraphicsInfo.MetalRhi: apiStr = "Metal (RHI)"; break; // the example has no other QSGRenderNode subclasses default: apiStr = "<UNSUPPORTED>"; break; } diff --git a/examples/quick/scenegraph/rendernode/metalrenderer.h b/examples/quick/scenegraph/rendernode/metalrenderer.h new file mode 100644 index 0000000000..77c9892313 --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalrenderer.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef METALRENDERER_H +#define METALRENDERER_H + +#include <qsgrendernode.h> + +#ifdef Q_OS_DARWIN + +QT_BEGIN_NAMESPACE + +class QQuickItem; +class QQuickWindow; + +QT_END_NAMESPACE + +class MetalRenderNodeResourceBuilder : public QObject +{ + Q_OBJECT + +public: + void setWindow(QQuickWindow *w) { m_window = w; } + +public slots: + void build(); + +private: + QQuickWindow *m_window = nullptr; +}; + +class MetalRenderNode : public QSGRenderNode +{ +public: + MetalRenderNode(QQuickItem *item); + ~MetalRenderNode(); + + void render(const RenderState *state) override; + void releaseResources() override; + StateFlags changedStates() const override; + RenderingFlags flags() const override; + QRectF rect() const override; + + MetalRenderNodeResourceBuilder *resourceBuilder() { return &m_resourceBuilder; } + +private: + QQuickItem *m_item; + MetalRenderNodeResourceBuilder m_resourceBuilder; +}; + +#endif // Q_OS_DARWIN + +#endif diff --git a/examples/quick/scenegraph/rendernode/metalrenderer.mm b/examples/quick/scenegraph/rendernode/metalrenderer.mm new file mode 100644 index 0000000000..4cb973abee --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalrenderer.mm @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "metalrenderer.h" +#include <QQuickItem> +#include <QQuickWindow> + +#include <Metal/Metal.h> + +using FuncAndLib = QPair<id<MTLFunction>, id<MTLLibrary> >; + +const int MAX_FRAMES_IN_FLIGHT = 3; + +struct { + id<MTLDevice> dev = nil; + QByteArray vsSource; + FuncAndLib vs; + QByteArray fsSource; + FuncAndLib fs; + id<MTLBuffer> vbuf[MAX_FRAMES_IN_FLIGHT]; + id<MTLBuffer> ubuf[MAX_FRAMES_IN_FLIGHT]; + id<MTLDepthStencilState> stencilEnabledDsState = nil; + id<MTLRenderPipelineState> pipeline = nil; +} g; + +static FuncAndLib compileShaderFromSource(const QByteArray &src, const QByteArray &entryPoint) +{ + FuncAndLib fl; + + NSString *srcstr = [NSString stringWithUTF8String: src.constData()]; + MTLCompileOptions *opts = [[MTLCompileOptions alloc] init]; + opts.languageVersion = MTLLanguageVersion1_2; + NSError *err = nil; + fl.second = [g.dev newLibraryWithSource: srcstr options: opts error: &err]; + [opts release]; + // srcstr is autoreleased + + if (err) { + const QString msg = QString::fromNSString(err.localizedDescription); + qFatal("%s", qPrintable(msg)); + return fl; + } + + NSString *name = [NSString stringWithUTF8String: entryPoint.constData()]; + fl.first = [fl.second newFunctionWithName: name]; + [name release]; + + return fl; +} + +const int VERTEX_SIZE = 6 * sizeof(float); + +static float colors[] = { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f +}; + +void MetalRenderNodeResourceBuilder::build() +{ + if (!g.dev) { + QSGRendererInterface *rif = m_window->rendererInterface(); + Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::MetalRhi); + + g.dev = (id<MTLDevice>) rif->getResource(m_window, QSGRendererInterface::DeviceResource); + Q_ASSERT(g.dev); + } + + if (g.vsSource.isEmpty()) { + const QString filename = QLatin1String(":/scenegraph/rendernode/metalshader.vert"); + QFile f(filename); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) + qFatal("Failed to read shader %s", qPrintable(filename)); + g.vsSource = f.readAll(); + g.vs = compileShaderFromSource(g.vsSource, QByteArrayLiteral("main0")); + } + + if (g.fsSource.isEmpty()) { + const QString filename = QLatin1String(":/scenegraph/rendernode/metalshader.frag"); + QFile f(filename); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) + qFatal("Failed to read shader %s", qPrintable(filename)); + g.fsSource = f.readAll(); + g.fs = compileShaderFromSource(g.fsSource, QByteArrayLiteral("main0")); + } + + const int framesInFlight = m_window->graphicsStateInfo()->framesInFlight; + + // For simplicity's sake we use shared mode (something like host visible + + // host coherent) for everything. + + for (int i = 0; i < framesInFlight; ++i) { + // Have multiple versions for vertex too since we'll just memcpy new + // vertices based on item width and height on every render(). This could + // be optimized further however. + if (!g.vbuf[i]) { + g.vbuf[i] = [g.dev newBufferWithLength: VERTEX_SIZE + sizeof(colors) options: MTLResourceStorageModeShared]; + char *p = (char *) [g.vbuf[i] contents]; + memcpy(p + VERTEX_SIZE, colors, sizeof(colors)); + } + + if (!g.ubuf[i]) + g.ubuf[i] = [g.dev newBufferWithLength: 256 options: MTLResourceStorageModeShared]; + } + + if (!g.stencilEnabledDsState) { + MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init]; + dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init]; + dsDesc.frontFaceStencil.stencilFailureOperation = MTLStencilOperationKeep; + dsDesc.frontFaceStencil.depthFailureOperation = MTLStencilOperationKeep; + dsDesc.frontFaceStencil.depthStencilPassOperation = MTLStencilOperationKeep; + dsDesc.frontFaceStencil.stencilCompareFunction = MTLCompareFunctionEqual; + dsDesc.frontFaceStencil.readMask = 0xFF; + dsDesc.frontFaceStencil.writeMask = 0xFF; + + dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init]; + dsDesc.backFaceStencil.stencilFailureOperation = MTLStencilOperationKeep; + dsDesc.backFaceStencil.depthFailureOperation = MTLStencilOperationKeep; + dsDesc.backFaceStencil.depthStencilPassOperation = MTLStencilOperationKeep; + dsDesc.backFaceStencil.stencilCompareFunction = MTLCompareFunctionEqual; + dsDesc.backFaceStencil.readMask = 0xFF; + dsDesc.backFaceStencil.writeMask = 0xFF; + + g.stencilEnabledDsState = [g.dev newDepthStencilStateWithDescriptor: dsDesc]; + [dsDesc release]; + } + + if (!g.pipeline) { + MTLVertexDescriptor *inputLayout = [MTLVertexDescriptor vertexDescriptor]; + inputLayout.attributes[0].format = MTLVertexFormatFloat2; + inputLayout.attributes[0].offset = 0; + inputLayout.attributes[0].bufferIndex = 1; // ubuf is 0, vbuf is 1 and 2 + inputLayout.attributes[1].format = MTLVertexFormatFloat3; + inputLayout.attributes[1].offset = 0; + inputLayout.attributes[1].bufferIndex = 2; + inputLayout.layouts[1].stride = 2 * sizeof(float); + inputLayout.layouts[2].stride = 3 * sizeof(float); + + MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init]; + rpDesc.vertexDescriptor = inputLayout; + + rpDesc.vertexFunction = g.vs.first; + rpDesc.fragmentFunction = g.fs.first; + + rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; + rpDesc.colorAttachments[0].blendingEnabled = true; + rpDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne; + rpDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; + rpDesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + rpDesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + + if (g.dev.depth24Stencil8PixelFormatSupported) { + rpDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth24Unorm_Stencil8; + rpDesc.stencilAttachmentPixelFormat = MTLPixelFormatDepth24Unorm_Stencil8; + } else { + rpDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + rpDesc.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + } + + NSError *err = nil; + g.pipeline = [g.dev newRenderPipelineStateWithDescriptor: rpDesc error: &err]; + if (!g.pipeline) { + const QString msg = QString::fromNSString(err.localizedDescription); + qFatal("Failed to create render pipeline state: %s", qPrintable(msg)); + } + [rpDesc release]; + } +} + +MetalRenderNode::MetalRenderNode(QQuickItem *item) + : m_item(item) +{ + g.vs.first = g.fs.first = nil; + g.vs.second = g.fs.second = nil; + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + g.vbuf[i] = nil; + g.ubuf[i] = nil; + } +} + +MetalRenderNode::~MetalRenderNode() +{ + releaseResources(); +} + +void MetalRenderNode::releaseResources() +{ + [g.stencilEnabledDsState release]; + g.stencilEnabledDsState = nil; + + [g.pipeline release]; + g.pipeline = nil; + + [g.vs.first release]; + [g.vs.second release]; + + [g.fs.first release]; + [g.fs.second release]; + + g.vs.first = g.fs.first = nil; + g.vs.second = g.fs.second = nil; + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + [g.vbuf[i] release]; + g.vbuf[i] = nil; + [g.ubuf[i] release]; + g.ubuf[i] = nil; + } +} + +void MetalRenderNode::render(const RenderState *state) +{ + QQuickWindow *window = m_item->window(); + const QQuickWindow::GraphicsStateInfo *stateInfo = window->graphicsStateInfo(); + id<MTLBuffer> vbuf = g.vbuf[stateInfo->currentFrameSlot]; + id<MTLBuffer> ubuf = g.ubuf[stateInfo->currentFrameSlot]; + + QPointF p0(m_item->width() - 1, m_item->height() - 1); + QPointF p1(0, 0); + QPointF p2(0, m_item->height() - 1); + + float vertices[6] = { float(p0.x()), float(p0.y()), + float(p1.x()), float(p1.y()), + float(p2.x()), float(p2.y()) }; + char *p = (char *) [vbuf contents]; + memcpy(p, vertices, VERTEX_SIZE); + + const QMatrix4x4 mvp = *state->projectionMatrix() * *matrix(); + const float opacity = inheritedOpacity(); + + p = (char *) [ubuf contents]; + memcpy(p, mvp.constData(), 64); + memcpy(p + 64, &opacity, 4); + + QSGRendererInterface *rif = window->rendererInterface(); + id<MTLRenderCommandEncoder> encoder = (id<MTLRenderCommandEncoder>) rif->getResource( + window, QSGRendererInterface::CommandEncoderResource); + Q_ASSERT(encoder); + + [encoder setVertexBuffer: vbuf offset: 0 atIndex: 1]; + [encoder setVertexBuffer: vbuf offset: VERTEX_SIZE atIndex: 2]; + + [encoder setVertexBuffer: ubuf offset: 0 atIndex: 0]; + [encoder setFragmentBuffer: ubuf offset: 0 atIndex: 0]; + + // Clip support. + if (state->scissorEnabled()) { + const QRect r = state->scissorRect(); // bottom-up + MTLScissorRect s; + s.x = r.x(); + s.y = (window->height() * window->effectiveDevicePixelRatio()) - (r.y() + r.height()); + s.width = r.width(); + s.height = r.height(); + [encoder setScissorRect: s]; + } + if (state->stencilEnabled()) { + [encoder setDepthStencilState: g.stencilEnabledDsState]; + [encoder setStencilReferenceValue: state->stencilValue()]; + } + + [encoder setRenderPipelineState: g.pipeline]; + [encoder drawPrimitives: MTLPrimitiveTypeTriangle vertexStart: 0 vertexCount: 3 instanceCount: 1 baseInstance: 0]; +} + +QSGRenderNode::StateFlags MetalRenderNode::changedStates() const +{ + return BlendState | ScissorState | StencilState; +} + +QSGRenderNode::RenderingFlags MetalRenderNode::flags() const +{ + return BoundedRectRendering | DepthAwareRendering; +} + +QRectF MetalRenderNode::rect() const +{ + return QRect(0, 0, m_item->width(), m_item->height()); +} diff --git a/examples/quick/scenegraph/rendernode/metalshader.frag b/examples/quick/scenegraph/rendernode/metalshader.frag new file mode 100644 index 0000000000..907faa537f --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalshader.frag @@ -0,0 +1,28 @@ +#include <metal_stdlib> +#include <simd/simd.h> + +using namespace metal; + +struct buf +{ + float4x4 matrix; + float opacity; +}; + +struct main0_out +{ + float4 fragColor [[color(0)]]; +}; + +struct main0_in +{ + float4 v_color [[user(locn0)]]; +}; + +fragment main0_out main0(main0_in in [[stage_in]], constant buf& ubuf [[buffer(0)]]) +{ + main0_out out = {}; + out.fragColor = in.v_color * ubuf.opacity; + return out; +} + diff --git a/examples/quick/scenegraph/rendernode/metalshader.vert b/examples/quick/scenegraph/rendernode/metalshader.vert new file mode 100644 index 0000000000..12b721f524 --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalshader.vert @@ -0,0 +1,31 @@ +#include <metal_stdlib> +#include <simd/simd.h> + +using namespace metal; + +struct buf +{ + float4x4 matrix; + float opacity; +}; + +struct main0_out +{ + float4 v_color [[user(locn0)]]; + float4 gl_Position [[position]]; +}; + +struct main0_in +{ + float4 pos [[attribute(0)]]; + float4 color [[attribute(1)]]; +}; + +vertex main0_out main0(main0_in in [[stage_in]], constant buf& ubuf [[buffer(0)]]) +{ + main0_out out = {}; + out.v_color = in.color; + out.gl_Position = ubuf.matrix * in.pos; + return out; +} + diff --git a/examples/quick/scenegraph/rendernode/rendernode.pro b/examples/quick/scenegraph/rendernode/rendernode.pro index 76e498042b..897b0b1f08 100644 --- a/examples/quick/scenegraph/rendernode/rendernode.pro +++ b/examples/quick/scenegraph/rendernode/rendernode.pro @@ -22,3 +22,9 @@ qtConfig(d3d12) { SOURCES += d3d12renderer.cpp LIBS += -ld3d12 } + +macos { + HEADERS += metalrenderer.h + SOURCES += metalrenderer.mm + LIBS += -framework Metal -framework AppKit +} diff --git a/examples/quick/scenegraph/rendernode/rendernode.qrc b/examples/quick/scenegraph/rendernode/rendernode.qrc index 049adcf8a6..5907eab62c 100644 --- a/examples/quick/scenegraph/rendernode/rendernode.qrc +++ b/examples/quick/scenegraph/rendernode/rendernode.qrc @@ -3,5 +3,7 @@ <file>main.qml</file> <file>shader_vert.cso</file> <file>shader_frag.cso</file> + <file>metalshader.vert</file> + <file>metalshader.frag</file> </qresource> </RCC> |