summaryrefslogtreecommitdiffstats
path: root/src/gui/rhi/qrhimetal.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/rhi/qrhimetal.mm')
-rw-r--r--src/gui/rhi/qrhimetal.mm3566
1 files changed, 3566 insertions, 0 deletions
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
new file mode 100644
index 0000000000..fa537a504b
--- /dev/null
+++ b/src/gui/rhi/qrhimetal.mm
@@ -0,0 +1,3566 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhimetal_p_p.h"
+#include "qshader_p.h"
+#include "qshaderdescription_p.h"
+#include <QGuiApplication>
+#include <QWindow>
+#include <qmath.h>
+
+#ifdef Q_OS_MACOS
+#include <AppKit/AppKit.h>
+#endif
+
+#include <Metal/Metal.h>
+#include <QuartzCore/CAMetalLayer.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Metal backend. Double buffers and throttles to vsync. "Dynamic" buffers are
+ Shared (host visible) and duplicated (to help having 2 frames in flight),
+ "static" and "immutable" are Managed on macOS and Shared on iOS/tvOS.
+ Textures are Private (device local) and a host visible staging buffer is
+ used to upload data to them. Does not rely on strong objects refs from
+ command buffers but does rely on the automatic resource tracking of the
+ command encoders.
+*/
+
+#if __has_feature(objc_arc)
+#error ARC not supported
+#endif
+
+// Note: we expect everything here pass the Metal API validation when running
+// in Debug mode in XCode. Some of the issues that break validation are not
+// obvious and not visible when running outside XCode.
+//
+// An exception is the nextDrawable Called Early blah blah warning, which is
+// plain and simply false.
+
+/*!
+ \class QRhiMetalInitParams
+ \inmodule QtRhi
+ \brief Metal specific initialization parameters.
+
+ A Metal-based QRhi needs no special parameters for initialization.
+
+ \badcode
+ QRhiMetalInitParams params;
+ rhi = QRhi::create(QRhi::Metal, &params);
+ \endcode
+
+ \note Metal API validation cannot be enabled by the application. Instead,
+ run the debug build of the application in XCode. Generating a
+ \c{.xcodeproj} file via \c{qmake -spec macx-xcode} provides a convenient
+ way to enable this.
+
+ \note QRhiSwapChain can only target QWindow instances that have their
+ surface type set to QSurface::MetalSurface.
+
+ \section2 Working with existing Metal devices
+
+ When interoperating with another graphics engine, it may be necessary to
+ get a QRhi instance that uses the same Metal device. This can be achieved
+ by passing a pointer to a QRhiMetalNativeHandles to QRhi::create(). The
+ device must be set to a non-null value then. Optionally, a command queue
+ object can be specified as well.
+
+ The QRhi does not take ownership of any of the external objects.
+ */
+
+/*!
+ \class QRhiMetalNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Metal device used by the QRhi.
+
+ \note The class uses \c{void *} as the type since including the Objective C
+ headers is not acceptable here. The actual types are \c{id<MTLDevice>} and
+ \c{id<MTLCommandQueue>}.
+ */
+
+/*!
+ \class QRhiMetalTextureNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Metal texture object that is backing a QRhiTexture instance.
+
+ \note The class uses \c{void *} as the type since including the Objective C
+ headers is not acceptable here. The actual type is \c{id<MTLTexture>}.
+ */
+
+/*!
+ \class QRhiMetalCommandBufferNativeHandles
+ \inmodule QtRhi
+ \brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer.
+
+ \note The command buffer object is only guaranteed to be valid while
+ recording a frame, that is, between a \l{QRhi::beginFrame()}{beginFrame()}
+ - \l{QRhi::endFrame()}{endFrame()} or
+ \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
+ \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair.
+
+ \note The command encoder is only valid while recording a pass, that is,
+ between \l{QRhiCommandBuffer::beginPass()} -
+ \l{QRhiCommandBuffer::endPass()}.
+ */
+
+struct QRhiMetalData
+{
+ QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }
+
+ id<MTLDevice> dev = nil;
+ id<MTLCommandQueue> cmdQueue = nil;
+
+ MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ int colorAttCount);
+ id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
+ QString *error, QByteArray *entryPoint);
+ id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);
+
+ struct DeferredReleaseEntry {
+ enum Type {
+ Buffer,
+ RenderBuffer,
+ Texture,
+ Sampler,
+ StagingBuffer
+ };
+ Type type;
+ int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
+ union {
+ struct {
+ id<MTLBuffer> buffers[QMTL_FRAMES_IN_FLIGHT];
+ } buffer;
+ struct {
+ id<MTLTexture> texture;
+ } renderbuffer;
+ struct {
+ id<MTLTexture> texture;
+ id<MTLBuffer> stagingBuffers[QMTL_FRAMES_IN_FLIGHT];
+ id<MTLTexture> views[QRhi::MAX_LEVELS];
+ } texture;
+ struct {
+ id<MTLSamplerState> samplerState;
+ } sampler;
+ struct {
+ id<MTLBuffer> buffer;
+ } stagingBuffer;
+ };
+ };
+ QVector<DeferredReleaseEntry> releaseQueue;
+
+ struct OffscreenFrame {
+ OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+ bool active = false;
+ QMetalCommandBuffer cbWrapper;
+ } ofr;
+
+ struct ActiveReadback {
+ int activeFrameSlot = -1;
+ QRhiReadbackDescription desc;
+ QRhiReadbackResult *result;
+ id<MTLBuffer> buf;
+ quint32 bufSize;
+ QSize pixelSize;
+ QRhiTexture::Format format;
+ };
+ QVector<ActiveReadback> activeReadbacks;
+
+ API_AVAILABLE(macos(10.13), ios(11.0)) MTLCaptureManager *captureMgr;
+ API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil;
+
+ static const int TEXBUF_ALIGN = 256; // probably not accurate
+};
+
+Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE);
+
+struct QMetalBufferData
+{
+ bool managed;
+ bool slotted;
+ id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT];
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
+};
+
+struct QMetalRenderBufferData
+{
+ MTLPixelFormat format;
+ id<MTLTexture> tex = nil;
+};
+
+struct QMetalTextureData
+{
+ QMetalTextureData(QMetalTexture *t) : q(t) { }
+
+ QMetalTexture *q;
+ MTLPixelFormat format;
+ id<MTLTexture> tex = nil;
+ id<MTLBuffer> stagingBuf[QMTL_FRAMES_IN_FLIGHT];
+ bool owns = true;
+ id<MTLTexture> perLevelViews[QRhi::MAX_LEVELS];
+
+ id<MTLTexture> viewForLevel(int level);
+};
+
+struct QMetalSamplerData
+{
+ id<MTLSamplerState> samplerState = nil;
+};
+
+struct QMetalCommandBufferData
+{
+ id<MTLCommandBuffer> cb;
+ id<MTLRenderCommandEncoder> currentRenderPassEncoder;
+ id<MTLComputeCommandEncoder> currentComputePassEncoder;
+ MTLRenderPassDescriptor *currentPassRpDesc;
+ int currentFirstVertexBinding;
+ QRhiBatchedBindings<id<MTLBuffer> > currentVertexInputsBuffers;
+ QRhiBatchedBindings<NSUInteger> currentVertexInputOffsets;
+};
+
+struct QMetalRenderTargetData
+{
+ QSize pixelSize;
+ float dpr = 1;
+ int sampleCount = 1;
+ int colorAttCount = 0;
+ int dsAttCount = 0;
+
+ struct ColorAtt {
+ bool needsDrawableForTex = false;
+ id<MTLTexture> tex = nil;
+ int layer = 0;
+ int level = 0;
+ bool needsDrawableForResolveTex = false;
+ id<MTLTexture> resolveTex = nil;
+ int resolveLayer = 0;
+ int resolveLevel = 0;
+ };
+
+ struct {
+ ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
+ id<MTLTexture> dsTex = nil;
+ bool hasStencil = false;
+ bool depthNeedsStore = false;
+ } fb;
+};
+
+struct QMetalGraphicsPipelineData
+{
+ id<MTLRenderPipelineState> ps = nil;
+ id<MTLDepthStencilState> ds = nil;
+ MTLPrimitiveType primitiveType;
+ MTLWinding winding;
+ MTLCullMode cullMode;
+ id<MTLLibrary> vsLib = nil;
+ id<MTLFunction> vsFunc = nil;
+ id<MTLLibrary> fsLib = nil;
+ id<MTLFunction> fsFunc = nil;
+};
+
+struct QMetalComputePipelineData
+{
+ id<MTLComputePipelineState> ps = nil;
+ id<MTLLibrary> csLib = nil;
+ id<MTLFunction> csFunc = nil;
+ MTLSize localSize;
+};
+
+struct QMetalSwapChainData
+{
+ CAMetalLayer *layer = nullptr;
+ id<CAMetalDrawable> curDrawable;
+ dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT];
+ MTLRenderPassDescriptor *rp = nullptr;
+ id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
+ QRhiTexture::Format rhiColorFormat;
+ MTLPixelFormat colorFormat;
+};
+
+QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
+{
+ Q_UNUSED(params);
+
+ d = new QRhiMetalData(this);
+
+ importedDevice = importDevice != nullptr;
+ if (importedDevice) {
+ if (d->dev) {
+ d->dev = (id<MTLDevice>) importDevice->dev;
+ importedCmdQueue = importDevice->cmdQueue != nullptr;
+ if (importedCmdQueue)
+ d->cmdQueue = (id<MTLCommandQueue>) importDevice->cmdQueue;
+ } else {
+ qWarning("No MTLDevice given, cannot import");
+ importedDevice = false;
+ }
+ }
+}
+
+QRhiMetal::~QRhiMetal()
+{
+ delete d;
+}
+
+static inline uint aligned(uint v, uint byteAlign)
+{
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+bool QRhiMetal::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+
+ if (importedDevice)
+ [d->dev retain];
+ else
+ d->dev = MTLCreateSystemDefaultDevice();
+
+ qDebug("Metal device: %s", qPrintable(QString::fromNSString([d->dev name])));
+
+ if (importedCmdQueue)
+ [d->cmdQueue retain];
+ else
+ d->cmdQueue = [d->dev newCommandQueue];
+
+ if (@available(macOS 10.13, iOS 11.0, *)) {
+ d->captureMgr = [MTLCaptureManager sharedCaptureManager];
+ // Have a custom capture scope as well which then shows up in XCode as
+ // an option when capturing, and becomes especially useful when having
+ // multiple windows with multiple QRhis.
+ d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
+ const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
+ d->captureScope.label = label.toNSString();
+ }
+
+#if defined(Q_OS_MACOS)
+ caps.maxTextureSize = 16384;
+#elif defined(Q_OS_TVOS)
+ if ([d->dev supportsFeatureSet: MTLFeatureSet(30003)]) // MTLFeatureSet_tvOS_GPUFamily2_v1
+ caps.maxTextureSize = 16384;
+ else
+ caps.maxTextureSize = 8192;
+#elif defined(Q_OS_IOS)
+ // welcome to feature set hell
+ if ([d->dev supportsFeatureSet: MTLFeatureSet(16)] // MTLFeatureSet_iOS_GPUFamily5_v1
+ || [d->dev supportsFeatureSet: MTLFeatureSet(11)] // MTLFeatureSet_iOS_GPUFamily4_v1
+ || [d->dev supportsFeatureSet: MTLFeatureSet(4)]) // MTLFeatureSet_iOS_GPUFamily3_v1
+ {
+ caps.maxTextureSize = 16384;
+ } else if ([d->dev supportsFeatureSet: MTLFeatureSet(3)] // MTLFeatureSet_iOS_GPUFamily2_v2
+ || [d->dev supportsFeatureSet: MTLFeatureSet(2)]) // MTLFeatureSet_iOS_GPUFamily1_v2
+ {
+ caps.maxTextureSize = 8192;
+ } else {
+ caps.maxTextureSize = 4096;
+ }
+#endif
+
+ nativeHandlesStruct.dev = d->dev;
+ nativeHandlesStruct.cmdQueue = d->cmdQueue;
+
+ return true;
+}
+
+void QRhiMetal::destroy()
+{
+ executeDeferredReleases(true);
+ finishActiveReadbacks(true);
+
+ if (@available(macOS 10.13, iOS 11.0, *)) {
+ [d->captureScope release];
+ d->captureScope = nil;
+ }
+
+ [d->cmdQueue release];
+ if (!importedCmdQueue)
+ d->cmdQueue = nil;
+
+ [d->dev release];
+ if (!importedDevice)
+ d->dev = nil;
+}
+
+QVector<int> QRhiMetal::supportedSampleCounts() const
+{
+ return { 1, 2, 4, 8 };
+}
+
+int QRhiMetal::effectiveSampleCount(int sampleCount) const
+{
+ // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
+ const int s = qBound(1, sampleCount, 64);
+ if (!supportedSampleCounts().contains(s)) {
+ qWarning("Attempted to set unsupported sample count %d", sampleCount);
+ return 1;
+ }
+ return s;
+}
+
+QRhiSwapChain *QRhiMetal::createSwapChain()
+{
+ return new QMetalSwapChain(this);
+}
+
+QRhiBuffer *QRhiMetal::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QMetalBuffer(this, type, usage, size);
+}
+
+int QRhiMetal::ubufAlignment() const
+{
+ return 256;
+}
+
+bool QRhiMetal::isYUpInFramebuffer() const
+{
+ return false;
+}
+
+bool QRhiMetal::isYUpInNDC() const
+{
+ return true;
+}
+
+bool QRhiMetal::isClipDepthZeroToOne() const
+{
+ return true;
+}
+
+QMatrix4x4 QRhiMetal::clipSpaceCorrMatrix() const
+{
+ // depth range 0..1
+ static QMatrix4x4 m;
+ if (m.isIdentity()) {
+ // NB the ctor takes row-major
+ m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.5f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ }
+ return m;
+}
+
+bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ Q_UNUSED(flags);
+
+#ifdef Q_OS_MACOS
+ if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
+ return false;
+ if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12)
+ return false;
+#else
+ if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
+ return false;
+#endif
+
+ return true;
+}
+
+bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
+{
+ switch (feature) {
+ case QRhi::MultisampleTexture:
+ return true;
+ case QRhi::MultisampleRenderBuffer:
+ return true;
+ case QRhi::DebugMarkers:
+ return true;
+ case QRhi::Timestamps:
+ return false;
+ case QRhi::Instancing:
+ return true;
+ case QRhi::CustomInstanceStepRate:
+ return true;
+ case QRhi::PrimitiveRestart:
+ return true;
+ case QRhi::NonDynamicUniformBuffers:
+ return true;
+ case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+ return false;
+ case QRhi::NPOTTextureRepeat:
+ return true;
+ case QRhi::RedOrAlpha8IsRed:
+ return true;
+ case QRhi::ElementIndexUint:
+ return true;
+ case QRhi::Compute:
+ return true;
+ case QRhi::WideLines:
+ return false;
+ case QRhi::VertexShaderPointSize:
+ return true;
+ case QRhi::BaseVertex:
+ return true;
+ case QRhi::BaseInstance:
+ return true;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return caps.maxTextureSize;
+ case QRhi::MaxColorAttachments:
+ return 8;
+ case QRhi::FramesInFlight:
+ return QMTL_FRAMES_IN_FLIGHT;
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiMetal::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiMetal::sendVMemStatsToProfiler()
+{
+ // nothing to do here
+}
+
+void QRhiMetal::makeThreadLocalNativeContextCurrent()
+{
+ // nothing to do here
+}
+
+QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QMetalTexture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QMetalTextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiMetal::createGraphicsPipeline()
+{
+ return new QMetalGraphicsPipeline(this);
+}
+
+QRhiComputePipeline *QRhiMetal::createComputePipeline()
+{
+ return new QMetalComputePipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiMetal::createShaderResourceBindings()
+{
+ return new QMetalShaderResourceBindings(this);
+}
+
+void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
+ bool offsetOnlyChange)
+{
+ static const int KNOWN_STAGES = 3;
+ struct {
+ QRhiBatchedBindings<id<MTLBuffer> > buffers;
+ QRhiBatchedBindings<NSUInteger> bufferOffsets;
+ QRhiBatchedBindings<id<MTLTexture> > textures;
+ QRhiBatchedBindings<id<MTLSamplerState> > samplers;
+ } res[KNOWN_STAGES];
+
+ for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
+ id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
+ uint offset = b->u.ubuf.offset;
+ for (int i = 0; i < dynamicOffsetCount; ++i) {
+ const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+ if (dynOfs.first == b->binding) {
+ offset = dynOfs.second;
+ break;
+ }
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ res[0].buffers.feed(b->binding, mtlbuf);
+ res[0].bufferOffsets.feed(b->binding, offset);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ res[1].buffers.feed(b->binding, mtlbuf);
+ res[1].bufferOffsets.feed(b->binding, offset);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ res[2].buffers.feed(b->binding, mtlbuf);
+ res[2].bufferOffsets.feed(b->binding, offset);
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
+ QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ res[0].textures.feed(b->binding, texD->d->tex);
+ res[0].samplers.feed(b->binding, samplerD->d->samplerState);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ res[1].textures.feed(b->binding, texD->d->tex);
+ res[1].samplers.feed(b->binding, samplerD->d->samplerState);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ res[2].textures.feed(b->binding, texD->d->tex);
+ res[2].samplers.feed(b->binding, samplerD->d->samplerState);
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
+ id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level);
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage))
+ res[0].textures.feed(b->binding, t);
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage))
+ res[1].textures.feed(b->binding, t);
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage))
+ res[2].textures.feed(b->binding, t);
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
+ id<MTLBuffer> mtlbuf = bufD->d->buf[0];
+ uint offset = b->u.sbuf.offset;
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ res[0].buffers.feed(b->binding, mtlbuf);
+ res[0].bufferOffsets.feed(b->binding, offset);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ res[1].buffers.feed(b->binding, mtlbuf);
+ res[1].bufferOffsets.feed(b->binding, offset);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ res[2].buffers.feed(b->binding, mtlbuf);
+ res[2].bufferOffsets.feed(b->binding, offset);
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ for (int idx = 0; idx < KNOWN_STAGES; ++idx) {
+ res[idx].buffers.finish();
+ res[idx].bufferOffsets.finish();
+
+ for (int i = 0, ie = res[idx].buffers.batches.count(); i != ie; ++i) {
+ const auto &bufferBatch(res[idx].buffers.batches[i]);
+ const auto &offsetBatch(res[idx].bufferOffsets.batches[i]);
+ switch (idx) {
+ case 0:
+ [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
+ break;
+ case 1:
+ [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
+ break;
+ case 2:
+ [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ if (offsetOnlyChange)
+ continue;
+
+ res[idx].textures.finish();
+ res[idx].samplers.finish();
+
+ for (int i = 0, ie = res[idx].textures.batches.count(); i != ie; ++i) {
+ const auto &batch(res[idx].textures.batches[i]);
+ switch (idx) {
+ case 0:
+ [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 1:
+ [cbD->d->currentRenderPassEncoder setFragmentTextures: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 2:
+ [cbD->d->currentComputePassEncoder setTextures: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+ for (int i = 0, ie = res[idx].samplers.batches.count(); i != ie; ++i) {
+ const auto &batch(res[idx].samplers.batches[i]);
+ switch (idx) {
+ case 0:
+ [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 1:
+ [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 2:
+ [cbD->d->currentComputePassEncoder setSamplerStates: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+ }
+}
+
+void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+ QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps);
+
+ if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
+ cbD->currentGraphicsPipeline = ps;
+ cbD->currentComputePipeline = nullptr;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ [cbD->d->currentRenderPassEncoder setRenderPipelineState: psD->d->ps];
+ [cbD->d->currentRenderPassEncoder setDepthStencilState: psD->d->ds];
+ [cbD->d->currentRenderPassEncoder setCullMode: psD->d->cullMode];
+ [cbD->d->currentRenderPassEncoder setFrontFacingWinding: psD->d->winding];
+ }
+
+ psD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass != QMetalCommandBuffer::NoPass);
+ QMetalGraphicsPipeline *gfxPsD = QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline);
+ QMetalComputePipeline *compPsD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline);
+
+ if (!srb) {
+ if (gfxPsD)
+ srb = gfxPsD->m_shaderResourceBindings;
+ else
+ srb = compPsD->m_shaderResourceBindings;
+ }
+
+ QMetalShaderResourceBindings *srbD = QRHI_RES(QMetalShaderResourceBindings, srb);
+ bool hasSlottedResourceInSrb = false;
+ bool hasDynamicOffsetInSrb = false;
+ bool resNeedsRebind = false;
+
+ // do buffer writes, figure out if we need to rebind, and mark as in-use
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
+ executeBufferHostWritesForCurrentFrame(bufD);
+ if (bufD->d->slotted)
+ hasSlottedResourceInSrb = true;
+ if (b->u.ubuf.hasDynamicOffset)
+ hasDynamicOffsetInSrb = true;
+ if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
+ resNeedsRebind = true;
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
+ QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
+ if (texD->generation != bd.stex.texGeneration
+ || texD->m_id != bd.stex.texId
+ || samplerD->generation != bd.stex.samplerGeneration
+ || samplerD->m_id != bd.stex.samplerId)
+ {
+ resNeedsRebind = true;
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ samplerD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
+ if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
+ resNeedsRebind = true;
+ bd.simage.id = texD->m_id;
+ bd.simage.generation = texD->generation;
+ }
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
+ executeBufferHostWritesForCurrentFrame(bufD);
+ if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
+ resNeedsRebind = true;
+ bd.sbuf.id = bufD->m_id;
+ bd.sbuf.generation = bufD->generation;
+ }
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ // make sure the resources for the correct slot get bound
+ const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
+ if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
+ resNeedsRebind = true;
+
+ const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
+ const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
+
+ // dynamic uniform buffer offsets always trigger a rebind
+ if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) {
+ if (gfxPsD) {
+ cbD->currentGraphicsSrb = srb;
+ cbD->currentComputeSrb = nullptr;
+ } else {
+ cbD->currentGraphicsSrb = nullptr;
+ cbD->currentComputeSrb = srb;
+ }
+ cbD->currentSrbGeneration = srbD->generation;
+ cbD->currentResSlot = resSlot;
+
+ const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt;
+ enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange);
+ }
+}
+
+void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ QRhiBatchedBindings<id<MTLBuffer> > buffers;
+ QRhiBatchedBindings<NSUInteger> offsets;
+ for (int i = 0; i < bindingCount; ++i) {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, bindings[i].first);
+ executeBufferHostWritesForCurrentFrame(bufD);
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
+ buffers.feed(startBinding + i, mtlbuf);
+ offsets.feed(startBinding + i, bindings[i].second);
+ }
+ buffers.finish();
+ offsets.finish();
+
+ // same binding space for vertex and constant buffers - work it around
+ QRhiShaderResourceBindings *srb = cbD->currentGraphicsSrb;
+ // There's nothing guaranteeing setShaderResources() was called before
+ // setVertexInput()... but whatever srb will get bound will have to be
+ // layout-compatible anyways so maxBinding is the same.
+ if (!srb)
+ srb = cbD->currentGraphicsPipeline->shaderResourceBindings();
+ const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, srb)->maxBinding + 1;
+
+ if (firstVertexBinding != cbD->d->currentFirstVertexBinding
+ || buffers != cbD->d->currentVertexInputsBuffers
+ || offsets != cbD->d->currentVertexInputOffsets)
+ {
+ cbD->d->currentFirstVertexBinding = firstVertexBinding;
+ cbD->d->currentVertexInputsBuffers = buffers;
+ cbD->d->currentVertexInputOffsets = offsets;
+
+ for (int i = 0, ie = buffers.batches.count(); i != ie; ++i) {
+ const auto &bufferBatch(buffers.batches[i]);
+ const auto &offsetBatch(offsets.batches[i]);
+ [cbD->d->currentRenderPassEncoder setVertexBuffers:
+ bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(firstVertexBinding + bufferBatch.startBinding, bufferBatch.resources.count())];
+ }
+ }
+
+ if (indexBuf) {
+ QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
+ executeBufferHostWritesForCurrentFrame(ibufD);
+ ibufD->lastActiveFrameSlot = currentFrameSlot;
+ cbD->currentIndexBuffer = indexBuf;
+ cbD->currentIndexOffset = indexOffset;
+ cbD->currentIndexFormat = indexFormat;
+ } else {
+ cbD->currentIndexBuffer = nullptr;
+ }
+}
+
+void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in MTLViewportRect but bottom-left in QRhiViewport
+ float x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h))
+ return;
+
+ MTLViewport vp;
+ vp.originX = x;
+ vp.originY = y;
+ vp.width = w;
+ vp.height = h;
+ vp.znear = viewport.minDepth();
+ vp.zfar = viewport.maxDepth();
+
+ [cbD->d->currentRenderPassEncoder setViewport: vp];
+
+ if (!QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
+ MTLScissorRect s;
+ s.x = x;
+ s.y = y;
+ s.width = w;
+ s.height = h;
+ [cbD->d->currentRenderPassEncoder setScissorRect: s];
+ }
+}
+
+void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+ Q_ASSERT(QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
+ int x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h))
+ return;
+
+ MTLScissorRect s;
+ s.x = x;
+ s.y = y;
+ s.width = w;
+ s.height = h;
+
+ [cbD->d->currentRenderPassEncoder setScissorRect: s];
+}
+
+void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ [cbD->d->currentRenderPassEncoder setBlendColorRed: c.redF() green: c.greenF() blue: c.blueF() alpha: c.alphaF()];
+}
+
+void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ [cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue];
+}
+
+void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ [cbD->d->currentRenderPassEncoder drawPrimitives:
+ QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
+ vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
+}
+
+void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ if (!cbD->currentIndexBuffer)
+ return;
+
+ const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
+ Q_ASSERT(indexOffset == aligned(indexOffset, 4));
+
+ QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, cbD->currentIndexBuffer);
+ id<MTLBuffer> mtlbuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0];
+
+ [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
+ indexCount: indexCount
+ indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
+ indexBuffer: mtlbuf
+ indexBufferOffset: indexOffset
+ instanceCount: instanceCount
+ baseVertex: vertexOffset
+ baseInstance: firstInstance];
+}
+
+void QRhiMetal::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ if (!debugMarkers)
+ return;
+
+ NSString *str = [NSString stringWithUTF8String: name.constData()];
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ if (cbD->recordingPass != QMetalCommandBuffer::NoPass) {
+ [cbD->d->currentRenderPassEncoder pushDebugGroup: str];
+ } else {
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [cbD->d->cb pushDebugGroup: str];
+ }
+}
+
+void QRhiMetal::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ if (!debugMarkers)
+ return;
+
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ if (cbD->recordingPass != QMetalCommandBuffer::NoPass) {
+ [cbD->d->currentRenderPassEncoder popDebugGroup];
+ } else {
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [cbD->d->cb popDebugGroup];
+ }
+}
+
+void QRhiMetal::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ if (!debugMarkers)
+ return;
+
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ if (cbD->recordingPass != QMetalCommandBuffer::NoPass)
+ [cbD->d->currentRenderPassEncoder insertDebugSignpost: [NSString stringWithUTF8String: msg.constData()]];
+}
+
+const QRhiNativeHandles *QRhiMetal::nativeHandles(QRhiCommandBuffer *cb)
+{
+ return QRHI_RES(QMetalCommandBuffer, cb)->nativeHandles();
+}
+
+void QRhiMetal::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+void QRhiMetal::endExternal(QRhiCommandBuffer *cb)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ cbD->resetPerPassCachedState();
+}
+
+QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+
+ QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
+
+ // This is a bit messed up since for this swapchain we want to wait for the
+ // commands+present to complete, while for others just for the commands
+ // (for this same frame slot) but not sure how to do that in a sane way so
+ // wait for full cb completion for now.
+ for (QMetalSwapChain *sc : qAsConst(swapchains)) {
+ dispatch_semaphore_t sem = sc->d->sem[swapChainD->currentFrameSlot];
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ if (sc != swapChainD)
+ dispatch_semaphore_signal(sem);
+ }
+
+ currentSwapChain = swapChainD;
+ currentFrameSlot = swapChainD->currentFrameSlot;
+ if (swapChainD->ds)
+ swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
+
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [d->captureScope beginScope];
+
+ // Do not let the command buffer mess with the refcount of objects. We do
+ // have a proper render loop and will manage lifetimes similarly to other
+ // backends (Vulkan).
+ swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+
+ QMetalRenderTargetData::ColorAtt colorAtt;
+ if (swapChainD->samples > 1) {
+ colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
+ colorAtt.needsDrawableForResolveTex = true;
+ } else {
+ colorAtt.needsDrawableForTex = true;
+ }
+
+ swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
+ swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
+ swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
+ swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+
+ executeDeferredReleases();
+ swapChainD->cbWrapper.resetState();
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
+ Q_ASSERT(currentSwapChain == swapChainD);
+
+ const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
+ if (needsPresent) {
+ [swapChainD->cbWrapper.d->cb presentDrawable: swapChainD->d->curDrawable];
+ swapChainD->d->curDrawable = nil;
+ }
+
+ __block int thisFrameSlot = currentFrameSlot;
+ [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer>) {
+ dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
+ }];
+
+ [swapChainD->cbWrapper.d->cb commit];
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [d->captureScope endScope];
+
+ if (needsPresent)
+ swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
+
+ swapChainD->frameCount += 1;
+ currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
+ if (swapchains.count() > 1) {
+ for (QMetalSwapChain *sc : qAsConst(swapchains)) {
+ // wait+signal is the general pattern to ensure the commands for a
+ // given frame slot have completed (if sem is 1, we go 0 then 1; if
+ // sem is 0 we go -1, block, completion increments to 0, then us to 1)
+ dispatch_semaphore_t sem = sc->d->sem[currentFrameSlot];
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(sem);
+ }
+ }
+
+ d->ofr.active = true;
+ *cb = &d->ofr.cbWrapper;
+ d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+
+ executeDeferredReleases();
+ d->ofr.cbWrapper.resetState();
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::endOffscreenFrame()
+{
+ Q_ASSERT(d->ofr.active);
+ d->ofr.active = false;
+
+ [d->ofr.cbWrapper.d->cb commit];
+
+ // offscreen frames wait for completion, unlike swapchain ones
+ [d->ofr.cbWrapper.d->cb waitUntilCompleted];
+
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::finish()
+{
+ id<MTLCommandBuffer> cb = nil;
+ QMetalSwapChain *swapChainD = nullptr;
+ if (inFrame) {
+ if (d->ofr.active) {
+ Q_ASSERT(!currentSwapChain);
+ Q_ASSERT(d->ofr.cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
+ cb = d->ofr.cbWrapper.d->cb;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = currentSwapChain;
+ Q_ASSERT(swapChainD->cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
+ cb = swapChainD->cbWrapper.d->cb;
+ }
+ }
+
+ for (QMetalSwapChain *sc : qAsConst(swapchains)) {
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (currentSwapChain && sc == currentSwapChain && i == currentFrameSlot) {
+ // no wait as this is the thing we're going to be commit below and
+ // beginFrame decremented sem already and going to be signaled by endFrame
+ continue;
+ }
+ dispatch_semaphore_t sem = sc->d->sem[i];
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(sem);
+ }
+ }
+
+ if (cb) {
+ [cb commit];
+ [cb waitUntilCompleted];
+ }
+
+ if (inFrame) {
+ if (d->ofr.active)
+ d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ else
+ swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ }
+
+ executeDeferredReleases(true);
+
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+MTLRenderPassDescriptor *QRhiMetalData::createDefaultRenderPass(bool hasDepthStencil,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ int colorAttCount)
+{
+ MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];
+ MTLClearColor c = MTLClearColorMake(colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
+ colorClearValue.alphaF());
+
+ for (int i = 0; i < colorAttCount; ++i) {
+ rp.colorAttachments[i].loadAction = MTLLoadActionClear;
+ rp.colorAttachments[i].storeAction = MTLStoreActionStore;
+ rp.colorAttachments[i].clearColor = c;
+ }
+
+ if (hasDepthStencil) {
+ rp.depthAttachment.loadAction = MTLLoadActionClear;
+ rp.depthAttachment.storeAction = MTLStoreActionDontCare;
+ rp.stencilAttachment.loadAction = MTLLoadActionClear;
+ rp.stencilAttachment.storeAction = MTLStoreActionDontCare;
+ rp.depthAttachment.clearDepth = depthStencilClearValue.depthClearValue();
+ rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
+ }
+
+ return rp;
+}
+
+qsizetype QRhiMetal::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
+{
+ qsizetype size = 0;
+ const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
+ subresDesc.data().size() : subresDesc.image().sizeInBytes();
+ if (imageSizeBytes > 0)
+ size += aligned(imageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
+ return size;
+}
+
+void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
+ qsizetype *curOfs)
+{
+ const QPoint dp = subresDesc.destinationTopLeft();
+ const QByteArray rawData = subresDesc.data();
+ QImage img = subresDesc.image();
+ id<MTLBlitCommandEncoder> blitEnc = (id<MTLBlitCommandEncoder>) blitEncPtr;
+
+ if (!img.isNull()) {
+ const qsizetype fullImageSizeBytes = img.sizeInBytes();
+ int w = img.width();
+ int h = img.height();
+ int bpl = img.bytesPerLine();
+ int srcOffset = 0;
+
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const int sx = subresDesc.sourceTopLeft().x();
+ const int sy = subresDesc.sourceTopLeft().y();
+ if (!subresDesc.sourceSize().isEmpty()) {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+ if (img.depth() == 32) {
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), fullImageSizeBytes);
+ srcOffset = sy * bpl + sx * 4;
+ // bpl remains set to the original image's row stride
+ } else {
+ img = img.copy(sx, sy, w, h);
+ bpl = img.bytesPerLine();
+ Q_ASSERT(img.sizeInBytes() <= fullImageSizeBytes);
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), img.sizeInBytes());
+ }
+ } else {
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), fullImageSizeBytes);
+ }
+
+ [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
+ sourceOffset: *curOfs + srcOffset
+ sourceBytesPerRow: bpl
+ sourceBytesPerImage: 0
+ sourceSize: MTLSizeMake(w, h, 1)
+ toTexture: texD->d->tex
+ destinationSlice: layer
+ destinationLevel: level
+ destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)
+ options: MTLBlitOptionNone];
+
+ *curOfs += aligned(fullImageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
+ } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
+ const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
+ const int subresw = subresSize.width();
+ const int subresh = subresSize.height();
+ int w, h;
+ if (subresDesc.sourceSize().isEmpty()) {
+ w = subresw;
+ h = subresh;
+ } else {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+
+ quint32 bpl = 0;
+ QSize blockDim;
+ compressedFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, &blockDim);
+
+ const int dx = aligned(dp.x(), blockDim.width());
+ const int dy = aligned(dp.y(), blockDim.height());
+ if (dx + w != subresw)
+ w = aligned(w, blockDim.width());
+ if (dy + h != subresh)
+ h = aligned(h, blockDim.height());
+
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), rawData.size());
+
+ [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
+ sourceOffset: *curOfs
+ sourceBytesPerRow: bpl
+ sourceBytesPerImage: 0
+ sourceSize: MTLSizeMake(w, h, 1)
+ toTexture: texD->d->tex
+ destinationSlice: layer
+ destinationLevel: level
+ destinationOrigin: MTLOriginMake(dx, dy, 0)
+ options: MTLBlitOptionNone];
+
+ *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
+ } else if (!rawData.isEmpty()) {
+ const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
+ const int subresw = subresSize.width();
+ const int subresh = subresSize.height();
+ int w, h;
+ if (subresDesc.sourceSize().isEmpty()) {
+ w = subresw;
+ h = subresh;
+ } else {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+
+ quint32 bpl = 0;
+ textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr);
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), rawData.size());
+
+ [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
+ sourceOffset: *curOfs
+ sourceBytesPerRow: bpl
+ sourceBytesPerImage: 0
+ sourceSize: MTLSizeMake(w, h, 1)
+ toTexture: texD->d->tex
+ destinationSlice: layer
+ destinationLevel: level
+ destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)
+ options: MTLBlitOptionNone];
+
+ *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ }
+}
+
+void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ bufD->d->pendingUpdates[i].append(u);
+ }
+
+ // Due to the Metal API the handling of static and dynamic buffers is
+ // basically the same. So go through the same pendingUpdates machinery.
+ for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
+ Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+ Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+ for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i)
+ bufD->d->pendingUpdates[i].append({ u.buf, u.offset, u.data.size(), u.data.constData() });
+ }
+
+ id<MTLBlitCommandEncoder> blitEnc = nil;
+ auto ensureBlit = [&blitEnc, cbD, this] {
+ if (!blitEnc) {
+ blitEnc = [cbD->d->cb blitCommandEncoder];
+ if (debugMarkers)
+ [blitEnc pushDebugGroup: @"Texture upload/copy"];
+ }
+ };
+
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.upload.tex);
+ qsizetype stagingSize = 0;
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ stagingSize += subresUploadByteSize(subresDesc);
+ }
+ }
+
+ ensureBlit();
+ Q_ASSERT(!utexD->d->stagingBuf[currentFrameSlot]);
+ utexD->d->stagingBuf[currentFrameSlot] = [d->dev newBufferWithLength: stagingSize
+ options: MTLResourceStorageModeShared];
+ QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, stagingSize));
+
+ void *mp = [utexD->d->stagingBuf[currentFrameSlot] contents];
+ qsizetype curOfs = 0;
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ enqueueSubresUpload(utexD, mp, blitEnc, layer, level, subresDesc, &curOfs);
+ }
+ }
+
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::StagingBuffer;
+ e.lastActiveFrameSlot = currentFrameSlot;
+ e.stagingBuffer.buffer = utexD->d->stagingBuf[currentFrameSlot];
+ utexD->d->stagingBuf[currentFrameSlot] = nil;
+ d->releaseQueue.append(e);
+ QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ Q_ASSERT(u.copy.src && u.copy.dst);
+ QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.copy.src);
+ QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.copy.dst);
+ const QPoint dp = u.copy.desc.destinationTopLeft();
+ const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
+ const QPoint sp = u.copy.desc.sourceTopLeft();
+
+ ensureBlit();
+ [blitEnc copyFromTexture: srcD->d->tex
+ sourceSlice: u.copy.desc.sourceLayer()
+ sourceLevel: u.copy.desc.sourceLevel()
+ sourceOrigin: MTLOriginMake(sp.x(), sp.y(), 0)
+ sourceSize: MTLSizeMake(size.width(), size.height(), 1)
+ toTexture: dstD->d->tex
+ destinationSlice: u.copy.desc.destinationLayer()
+ destinationLevel: u.copy.desc.destinationLevel()
+ destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)];
+
+ srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ QRhiMetalData::ActiveReadback aRb;
+ aRb.activeFrameSlot = currentFrameSlot;
+ aRb.desc = u.read.rb;
+ aRb.result = u.read.result;
+
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, u.read.rb.texture());
+ QMetalSwapChain *swapChainD = nullptr;
+ id<MTLTexture> src;
+ QSize srcSize;
+ if (texD) {
+ if (texD->samples > 1) {
+ qWarning("Multisample texture cannot be read back");
+ continue;
+ }
+ aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize)
+ : texD->m_pixelSize;
+ aRb.format = texD->m_format;
+ src = texD->d->tex;
+ srcSize = texD->m_pixelSize;
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
+ aRb.pixelSize = swapChainD->pixelSize;
+ aRb.format = swapChainD->d->rhiColorFormat;
+ // Multisample swapchains need nothing special since resolving
+ // happens when ending a renderpass.
+ const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
+ src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
+ srcSize = swapChainD->rtWrapper.d->pixelSize;
+ }
+
+ quint32 bpl = 0;
+ textureFormatInfo(aRb.format, aRb.pixelSize, &bpl, &aRb.bufSize);
+ aRb.buf = [d->dev newBufferWithLength: aRb.bufSize options: MTLResourceStorageModeShared];
+
+ QRHI_PROF_F(newReadbackBuffer(quint64(quintptr(aRb.buf)),
+ texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
+ aRb.bufSize));
+
+ ensureBlit();
+ [blitEnc copyFromTexture: src
+ sourceSlice: u.read.rb.layer()
+ sourceLevel: u.read.rb.level()
+ sourceOrigin: MTLOriginMake(0, 0, 0)
+ sourceSize: MTLSizeMake(srcSize.width(), srcSize.height(), 1)
+ toBuffer: aRb.buf
+ destinationOffset: 0
+ destinationBytesPerRow: bpl
+ destinationBytesPerImage: 0
+ options: MTLBlitOptionNone];
+
+ d->activeReadbacks.append(aRb);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
+ QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.mipgen.tex);
+ ensureBlit();
+ [blitEnc generateMipmapsForTexture: utexD->d->tex];
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ }
+
+ if (blitEnc) {
+ if (debugMarkers)
+ [blitEnc popDebugGroup];
+ [blitEnc endEncoding];
+ }
+
+ ud->free();
+}
+
+// this handles all types of buffers, not just Dynamic
+void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD)
+{
+ const int idx = bufD->d->slotted ? currentFrameSlot : 0;
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->d->pendingUpdates[idx]);
+ if (updates.isEmpty())
+ return;
+
+ void *p = [bufD->d->buf[idx] contents];
+ int changeBegin = -1;
+ int changeEnd = -1;
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) {
+ Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf));
+ memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), u.data.size());
+ if (changeBegin == -1 || u.offset < changeBegin)
+ changeBegin = u.offset;
+ if (changeEnd == -1 || u.offset + u.data.size() > changeEnd)
+ changeEnd = u.offset + u.data.size();
+ }
+ if (changeBegin >= 0 && bufD->d->managed)
+ [bufD->d->buf[idx] didModifyRange: NSMakeRange(changeBegin, changeEnd - changeBegin)];
+
+ updates.clear();
+}
+
+void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(QRHI_RES(QMetalCommandBuffer, cb)->recordingPass == QMetalCommandBuffer::NoPass);
+
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ QMetalRenderTargetData *rtD = nullptr;
+ switch (rt->resourceType()) {
+ case QRhiResource::RenderTarget:
+ rtD = QRHI_RES(QMetalReferenceRenderTarget, rt)->d;
+ cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
+ if (rtD->colorAttCount) {
+ QMetalRenderTargetData::ColorAtt &color0(rtD->fb.colorAtt[0]);
+ if (color0.needsDrawableForTex || color0.needsDrawableForResolveTex) {
+ Q_ASSERT(currentSwapChain);
+ QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
+ if (!swapChainD->d->curDrawable)
+ swapChainD->d->curDrawable = [swapChainD->d->layer nextDrawable];
+ if (!swapChainD->d->curDrawable) {
+ qWarning("No drawable");
+ return;
+ }
+ id<MTLTexture> scTex = swapChainD->d->curDrawable.texture;
+ if (color0.needsDrawableForTex) {
+ color0.tex = scTex;
+ color0.needsDrawableForTex = false;
+ } else {
+ color0.resolveTex = scTex;
+ color0.needsDrawableForResolveTex = false;
+ }
+ }
+ }
+ break;
+ case QRhiResource::TextureRenderTarget:
+ {
+ QMetalTextureRenderTarget *rtTex = QRHI_RES(QMetalTextureRenderTarget, rt);
+ rtD = rtTex->d;
+ cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
+ if (rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents)) {
+ for (int i = 0; i < rtD->colorAttCount; ++i)
+ cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
+ }
+ if (rtD->dsAttCount && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents)) {
+ cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
+ cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
+ }
+ const QVector<QRhiColorAttachment> colorAttachments = rtTex->m_desc.colorAttachments();
+ for (const QRhiColorAttachment &colorAttachment : colorAttachments) {
+ if (colorAttachment.texture())
+ QRHI_RES(QMetalTexture, colorAttachment.texture())->lastActiveFrameSlot = currentFrameSlot;
+ else if (colorAttachment.renderBuffer())
+ QRHI_RES(QMetalRenderBuffer, colorAttachment.renderBuffer())->lastActiveFrameSlot = currentFrameSlot;
+ if (colorAttachment.resolveTexture())
+ QRHI_RES(QMetalTexture, colorAttachment.resolveTexture())->lastActiveFrameSlot = currentFrameSlot;
+ }
+ if (rtTex->m_desc.depthStencilBuffer())
+ QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
+ if (rtTex->m_desc.depthTexture())
+ QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture())->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ for (int i = 0; i < rtD->colorAttCount; ++i) {
+ cbD->d->currentPassRpDesc.colorAttachments[i].texture = rtD->fb.colorAtt[i].tex;
+ cbD->d->currentPassRpDesc.colorAttachments[i].slice = rtD->fb.colorAtt[i].layer;
+ cbD->d->currentPassRpDesc.colorAttachments[i].level = rtD->fb.colorAtt[i].level;
+ if (rtD->fb.colorAtt[i].resolveTex) {
+ cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
+ cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex;
+ cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = rtD->fb.colorAtt[i].resolveLayer;
+ cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = rtD->fb.colorAtt[i].resolveLevel;
+ }
+ }
+
+ if (rtD->dsAttCount) {
+ Q_ASSERT(rtD->fb.dsTex);
+ cbD->d->currentPassRpDesc.depthAttachment.texture = rtD->fb.dsTex;
+ cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil;
+ if (rtD->fb.depthNeedsStore) // Depth/Stencil is set to DontCare by default, override if needed
+ cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore;
+ }
+
+ cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
+
+ cbD->resetPerPassState();
+
+ cbD->recordingPass = QMetalCommandBuffer::RenderPass;
+ cbD->currentTarget = rt;
+}
+
+void QRhiMetal::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ [cbD->d->currentRenderPassEncoder endEncoding];
+
+ cbD->recordingPass = QMetalCommandBuffer::NoPass;
+ cbD->currentTarget = nullptr;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiMetal::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ cbD->d->currentComputePassEncoder = [cbD->d->cb computeCommandEncoder];
+ cbD->resetPerPassState();
+ cbD->recordingPass = QMetalCommandBuffer::ComputePass;
+}
+
+void QRhiMetal::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
+
+ [cbD->d->currentComputePassEncoder endEncoding];
+ cbD->recordingPass = QMetalCommandBuffer::NoPass;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiMetal::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
+ QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, ps);
+
+ if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
+ cbD->currentGraphicsPipeline = nullptr;
+ cbD->currentComputePipeline = ps;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ [cbD->d->currentComputePassEncoder setComputePipelineState: psD->d->ps];
+ }
+
+ psD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+void QRhiMetal::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
+ QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline);
+
+ [cbD->d->currentComputePassEncoder dispatchThreadgroups: MTLSizeMake(x, y, z)
+ threadsPerThreadgroup: psD->d->localSize];
+}
+
+static void qrhimtl_releaseBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ [e.buffer.buffers[i] release];
+}
+
+static void qrhimtl_releaseRenderBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ [e.renderbuffer.texture release];
+}
+
+static void qrhimtl_releaseTexture(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ [e.texture.texture release];
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ [e.texture.stagingBuffers[i] release];
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
+ [e.texture.views[i] release];
+}
+
+static void qrhimtl_releaseSampler(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ [e.sampler.samplerState release];
+}
+
+void QRhiMetal::executeDeferredReleases(bool forced)
+{
+ for (int i = d->releaseQueue.count() - 1; i >= 0; --i) {
+ const QRhiMetalData::DeferredReleaseEntry &e(d->releaseQueue[i]);
+ if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
+ switch (e.type) {
+ case QRhiMetalData::DeferredReleaseEntry::Buffer:
+ qrhimtl_releaseBuffer(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::RenderBuffer:
+ qrhimtl_releaseRenderBuffer(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::Texture:
+ qrhimtl_releaseTexture(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::Sampler:
+ qrhimtl_releaseSampler(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::StagingBuffer:
+ [e.stagingBuffer.buffer release];
+ break;
+ default:
+ break;
+ }
+ d->releaseQueue.removeAt(i);
+ }
+ }
+}
+
+void QRhiMetal::finishActiveReadbacks(bool forced)
+{
+ QVarLengthArray<std::function<void()>, 4> completedCallbacks;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (int i = d->activeReadbacks.count() - 1; i >= 0; --i) {
+ const QRhiMetalData::ActiveReadback &aRb(d->activeReadbacks[i]);
+ if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) {
+ aRb.result->format = aRb.format;
+ aRb.result->pixelSize = aRb.pixelSize;
+ aRb.result->data.resize(aRb.bufSize);
+ void *p = [aRb.buf contents];
+ memcpy(aRb.result->data.data(), p, aRb.bufSize);
+ [aRb.buf release];
+
+ QRHI_PROF_F(releaseReadbackBuffer(quint64(quintptr(aRb.buf))));
+
+ if (aRb.result->completed)
+ completedCallbacks.append(aRb.result->completed);
+
+ d->activeReadbacks.removeAt(i);
+ }
+ }
+
+ for (auto f : completedCallbacks)
+ f();
+}
+
+QMetalBuffer::QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size),
+ d(new QMetalBufferData)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ d->buf[i] = nil;
+}
+
+QMetalBuffer::~QMetalBuffer()
+{
+ release();
+ delete d;
+}
+
+void QMetalBuffer::release()
+{
+ if (!d->buf[0])
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::Buffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ e.buffer.buffers[i] = d->buf[i];
+ d->buf[i] = nil;
+ d->pendingUpdates[i].clear();
+ }
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QMetalBuffer::build()
+{
+ if (d->buf[0])
+ release();
+
+ if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
+ qWarning("StorageBuffer cannot be combined with Dynamic");
+ return false;
+ }
+
+ const int nonZeroSize = m_size <= 0 ? 256 : m_size;
+ const int roundedSize = m_usage.testFlag(QRhiBuffer::UniformBuffer) ? aligned(nonZeroSize, 256) : nonZeroSize;
+
+ d->managed = false;
+ MTLResourceOptions opts = MTLResourceStorageModeShared;
+#ifdef Q_OS_MACOS
+ if (m_type != Dynamic) {
+ opts = MTLResourceStorageModeManaged;
+ d->managed = true;
+ }
+#endif
+
+ // Immutable and Static only has buf[0] and pendingUpdates[0] in use.
+ // Dynamic uses all.
+ d->slotted = m_type == Dynamic;
+
+ QRHI_RES_RHI(QRhiMetal);
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (i == 0 || d->slotted) {
+ d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts];
+ d->pendingUpdates[i].reserve(16);
+ if (!m_objectName.isEmpty()) {
+ if (!d->slotted) {
+ d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()];
+ } else {
+ const QByteArray name = m_objectName + '/' + QByteArray::number(i);
+ d->buf[i].label = [NSString stringWithUTF8String: name.constData()];
+ }
+ }
+ }
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(newBuffer(this, roundedSize, d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1, 0));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags),
+ d(new QMetalRenderBufferData)
+{
+}
+
+QMetalRenderBuffer::~QMetalRenderBuffer()
+{
+ release();
+ delete d;
+}
+
+void QMetalRenderBuffer::release()
+{
+ if (!d->tex)
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::RenderBuffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.renderbuffer.texture = d->tex;
+ d->tex = nil;
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QMetalRenderBuffer::build()
+{
+ if (d->tex)
+ release();
+
+ if (m_pixelSize.isEmpty())
+ return false;
+
+ QRHI_RES_RHI(QRhiMetal);
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+
+ MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
+ desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
+ desc.width = m_pixelSize.width();
+ desc.height = m_pixelSize.height();
+ if (samples > 1)
+ desc.sampleCount = samples;
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.usage = MTLTextureUsageRenderTarget;
+
+ bool transientBacking = false;
+ switch (m_type) {
+ case DepthStencil:
+#ifdef Q_OS_MACOS
+ desc.storageMode = MTLStorageModePrivate;
+#else
+ desc.storageMode = MTLResourceStorageModeMemoryless;
+ transientBacking = true;
+#endif
+ d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported
+ ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
+ desc.pixelFormat = d->format;
+ break;
+ case Color:
+ desc.storageMode = MTLStorageModePrivate;
+ d->format = MTLPixelFormatRGBA8Unorm;
+ desc.pixelFormat = d->format;
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
+ [desc release];
+
+ if (!m_objectName.isEmpty())
+ d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
+
+ QRHI_PROF;
+ QRHI_PROF_F(newRenderBuffer(this, transientBacking, false, samples));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QRhiTexture::Format QMetalRenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QMetalTexture::QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags),
+ d(new QMetalTextureData(this))
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ d->stagingBuf[i] = nil;
+
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
+ d->perLevelViews[i] = nil;
+}
+
+QMetalTexture::~QMetalTexture()
+{
+ release();
+ delete d;
+}
+
+void QMetalTexture::release()
+{
+ if (!d->tex)
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::Texture;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.texture.texture = d->owns ? d->tex : nil;
+ d->tex = nil;
+ nativeHandlesStruct.texture = nullptr;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ e.texture.stagingBuffers[i] = d->stagingBuf[i];
+ d->stagingBuf[i] = nil;
+ }
+
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i) {
+ e.texture.views[i] = d->perLevelViews[i];
+ d->perLevelViews[i] = nil;
+ }
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+ rhiD->unregisterResource(this);
+}
+
+static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+ const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+ switch (format) {
+ case QRhiTexture::RGBA8:
+ return srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm;
+ case QRhiTexture::BGRA8:
+ return srgb ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
+ case QRhiTexture::R8:
+#ifdef Q_OS_MACOS
+ return MTLPixelFormatR8Unorm;
+#else
+ return srgb ? MTLPixelFormatR8Unorm_sRGB : MTLPixelFormatR8Unorm;
+#endif
+ case QRhiTexture::R16:
+ return MTLPixelFormatR16Unorm;
+ case QRhiTexture::RED_OR_ALPHA8:
+ return MTLPixelFormatR8Unorm;
+
+ case QRhiTexture::RGBA16F:
+ return MTLPixelFormatRGBA16Float;
+ case QRhiTexture::RGBA32F:
+ return MTLPixelFormatRGBA32Float;
+
+ case QRhiTexture::D16:
+#ifdef Q_OS_MACOS
+ return MTLPixelFormatDepth16Unorm;
+#else
+ return MTLPixelFormatDepth32Float;
+#endif
+ case QRhiTexture::D32F:
+ return MTLPixelFormatDepth32Float;
+
+#ifdef Q_OS_MACOS
+ case QRhiTexture::BC1:
+ return srgb ? MTLPixelFormatBC1_RGBA_sRGB : MTLPixelFormatBC1_RGBA;
+ case QRhiTexture::BC2:
+ return srgb ? MTLPixelFormatBC2_RGBA_sRGB : MTLPixelFormatBC2_RGBA;
+ case QRhiTexture::BC3:
+ return srgb ? MTLPixelFormatBC3_RGBA_sRGB : MTLPixelFormatBC3_RGBA;
+ case QRhiTexture::BC4:
+ return MTLPixelFormatBC4_RUnorm;
+ case QRhiTexture::BC5:
+ qWarning("QRhiMetal does not support BC5");
+ return MTLPixelFormatRGBA8Unorm;
+ case QRhiTexture::BC6H:
+ return MTLPixelFormatBC6H_RGBUfloat;
+ case QRhiTexture::BC7:
+ return srgb ? MTLPixelFormatBC7_RGBAUnorm_sRGB : MTLPixelFormatBC7_RGBAUnorm;
+#else
+ case QRhiTexture::BC1:
+ case QRhiTexture::BC2:
+ case QRhiTexture::BC3:
+ case QRhiTexture::BC4:
+ case QRhiTexture::BC5:
+ case QRhiTexture::BC6H:
+ case QRhiTexture::BC7:
+ qWarning("QRhiMetal: BCx compression not supported on this platform");
+ return MTLPixelFormatRGBA8Unorm;
+#endif
+
+#ifndef Q_OS_MACOS
+ case QRhiTexture::ETC2_RGB8:
+ return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
+ case QRhiTexture::ETC2_RGB8A1:
+ return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
+ case QRhiTexture::ETC2_RGBA8:
+ return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
+
+ case QRhiTexture::ASTC_4x4:
+ return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
+ case QRhiTexture::ASTC_5x4:
+ return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
+ case QRhiTexture::ASTC_5x5:
+ return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
+ case QRhiTexture::ASTC_6x5:
+ return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
+ case QRhiTexture::ASTC_6x6:
+ return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
+ case QRhiTexture::ASTC_8x5:
+ return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
+ case QRhiTexture::ASTC_8x6:
+ return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
+ case QRhiTexture::ASTC_8x8:
+ return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
+ case QRhiTexture::ASTC_10x5:
+ return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
+ case QRhiTexture::ASTC_10x6:
+ return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
+ case QRhiTexture::ASTC_10x8:
+ return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
+ case QRhiTexture::ASTC_10x10:
+ return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
+ case QRhiTexture::ASTC_12x10:
+ return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
+ case QRhiTexture::ASTC_12x12:
+ return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
+#else
+ case QRhiTexture::ETC2_RGB8:
+ case QRhiTexture::ETC2_RGB8A1:
+ case QRhiTexture::ETC2_RGBA8:
+ qWarning("QRhiMetal: ETC2 compression not supported on this platform");
+ return MTLPixelFormatRGBA8Unorm;
+
+ case QRhiTexture::ASTC_4x4:
+ case QRhiTexture::ASTC_5x4:
+ case QRhiTexture::ASTC_5x5:
+ case QRhiTexture::ASTC_6x5:
+ case QRhiTexture::ASTC_6x6:
+ case QRhiTexture::ASTC_8x5:
+ case QRhiTexture::ASTC_8x6:
+ case QRhiTexture::ASTC_8x8:
+ case QRhiTexture::ASTC_10x5:
+ case QRhiTexture::ASTC_10x6:
+ case QRhiTexture::ASTC_10x8:
+ case QRhiTexture::ASTC_10x10:
+ case QRhiTexture::ASTC_12x10:
+ case QRhiTexture::ASTC_12x12:
+ qWarning("QRhiMetal: ASTC compression not supported on this platform");
+ return MTLPixelFormatRGBA8Unorm;
+#endif
+
+ default:
+ Q_UNREACHABLE();
+ return MTLPixelFormatRGBA8Unorm;
+ }
+}
+
+bool QMetalTexture::prepareBuild(QSize *adjustedSize)
+{
+ if (d->tex)
+ release();
+
+ const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+
+ QRHI_RES_RHI(QRhiMetal);
+ d->format = toMetalTextureFormat(m_format, m_flags);
+ mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+ if (samples > 1) {
+ if (isCube) {
+ qWarning("Cubemap texture cannot be multisample");
+ return false;
+ }
+ if (hasMipMaps) {
+ qWarning("Multisample texture cannot have mipmaps");
+ return false;
+ }
+ }
+
+ if (adjustedSize)
+ *adjustedSize = size;
+
+ return true;
+}
+
+bool QMetalTexture::build()
+{
+ QSize size;
+ if (!prepareBuild(&size))
+ return false;
+
+ MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
+
+ const bool isCube = m_flags.testFlag(CubeMap);
+ if (isCube)
+ desc.textureType = MTLTextureTypeCube;
+ else
+ desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
+ desc.pixelFormat = d->format;
+ desc.width = size.width();
+ desc.height = size.height();
+ desc.mipmapLevelCount = mipLevelCount;
+ if (samples > 1)
+ desc.sampleCount = samples;
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.storageMode = MTLStorageModePrivate;
+ desc.usage = MTLTextureUsageShaderRead;
+ if (m_flags.testFlag(RenderTarget))
+ desc.usage |= MTLTextureUsageRenderTarget;
+ if (m_flags.testFlag(UsedWithLoadStore))
+ desc.usage |= MTLTextureUsageShaderWrite;
+
+ QRHI_RES_RHI(QRhiMetal);
+ d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
+ [desc release];
+
+ if (!m_objectName.isEmpty())
+ d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
+
+ d->owns = true;
+ nativeHandlesStruct.texture = d->tex;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, samples));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+bool QMetalTexture::buildFrom(const QRhiNativeHandles *src)
+{
+ const QRhiMetalTextureNativeHandles *h = static_cast<const QRhiMetalTextureNativeHandles *>(src);
+ if (!h || !h->texture)
+ return false;
+
+ if (!prepareBuild())
+ return false;
+
+ d->tex = (id<MTLTexture>) h->texture;
+
+ d->owns = false;
+ nativeHandlesStruct.texture = d->tex;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, samples));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->registerResource(this);
+ return true;
+}
+
+const QRhiNativeHandles *QMetalTexture::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+id<MTLTexture> QMetalTextureData::viewForLevel(int level)
+{
+ Q_ASSERT(level >= 0 && level < int(q->mipLevelCount));
+ if (perLevelViews[level])
+ return perLevelViews[level];
+
+ const MTLTextureType type = [tex textureType];
+ const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
+ id<MTLTexture> view = [tex newTextureViewWithPixelFormat: format textureType: type
+ levels: NSMakeRange(level, 1) slices: NSMakeRange(0, isCube ? 6 : 1)];
+
+ perLevelViews[level] = view;
+ return view;
+}
+
+QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v),
+ d(new QMetalSamplerData)
+{
+}
+
+QMetalSampler::~QMetalSampler()
+{
+ release();
+ delete d;
+}
+
+void QMetalSampler::release()
+{
+ if (!d->samplerState)
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::Sampler;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.sampler.samplerState = d->samplerState;
+ d->samplerState = nil;
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ rhiD->unregisterResource(this);
+}
+
+static inline MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::Nearest:
+ return MTLSamplerMinMagFilterNearest;
+ case QRhiSampler::Linear:
+ return MTLSamplerMinMagFilterLinear;
+ default:
+ Q_UNREACHABLE();
+ return MTLSamplerMinMagFilterNearest;
+ }
+}
+
+static inline MTLSamplerMipFilter toMetalMipmapMode(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::None:
+ return MTLSamplerMipFilterNotMipmapped;
+ case QRhiSampler::Nearest:
+ return MTLSamplerMipFilterNearest;
+ case QRhiSampler::Linear:
+ return MTLSamplerMipFilterLinear;
+ default:
+ Q_UNREACHABLE();
+ return MTLSamplerMipFilterNotMipmapped;
+ }
+}
+
+static inline MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode m)
+{
+ switch (m) {
+ case QRhiSampler::Repeat:
+ return MTLSamplerAddressModeRepeat;
+ case QRhiSampler::ClampToEdge:
+ return MTLSamplerAddressModeClampToEdge;
+ case QRhiSampler::Border:
+ return MTLSamplerAddressModeClampToBorderColor;
+ case QRhiSampler::Mirror:
+ return MTLSamplerAddressModeMirrorRepeat;
+ case QRhiSampler::MirrorOnce:
+ return MTLSamplerAddressModeMirrorClampToEdge;
+ default:
+ Q_UNREACHABLE();
+ return MTLSamplerAddressModeClampToEdge;
+ }
+}
+
+static inline MTLCompareFunction toMetalTextureCompareFunction(QRhiSampler::CompareOp op)
+{
+ switch (op) {
+ case QRhiSampler::Never:
+ return MTLCompareFunctionNever;
+ case QRhiSampler::Less:
+ return MTLCompareFunctionLess;
+ case QRhiSampler::Equal:
+ return MTLCompareFunctionEqual;
+ case QRhiSampler::LessOrEqual:
+ return MTLCompareFunctionLessEqual;
+ case QRhiSampler::Greater:
+ return MTLCompareFunctionGreater;
+ case QRhiSampler::NotEqual:
+ return MTLCompareFunctionNotEqual;
+ case QRhiSampler::GreaterOrEqual:
+ return MTLCompareFunctionGreaterEqual;
+ case QRhiSampler::Always:
+ return MTLCompareFunctionAlways;
+ default:
+ Q_UNREACHABLE();
+ return MTLCompareFunctionNever;
+ }
+}
+
+bool QMetalSampler::build()
+{
+ if (d->samplerState)
+ release();
+
+ MTLSamplerDescriptor *desc = [[MTLSamplerDescriptor alloc] init];
+ desc.minFilter = toMetalFilter(m_minFilter);
+ desc.magFilter = toMetalFilter(m_magFilter);
+ desc.mipFilter = toMetalMipmapMode(m_mipmapMode);
+ desc.sAddressMode = toMetalAddressMode(m_addressU);
+ desc.tAddressMode = toMetalAddressMode(m_addressV);
+ desc.rAddressMode = toMetalAddressMode(m_addressW);
+ desc.compareFunction = toMetalTextureCompareFunction(m_compareOp);
+
+ QRHI_RES_RHI(QRhiMetal);
+ d->samplerState = [rhiD->d->dev newSamplerStateWithDescriptor: desc];
+ [desc release];
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+// dummy, no Vulkan-style RenderPass+Framebuffer concept here.
+// We do have MTLRenderPassDescriptor of course, but it will be created on the fly for each pass.
+QMetalRenderPassDescriptor::QMetalRenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QMetalRenderPassDescriptor::~QMetalRenderPassDescriptor()
+{
+ release();
+}
+
+void QMetalRenderPassDescriptor::release()
+{
+ // nothing to do here
+}
+
+QMetalReferenceRenderTarget::QMetalReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi),
+ d(new QMetalRenderTargetData)
+{
+}
+
+QMetalReferenceRenderTarget::~QMetalReferenceRenderTarget()
+{
+ release();
+ delete d;
+}
+
+void QMetalReferenceRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QSize QMetalReferenceRenderTarget::pixelSize() const
+{
+ return d->pixelSize;
+}
+
+float QMetalReferenceRenderTarget::devicePixelRatio() const
+{
+ return d->dpr;
+}
+
+int QMetalReferenceRenderTarget::sampleCount() const
+{
+ return d->sampleCount;
+}
+
+QMetalTextureRenderTarget::QMetalTextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags),
+ d(new QMetalRenderTargetData)
+{
+}
+
+QMetalTextureRenderTarget::~QMetalTextureRenderTarget()
+{
+ release();
+ delete d;
+}
+
+void QMetalTextureRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
+ rpD->colorAttachmentCount = colorAttachments.count();
+ rpD->hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ for (int i = 0, ie = colorAttachments.count(); i != ie; ++i) {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAttachments[i].texture());
+ QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAttachments[i].renderBuffer());
+ rpD->colorFormat[i] = texD ? texD->d->format : rbD->d->format;
+ }
+
+ if (m_desc.depthTexture())
+ rpD->dsFormat = QRHI_RES(QMetalTexture, m_desc.depthTexture())->d->format;
+ else if (m_desc.depthStencilBuffer())
+ rpD->dsFormat = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format;
+
+ return rpD;
+}
+
+bool QMetalTextureRenderTarget::build()
+{
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
+ Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+ const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ d->colorAttCount = colorAttachments.count();
+ for (int i = 0; i < d->colorAttCount; ++i) {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAttachments[i].texture());
+ QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAttachments[i].renderBuffer());
+ Q_ASSERT(texD || rbD);
+ id<MTLTexture> dst = nil;
+ if (texD) {
+ dst = texD->d->tex;
+ if (i == 0) {
+ d->pixelSize = texD->pixelSize();
+ d->sampleCount = texD->samples;
+ }
+ } else if (rbD) {
+ dst = rbD->d->tex;
+ if (i == 0) {
+ d->pixelSize = rbD->pixelSize();
+ d->sampleCount = rbD->samples;
+ }
+ }
+ QMetalRenderTargetData::ColorAtt colorAtt;
+ colorAtt.tex = dst;
+ colorAtt.layer = colorAttachments[i].layer();
+ colorAtt.level = colorAttachments[i].level();
+ QMetalTexture *resTexD = QRHI_RES(QMetalTexture, colorAttachments[i].resolveTexture());
+ colorAtt.resolveTex = resTexD ? resTexD->d->tex : nil;
+ colorAtt.resolveLayer = colorAttachments[i].resolveLayer();
+ colorAtt.resolveLevel = colorAttachments[i].resolveLevel();
+ d->fb.colorAtt[i] = colorAtt;
+ }
+ d->dpr = 1;
+
+ if (hasDepthStencil) {
+ if (m_desc.depthTexture()) {
+ QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture());
+ d->fb.dsTex = depthTexD->d->tex;
+ d->fb.hasStencil = false;
+ d->fb.depthNeedsStore = true;
+ if (d->colorAttCount == 0) {
+ d->pixelSize = depthTexD->pixelSize();
+ d->sampleCount = depthTexD->samples;
+ }
+ } else {
+ QMetalRenderBuffer *depthRbD = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer());
+ d->fb.dsTex = depthRbD->d->tex;
+ d->fb.hasStencil = true;
+ d->fb.depthNeedsStore = false;
+ if (d->colorAttCount == 0) {
+ d->pixelSize = depthRbD->pixelSize();
+ d->sampleCount = depthRbD->samples;
+ }
+ }
+ d->dsAttCount = 1;
+ } else {
+ d->dsAttCount = 0;
+ }
+
+ return true;
+}
+
+QSize QMetalTextureRenderTarget::pixelSize() const
+{
+ return d->pixelSize;
+}
+
+float QMetalTextureRenderTarget::devicePixelRatio() const
+{
+ return d->dpr;
+}
+
+int QMetalTextureRenderTarget::sampleCount() const
+{
+ return d->sampleCount;
+}
+
+QMetalShaderResourceBindings::QMetalShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QMetalShaderResourceBindings::~QMetalShaderResourceBindings()
+{
+ release();
+}
+
+void QMetalShaderResourceBindings::release()
+{
+ sortedBindings.clear();
+ maxBinding = -1;
+}
+
+bool QMetalShaderResourceBindings::build()
+{
+ if (!sortedBindings.isEmpty())
+ release();
+
+ sortedBindings = m_bindings;
+ std::sort(sortedBindings.begin(), sortedBindings.end(),
+ [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
+ {
+ return QRhiShaderResourceBindingPrivate::get(&a)->binding < QRhiShaderResourceBindingPrivate::get(&b)->binding;
+ });
+ if (!sortedBindings.isEmpty())
+ maxBinding = QRhiShaderResourceBindingPrivate::get(&sortedBindings.last())->binding;
+ else
+ maxBinding = -1;
+
+ boundResourceData.resize(sortedBindings.count());
+
+ for (int i = 0, ie = sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&sortedBindings[i]);
+ QMetalShaderResourceBindings::BoundResourceData &bd(boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
+ QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
+ bd.simage.id = texD->m_id;
+ bd.simage.generation = texD->generation;
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
+ bd.sbuf.id = bufD->m_id;
+ bd.sbuf.generation = bufD->generation;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ generation += 1;
+ return true;
+}
+
+QMetalGraphicsPipeline::QMetalGraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi),
+ d(new QMetalGraphicsPipelineData)
+{
+}
+
+QMetalGraphicsPipeline::~QMetalGraphicsPipeline()
+{
+ release();
+ delete d;
+}
+
+void QMetalGraphicsPipeline::release()
+{
+ QRHI_RES_RHI(QRhiMetal);
+
+ if (!d->ps)
+ return;
+
+ if (d->ps) {
+ [d->ps release];
+ d->ps = nil;
+ }
+
+ if (d->ds) {
+ [d->ds release];
+ d->ds = nil;
+ }
+
+ if (d->vsFunc) {
+ [d->vsFunc release];
+ d->vsFunc = nil;
+ }
+ if (d->vsLib) {
+ [d->vsLib release];
+ d->vsLib = nil;
+ }
+
+ if (d->fsFunc) {
+ [d->fsFunc release];
+ d->fsFunc = nil;
+ }
+ if (d->fsLib) {
+ [d->fsLib release];
+ d->fsLib = nil;
+ }
+
+ rhiD->unregisterResource(this);
+}
+
+static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
+{
+ switch (format) {
+ case QRhiVertexInputAttribute::Float4:
+ return MTLVertexFormatFloat4;
+ case QRhiVertexInputAttribute::Float3:
+ return MTLVertexFormatFloat3;
+ case QRhiVertexInputAttribute::Float2:
+ return MTLVertexFormatFloat2;
+ case QRhiVertexInputAttribute::Float:
+ return MTLVertexFormatFloat;
+ case QRhiVertexInputAttribute::UNormByte4:
+ return MTLVertexFormatUChar4Normalized;
+ case QRhiVertexInputAttribute::UNormByte2:
+ return MTLVertexFormatUChar2Normalized;
+ case QRhiVertexInputAttribute::UNormByte:
+ if (@available(macOS 10.13, iOS 11.0, *))
+ return MTLVertexFormatUCharNormalized;
+ else
+ Q_UNREACHABLE();
+ default:
+ Q_UNREACHABLE();
+ return MTLVertexFormatFloat4;
+ }
+}
+
+static inline MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::Zero:
+ return MTLBlendFactorZero;
+ case QRhiGraphicsPipeline::One:
+ return MTLBlendFactorOne;
+ case QRhiGraphicsPipeline::SrcColor:
+ return MTLBlendFactorSourceColor;
+ case QRhiGraphicsPipeline::OneMinusSrcColor:
+ return MTLBlendFactorOneMinusSourceColor;
+ case QRhiGraphicsPipeline::DstColor:
+ return MTLBlendFactorDestinationColor;
+ case QRhiGraphicsPipeline::OneMinusDstColor:
+ return MTLBlendFactorOneMinusDestinationColor;
+ case QRhiGraphicsPipeline::SrcAlpha:
+ return MTLBlendFactorSourceAlpha;
+ case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+ return MTLBlendFactorOneMinusSourceAlpha;
+ case QRhiGraphicsPipeline::DstAlpha:
+ return MTLBlendFactorDestinationAlpha;
+ case QRhiGraphicsPipeline::OneMinusDstAlpha:
+ return MTLBlendFactorOneMinusDestinationAlpha;
+ case QRhiGraphicsPipeline::ConstantColor:
+ return MTLBlendFactorBlendColor;
+ case QRhiGraphicsPipeline::ConstantAlpha:
+ return MTLBlendFactorBlendAlpha;
+ case QRhiGraphicsPipeline::OneMinusConstantColor:
+ return MTLBlendFactorOneMinusBlendColor;
+ case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+ return MTLBlendFactorOneMinusBlendAlpha;
+ case QRhiGraphicsPipeline::SrcAlphaSaturate:
+ return MTLBlendFactorSourceAlphaSaturated;
+ case QRhiGraphicsPipeline::Src1Color:
+ return MTLBlendFactorSource1Color;
+ case QRhiGraphicsPipeline::OneMinusSrc1Color:
+ return MTLBlendFactorOneMinusSource1Color;
+ case QRhiGraphicsPipeline::Src1Alpha:
+ return MTLBlendFactorSource1Alpha;
+ case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+ return MTLBlendFactorOneMinusSource1Alpha;
+ default:
+ Q_UNREACHABLE();
+ return MTLBlendFactorZero;
+ }
+}
+
+static inline MTLBlendOperation toMetalBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Add:
+ return MTLBlendOperationAdd;
+ case QRhiGraphicsPipeline::Subtract:
+ return MTLBlendOperationSubtract;
+ case QRhiGraphicsPipeline::ReverseSubtract:
+ return MTLBlendOperationReverseSubtract;
+ case QRhiGraphicsPipeline::Min:
+ return MTLBlendOperationMin;
+ case QRhiGraphicsPipeline::Max:
+ return MTLBlendOperationMax;
+ default:
+ Q_UNREACHABLE();
+ return MTLBlendOperationAdd;
+ }
+}
+
+static inline uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
+{
+ uint f = 0;
+ if (c.testFlag(QRhiGraphicsPipeline::R))
+ f |= MTLColorWriteMaskRed;
+ if (c.testFlag(QRhiGraphicsPipeline::G))
+ f |= MTLColorWriteMaskGreen;
+ if (c.testFlag(QRhiGraphicsPipeline::B))
+ f |= MTLColorWriteMaskBlue;
+ if (c.testFlag(QRhiGraphicsPipeline::A))
+ f |= MTLColorWriteMaskAlpha;
+ return f;
+}
+
+static inline MTLCompareFunction toMetalCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Never:
+ return MTLCompareFunctionNever;
+ case QRhiGraphicsPipeline::Less:
+ return MTLCompareFunctionLess;
+ case QRhiGraphicsPipeline::Equal:
+ return MTLCompareFunctionEqual;
+ case QRhiGraphicsPipeline::LessOrEqual:
+ return MTLCompareFunctionLessEqual;
+ case QRhiGraphicsPipeline::Greater:
+ return MTLCompareFunctionGreater;
+ case QRhiGraphicsPipeline::NotEqual:
+ return MTLCompareFunctionNotEqual;
+ case QRhiGraphicsPipeline::GreaterOrEqual:
+ return MTLCompareFunctionGreaterEqual;
+ case QRhiGraphicsPipeline::Always:
+ return MTLCompareFunctionAlways;
+ default:
+ Q_UNREACHABLE();
+ return MTLCompareFunctionAlways;
+ }
+}
+
+static inline MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::StencilZero:
+ return MTLStencilOperationZero;
+ case QRhiGraphicsPipeline::Keep:
+ return MTLStencilOperationKeep;
+ case QRhiGraphicsPipeline::Replace:
+ return MTLStencilOperationReplace;
+ case QRhiGraphicsPipeline::IncrementAndClamp:
+ return MTLStencilOperationIncrementClamp;
+ case QRhiGraphicsPipeline::DecrementAndClamp:
+ return MTLStencilOperationDecrementClamp;
+ case QRhiGraphicsPipeline::Invert:
+ return MTLStencilOperationInvert;
+ case QRhiGraphicsPipeline::IncrementAndWrap:
+ return MTLStencilOperationIncrementWrap;
+ case QRhiGraphicsPipeline::DecrementAndWrap:
+ return MTLStencilOperationDecrementWrap;
+ default:
+ Q_UNREACHABLE();
+ return MTLStencilOperationKeep;
+ }
+}
+
+static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ return MTLPrimitiveTypeTriangle;
+ case QRhiGraphicsPipeline::TriangleStrip:
+ return MTLPrimitiveTypeTriangleStrip;
+ case QRhiGraphicsPipeline::Lines:
+ return MTLPrimitiveTypeLine;
+ case QRhiGraphicsPipeline::LineStrip:
+ return MTLPrimitiveTypeLineStrip;
+ case QRhiGraphicsPipeline::Points:
+ return MTLPrimitiveTypePoint;
+ default:
+ Q_UNREACHABLE();
+ return MTLPrimitiveTypeTriangle;
+ }
+}
+
+static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+ switch (c) {
+ case QRhiGraphicsPipeline::None:
+ return MTLCullModeNone;
+ case QRhiGraphicsPipeline::Front:
+ return MTLCullModeFront;
+ case QRhiGraphicsPipeline::Back:
+ return MTLCullModeBack;
+ default:
+ Q_UNREACHABLE();
+ return MTLCullModeNone;
+ }
+}
+
+id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
+ QString *error, QByteArray *entryPoint)
+{
+ QShaderCode mtllib = shader.shader({ QShader::MetalLibShader, 12, shaderVariant });
+ if (!mtllib.shader().isEmpty()) {
+ dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
+ mtllib.shader().size(),
+ dispatch_get_global_queue(0, 0),
+ DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+ NSError *err = nil;
+ id<MTLLibrary> lib = [dev newLibraryWithData: data error: &err];
+ dispatch_release(data);
+ if (!err) {
+ *entryPoint = mtllib.entryPoint();
+ return lib;
+ } else {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to load metallib from baked shader: %s", qPrintable(msg));
+ }
+ }
+
+ QShaderCode mslSource = shader.shader({ QShader::MslShader, 12, shaderVariant });
+ if (mslSource.shader().isEmpty()) {
+ qWarning() << "No MSL 1.2 code found in baked shader" << shader;
+ return nil;
+ }
+
+ NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()];
+ MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
+ opts.languageVersion = MTLLanguageVersion1_2;
+ NSError *err = nil;
+ id<MTLLibrary> lib = [dev newLibraryWithSource: src options: opts error: &err];
+ [opts release];
+ // src is autoreleased
+
+ if (err) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ *error = msg;
+ return nil;
+ }
+
+ *entryPoint = mslSource.entryPoint();
+ return lib;
+}
+
+id<MTLFunction> QRhiMetalData::createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint)
+{
+ NSString *name = [NSString stringWithUTF8String: entryPoint.constData()];
+ id<MTLFunction> f = [lib newFunctionWithName: name];
+ [name release];
+ return f;
+}
+
+bool QMetalGraphicsPipeline::build()
+{
+ if (d->ps)
+ release();
+
+ QRHI_RES_RHI(QRhiMetal);
+
+ // same binding space for vertex and constant buffers - work it around
+ const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, m_shaderResourceBindings)->maxBinding + 1;
+
+ MTLVertexDescriptor *inputLayout = [MTLVertexDescriptor vertexDescriptor];
+ const QVector<QRhiVertexInputAttribute> attributes = m_vertexInputLayout.attributes();
+ for (const QRhiVertexInputAttribute &attribute : attributes) {
+ const int loc = attribute.location();
+ inputLayout.attributes[loc].format = toMetalAttributeFormat(attribute.format());
+ inputLayout.attributes[loc].offset = attribute.offset();
+ inputLayout.attributes[loc].bufferIndex = firstVertexBinding + attribute.binding();
+ }
+ const QVector<QRhiVertexInputBinding> bindings = m_vertexInputLayout.bindings();
+ for (int i = 0, ie = bindings.count(); i != ie; ++i) {
+ const QRhiVertexInputBinding &binding(bindings[i]);
+ const int layoutIdx = firstVertexBinding + i;
+ inputLayout.layouts[layoutIdx].stepFunction =
+ binding.classification() == QRhiVertexInputBinding::PerInstance
+ ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
+ inputLayout.layouts[layoutIdx].stepRate = binding.instanceStepRate();
+ inputLayout.layouts[layoutIdx].stride = binding.stride();
+ }
+
+ MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
+
+ rpDesc.vertexDescriptor = inputLayout;
+
+ // mutability cannot be determined (slotted buffers could be set as
+ // MTLMutabilityImmutable, but then we potentially need a different
+ // descriptor for each buffer combination as this depends on the actual
+ // buffers not just the resource binding layout) so leave it at the default
+
+ for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
+ QString error;
+ QByteArray entryPoint;
+ id<MTLLibrary> lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint);
+ if (!lib) {
+ qWarning("MSL shader compilation failed: %s", qPrintable(error));
+ return false;
+ }
+ id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
+ if (!func) {
+ qWarning("MSL function for entry point %s not found", entryPoint.constData());
+ [lib release];
+ return false;
+ }
+ switch (shaderStage.type()) {
+ case QRhiShaderStage::Vertex:
+ rpDesc.vertexFunction = func;
+ d->vsLib = lib;
+ d->vsFunc = func;
+ break;
+ case QRhiShaderStage::Fragment:
+ rpDesc.fragmentFunction = func;
+ d->fsLib = lib;
+ d->fsFunc = func;
+ break;
+ default:
+ [func release];
+ [lib release];
+ break;
+ }
+ }
+
+ QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc);
+
+ if (rpD->colorAttachmentCount) {
+ // defaults when no targetBlends are provided
+ rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormat(rpD->colorFormat[0]);
+ rpDesc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
+ rpDesc.colorAttachments[0].blendingEnabled = false;
+
+ Q_ASSERT(m_targetBlends.count() == rpD->colorAttachmentCount
+ || (m_targetBlends.isEmpty() && rpD->colorAttachmentCount == 1));
+
+ for (int i = 0, ie = m_targetBlends.count(); i != ie; ++i) {
+ const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[i]);
+ rpDesc.colorAttachments[i].pixelFormat = MTLPixelFormat(rpD->colorFormat[i]);
+ rpDesc.colorAttachments[i].blendingEnabled = b.enable;
+ rpDesc.colorAttachments[i].sourceRGBBlendFactor = toMetalBlendFactor(b.srcColor);
+ rpDesc.colorAttachments[i].destinationRGBBlendFactor = toMetalBlendFactor(b.dstColor);
+ rpDesc.colorAttachments[i].rgbBlendOperation = toMetalBlendOp(b.opColor);
+ rpDesc.colorAttachments[i].sourceAlphaBlendFactor = toMetalBlendFactor(b.srcAlpha);
+ rpDesc.colorAttachments[i].destinationAlphaBlendFactor = toMetalBlendFactor(b.dstAlpha);
+ rpDesc.colorAttachments[i].alphaBlendOperation = toMetalBlendOp(b.opAlpha);
+ rpDesc.colorAttachments[i].writeMask = toMetalColorWriteMask(b.colorWrite);
+ }
+ }
+
+ if (rpD->hasDepthStencil) {
+ // Must only be set when a depth-stencil buffer will actually be bound,
+ // validation blows up otherwise.
+ MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat);
+ rpDesc.depthAttachmentPixelFormat = fmt;
+ if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float)
+ rpDesc.stencilAttachmentPixelFormat = fmt;
+ }
+
+ rpDesc.sampleCount = rhiD->effectiveSampleCount(m_sampleCount);
+
+ NSError *err = nil;
+ d->ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
+ if (!d->ps) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
+ [rpDesc release];
+ return false;
+ }
+ [rpDesc release];
+
+ MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
+ dsDesc.depthCompareFunction = m_depthTest ? toMetalCompareOp(m_depthOp) : MTLCompareFunctionAlways;
+ dsDesc.depthWriteEnabled = m_depthWrite;
+ if (m_stencilTest) {
+ dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
+ dsDesc.frontFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilFront.failOp);
+ dsDesc.frontFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilFront.depthFailOp);
+ dsDesc.frontFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilFront.passOp);
+ dsDesc.frontFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilFront.compareOp);
+ dsDesc.frontFaceStencil.readMask = m_stencilReadMask;
+ dsDesc.frontFaceStencil.writeMask = m_stencilWriteMask;
+
+ dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
+ dsDesc.backFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilBack.failOp);
+ dsDesc.backFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilBack.depthFailOp);
+ dsDesc.backFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilBack.passOp);
+ dsDesc.backFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilBack.compareOp);
+ dsDesc.backFaceStencil.readMask = m_stencilReadMask;
+ dsDesc.backFaceStencil.writeMask = m_stencilWriteMask;
+ }
+
+ d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
+ [dsDesc release];
+
+ d->primitiveType = toMetalPrimitiveType(m_topology);
+ d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
+ d->cullMode = toMetalCullMode(m_cullMode);
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QMetalComputePipeline::QMetalComputePipeline(QRhiImplementation *rhi)
+ : QRhiComputePipeline(rhi),
+ d(new QMetalComputePipelineData)
+{
+}
+
+QMetalComputePipeline::~QMetalComputePipeline()
+{
+ release();
+ delete d;
+}
+
+void QMetalComputePipeline::release()
+{
+ QRHI_RES_RHI(QRhiMetal);
+
+ if (d->csFunc) {
+ [d->csFunc release];
+ d->csFunc = nil;
+ }
+ if (d->csLib) {
+ [d->csLib release];
+ d->csLib = nil;
+ }
+
+ if (!d->ps)
+ return;
+
+ if (d->ps) {
+ [d->ps release];
+ d->ps = nil;
+ }
+
+ rhiD->unregisterResource(this);
+}
+
+bool QMetalComputePipeline::build()
+{
+ if (d->ps)
+ release();
+
+ QRHI_RES_RHI(QRhiMetal);
+
+ const QShader shader = m_shaderStage.shader();
+ QString error;
+ QByteArray entryPoint;
+ id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(),
+ &error, &entryPoint);
+ if (!lib) {
+ qWarning("MSL shader compilation failed: %s", qPrintable(error));
+ return false;
+ }
+ id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
+ if (!func) {
+ qWarning("MSL function for entry point %s not found", entryPoint.constData());
+ [lib release];
+ return false;
+ }
+ d->csLib = lib;
+ d->csFunc = func;
+ std::array<uint, 3> localSize = shader.description().computeShaderLocalSize();
+ d->localSize = MTLSizeMake(localSize[0], localSize[1], localSize[2]);
+
+ NSError *err = nil;
+ d->ps = [rhiD->d->dev newComputePipelineStateWithFunction: d->csFunc error: &err];
+ if (!d->ps) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
+ return false;
+ }
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QMetalCommandBuffer::QMetalCommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi),
+ d(new QMetalCommandBufferData)
+{
+ resetState();
+}
+
+QMetalCommandBuffer::~QMetalCommandBuffer()
+{
+ release();
+ delete d;
+}
+
+void QMetalCommandBuffer::release()
+{
+ // nothing to do here, we do not own the MTL cb object
+}
+
+const QRhiNativeHandles *QMetalCommandBuffer::nativeHandles()
+{
+ nativeHandlesStruct.commandBuffer = d->cb;
+ nativeHandlesStruct.encoder = d->currentRenderPassEncoder;
+ return &nativeHandlesStruct;
+}
+
+void QMetalCommandBuffer::resetState()
+{
+ d->currentRenderPassEncoder = nil;
+ d->currentComputePassEncoder = nil;
+ d->currentPassRpDesc = nil;
+ resetPerPassState();
+}
+
+void QMetalCommandBuffer::resetPerPassState()
+{
+ recordingPass = NoPass;
+ currentTarget = nullptr;
+ resetPerPassCachedState();
+}
+
+void QMetalCommandBuffer::resetPerPassCachedState()
+{
+ currentGraphicsPipeline = nullptr;
+ currentComputePipeline = nullptr;
+ currentPipelineGeneration = 0;
+ currentGraphicsSrb = nullptr;
+ currentComputeSrb = nullptr;
+ currentSrbGeneration = 0;
+ currentResSlot = -1;
+ currentIndexBuffer = nullptr;
+
+ d->currentFirstVertexBinding = -1;
+ d->currentVertexInputsBuffers.clear();
+ d->currentVertexInputOffsets.clear();
+}
+
+QMetalSwapChain::QMetalSwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rtWrapper(rhi),
+ cbWrapper(rhi),
+ d(new QMetalSwapChainData)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ d->sem[i] = nullptr;
+ d->msaaTex[i] = nil;
+ }
+}
+
+QMetalSwapChain::~QMetalSwapChain()
+{
+ release();
+ delete d;
+}
+
+void QMetalSwapChain::release()
+{
+ if (!d->layer)
+ return;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (d->sem[i]) {
+ // the semaphores cannot be released if they do not have the initial value
+ dispatch_semaphore_wait(d->sem[i], DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(d->sem[i]);
+
+ dispatch_release(d->sem[i]);
+ d->sem[i] = nullptr;
+ }
+ }
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ [d->msaaTex[i] release];
+ d->msaaTex[i] = nil;
+ }
+
+ d->layer = nullptr;
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->swapchains.remove(this);
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiCommandBuffer *QMetalSwapChain::currentFrameCommandBuffer()
+{
+ return &cbWrapper;
+}
+
+QRhiRenderTarget *QMetalSwapChain::currentFrameRenderTarget()
+{
+ return &rtWrapper;
+}
+
+QSize QMetalSwapChain::surfacePixelSize()
+{
+ // may be called before build, must not access other than m_*
+
+ NSView *v = (NSView *) m_window->winId();
+ if (v) {
+ CAMetalLayer *layer = (CAMetalLayer *) [v layer];
+ if (layer) {
+ CGSize size = [layer drawableSize];
+ return QSize(size.width, size.height);
+ }
+ }
+ return QSize();
+}
+
+QRhiRenderPassDescriptor *QMetalSwapChain::newCompatibleRenderPassDescriptor()
+{
+ chooseFormats(); // ensure colorFormat and similar are filled out
+
+ QRHI_RES_RHI(QRhiMetal);
+ QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
+ rpD->colorAttachmentCount = 1;
+ rpD->hasDepthStencil = m_depthStencil != nullptr;
+
+ rpD->colorFormat[0] = d->colorFormat;
+
+ // m_depthStencil may not be built yet so cannot rely on computed fields in it
+ rpD->dsFormat = rhiD->d->dev.depth24Stencil8PixelFormatSupported
+ ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
+
+ return rpD;
+}
+
+void QMetalSwapChain::chooseFormats()
+{
+ QRHI_RES_RHI(QRhiMetal);
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+ // pick a format that is allowed for CAMetalLayer.pixelFormat
+ d->colorFormat = m_flags.testFlag(sRGB) ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
+ d->rhiColorFormat = QRhiTexture::BGRA8;
+}
+
+bool QMetalSwapChain::buildOrResize()
+{
+ Q_ASSERT(m_window);
+
+ const bool needsRegistration = !window || window != m_window;
+
+ if (window && window != m_window)
+ release();
+ // else no release(), this is intentional
+
+ QRHI_RES_RHI(QRhiMetal);
+ if (needsRegistration)
+ rhiD->swapchains.insert(this);
+
+ window = m_window;
+
+ if (window->surfaceType() != QSurface::MetalSurface) {
+ qWarning("QMetalSwapChain only supports MetalSurface windows");
+ return false;
+ }
+
+ NSView *v = (NSView *) window->winId();
+ d->layer = (CAMetalLayer *) [v layer];
+ Q_ASSERT(d->layer);
+
+ chooseFormats();
+ if (d->colorFormat != d->layer.pixelFormat)
+ d->layer.pixelFormat = d->colorFormat;
+
+ if (m_flags.testFlag(UsedAsTransferSource))
+ d->layer.framebufferOnly = NO;
+
+#ifdef Q_OS_MACOS
+ if (m_flags.testFlag(NoVSync)) {
+ if (@available(macOS 10.13, *))
+ d->layer.displaySyncEnabled = NO;
+ }
+#endif
+
+ m_currentPixelSize = surfacePixelSize();
+ pixelSize = m_currentPixelSize;
+
+ [d->layer setDevice: rhiD->d->dev];
+
+ d->curDrawable = nil;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (!d->sem[i])
+ d->sem[i] = dispatch_semaphore_create(QMTL_FRAMES_IN_FLIGHT - 1);
+ }
+
+ currentFrameSlot = 0;
+ frameCount = 0;
+
+ ds = m_depthStencil ? QRHI_RES(QMetalRenderBuffer, m_depthStencil) : nullptr;
+ if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
+ qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
+ m_depthStencil->sampleCount(), m_sampleCount);
+ }
+ if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
+ qWarning("Depth-stencil buffer's size (%dx%d) does not match the layer size (%dx%d). Expect problems.",
+ m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
+ pixelSize.width(), pixelSize.height());
+ }
+
+ rtWrapper.d->pixelSize = pixelSize;
+ rtWrapper.d->dpr = window->devicePixelRatio();
+ rtWrapper.d->sampleCount = samples;
+ rtWrapper.d->colorAttCount = 1;
+ rtWrapper.d->dsAttCount = ds ? 1 : 0;
+
+ qDebug("got CAMetalLayer, size %dx%d", pixelSize.width(), pixelSize.height());
+
+ if (samples > 1) {
+ MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
+ desc.textureType = MTLTextureType2DMultisample;
+ desc.pixelFormat = d->colorFormat;
+ desc.width = pixelSize.width();
+ desc.height = pixelSize.height();
+ desc.sampleCount = samples;
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.storageMode = MTLStorageModePrivate;
+ desc.usage = MTLTextureUsageRenderTarget;
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ [d->msaaTex[i] release];
+ d->msaaTex[i] = [rhiD->d->dev newTextureWithDescriptor: desc];
+ }
+ [desc release];
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(resizeSwapChain(this, QMTL_FRAMES_IN_FLIGHT, samples > 1 ? QMTL_FRAMES_IN_FLIGHT : 0, samples));
+
+ if (needsRegistration)
+ rhiD->registerResource(this);
+
+ return true;
+}
+
+QT_END_NAMESPACE