1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the Qt Gui module
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qrhimetal_p_p.h"
38#include <QGuiApplication>
39#include <QWindow>
40#include <qmath.h>
41
42#ifdef Q_OS_MACOS
43#include <AppKit/AppKit.h>
44#else
45#include <UIKit/UIKit.h>
46#endif
47
48#include <Metal/Metal.h>
49#include <QuartzCore/CAMetalLayer.h>
50
51QT_BEGIN_NAMESPACE
52
53/*
54    Metal backend. Double buffers and throttles to vsync. "Dynamic" buffers are
55    Shared (host visible) and duplicated (to help having 2 frames in flight),
56    "static" and "immutable" are Managed on macOS and Shared on iOS/tvOS.
57    Textures are Private (device local) and a host visible staging buffer is
58    used to upload data to them. Does not rely on strong objects refs from
59    command buffers but does rely on the automatic resource tracking of the
60    command encoders. Assumes that an autorelease pool (ideally per frame) is
61    available on the thread on which QRhi is used.
62*/
63
64#if __has_feature(objc_arc)
65#error ARC not supported
66#endif
67
68// Note: we expect everything here pass the Metal API validation when running
69// in Debug mode in XCode. Some of the issues that break validation are not
70// obvious and not visible when running outside XCode.
71//
72// An exception is the nextDrawable Called Early blah blah warning, which is
73// plain and simply false.
74
75/*!
76    \class QRhiMetalInitParams
77    \inmodule QtRhi
78    \internal
79    \brief Metal specific initialization parameters.
80
81    A Metal-based QRhi needs no special parameters for initialization.
82
83    \badcode
84        QRhiMetalInitParams params;
85        rhi = QRhi::create(QRhi::Metal, &params);
86    \endcode
87
88    \note Metal API validation cannot be enabled by the application. Instead,
89    run the debug build of the application in XCode. Generating a
90    \c{.xcodeproj} file via \c{qmake -spec macx-xcode} provides a convenient
91    way to enable this.
92
93    \note QRhiSwapChain can only target QWindow instances that have their
94    surface type set to QSurface::MetalSurface.
95
96    \section2 Working with existing Metal devices
97
98    When interoperating with another graphics engine, it may be necessary to
99    get a QRhi instance that uses the same Metal device. This can be achieved
100    by passing a pointer to a QRhiMetalNativeHandles to QRhi::create(). The
101    device must be set to a non-null value then. Optionally, a command queue
102    object can be specified as well.
103
104    The QRhi does not take ownership of any of the external objects.
105 */
106
107/*!
108    \class QRhiMetalNativeHandles
109    \inmodule QtRhi
110    \internal
111    \brief Holds the Metal device used by the QRhi.
112
113    \note The class uses \c{void *} as the type since including the Objective C
114    headers is not acceptable here. The actual types are \c{id<MTLDevice>} and
115    \c{id<MTLCommandQueue>}.
116 */
117
118/*!
119    \class QRhiMetalCommandBufferNativeHandles
120    \inmodule QtRhi
121    \internal
122    \brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer.
123
124    \note The command buffer object is only guaranteed to be valid while
125    recording a frame, that is, between a \l{QRhi::beginFrame()}{beginFrame()}
126    - \l{QRhi::endFrame()}{endFrame()} or
127    \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
128    \l{QRhi::endOffscreenFrame()}{endOffsrceenFrame()} pair.
129
130    \note The command encoder is only valid while recording a pass, that is,
131    between \l{QRhiCommandBuffer::beginPass()} -
132    \l{QRhiCommandBuffer::endPass()}.
133 */
134
135struct QMetalShader
136{
137    id<MTLLibrary> lib = nil;
138    id<MTLFunction> func = nil;
139    std::array<uint, 3> localSize;
140    QShader::NativeResourceBindingMap nativeResourceBindingMap;
141
142    void release() {
143        nativeResourceBindingMap.clear();
144        [lib release];
145        lib = nil;
146        [func release];
147        func = nil;
148    }
149};
150
151struct QRhiMetalData
152{
153    QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }
154
155    id<MTLDevice> dev = nil;
156    id<MTLCommandQueue> cmdQueue = nil;
157
158    MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
159                                                     const QColor &colorClearValue,
160                                                     const QRhiDepthStencilClearValue &depthStencilClearValue,
161                                                     int colorAttCount);
162    id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
163                                  QString *error, QByteArray *entryPoint, QShaderKey *activeKey);
164    id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);
165
166    struct DeferredReleaseEntry {
167        enum Type {
168            Buffer,
169            RenderBuffer,
170            Texture,
171            Sampler,
172            StagingBuffer
173        };
174        Type type;
175        int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
176        union {
177            struct {
178                id<MTLBuffer> buffers[QMTL_FRAMES_IN_FLIGHT];
179            } buffer;
180            struct {
181                id<MTLTexture> texture;
182            } renderbuffer;
183            struct {
184                id<MTLTexture> texture;
185                id<MTLBuffer> stagingBuffers[QMTL_FRAMES_IN_FLIGHT];
186                id<MTLTexture> views[QRhi::MAX_LEVELS];
187            } texture;
188            struct {
189                id<MTLSamplerState> samplerState;
190            } sampler;
191            struct {
192                id<MTLBuffer> buffer;
193            } stagingBuffer;
194        };
195    };
196    QVector<DeferredReleaseEntry> releaseQueue;
197
198    struct OffscreenFrame {
199        OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
200        bool active = false;
201        QMetalCommandBuffer cbWrapper;
202    } ofr;
203
204    struct TextureReadback {
205        int activeFrameSlot = -1;
206        QRhiReadbackDescription desc;
207        QRhiReadbackResult *result;
208        id<MTLBuffer> buf;
209        quint32 bufSize;
210        QSize pixelSize;
211        QRhiTexture::Format format;
212    };
213    QVector<TextureReadback> activeTextureReadbacks;
214
215    API_AVAILABLE(macos(10.13), ios(11.0)) MTLCaptureManager *captureMgr;
216    API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil;
217
218    static const int TEXBUF_ALIGN = 256; // probably not accurate
219
220    QHash<QRhiShaderStage, QMetalShader> shaderCache;
221};
222
223Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
224Q_DECLARE_TYPEINFO(QRhiMetalData::TextureReadback, Q_MOVABLE_TYPE);
225
226struct QMetalBufferData
227{
228    bool managed;
229    bool slotted;
230    id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT];
231    QVarLengthArray<QRhiResourceUpdateBatchPrivate::BufferOp, 16> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
232};
233
234struct QMetalRenderBufferData
235{
236    MTLPixelFormat format;
237    id<MTLTexture> tex = nil;
238};
239
240struct QMetalTextureData
241{
242    QMetalTextureData(QMetalTexture *t) : q(t) { }
243
244    QMetalTexture *q;
245    MTLPixelFormat format;
246    id<MTLTexture> tex = nil;
247    id<MTLBuffer> stagingBuf[QMTL_FRAMES_IN_FLIGHT];
248    bool owns = true;
249    id<MTLTexture> perLevelViews[QRhi::MAX_LEVELS];
250
251    id<MTLTexture> viewForLevel(int level);
252};
253
254struct QMetalSamplerData
255{
256    id<MTLSamplerState> samplerState = nil;
257};
258
259struct QMetalCommandBufferData
260{
261    id<MTLCommandBuffer> cb;
262    id<MTLRenderCommandEncoder> currentRenderPassEncoder;
263    id<MTLComputeCommandEncoder> currentComputePassEncoder;
264    MTLRenderPassDescriptor *currentPassRpDesc;
265    int currentFirstVertexBinding;
266    QRhiBatchedBindings<id<MTLBuffer> > currentVertexInputsBuffers;
267    QRhiBatchedBindings<NSUInteger> currentVertexInputOffsets;
268};
269
270struct QMetalRenderTargetData
271{
272    QSize pixelSize;
273    float dpr = 1;
274    int sampleCount = 1;
275    int colorAttCount = 0;
276    int dsAttCount = 0;
277
278    struct ColorAtt {
279        bool needsDrawableForTex = false;
280        id<MTLTexture> tex = nil;
281        int layer = 0;
282        int level = 0;
283        bool needsDrawableForResolveTex = false;
284        id<MTLTexture> resolveTex = nil;
285        int resolveLayer = 0;
286        int resolveLevel = 0;
287    };
288
289    struct {
290        ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
291        id<MTLTexture> dsTex = nil;
292        bool hasStencil = false;
293        bool depthNeedsStore = false;
294    } fb;
295};
296
297struct QMetalGraphicsPipelineData
298{
299    id<MTLRenderPipelineState> ps = nil;
300    id<MTLDepthStencilState> ds = nil;
301    MTLPrimitiveType primitiveType;
302    MTLWinding winding;
303    MTLCullMode cullMode;
304    float depthBias;
305    float slopeScaledDepthBias;
306    QMetalShader vs;
307    QMetalShader fs;
308};
309
310struct QMetalComputePipelineData
311{
312    id<MTLComputePipelineState> ps = nil;
313    QMetalShader cs;
314    MTLSize localSize;
315};
316
317struct QMetalSwapChainData
318{
319    // The iOS simulator's headers mark CAMetalLayer as iOS 13.0+ only.
320    // (for real device SDKs it is 8.0+)
321#ifdef TARGET_IPHONE_SIMULATOR
322    API_AVAILABLE(ios(13.0)) CAMetalLayer *layer = nullptr;
323#else
324    CAMetalLayer *layer = nullptr;
325#endif
326    id<CAMetalDrawable> curDrawable;
327    dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT];
328    MTLRenderPassDescriptor *rp = nullptr;
329    id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
330    QRhiTexture::Format rhiColorFormat;
331    MTLPixelFormat colorFormat;
332};
333
334QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
335{
336    Q_UNUSED(params);
337
338    d = new QRhiMetalData(this);
339
340    importedDevice = importDevice != nullptr;
341    if (importedDevice) {
342        if (d->dev) {
343            d->dev = (id<MTLDevice>) importDevice->dev;
344            importedCmdQueue = importDevice->cmdQueue != nullptr;
345            if (importedCmdQueue)
346                d->cmdQueue = (id<MTLCommandQueue>) importDevice->cmdQueue;
347        } else {
348            qWarning("No MTLDevice given, cannot import");
349            importedDevice = false;
350        }
351    }
352}
353
354QRhiMetal::~QRhiMetal()
355{
356    delete d;
357}
358
359template <class Int>
360inline Int aligned(Int v, Int byteAlign)
361{
362    return (v + byteAlign - 1) & ~(byteAlign - 1);
363}
364
365bool QRhiMetal::create(QRhi::Flags flags)
366{
367    Q_UNUSED(flags);
368
369    if (importedDevice)
370        [d->dev retain];
371    else
372        d->dev = MTLCreateSystemDefaultDevice();
373
374    if (!d->dev) {
375        qWarning("No MTLDevice");
376        return false;
377    }
378
379    qCDebug(QRHI_LOG_INFO, "Metal device: %s", qPrintable(QString::fromNSString([d->dev name])));
380
381    if (importedCmdQueue)
382        [d->cmdQueue retain];
383    else
384        d->cmdQueue = [d->dev newCommandQueue];
385
386    if (@available(macOS 10.13, iOS 11.0, *)) {
387        d->captureMgr = [MTLCaptureManager sharedCaptureManager];
388        // Have a custom capture scope as well which then shows up in XCode as
389        // an option when capturing, and becomes especially useful when having
390        // multiple windows with multiple QRhis.
391        d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
392        const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
393        d->captureScope.label = label.toNSString();
394    }
395
396#if defined(Q_OS_MACOS)
397    caps.maxTextureSize = 16384;
398#elif defined(Q_OS_TVOS)
399    if ([d->dev supportsFeatureSet: MTLFeatureSet(30003)]) // MTLFeatureSet_tvOS_GPUFamily2_v1
400        caps.maxTextureSize = 16384;
401    else
402        caps.maxTextureSize = 8192;
403#elif defined(Q_OS_IOS)
404    // welcome to feature set hell
405    if ([d->dev supportsFeatureSet: MTLFeatureSet(16)] // MTLFeatureSet_iOS_GPUFamily5_v1
406            || [d->dev supportsFeatureSet: MTLFeatureSet(11)] // MTLFeatureSet_iOS_GPUFamily4_v1
407            || [d->dev supportsFeatureSet: MTLFeatureSet(4)]) // MTLFeatureSet_iOS_GPUFamily3_v1
408    {
409        caps.maxTextureSize = 16384;
410    } else if ([d->dev supportsFeatureSet: MTLFeatureSet(3)] // MTLFeatureSet_iOS_GPUFamily2_v2
411            || [d->dev supportsFeatureSet: MTLFeatureSet(2)]) // MTLFeatureSet_iOS_GPUFamily1_v2
412    {
413        caps.maxTextureSize = 8192;
414    } else {
415        caps.maxTextureSize = 4096;
416    }
417#endif
418
419    nativeHandlesStruct.dev = d->dev;
420    nativeHandlesStruct.cmdQueue = d->cmdQueue;
421
422    return true;
423}
424
425void QRhiMetal::destroy()
426{
427    executeDeferredReleases(true);
428    finishActiveReadbacks(true);
429
430    for (QMetalShader &s : d->shaderCache)
431        s.release();
432    d->shaderCache.clear();
433
434    if (@available(macOS 10.13, iOS 11.0, *)) {
435        [d->captureScope release];
436        d->captureScope = nil;
437    }
438
439    [d->cmdQueue release];
440    if (!importedCmdQueue)
441        d->cmdQueue = nil;
442
443    [d->dev release];
444    if (!importedDevice)
445        d->dev = nil;
446}
447
448QVector<int> QRhiMetal::supportedSampleCounts() const
449{
450    return { 1, 2, 4, 8 };
451}
452
453int QRhiMetal::effectiveSampleCount(int sampleCount) const
454{
455    // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
456    const int s = qBound(1, sampleCount, 64);
457    if (!supportedSampleCounts().contains(s)) {
458        qWarning("Attempted to set unsupported sample count %d", sampleCount);
459        return 1;
460    }
461    return s;
462}
463
464QRhiSwapChain *QRhiMetal::createSwapChain()
465{
466    return new QMetalSwapChain(this);
467}
468
469QRhiBuffer *QRhiMetal::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
470{
471    return new QMetalBuffer(this, type, usage, size);
472}
473
474int QRhiMetal::ubufAlignment() const
475{
476    return 256;
477}
478
479bool QRhiMetal::isYUpInFramebuffer() const
480{
481    return false;
482}
483
484bool QRhiMetal::isYUpInNDC() const
485{
486    return true;
487}
488
489bool QRhiMetal::isClipDepthZeroToOne() const
490{
491    return true;
492}
493
494QMatrix4x4 QRhiMetal::clipSpaceCorrMatrix() const
495{
496    // depth range 0..1
497    static QMatrix4x4 m;
498    if (m.isIdentity()) {
499        // NB the ctor takes row-major
500        m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
501                       0.0f, 1.0f, 0.0f, 0.0f,
502                       0.0f, 0.0f, 0.5f, 0.5f,
503                       0.0f, 0.0f, 0.0f, 1.0f);
504    }
505    return m;
506}
507
508bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
509{
510    Q_UNUSED(flags);
511
512#ifdef Q_OS_MACOS
513    if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
514        return false;
515    if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12)
516        return false;
517#else
518    if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
519        return false;
520#endif
521
522    return true;
523}
524
525bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
526{
527    switch (feature) {
528    case QRhi::MultisampleTexture:
529        return true;
530    case QRhi::MultisampleRenderBuffer:
531        return true;
532    case QRhi::DebugMarkers:
533        return true;
534    case QRhi::Timestamps:
535        return false;
536    case QRhi::Instancing:
537        return true;
538    case QRhi::CustomInstanceStepRate:
539        return true;
540    case QRhi::PrimitiveRestart:
541        return true;
542    case QRhi::NonDynamicUniformBuffers:
543        return true;
544    case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
545        return false;
546    case QRhi::NPOTTextureRepeat:
547        return true;
548    case QRhi::RedOrAlpha8IsRed:
549        return true;
550    case QRhi::ElementIndexUint:
551        return true;
552    case QRhi::Compute:
553        return true;
554    case QRhi::WideLines:
555        return false;
556    case QRhi::VertexShaderPointSize:
557        return true;
558    case QRhi::BaseVertex:
559        return true;
560    case QRhi::BaseInstance:
561        return true;
562    case QRhi::TriangleFanTopology:
563        return false;
564    case QRhi::ReadBackNonUniformBuffer:
565        return true;
566    case QRhi::ReadBackNonBaseMipLevel:
567        return true;
568    case QRhi::TexelFetch:
569        return true;
570    default:
571        Q_UNREACHABLE();
572        return false;
573    }
574}
575
576int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
577{
578    switch (limit) {
579    case QRhi::TextureSizeMin:
580        return 1;
581    case QRhi::TextureSizeMax:
582        return caps.maxTextureSize;
583    case QRhi::MaxColorAttachments:
584        return 8;
585    case QRhi::FramesInFlight:
586        return QMTL_FRAMES_IN_FLIGHT;
587    case QRhi::MaxAsyncReadbackFrames:
588        return QMTL_FRAMES_IN_FLIGHT;
589    default:
590        Q_UNREACHABLE();
591        return 0;
592    }
593}
594
595const QRhiNativeHandles *QRhiMetal::nativeHandles()
596{
597    return &nativeHandlesStruct;
598}
599
600void QRhiMetal::sendVMemStatsToProfiler()
601{
602    // nothing to do here
603}
604
605bool QRhiMetal::makeThreadLocalNativeContextCurrent()
606{
607    // not applicable
608    return false;
609}
610
611void QRhiMetal::releaseCachedResources()
612{
613    for (QMetalShader &s : d->shaderCache)
614        s.release();
615
616    d->shaderCache.clear();
617}
618
619bool QRhiMetal::isDeviceLost() const
620{
621    return false;
622}
623
624QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
625                                                int sampleCount, QRhiRenderBuffer::Flags flags)
626{
627    return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags);
628}
629
630QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
631                                      int sampleCount, QRhiTexture::Flags flags)
632{
633    return new QMetalTexture(this, format, pixelSize, sampleCount, flags);
634}
635
636QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
637                                      QRhiSampler::Filter mipmapMode,
638                                      QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
639{
640    return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v, w);
641}
642
643QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
644                                                              QRhiTextureRenderTarget::Flags flags)
645{
646    return new QMetalTextureRenderTarget(this, desc, flags);
647}
648
649QRhiGraphicsPipeline *QRhiMetal::createGraphicsPipeline()
650{
651    return new QMetalGraphicsPipeline(this);
652}
653
654QRhiComputePipeline *QRhiMetal::createComputePipeline()
655{
656    return new QMetalComputePipeline(this);
657}
658
659QRhiShaderResourceBindings *QRhiMetal::createShaderResourceBindings()
660{
661    return new QMetalShaderResourceBindings(this);
662}
663
664enum class BindingType {
665    Buffer,
666    Texture,
667    Sampler
668};
669
670static inline int mapBinding(int binding,
671                             int stageIndex,
672                             const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[],
673                             BindingType type)
674{
675    const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex];
676    if (!map || map->isEmpty())
677        return binding; // old QShader versions do not have this map, assume 1:1 mapping then
678
679    auto it = map->constFind(binding);
680    if (it != map->cend())
681        return type == BindingType::Sampler ? it->second : it->first; // may be -1, if the resource is inactive
682
683    // Hitting this path is normal too. It is not given that the resource (for
684    // example, a uniform block) is present in the shaders for all the stages
685    // specified by the visibility mask in the QRhiShaderResourceBinding.
686    return -1;
687}
688
689void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD,
690                                              QMetalCommandBuffer *cbD,
691                                              int dynamicOffsetCount,
692                                              const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
693                                              bool offsetOnlyChange,
694                                              const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES])
695{
696    struct Stage {
697        struct Buffer {
698            int nativeBinding;
699            id<MTLBuffer> mtlbuf;
700            uint offset;
701        };
702        struct Texture {
703            int nativeBinding;
704            id<MTLTexture> mtltex;
705        };
706        struct Sampler {
707            int nativeBinding;
708            id<MTLSamplerState> mtlsampler;
709        };
710        QVarLengthArray<Buffer, 8> buffers;
711        QVarLengthArray<Texture, 8> textures;
712        QVarLengthArray<Sampler, 8> samplers;
713        QRhiBatchedBindings<id<MTLBuffer> > bufferBatches;
714        QRhiBatchedBindings<NSUInteger> bufferOffsetBatches;
715        QRhiBatchedBindings<id<MTLTexture> > textureBatches;
716        QRhiBatchedBindings<id<MTLSamplerState> > samplerBatches;
717    } res[SUPPORTED_STAGES];
718    enum { VERTEX = 0, FRAGMENT = 1, COMPUTE = 2 };
719
720    for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
721        const QRhiShaderResourceBinding::Data *b = binding.data();
722        switch (b->type) {
723        case QRhiShaderResourceBinding::UniformBuffer:
724        {
725            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
726            id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
727            uint offset = uint(b->u.ubuf.offset);
728            for (int i = 0; i < dynamicOffsetCount; ++i) {
729                const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
730                if (dynOfs.first == b->binding) {
731                    offset = dynOfs.second;
732                    break;
733                }
734            }
735            if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
736                const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer);
737                if (nativeBinding >= 0)
738                    res[VERTEX].buffers.append({ nativeBinding, mtlbuf, offset });
739            }
740            if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
741                const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer);
742                if (nativeBinding >= 0)
743                    res[FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset });
744            }
745            if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
746                const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer);
747                if (nativeBinding >= 0)
748                    res[COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset });
749            }
750        }
751            break;
752        case QRhiShaderResourceBinding::SampledTexture:
753        {
754            const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
755            for (int elem = 0; elem < data->count; ++elem) {
756                QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.texSamplers[elem].tex);
757                QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.texSamplers[elem].sampler);
758                if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
759                    const int nativeBindingTexture = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture);
760                    const int nativeBindingSampler = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler);
761                    if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) {
762                        res[VERTEX].textures.append({ nativeBindingTexture + elem, texD->d->tex });
763                        res[VERTEX].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState });
764                    }
765                }
766                if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
767                    const int nativeBindingTexture = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture);
768                    const int nativeBindingSampler = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler);
769                    if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) {
770                        res[FRAGMENT].textures.append({ nativeBindingTexture + elem, texD->d->tex });
771                        res[FRAGMENT].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState });
772                    }
773                }
774                if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
775                    const int nativeBindingTexture = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture);
776                    const int nativeBindingSampler = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler);
777                    if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) {
778                        res[COMPUTE].textures.append({ nativeBindingTexture + elem, texD->d->tex });
779                        res[COMPUTE].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState });
780                    }
781                }
782            }
783        }
784            break;
785        case QRhiShaderResourceBinding::ImageLoad:
786        case QRhiShaderResourceBinding::ImageStore:
787        case QRhiShaderResourceBinding::ImageLoadStore:
788        {
789            QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
790            id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level);
791            if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
792                const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture);
793                if (nativeBinding >= 0)
794                    res[VERTEX].textures.append({ nativeBinding, t });
795            }
796            if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
797                const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture);
798                if (nativeBinding >= 0)
799                    res[FRAGMENT].textures.append({ nativeBinding, t });
800            }
801            if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
802                const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture);
803                if (nativeBinding >= 0)
804                    res[COMPUTE].textures.append({ nativeBinding, t });
805            }
806        }
807            break;
808        case QRhiShaderResourceBinding::BufferLoad:
809        case QRhiShaderResourceBinding::BufferStore:
810        case QRhiShaderResourceBinding::BufferLoadStore:
811        {
812            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
813            id<MTLBuffer> mtlbuf = bufD->d->buf[0];
814            uint offset = uint(b->u.sbuf.offset);
815            if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
816                const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer);
817                if (nativeBinding >= 0)
818                    res[VERTEX].buffers.append({ nativeBinding, mtlbuf, offset });
819            }
820            if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
821                const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer);
822                if (nativeBinding >= 0)
823                    res[FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset });
824            }
825            if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
826                const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer);
827                if (nativeBinding >= 0)
828                    res[COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset });
829            }
830        }
831            break;
832        default:
833            Q_UNREACHABLE();
834            break;
835        }
836    }
837
838    for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
839        if (cbD->recordingPass != QMetalCommandBuffer::RenderPass && (stage == VERTEX || stage == FRAGMENT))
840            continue;
841        if (cbD->recordingPass != QMetalCommandBuffer::ComputePass && stage == COMPUTE)
842            continue;
843
844        // QRhiBatchedBindings works with the native bindings and expects
845        // sorted input. The pre-sorted QRhiShaderResourceBinding list (based
846        // on the QRhi (SPIR-V) binding) is not helpful in this regard, so we
847        // have to sort here every time.
848
849        std::sort(res[stage].buffers.begin(), res[stage].buffers.end(), [](const Stage::Buffer &a, const Stage::Buffer &b) {
850            return a.nativeBinding < b.nativeBinding;
851        });
852
853        for (const Stage::Buffer &buf : qAsConst(res[stage].buffers)) {
854            res[stage].bufferBatches.feed(buf.nativeBinding, buf.mtlbuf);
855            res[stage].bufferOffsetBatches.feed(buf.nativeBinding, buf.offset);
856        }
857
858        res[stage].bufferBatches.finish();
859        res[stage].bufferOffsetBatches.finish();
860
861        for (int i = 0, ie = res[stage].bufferBatches.batches.count(); i != ie; ++i) {
862            const auto &bufferBatch(res[stage].bufferBatches.batches[i]);
863            const auto &offsetBatch(res[stage].bufferOffsetBatches.batches[i]);
864            switch (stage) {
865            case VERTEX:
866                [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
867                  offsets: offsetBatch.resources.constData()
868                  withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
869                break;
870            case FRAGMENT:
871                [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
872                  offsets: offsetBatch.resources.constData()
873                  withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
874                break;
875            case COMPUTE:
876                [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData()
877                  offsets: offsetBatch.resources.constData()
878                  withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
879                break;
880            default:
881                Q_UNREACHABLE();
882                break;
883            }
884        }
885
886        if (offsetOnlyChange)
887            continue;
888
889        std::sort(res[stage].textures.begin(), res[stage].textures.end(), [](const Stage::Texture &a, const Stage::Texture &b) {
890            return a.nativeBinding < b.nativeBinding;
891        });
892
893        std::sort(res[stage].samplers.begin(), res[stage].samplers.end(), [](const Stage::Sampler &a, const Stage::Sampler &b) {
894            return a.nativeBinding < b.nativeBinding;
895        });
896
897        for (const Stage::Texture &t : qAsConst(res[stage].textures))
898            res[stage].textureBatches.feed(t.nativeBinding, t.mtltex);
899
900        for (const Stage::Sampler &s : qAsConst(res[stage].samplers))
901            res[stage].samplerBatches.feed(s.nativeBinding, s.mtlsampler);
902
903        res[stage].textureBatches.finish();
904        res[stage].samplerBatches.finish();
905
906        for (int i = 0, ie = res[stage].textureBatches.batches.count(); i != ie; ++i) {
907            const auto &batch(res[stage].textureBatches.batches[i]);
908            switch (stage) {
909            case VERTEX:
910                [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData()
911                  withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
912                break;
913            case FRAGMENT:
914                [cbD->d->currentRenderPassEncoder setFragmentTextures: batch.resources.constData()
915                  withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
916                break;
917            case COMPUTE:
918                [cbD->d->currentComputePassEncoder setTextures: batch.resources.constData()
919                  withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
920                break;
921            default:
922                Q_UNREACHABLE();
923                break;
924            }
925        }
926        for (int i = 0, ie = res[stage].samplerBatches.batches.count(); i != ie; ++i) {
927            const auto &batch(res[stage].samplerBatches.batches[i]);
928            switch (stage) {
929            case VERTEX:
930                [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData()
931                  withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
932                break;
933            case FRAGMENT:
934                [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: batch.resources.constData()
935                  withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
936                break;
937            case COMPUTE:
938                [cbD->d->currentComputePassEncoder setSamplerStates: batch.resources.constData()
939                  withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
940                break;
941            default:
942                Q_UNREACHABLE();
943                break;
944            }
945        }
946    }
947}
948
949void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
950{
951    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
952    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
953    QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps);
954
955    if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
956        cbD->currentGraphicsPipeline = ps;
957        cbD->currentComputePipeline = nullptr;
958        cbD->currentPipelineGeneration = psD->generation;
959
960        [cbD->d->currentRenderPassEncoder setRenderPipelineState: psD->d->ps];
961        [cbD->d->currentRenderPassEncoder setDepthStencilState: psD->d->ds];
962
963        if (cbD->currentCullMode == -1 || psD->d->cullMode != uint(cbD->currentCullMode)) {
964            [cbD->d->currentRenderPassEncoder setCullMode: psD->d->cullMode];
965            cbD->currentCullMode = int(psD->d->cullMode);
966        }
967        if (cbD->currentFrontFaceWinding == -1 || psD->d->winding != uint(cbD->currentFrontFaceWinding)) {
968            [cbD->d->currentRenderPassEncoder setFrontFacingWinding: psD->d->winding];
969            cbD->currentFrontFaceWinding = int(psD->d->winding);
970        }
971        if (!qFuzzyCompare(psD->d->depthBias, cbD->currentDepthBiasValues.first)
972                || !qFuzzyCompare(psD->d->slopeScaledDepthBias, cbD->currentDepthBiasValues.second))
973        {
974            [cbD->d->currentRenderPassEncoder setDepthBias: psD->d->depthBias
975                                                            slopeScale: psD->d->slopeScaledDepthBias
976                                                            clamp: 0.0f];
977            cbD->currentDepthBiasValues = { psD->d->depthBias, psD->d->slopeScaledDepthBias };
978        }
979    }
980
981    psD->lastActiveFrameSlot = currentFrameSlot;
982}
983
984void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
985                                   int dynamicOffsetCount,
986                                   const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
987{
988    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
989    Q_ASSERT(cbD->recordingPass != QMetalCommandBuffer::NoPass);
990    QMetalGraphicsPipeline *gfxPsD = QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline);
991    QMetalComputePipeline *compPsD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline);
992
993    if (!srb) {
994        if (gfxPsD)
995            srb = gfxPsD->m_shaderResourceBindings;
996        else
997            srb = compPsD->m_shaderResourceBindings;
998    }
999
1000    QMetalShaderResourceBindings *srbD = QRHI_RES(QMetalShaderResourceBindings, srb);
1001    bool hasSlottedResourceInSrb = false;
1002    bool hasDynamicOffsetInSrb = false;
1003    bool resNeedsRebind = false;
1004
1005    // do buffer writes, figure out if we need to rebind, and mark as in-use
1006    for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
1007        const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(i).data();
1008        QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
1009        switch (b->type) {
1010        case QRhiShaderResourceBinding::UniformBuffer:
1011        {
1012            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
1013            Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
1014            executeBufferHostWritesForCurrentFrame(bufD);
1015            if (bufD->d->slotted)
1016                hasSlottedResourceInSrb = true;
1017            if (b->u.ubuf.hasDynamicOffset)
1018                hasDynamicOffsetInSrb = true;
1019            if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
1020                resNeedsRebind = true;
1021                bd.ubuf.id = bufD->m_id;
1022                bd.ubuf.generation = bufD->generation;
1023            }
1024            bufD->lastActiveFrameSlot = currentFrameSlot;
1025        }
1026            break;
1027        case QRhiShaderResourceBinding::SampledTexture:
1028        {
1029            const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
1030            if (bd.stex.count != data->count) {
1031                bd.stex.count = data->count;
1032                resNeedsRebind = true;
1033            }
1034            for (int elem = 0; elem < data->count; ++elem) {
1035                QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex);
1036                QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler);
1037                if (texD->generation != bd.stex.d[elem].texGeneration
1038                        || texD->m_id != bd.stex.d[elem].texId
1039                        || samplerD->generation != bd.stex.d[elem].samplerGeneration
1040                        || samplerD->m_id != bd.stex.d[elem].samplerId)
1041                {
1042                    resNeedsRebind = true;
1043                    bd.stex.d[elem].texId = texD->m_id;
1044                    bd.stex.d[elem].texGeneration = texD->generation;
1045                    bd.stex.d[elem].samplerId = samplerD->m_id;
1046                    bd.stex.d[elem].samplerGeneration = samplerD->generation;
1047                }
1048                texD->lastActiveFrameSlot = currentFrameSlot;
1049                samplerD->lastActiveFrameSlot = currentFrameSlot;
1050            }
1051        }
1052            break;
1053        case QRhiShaderResourceBinding::ImageLoad:
1054        case QRhiShaderResourceBinding::ImageStore:
1055        case QRhiShaderResourceBinding::ImageLoadStore:
1056        {
1057            QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
1058            if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
1059                resNeedsRebind = true;
1060                bd.simage.id = texD->m_id;
1061                bd.simage.generation = texD->generation;
1062            }
1063            texD->lastActiveFrameSlot = currentFrameSlot;
1064        }
1065            break;
1066        case QRhiShaderResourceBinding::BufferLoad:
1067        case QRhiShaderResourceBinding::BufferStore:
1068        case QRhiShaderResourceBinding::BufferLoadStore:
1069        {
1070            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
1071            Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
1072            executeBufferHostWritesForCurrentFrame(bufD);
1073            if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
1074                resNeedsRebind = true;
1075                bd.sbuf.id = bufD->m_id;
1076                bd.sbuf.generation = bufD->generation;
1077            }
1078            bufD->lastActiveFrameSlot = currentFrameSlot;
1079        }
1080            break;
1081        default:
1082            Q_UNREACHABLE();
1083            break;
1084        }
1085    }
1086
1087    // make sure the resources for the correct slot get bound
1088    const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
1089    if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
1090        resNeedsRebind = true;
1091
1092    const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
1093    const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
1094
1095    // dynamic uniform buffer offsets always trigger a rebind
1096    if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) {
1097        const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr };
1098        if (gfxPsD) {
1099            cbD->currentGraphicsSrb = srb;
1100            cbD->currentComputeSrb = nullptr;
1101            resBindMaps[0] = &gfxPsD->d->vs.nativeResourceBindingMap;
1102            resBindMaps[1] = &gfxPsD->d->fs.nativeResourceBindingMap;
1103        } else {
1104            cbD->currentGraphicsSrb = nullptr;
1105            cbD->currentComputeSrb = srb;
1106            resBindMaps[2] = &compPsD->d->cs.nativeResourceBindingMap;
1107        }
1108        cbD->currentSrbGeneration = srbD->generation;
1109        cbD->currentResSlot = resSlot;
1110
1111        const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt;
1112        enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange, resBindMaps);
1113    }
1114}
1115
1116void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
1117                               int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
1118                               QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
1119{
1120    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1121    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1122
1123    QRhiBatchedBindings<id<MTLBuffer> > buffers;
1124    QRhiBatchedBindings<NSUInteger> offsets;
1125    for (int i = 0; i < bindingCount; ++i) {
1126        QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, bindings[i].first);
1127        executeBufferHostWritesForCurrentFrame(bufD);
1128        bufD->lastActiveFrameSlot = currentFrameSlot;
1129        id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
1130        buffers.feed(startBinding + i, mtlbuf);
1131        offsets.feed(startBinding + i, bindings[i].second);
1132    }
1133    buffers.finish();
1134    offsets.finish();
1135
1136    // same binding space for vertex and constant buffers - work it around
1137    QRhiShaderResourceBindings *srb = cbD->currentGraphicsSrb;
1138    // There's nothing guaranteeing setShaderResources() was called before
1139    // setVertexInput()... but whatever srb will get bound will have to be
1140    // layout-compatible anyways so maxBinding is the same.
1141    if (!srb)
1142        srb = cbD->currentGraphicsPipeline->shaderResourceBindings();
1143    const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, srb)->maxBinding + 1;
1144
1145    if (firstVertexBinding != cbD->d->currentFirstVertexBinding
1146            || buffers != cbD->d->currentVertexInputsBuffers
1147            || offsets != cbD->d->currentVertexInputOffsets)
1148    {
1149        cbD->d->currentFirstVertexBinding = firstVertexBinding;
1150        cbD->d->currentVertexInputsBuffers = buffers;
1151        cbD->d->currentVertexInputOffsets = offsets;
1152
1153        for (int i = 0, ie = buffers.batches.count(); i != ie; ++i) {
1154            const auto &bufferBatch(buffers.batches[i]);
1155            const auto &offsetBatch(offsets.batches[i]);
1156            [cbD->d->currentRenderPassEncoder setVertexBuffers:
1157                bufferBatch.resources.constData()
1158              offsets: offsetBatch.resources.constData()
1159              withRange: NSMakeRange(uint(firstVertexBinding) + bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1160        }
1161    }
1162
1163    if (indexBuf) {
1164        QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
1165        executeBufferHostWritesForCurrentFrame(ibufD);
1166        ibufD->lastActiveFrameSlot = currentFrameSlot;
1167        cbD->currentIndexBuffer = indexBuf;
1168        cbD->currentIndexOffset = indexOffset;
1169        cbD->currentIndexFormat = indexFormat;
1170    } else {
1171        cbD->currentIndexBuffer = nullptr;
1172    }
1173}
1174
1175void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
1176{
1177    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1178    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1179    const QSize outputSize = cbD->currentTarget->pixelSize();
1180
1181    // x,y is top-left in MTLViewportRect but bottom-left in QRhiViewport
1182    float x, y, w, h;
1183    if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h))
1184        return;
1185
1186    MTLViewport vp;
1187    vp.originX = double(x);
1188    vp.originY = double(y);
1189    vp.width = double(w);
1190    vp.height = double(h);
1191    vp.znear = double(viewport.minDepth());
1192    vp.zfar = double(viewport.maxDepth());
1193
1194    [cbD->d->currentRenderPassEncoder setViewport: vp];
1195
1196    if (!QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
1197        MTLScissorRect s;
1198        s.x = NSUInteger(x);
1199        s.y = NSUInteger(y);
1200        s.width = NSUInteger(w);
1201        s.height = NSUInteger(h);
1202        [cbD->d->currentRenderPassEncoder setScissorRect: s];
1203    }
1204}
1205
1206void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
1207{
1208    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1209    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1210    Q_ASSERT(QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
1211    const QSize outputSize = cbD->currentTarget->pixelSize();
1212
1213    // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
1214    int x, y, w, h;
1215    if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h))
1216        return;
1217
1218    MTLScissorRect s;
1219    s.x = NSUInteger(x);
1220    s.y = NSUInteger(y);
1221    s.width = NSUInteger(w);
1222    s.height = NSUInteger(h);
1223
1224    [cbD->d->currentRenderPassEncoder setScissorRect: s];
1225}
1226
1227void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
1228{
1229    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1230    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1231
1232    [cbD->d->currentRenderPassEncoder setBlendColorRed: float(c.redF())
1233      green: float(c.greenF()) blue: float(c.blueF()) alpha: float(c.alphaF())];
1234}
1235
1236void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
1237{
1238    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1239    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1240
1241    [cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue];
1242}
1243
1244void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
1245                     quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
1246{
1247    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1248    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1249
1250    [cbD->d->currentRenderPassEncoder drawPrimitives:
1251        QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
1252      vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
1253}
1254
1255void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
1256                            quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
1257{
1258    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1259    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1260
1261    if (!cbD->currentIndexBuffer)
1262        return;
1263
1264    const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
1265    Q_ASSERT(indexOffset == aligned<quint32>(indexOffset, 4));
1266
1267    QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, cbD->currentIndexBuffer);
1268    id<MTLBuffer> mtlbuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0];
1269
1270    [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
1271      indexCount: indexCount
1272      indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
1273      indexBuffer: mtlbuf
1274      indexBufferOffset: indexOffset
1275      instanceCount: instanceCount
1276      baseVertex: vertexOffset
1277      baseInstance: firstInstance];
1278}
1279
1280void QRhiMetal::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
1281{
1282    if (!debugMarkers)
1283        return;
1284
1285    NSString *str = [NSString stringWithUTF8String: name.constData()];
1286    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1287    if (cbD->recordingPass != QMetalCommandBuffer::NoPass) {
1288        [cbD->d->currentRenderPassEncoder pushDebugGroup: str];
1289    } else {
1290        if (@available(macOS 10.13, iOS 11.0, *))
1291            [cbD->d->cb pushDebugGroup: str];
1292    }
1293}
1294
1295void QRhiMetal::debugMarkEnd(QRhiCommandBuffer *cb)
1296{
1297    if (!debugMarkers)
1298        return;
1299
1300    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1301    if (cbD->recordingPass != QMetalCommandBuffer::NoPass) {
1302        [cbD->d->currentRenderPassEncoder popDebugGroup];
1303    } else {
1304        if (@available(macOS 10.13, iOS 11.0, *))
1305            [cbD->d->cb popDebugGroup];
1306    }
1307}
1308
1309void QRhiMetal::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
1310{
1311    if (!debugMarkers)
1312        return;
1313
1314    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1315    if (cbD->recordingPass != QMetalCommandBuffer::NoPass)
1316        [cbD->d->currentRenderPassEncoder insertDebugSignpost: [NSString stringWithUTF8String: msg.constData()]];
1317}
1318
1319const QRhiNativeHandles *QRhiMetal::nativeHandles(QRhiCommandBuffer *cb)
1320{
1321    return QRHI_RES(QMetalCommandBuffer, cb)->nativeHandles();
1322}
1323
1324void QRhiMetal::beginExternal(QRhiCommandBuffer *cb)
1325{
1326    Q_UNUSED(cb);
1327}
1328
1329void QRhiMetal::endExternal(QRhiCommandBuffer *cb)
1330{
1331    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1332    cbD->resetPerPassCachedState();
1333}
1334
1335QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
1336{
1337    Q_UNUSED(flags);
1338
1339    QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
1340
1341    // This is a bit messed up since for this swapchain we want to wait for the
1342    // commands+present to complete, while for others just for the commands
1343    // (for this same frame slot) but not sure how to do that in a sane way so
1344    // wait for full cb completion for now.
1345    for (QMetalSwapChain *sc : qAsConst(swapchains)) {
1346        dispatch_semaphore_t sem = sc->d->sem[swapChainD->currentFrameSlot];
1347        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
1348        if (sc != swapChainD)
1349            dispatch_semaphore_signal(sem);
1350    }
1351
1352    currentSwapChain = swapChainD;
1353    currentFrameSlot = swapChainD->currentFrameSlot;
1354    if (swapChainD->ds)
1355        swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
1356
1357    if (@available(macOS 10.13, iOS 11.0, *))
1358        [d->captureScope beginScope];
1359
1360    // Do not let the command buffer mess with the refcount of objects. We do
1361    // have a proper render loop and will manage lifetimes similarly to other
1362    // backends (Vulkan).
1363    swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
1364
1365    QMetalRenderTargetData::ColorAtt colorAtt;
1366    if (swapChainD->samples > 1) {
1367        colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
1368        colorAtt.needsDrawableForResolveTex = true;
1369    } else {
1370        colorAtt.needsDrawableForTex = true;
1371    }
1372
1373    swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
1374    swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
1375    swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
1376    swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
1377
1378    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1379    QRHI_PROF_F(beginSwapChainFrame(swapChain));
1380
1381    executeDeferredReleases();
1382    swapChainD->cbWrapper.resetState();
1383    finishActiveReadbacks();
1384
1385    return QRhi::FrameOpSuccess;
1386}
1387
1388QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
1389{
1390    QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
1391    Q_ASSERT(currentSwapChain == swapChainD);
1392
1393    const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
1394    if (needsPresent)
1395        [swapChainD->cbWrapper.d->cb presentDrawable: swapChainD->d->curDrawable];
1396
1397    // Must not hold on to the drawable, regardless of needsPresent.
1398    // (internally it is autoreleased or something, it seems)
1399    swapChainD->d->curDrawable = nil;
1400
1401    __block int thisFrameSlot = currentFrameSlot;
1402    [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer>) {
1403        dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
1404    }];
1405
1406    [swapChainD->cbWrapper.d->cb commit];
1407
1408    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1409    QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
1410
1411    if (@available(macOS 10.13, iOS 11.0, *))
1412        [d->captureScope endScope];
1413
1414    if (needsPresent)
1415        swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
1416
1417    swapChainD->frameCount += 1;
1418    currentSwapChain = nullptr;
1419    return QRhi::FrameOpSuccess;
1420}
1421
1422QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags)
1423{
1424    Q_UNUSED(flags);
1425
1426    currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
1427    if (swapchains.count() > 1) {
1428        for (QMetalSwapChain *sc : qAsConst(swapchains)) {
1429            // wait+signal is the general pattern to ensure the commands for a
1430            // given frame slot have completed (if sem is 1, we go 0 then 1; if
1431            // sem is 0 we go -1, block, completion increments to 0, then us to 1)
1432            dispatch_semaphore_t sem = sc->d->sem[currentFrameSlot];
1433            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
1434            dispatch_semaphore_signal(sem);
1435        }
1436    }
1437
1438    d->ofr.active = true;
1439    *cb = &d->ofr.cbWrapper;
1440    d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
1441
1442    executeDeferredReleases();
1443    d->ofr.cbWrapper.resetState();
1444    finishActiveReadbacks();
1445
1446    return QRhi::FrameOpSuccess;
1447}
1448
1449QRhi::FrameOpResult QRhiMetal::endOffscreenFrame(QRhi::EndFrameFlags flags)
1450{
1451    Q_UNUSED(flags);
1452    Q_ASSERT(d->ofr.active);
1453    d->ofr.active = false;
1454
1455    [d->ofr.cbWrapper.d->cb commit];
1456
1457    // offscreen frames wait for completion, unlike swapchain ones
1458    [d->ofr.cbWrapper.d->cb waitUntilCompleted];
1459
1460    finishActiveReadbacks(true);
1461
1462    return QRhi::FrameOpSuccess;
1463}
1464
1465QRhi::FrameOpResult QRhiMetal::finish()
1466{
1467    id<MTLCommandBuffer> cb = nil;
1468    QMetalSwapChain *swapChainD = nullptr;
1469    if (inFrame) {
1470        if (d->ofr.active) {
1471            Q_ASSERT(!currentSwapChain);
1472            Q_ASSERT(d->ofr.cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
1473            cb = d->ofr.cbWrapper.d->cb;
1474        } else {
1475            Q_ASSERT(currentSwapChain);
1476            swapChainD = currentSwapChain;
1477            Q_ASSERT(swapChainD->cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
1478            cb = swapChainD->cbWrapper.d->cb;
1479        }
1480    }
1481
1482    for (QMetalSwapChain *sc : qAsConst(swapchains)) {
1483        for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
1484            if (currentSwapChain && sc == currentSwapChain && i == currentFrameSlot) {
1485                // no wait as this is the thing we're going to be commit below and
1486                // beginFrame decremented sem already and going to be signaled by endFrame
1487                continue;
1488            }
1489            dispatch_semaphore_t sem = sc->d->sem[i];
1490            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
1491            dispatch_semaphore_signal(sem);
1492        }
1493    }
1494
1495    if (cb) {
1496        [cb commit];
1497        [cb waitUntilCompleted];
1498    }
1499
1500    if (inFrame) {
1501        if (d->ofr.active)
1502            d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
1503        else
1504            swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
1505    }
1506
1507    executeDeferredReleases(true);
1508
1509    finishActiveReadbacks(true);
1510
1511    return QRhi::FrameOpSuccess;
1512}
1513
1514MTLRenderPassDescriptor *QRhiMetalData::createDefaultRenderPass(bool hasDepthStencil,
1515                                                                const QColor &colorClearValue,
1516                                                                const QRhiDepthStencilClearValue &depthStencilClearValue,
1517                                                                int colorAttCount)
1518{
1519    MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];
1520    MTLClearColor c = MTLClearColorMake(colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
1521                                        colorClearValue.alphaF());
1522
1523    for (uint i = 0; i < uint(colorAttCount); ++i) {
1524        rp.colorAttachments[i].loadAction = MTLLoadActionClear;
1525        rp.colorAttachments[i].storeAction = MTLStoreActionStore;
1526        rp.colorAttachments[i].clearColor = c;
1527    }
1528
1529    if (hasDepthStencil) {
1530        rp.depthAttachment.loadAction = MTLLoadActionClear;
1531        rp.depthAttachment.storeAction = MTLStoreActionDontCare;
1532        rp.stencilAttachment.loadAction = MTLLoadActionClear;
1533        rp.stencilAttachment.storeAction = MTLStoreActionDontCare;
1534        rp.depthAttachment.clearDepth = double(depthStencilClearValue.depthClearValue());
1535        rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
1536    }
1537
1538    return rp;
1539}
1540
1541qsizetype QRhiMetal::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
1542{
1543    qsizetype size = 0;
1544    const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
1545                subresDesc.data().size() : subresDesc.image().sizeInBytes();
1546    if (imageSizeBytes > 0)
1547        size += aligned<qsizetype>(imageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
1548    return size;
1549}
1550
1551void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
1552                                    int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
1553                                    qsizetype *curOfs)
1554{
1555    const QPoint dp = subresDesc.destinationTopLeft();
1556    const QByteArray rawData = subresDesc.data();
1557    QImage img = subresDesc.image();
1558    id<MTLBlitCommandEncoder> blitEnc = (id<MTLBlitCommandEncoder>) blitEncPtr;
1559
1560    if (!img.isNull()) {
1561        const qsizetype fullImageSizeBytes = img.sizeInBytes();
1562        int w = img.width();
1563        int h = img.height();
1564        int bpl = img.bytesPerLine();
1565        int srcOffset = 0;
1566
1567        if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
1568            const int sx = subresDesc.sourceTopLeft().x();
1569            const int sy = subresDesc.sourceTopLeft().y();
1570            if (!subresDesc.sourceSize().isEmpty()) {
1571                w = subresDesc.sourceSize().width();
1572                h = subresDesc.sourceSize().height();
1573            }
1574            if (img.depth() == 32) {
1575                memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(fullImageSizeBytes));
1576                srcOffset = sy * bpl + sx * 4;
1577                // bpl remains set to the original image's row stride
1578            } else {
1579                img = img.copy(sx, sy, w, h);
1580                bpl = img.bytesPerLine();
1581                Q_ASSERT(img.sizeInBytes() <= fullImageSizeBytes);
1582                memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(img.sizeInBytes()));
1583            }
1584        } else {
1585            memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(fullImageSizeBytes));
1586        }
1587
1588        [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
1589                                 sourceOffset: NSUInteger(*curOfs + srcOffset)
1590                                 sourceBytesPerRow: NSUInteger(bpl)
1591                                 sourceBytesPerImage: 0
1592                                 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
1593          toTexture: texD->d->tex
1594          destinationSlice: NSUInteger(layer)
1595          destinationLevel: NSUInteger(level)
1596          destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), 0)
1597          options: MTLBlitOptionNone];
1598
1599        *curOfs += aligned<qsizetype>(fullImageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
1600    } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
1601        const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
1602        const int subresw = subresSize.width();
1603        const int subresh = subresSize.height();
1604        int w, h;
1605        if (subresDesc.sourceSize().isEmpty()) {
1606            w = subresw;
1607            h = subresh;
1608        } else {
1609            w = subresDesc.sourceSize().width();
1610            h = subresDesc.sourceSize().height();
1611        }
1612
1613        quint32 bpl = 0;
1614        QSize blockDim;
1615        compressedFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, &blockDim);
1616
1617        const int dx = aligned(dp.x(), blockDim.width());
1618        const int dy = aligned(dp.y(), blockDim.height());
1619        if (dx + w != subresw)
1620            w = aligned(w, blockDim.width());
1621        if (dy + h != subresh)
1622            h = aligned(h, blockDim.height());
1623
1624        memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), size_t(rawData.size()));
1625
1626        [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
1627                                 sourceOffset: NSUInteger(*curOfs)
1628                                 sourceBytesPerRow: bpl
1629                                 sourceBytesPerImage: 0
1630                                 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
1631          toTexture: texD->d->tex
1632          destinationSlice: NSUInteger(layer)
1633          destinationLevel: NSUInteger(level)
1634          destinationOrigin: MTLOriginMake(NSUInteger(dx), NSUInteger(dy), 0)
1635          options: MTLBlitOptionNone];
1636
1637        *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
1638    } else if (!rawData.isEmpty()) {
1639        const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
1640        const int subresw = subresSize.width();
1641        const int subresh = subresSize.height();
1642        int w, h;
1643        if (subresDesc.sourceSize().isEmpty()) {
1644            w = subresw;
1645            h = subresh;
1646        } else {
1647            w = subresDesc.sourceSize().width();
1648            h = subresDesc.sourceSize().height();
1649        }
1650
1651        quint32 bpl = 0;
1652        textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr);
1653        memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), size_t(rawData.size()));
1654
1655        [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
1656                                 sourceOffset: NSUInteger(*curOfs)
1657                                 sourceBytesPerRow: bpl
1658                                 sourceBytesPerImage: 0
1659                                 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
1660          toTexture: texD->d->tex
1661          destinationSlice: NSUInteger(layer)
1662          destinationLevel: NSUInteger(level)
1663          destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), 0)
1664          options: MTLBlitOptionNone];
1665
1666        *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
1667    } else {
1668        qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
1669    }
1670}
1671
1672void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1673{
1674    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1675    QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
1676    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1677
1678    for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) {
1679        if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
1680            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
1681            Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
1682            for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
1683                bufD->d->pendingUpdates[i].append(u);
1684        } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
1685            // Due to the Metal API the handling of static and dynamic buffers is
1686            // basically the same. So go through the same pendingUpdates machinery.
1687            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
1688            Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
1689            Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
1690            for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i)
1691                bufD->d->pendingUpdates[i].append(
1692                            QRhiResourceUpdateBatchPrivate::BufferOp::dynamicUpdate(u.buf, u.offset, u.data.size(), u.data.constData()));
1693        } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
1694            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
1695            executeBufferHostWritesForCurrentFrame(bufD);
1696            const int idx = bufD->d->slotted ? currentFrameSlot : 0;
1697            char *p = reinterpret_cast<char *>([bufD->d->buf[idx] contents]);
1698            if (p) {
1699                u.result->data.resize(u.readSize);
1700                memcpy(u.result->data.data(), p + u.offset, size_t(u.readSize));
1701            }
1702            if (u.result->completed)
1703                u.result->completed();
1704        }
1705    }
1706
1707    id<MTLBlitCommandEncoder> blitEnc = nil;
1708    auto ensureBlit = [&blitEnc, cbD, this] {
1709        if (!blitEnc) {
1710            blitEnc = [cbD->d->cb blitCommandEncoder];
1711            if (debugMarkers)
1712                [blitEnc pushDebugGroup: @"Texture upload/copy"];
1713        }
1714    };
1715
1716    for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
1717        if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
1718            QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
1719            qsizetype stagingSize = 0;
1720            for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
1721                for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
1722                    for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level]))
1723                        stagingSize += subresUploadByteSize(subresDesc);
1724                }
1725            }
1726
1727            ensureBlit();
1728            Q_ASSERT(!utexD->d->stagingBuf[currentFrameSlot]);
1729            utexD->d->stagingBuf[currentFrameSlot] = [d->dev newBufferWithLength: NSUInteger(stagingSize)
1730                        options: MTLResourceStorageModeShared];
1731            QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, quint32(stagingSize)));
1732
1733            void *mp = [utexD->d->stagingBuf[currentFrameSlot] contents];
1734            qsizetype curOfs = 0;
1735            for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
1736                for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
1737                    for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level]))
1738                        enqueueSubresUpload(utexD, mp, blitEnc, layer, level, subresDesc, &curOfs);
1739                }
1740            }
1741
1742            utexD->lastActiveFrameSlot = currentFrameSlot;
1743
1744            QRhiMetalData::DeferredReleaseEntry e;
1745            e.type = QRhiMetalData::DeferredReleaseEntry::StagingBuffer;
1746            e.lastActiveFrameSlot = currentFrameSlot;
1747            e.stagingBuffer.buffer = utexD->d->stagingBuf[currentFrameSlot];
1748            utexD->d->stagingBuf[currentFrameSlot] = nil;
1749            d->releaseQueue.append(e);
1750            QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
1751        } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
1752            Q_ASSERT(u.src && u.dst);
1753            QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.src);
1754            QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.dst);
1755            const QPoint dp = u.desc.destinationTopLeft();
1756            const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
1757            const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
1758            const QPoint sp = u.desc.sourceTopLeft();
1759
1760            ensureBlit();
1761            [blitEnc copyFromTexture: srcD->d->tex
1762                                      sourceSlice: NSUInteger(u.desc.sourceLayer())
1763                                      sourceLevel: NSUInteger(u.desc.sourceLevel())
1764                                      sourceOrigin: MTLOriginMake(NSUInteger(sp.x()), NSUInteger(sp.y()), 0)
1765                                      sourceSize: MTLSizeMake(NSUInteger(copySize.width()), NSUInteger(copySize.height()), 1)
1766                                      toTexture: dstD->d->tex
1767                                      destinationSlice: NSUInteger(u.desc.destinationLayer())
1768                                      destinationLevel: NSUInteger(u.desc.destinationLevel())
1769                                      destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), 0)];
1770
1771            srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
1772        } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
1773            QRhiMetalData::TextureReadback readback;
1774            readback.activeFrameSlot = currentFrameSlot;
1775            readback.desc = u.rb;
1776            readback.result = u.result;
1777
1778            QMetalTexture *texD = QRHI_RES(QMetalTexture, u.rb.texture());
1779            QMetalSwapChain *swapChainD = nullptr;
1780            id<MTLTexture> src;
1781            QSize srcSize;
1782            if (texD) {
1783                if (texD->samples > 1) {
1784                    qWarning("Multisample texture cannot be read back");
1785                    continue;
1786                }
1787                readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
1788                readback.format = texD->m_format;
1789                src = texD->d->tex;
1790                srcSize = readback.pixelSize;
1791                texD->lastActiveFrameSlot = currentFrameSlot;
1792            } else {
1793                Q_ASSERT(currentSwapChain);
1794                swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
1795                readback.pixelSize = swapChainD->pixelSize;
1796                readback.format = swapChainD->d->rhiColorFormat;
1797                // Multisample swapchains need nothing special since resolving
1798                // happens when ending a renderpass.
1799                const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
1800                src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
1801                srcSize = swapChainD->rtWrapper.d->pixelSize;
1802            }
1803
1804            quint32 bpl = 0;
1805            textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize);
1806            readback.buf = [d->dev newBufferWithLength: readback.bufSize options: MTLResourceStorageModeShared];
1807
1808            QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(readback.buf)),
1809                                          texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
1810                                          readback.bufSize));
1811
1812            ensureBlit();
1813            [blitEnc copyFromTexture: src
1814                                      sourceSlice: NSUInteger(u.rb.layer())
1815                                      sourceLevel: NSUInteger(u.rb.level())
1816                                      sourceOrigin: MTLOriginMake(0, 0, 0)
1817                                      sourceSize: MTLSizeMake(NSUInteger(srcSize.width()), NSUInteger(srcSize.height()), 1)
1818                                      toBuffer: readback.buf
1819                                      destinationOffset: 0
1820                                      destinationBytesPerRow: bpl
1821                                      destinationBytesPerImage: 0
1822                                      options: MTLBlitOptionNone];
1823
1824            d->activeTextureReadbacks.append(readback);
1825        } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
1826            QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
1827            ensureBlit();
1828            [blitEnc generateMipmapsForTexture: utexD->d->tex];
1829            utexD->lastActiveFrameSlot = currentFrameSlot;
1830        }
1831    }
1832
1833    if (blitEnc) {
1834        if (debugMarkers)
1835            [blitEnc popDebugGroup];
1836        [blitEnc endEncoding];
1837    }
1838
1839    ud->free();
1840}
1841
1842// this handles all types of buffers, not just Dynamic
1843void QRhiMetal::executeBufferHostWritesForSlot(QMetalBuffer *bufD, int slot)
1844{
1845    if (bufD->d->pendingUpdates[slot].isEmpty())
1846        return;
1847
1848    void *p = [bufD->d->buf[slot] contents];
1849    int changeBegin = -1;
1850    int changeEnd = -1;
1851    for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(bufD->d->pendingUpdates[slot])) {
1852        Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf));
1853        memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size()));
1854        if (changeBegin == -1 || u.offset < changeBegin)
1855            changeBegin = u.offset;
1856        if (changeEnd == -1 || u.offset + u.data.size() > changeEnd)
1857            changeEnd = u.offset + u.data.size();
1858    }
1859#ifdef Q_OS_MACOS
1860    if (changeBegin >= 0 && bufD->d->managed)
1861        [bufD->d->buf[slot] didModifyRange: NSMakeRange(NSUInteger(changeBegin), NSUInteger(changeEnd - changeBegin))];
1862#endif
1863
1864    bufD->d->pendingUpdates[slot].clear();
1865}
1866
1867void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD)
1868{
1869    executeBufferHostWritesForSlot(bufD, bufD->d->slotted ? currentFrameSlot : 0);
1870}
1871
1872void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1873{
1874    Q_ASSERT(QRHI_RES(QMetalCommandBuffer, cb)->recordingPass == QMetalCommandBuffer::NoPass);
1875
1876    enqueueResourceUpdates(cb, resourceUpdates);
1877}
1878
1879void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
1880                          QRhiRenderTarget *rt,
1881                          const QColor &colorClearValue,
1882                          const QRhiDepthStencilClearValue &depthStencilClearValue,
1883                          QRhiResourceUpdateBatch *resourceUpdates)
1884{
1885    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1886    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass);
1887
1888    if (resourceUpdates)
1889        enqueueResourceUpdates(cb, resourceUpdates);
1890
1891    QMetalRenderTargetData *rtD = nullptr;
1892    switch (rt->resourceType()) {
1893    case QRhiResource::RenderTarget:
1894        rtD = QRHI_RES(QMetalReferenceRenderTarget, rt)->d;
1895        cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
1896        if (rtD->colorAttCount) {
1897            QMetalRenderTargetData::ColorAtt &color0(rtD->fb.colorAtt[0]);
1898            if (color0.needsDrawableForTex || color0.needsDrawableForResolveTex) {
1899                Q_ASSERT(currentSwapChain);
1900                QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
1901                if (!swapChainD->d->curDrawable) {
1902#ifdef TARGET_IPHONE_SIMULATOR
1903                    if (@available(ios 13.0, *))
1904#endif
1905                        swapChainD->d->curDrawable = [swapChainD->d->layer nextDrawable];
1906                }
1907                if (!swapChainD->d->curDrawable) {
1908                    qWarning("No drawable");
1909                    return;
1910                }
1911                id<MTLTexture> scTex = swapChainD->d->curDrawable.texture;
1912                if (color0.needsDrawableForTex) {
1913                    color0.tex = scTex;
1914                    color0.needsDrawableForTex = false;
1915                } else {
1916                    color0.resolveTex = scTex;
1917                    color0.needsDrawableForResolveTex = false;
1918                }
1919            }
1920        }
1921        break;
1922    case QRhiResource::TextureRenderTarget:
1923    {
1924        QMetalTextureRenderTarget *rtTex = QRHI_RES(QMetalTextureRenderTarget, rt);
1925        rtD = rtTex->d;
1926        cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
1927        if (rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents)) {
1928            for (uint i = 0; i < uint(rtD->colorAttCount); ++i)
1929                cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
1930        }
1931        if (rtD->dsAttCount && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents)) {
1932            cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
1933            cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
1934        }
1935        for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
1936             it != itEnd; ++it)
1937        {
1938            if (it->texture())
1939                QRHI_RES(QMetalTexture, it->texture())->lastActiveFrameSlot = currentFrameSlot;
1940            else if (it->renderBuffer())
1941                QRHI_RES(QMetalRenderBuffer, it->renderBuffer())->lastActiveFrameSlot = currentFrameSlot;
1942            if (it->resolveTexture())
1943                QRHI_RES(QMetalTexture, it->resolveTexture())->lastActiveFrameSlot = currentFrameSlot;
1944        }
1945        if (rtTex->m_desc.depthStencilBuffer())
1946            QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
1947        if (rtTex->m_desc.depthTexture())
1948            QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture())->lastActiveFrameSlot = currentFrameSlot;
1949    }
1950        break;
1951    default:
1952        Q_UNREACHABLE();
1953        break;
1954    }
1955
1956    for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
1957        cbD->d->currentPassRpDesc.colorAttachments[i].texture = rtD->fb.colorAtt[i].tex;
1958        cbD->d->currentPassRpDesc.colorAttachments[i].slice = NSUInteger(rtD->fb.colorAtt[i].layer);
1959        cbD->d->currentPassRpDesc.colorAttachments[i].level = NSUInteger(rtD->fb.colorAtt[i].level);
1960        if (rtD->fb.colorAtt[i].resolveTex) {
1961            cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
1962            cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex;
1963            cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = NSUInteger(rtD->fb.colorAtt[i].resolveLayer);
1964            cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = NSUInteger(rtD->fb.colorAtt[i].resolveLevel);
1965        }
1966    }
1967
1968    if (rtD->dsAttCount) {
1969        Q_ASSERT(rtD->fb.dsTex);
1970        cbD->d->currentPassRpDesc.depthAttachment.texture = rtD->fb.dsTex;
1971        cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil;
1972        if (rtD->fb.depthNeedsStore) // Depth/Stencil is set to DontCare by default, override if  needed
1973            cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore;
1974    }
1975
1976    cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
1977
1978    cbD->resetPerPassState();
1979
1980    cbD->recordingPass = QMetalCommandBuffer::RenderPass;
1981    cbD->currentTarget = rt;
1982}
1983
1984void QRhiMetal::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1985{
1986    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1987    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1988
1989    [cbD->d->currentRenderPassEncoder endEncoding];
1990
1991    cbD->recordingPass = QMetalCommandBuffer::NoPass;
1992    cbD->currentTarget = nullptr;
1993
1994    if (resourceUpdates)
1995        enqueueResourceUpdates(cb, resourceUpdates);
1996}
1997
1998void QRhiMetal::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1999{
2000    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2001    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass);
2002
2003    if (resourceUpdates)
2004        enqueueResourceUpdates(cb, resourceUpdates);
2005
2006    cbD->d->currentComputePassEncoder = [cbD->d->cb computeCommandEncoder];
2007    cbD->resetPerPassState();
2008    cbD->recordingPass = QMetalCommandBuffer::ComputePass;
2009}
2010
2011void QRhiMetal::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
2012{
2013    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2014    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
2015
2016    [cbD->d->currentComputePassEncoder endEncoding];
2017    cbD->recordingPass = QMetalCommandBuffer::NoPass;
2018
2019    if (resourceUpdates)
2020        enqueueResourceUpdates(cb, resourceUpdates);
2021}
2022
2023void QRhiMetal::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
2024{
2025    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2026    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
2027    QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, ps);
2028
2029    if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
2030        cbD->currentGraphicsPipeline = nullptr;
2031        cbD->currentComputePipeline = ps;
2032        cbD->currentPipelineGeneration = psD->generation;
2033
2034        [cbD->d->currentComputePassEncoder setComputePipelineState: psD->d->ps];
2035    }
2036
2037    psD->lastActiveFrameSlot = currentFrameSlot;
2038}
2039
2040void QRhiMetal::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
2041{
2042    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2043    Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
2044    QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline);
2045
2046    [cbD->d->currentComputePassEncoder dispatchThreadgroups: MTLSizeMake(NSUInteger(x), NSUInteger(y), NSUInteger(z))
2047      threadsPerThreadgroup: psD->d->localSize];
2048}
2049
2050static void qrhimtl_releaseBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
2051{
2052    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
2053        [e.buffer.buffers[i] release];
2054}
2055
2056static void qrhimtl_releaseRenderBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
2057{
2058    [e.renderbuffer.texture release];
2059}
2060
2061static void qrhimtl_releaseTexture(const QRhiMetalData::DeferredReleaseEntry &e)
2062{
2063    [e.texture.texture release];
2064    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
2065        [e.texture.stagingBuffers[i] release];
2066    for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
2067        [e.texture.views[i] release];
2068}
2069
2070static void qrhimtl_releaseSampler(const QRhiMetalData::DeferredReleaseEntry &e)
2071{
2072    [e.sampler.samplerState release];
2073}
2074
2075void QRhiMetal::executeDeferredReleases(bool forced)
2076{
2077    for (int i = d->releaseQueue.count() - 1; i >= 0; --i) {
2078        const QRhiMetalData::DeferredReleaseEntry &e(d->releaseQueue[i]);
2079        if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
2080            switch (e.type) {
2081            case QRhiMetalData::DeferredReleaseEntry::Buffer:
2082                qrhimtl_releaseBuffer(e);
2083                break;
2084            case QRhiMetalData::DeferredReleaseEntry::RenderBuffer:
2085                qrhimtl_releaseRenderBuffer(e);
2086                break;
2087            case QRhiMetalData::DeferredReleaseEntry::Texture:
2088                qrhimtl_releaseTexture(e);
2089                break;
2090            case QRhiMetalData::DeferredReleaseEntry::Sampler:
2091                qrhimtl_releaseSampler(e);
2092                break;
2093            case QRhiMetalData::DeferredReleaseEntry::StagingBuffer:
2094                [e.stagingBuffer.buffer release];
2095                break;
2096            default:
2097                break;
2098            }
2099            d->releaseQueue.removeAt(i);
2100        }
2101    }
2102}
2103
2104void QRhiMetal::finishActiveReadbacks(bool forced)
2105{
2106    QVarLengthArray<std::function<void()>, 4> completedCallbacks;
2107    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
2108
2109    for (int i = d->activeTextureReadbacks.count() - 1; i >= 0; --i) {
2110        const QRhiMetalData::TextureReadback &readback(d->activeTextureReadbacks[i]);
2111        if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
2112            readback.result->format = readback.format;
2113            readback.result->pixelSize = readback.pixelSize;
2114            readback.result->data.resize(int(readback.bufSize));
2115            void *p = [readback.buf contents];
2116            memcpy(readback.result->data.data(), p, readback.bufSize);
2117            [readback.buf release];
2118
2119            QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.buf))));
2120
2121            if (readback.result->completed)
2122                completedCallbacks.append(readback.result->completed);
2123
2124            d->activeTextureReadbacks.removeAt(i);
2125        }
2126    }
2127
2128    for (auto f : completedCallbacks)
2129        f();
2130}
2131
2132QMetalBuffer::QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
2133    : QRhiBuffer(rhi, type, usage, size),
2134      d(new QMetalBufferData)
2135{
2136    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
2137        d->buf[i] = nil;
2138}
2139
2140QMetalBuffer::~QMetalBuffer()
2141{
2142    release();
2143    delete d;
2144}
2145
2146void QMetalBuffer::release()
2147{
2148    if (!d->buf[0])
2149        return;
2150
2151    QRhiMetalData::DeferredReleaseEntry e;
2152    e.type = QRhiMetalData::DeferredReleaseEntry::Buffer;
2153    e.lastActiveFrameSlot = lastActiveFrameSlot;
2154
2155    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
2156        e.buffer.buffers[i] = d->buf[i];
2157        d->buf[i] = nil;
2158        d->pendingUpdates[i].clear();
2159    }
2160
2161    QRHI_RES_RHI(QRhiMetal);
2162    rhiD->d->releaseQueue.append(e);
2163    QRHI_PROF;
2164    QRHI_PROF_F(releaseBuffer(this));
2165    rhiD->unregisterResource(this);
2166}
2167
2168bool QMetalBuffer::build()
2169{
2170    if (d->buf[0])
2171        release();
2172
2173    if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
2174        qWarning("StorageBuffer cannot be combined with Dynamic");
2175        return false;
2176    }
2177
2178    const uint nonZeroSize = m_size <= 0 ? 256 : uint(m_size);
2179    const uint roundedSize = m_usage.testFlag(QRhiBuffer::UniformBuffer) ? aligned<uint>(nonZeroSize, 256) : nonZeroSize;
2180
2181    d->managed = false;
2182    MTLResourceOptions opts = MTLResourceStorageModeShared;
2183#ifdef Q_OS_MACOS
2184    if (m_type != Dynamic) {
2185        opts = MTLResourceStorageModeManaged;
2186        d->managed = true;
2187    }
2188#endif
2189
2190    // Have QMTL_FRAMES_IN_FLIGHT versions regardless of the type, for now.
2191    // This is because writing to a Managed buffer (which is what Immutable and
2192    // Static maps to on macOS) is not safe when another frame reading from the
2193    // same buffer is still in flight.
2194    d->slotted = !m_usage.testFlag(QRhiBuffer::StorageBuffer); // except for SSBOs written in the shader
2195
2196    QRHI_RES_RHI(QRhiMetal);
2197    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
2198        if (i == 0 || d->slotted) {
2199            d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts];
2200            if (!m_objectName.isEmpty()) {
2201                if (!d->slotted) {
2202                    d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()];
2203                } else {
2204                    const QByteArray name = m_objectName + '/' + QByteArray::number(i);
2205                    d->buf[i].label = [NSString stringWithUTF8String: name.constData()];
2206                }
2207            }
2208        }
2209    }
2210
2211    QRHI_PROF;
2212    QRHI_PROF_F(newBuffer(this, roundedSize, d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1, 0));
2213
2214    lastActiveFrameSlot = -1;
2215    generation += 1;
2216    rhiD->registerResource(this);
2217    return true;
2218}
2219
2220QRhiBuffer::NativeBuffer QMetalBuffer::nativeBuffer()
2221{
2222    if (d->slotted) {
2223        NativeBuffer b;
2224        Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QMTL_FRAMES_IN_FLIGHT));
2225        for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
2226            QRHI_RES_RHI(QRhiMetal);
2227            rhiD->executeBufferHostWritesForSlot(this, i);
2228            b.objects[i] = &d->buf[i];
2229        }
2230        b.slotCount = QMTL_FRAMES_IN_FLIGHT;
2231        return b;
2232    }
2233    return { { &d->buf[0] }, 1 };
2234}
2235
2236QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
2237                                       int sampleCount, QRhiRenderBuffer::Flags flags)
2238    : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags),
2239      d(new QMetalRenderBufferData)
2240{
2241}
2242
2243QMetalRenderBuffer::~QMetalRenderBuffer()
2244{
2245    release();
2246    delete d;
2247}
2248
2249void QMetalRenderBuffer::release()
2250{
2251    if (!d->tex)
2252        return;
2253
2254    QRhiMetalData::DeferredReleaseEntry e;
2255    e.type = QRhiMetalData::DeferredReleaseEntry::RenderBuffer;
2256    e.lastActiveFrameSlot = lastActiveFrameSlot;
2257
2258    e.renderbuffer.texture = d->tex;
2259    d->tex = nil;
2260
2261    QRHI_RES_RHI(QRhiMetal);
2262    rhiD->d->releaseQueue.append(e);
2263    QRHI_PROF;
2264    QRHI_PROF_F(releaseRenderBuffer(this));
2265    rhiD->unregisterResource(this);
2266}
2267
2268bool QMetalRenderBuffer::build()
2269{
2270    if (d->tex)
2271        release();
2272
2273    if (m_pixelSize.isEmpty())
2274        return false;
2275
2276    QRHI_RES_RHI(QRhiMetal);
2277    samples = rhiD->effectiveSampleCount(m_sampleCount);
2278
2279    MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
2280    desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
2281    desc.width = NSUInteger(m_pixelSize.width());
2282    desc.height = NSUInteger(m_pixelSize.height());
2283    if (samples > 1)
2284        desc.sampleCount = NSUInteger(samples);
2285    desc.resourceOptions = MTLResourceStorageModePrivate;
2286    desc.usage = MTLTextureUsageRenderTarget;
2287
2288    bool transientBacking = false;
2289    switch (m_type) {
2290    case DepthStencil:
2291#ifdef Q_OS_MACOS
2292        desc.storageMode = MTLStorageModePrivate;
2293        d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported
2294                ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
2295#else
2296        desc.storageMode = MTLStorageModeMemoryless;
2297        transientBacking = true;
2298        d->format = MTLPixelFormatDepth32Float_Stencil8;
2299#endif
2300        desc.pixelFormat = d->format;
2301        break;
2302    case Color:
2303        desc.storageMode = MTLStorageModePrivate;
2304        d->format = MTLPixelFormatRGBA8Unorm;
2305        desc.pixelFormat = d->format;
2306        break;
2307    default:
2308        Q_UNREACHABLE();
2309        break;
2310    }
2311
2312    d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
2313    [desc release];
2314
2315    if (!m_objectName.isEmpty())
2316        d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
2317
2318    QRHI_PROF;
2319    QRHI_PROF_F(newRenderBuffer(this, transientBacking, false, samples));
2320
2321    lastActiveFrameSlot = -1;
2322    generation += 1;
2323    rhiD->registerResource(this);
2324    return true;
2325}
2326
2327QRhiTexture::Format QMetalRenderBuffer::backingFormat() const
2328{
2329    return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
2330}
2331
2332QMetalTexture::QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
2333                             int sampleCount, Flags flags)
2334    : QRhiTexture(rhi, format, pixelSize, sampleCount, flags),
2335      d(new QMetalTextureData(this))
2336{
2337    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
2338        d->stagingBuf[i] = nil;
2339
2340    for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
2341        d->perLevelViews[i] = nil;
2342}
2343
2344QMetalTexture::~QMetalTexture()
2345{
2346    release();
2347    delete d;
2348}
2349
2350void QMetalTexture::release()
2351{
2352    if (!d->tex)
2353        return;
2354
2355    QRhiMetalData::DeferredReleaseEntry e;
2356    e.type = QRhiMetalData::DeferredReleaseEntry::Texture;
2357    e.lastActiveFrameSlot = lastActiveFrameSlot;
2358
2359    e.texture.texture = d->owns ? d->tex : nil;
2360    d->tex = nil;
2361
2362    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
2363        e.texture.stagingBuffers[i] = d->stagingBuf[i];
2364        d->stagingBuf[i] = nil;
2365    }
2366
2367    for (int i = 0; i < QRhi::MAX_LEVELS; ++i) {
2368        e.texture.views[i] = d->perLevelViews[i];
2369        d->perLevelViews[i] = nil;
2370    }
2371
2372    QRHI_RES_RHI(QRhiMetal);
2373    rhiD->d->releaseQueue.append(e);
2374    QRHI_PROF;
2375    QRHI_PROF_F(releaseTexture(this));
2376    rhiD->unregisterResource(this);
2377}
2378
2379static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
2380{
2381    const bool srgb = flags.testFlag(QRhiTexture::sRGB);
2382    switch (format) {
2383    case QRhiTexture::RGBA8:
2384        return srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm;
2385    case QRhiTexture::BGRA8:
2386        return srgb ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
2387    case QRhiTexture::R8:
2388#ifdef Q_OS_MACOS
2389        return MTLPixelFormatR8Unorm;
2390#else
2391        return srgb ? MTLPixelFormatR8Unorm_sRGB : MTLPixelFormatR8Unorm;
2392#endif
2393    case QRhiTexture::R16:
2394        return MTLPixelFormatR16Unorm;
2395    case QRhiTexture::RED_OR_ALPHA8:
2396        return MTLPixelFormatR8Unorm;
2397
2398    case QRhiTexture::RGBA16F:
2399        return MTLPixelFormatRGBA16Float;
2400    case QRhiTexture::RGBA32F:
2401        return MTLPixelFormatRGBA32Float;
2402    case QRhiTexture::R16F:
2403        return MTLPixelFormatR16Float;
2404    case QRhiTexture::R32F:
2405        return MTLPixelFormatR32Float;
2406
2407    case QRhiTexture::D16:
2408#ifdef Q_OS_MACOS
2409        return MTLPixelFormatDepth16Unorm;
2410#else
2411        return MTLPixelFormatDepth32Float;
2412#endif
2413    case QRhiTexture::D32F:
2414        return MTLPixelFormatDepth32Float;
2415
2416#ifdef Q_OS_MACOS
2417    case QRhiTexture::BC1:
2418        return srgb ? MTLPixelFormatBC1_RGBA_sRGB : MTLPixelFormatBC1_RGBA;
2419    case QRhiTexture::BC2:
2420        return srgb ? MTLPixelFormatBC2_RGBA_sRGB : MTLPixelFormatBC2_RGBA;
2421    case QRhiTexture::BC3:
2422        return srgb ? MTLPixelFormatBC3_RGBA_sRGB : MTLPixelFormatBC3_RGBA;
2423    case QRhiTexture::BC4:
2424        return MTLPixelFormatBC4_RUnorm;
2425    case QRhiTexture::BC5:
2426        qWarning("QRhiMetal does not support BC5");
2427        return MTLPixelFormatRGBA8Unorm;
2428    case QRhiTexture::BC6H:
2429        return MTLPixelFormatBC6H_RGBUfloat;
2430    case QRhiTexture::BC7:
2431        return srgb ? MTLPixelFormatBC7_RGBAUnorm_sRGB : MTLPixelFormatBC7_RGBAUnorm;
2432#else
2433    case QRhiTexture::BC1:
2434    case QRhiTexture::BC2:
2435    case QRhiTexture::BC3:
2436    case QRhiTexture::BC4:
2437    case QRhiTexture::BC5:
2438    case QRhiTexture::BC6H:
2439    case QRhiTexture::BC7:
2440        qWarning("QRhiMetal: BCx compression not supported on this platform");
2441        return MTLPixelFormatRGBA8Unorm;
2442#endif
2443
2444#ifndef Q_OS_MACOS
2445    case QRhiTexture::ETC2_RGB8:
2446        return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
2447    case QRhiTexture::ETC2_RGB8A1:
2448        return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
2449    case QRhiTexture::ETC2_RGBA8:
2450        return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
2451
2452    case QRhiTexture::ASTC_4x4:
2453        return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
2454    case QRhiTexture::ASTC_5x4:
2455        return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
2456    case QRhiTexture::ASTC_5x5:
2457        return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
2458    case QRhiTexture::ASTC_6x5:
2459        return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
2460    case QRhiTexture::ASTC_6x6:
2461        return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
2462    case QRhiTexture::ASTC_8x5:
2463        return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
2464    case QRhiTexture::ASTC_8x6:
2465        return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
2466    case QRhiTexture::ASTC_8x8:
2467        return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
2468    case QRhiTexture::ASTC_10x5:
2469        return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
2470    case QRhiTexture::ASTC_10x6:
2471        return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
2472    case QRhiTexture::ASTC_10x8:
2473        return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
2474    case QRhiTexture::ASTC_10x10:
2475        return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
2476    case QRhiTexture::ASTC_12x10:
2477        return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
2478    case QRhiTexture::ASTC_12x12:
2479        return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
2480#else
2481    case QRhiTexture::ETC2_RGB8:
2482    case QRhiTexture::ETC2_RGB8A1:
2483    case QRhiTexture::ETC2_RGBA8:
2484        qWarning("QRhiMetal: ETC2 compression not supported on this platform");
2485        return MTLPixelFormatRGBA8Unorm;
2486
2487    case QRhiTexture::ASTC_4x4:
2488    case QRhiTexture::ASTC_5x4:
2489    case QRhiTexture::ASTC_5x5:
2490    case QRhiTexture::ASTC_6x5:
2491    case QRhiTexture::ASTC_6x6:
2492    case QRhiTexture::ASTC_8x5:
2493    case QRhiTexture::ASTC_8x6:
2494    case QRhiTexture::ASTC_8x8:
2495    case QRhiTexture::ASTC_10x5:
2496    case QRhiTexture::ASTC_10x6:
2497    case QRhiTexture::ASTC_10x8:
2498    case QRhiTexture::ASTC_10x10:
2499    case QRhiTexture::ASTC_12x10:
2500    case QRhiTexture::ASTC_12x12:
2501        qWarning("QRhiMetal: ASTC compression not supported on this platform");
2502        return MTLPixelFormatRGBA8Unorm;
2503#endif
2504
2505    default:
2506        Q_UNREACHABLE();
2507        return MTLPixelFormatRGBA8Unorm;
2508    }
2509}
2510
2511bool QMetalTexture::prepareBuild(QSize *adjustedSize)
2512{
2513    if (d->tex)
2514        release();
2515
2516    const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
2517    const bool isCube = m_flags.testFlag(CubeMap);
2518    const bool hasMipMaps = m_flags.testFlag(MipMapped);
2519
2520    QRHI_RES_RHI(QRhiMetal);
2521    d->format = toMetalTextureFormat(m_format, m_flags);
2522    mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
2523    samples = rhiD->effectiveSampleCount(m_sampleCount);
2524    if (samples > 1) {
2525        if (isCube) {
2526            qWarning("Cubemap texture cannot be multisample");
2527            return false;
2528        }
2529        if (hasMipMaps) {
2530            qWarning("Multisample texture cannot have mipmaps");
2531            return false;
2532        }
2533    }
2534
2535    if (adjustedSize)
2536        *adjustedSize = size;
2537
2538    return true;
2539}
2540
2541bool QMetalTexture::build()
2542{
2543    QSize size;
2544    if (!prepareBuild(&size))
2545        return false;
2546
2547    MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
2548
2549    const bool isCube = m_flags.testFlag(CubeMap);
2550    if (isCube)
2551        desc.textureType = MTLTextureTypeCube;
2552    else
2553        desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
2554    desc.pixelFormat = d->format;
2555    desc.width = NSUInteger(size.width());
2556    desc.height = NSUInteger(size.height());
2557    desc.mipmapLevelCount = NSUInteger(mipLevelCount);
2558    if (samples > 1)
2559        desc.sampleCount = NSUInteger(samples);
2560    desc.resourceOptions = MTLResourceStorageModePrivate;
2561    desc.storageMode = MTLStorageModePrivate;
2562    desc.usage = MTLTextureUsageShaderRead;
2563    if (m_flags.testFlag(RenderTarget))
2564        desc.usage |= MTLTextureUsageRenderTarget;
2565    if (m_flags.testFlag(UsedWithLoadStore))
2566        desc.usage |= MTLTextureUsageShaderWrite;
2567
2568    QRHI_RES_RHI(QRhiMetal);
2569    d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
2570    [desc release];
2571
2572    if (!m_objectName.isEmpty())
2573        d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
2574
2575    d->owns = true;
2576
2577    QRHI_PROF;
2578    QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, samples));
2579
2580    lastActiveFrameSlot = -1;
2581    generation += 1;
2582    rhiD->registerResource(this);
2583    return true;
2584}
2585
2586bool QMetalTexture::buildFrom(QRhiTexture::NativeTexture src)
2587{
2588    void * const * tex = (void * const *) src.object;
2589    if (!tex || !*tex)
2590        return false;
2591
2592    if (!prepareBuild())
2593        return false;
2594
2595    d->tex = (id<MTLTexture>) *tex;
2596
2597    d->owns = false;
2598
2599    QRHI_PROF;
2600    QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, samples));
2601
2602    lastActiveFrameSlot = -1;
2603    generation += 1;
2604    QRHI_RES_RHI(QRhiMetal);
2605    rhiD->registerResource(this);
2606    return true;
2607}
2608
2609QRhiTexture::NativeTexture QMetalTexture::nativeTexture()
2610{
2611    return {&d->tex, 0};
2612}
2613
2614id<MTLTexture> QMetalTextureData::viewForLevel(int level)
2615{
2616    Q_ASSERT(level >= 0 && level < int(q->mipLevelCount));
2617    if (perLevelViews[level])
2618        return perLevelViews[level];
2619
2620    const MTLTextureType type = [tex textureType];
2621    const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
2622    id<MTLTexture> view = [tex newTextureViewWithPixelFormat: format textureType: type
2623            levels: NSMakeRange(NSUInteger(level), 1) slices: NSMakeRange(0, isCube ? 6 : 1)];
2624
2625    perLevelViews[level] = view;
2626    return view;
2627}
2628
2629QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
2630                             AddressMode u, AddressMode v, AddressMode w)
2631    : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w),
2632      d(new QMetalSamplerData)
2633{
2634}
2635
2636QMetalSampler::~QMetalSampler()
2637{
2638    release();
2639    delete d;
2640}
2641
2642void QMetalSampler::release()
2643{
2644    if (!d->samplerState)
2645        return;
2646
2647    QRhiMetalData::DeferredReleaseEntry e;
2648    e.type = QRhiMetalData::DeferredReleaseEntry::Sampler;
2649    e.lastActiveFrameSlot = lastActiveFrameSlot;
2650
2651    e.sampler.samplerState = d->samplerState;
2652    d->samplerState = nil;
2653
2654    QRHI_RES_RHI(QRhiMetal);
2655    rhiD->d->releaseQueue.append(e);
2656    rhiD->unregisterResource(this);
2657}
2658
2659static inline MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
2660{
2661    switch (f) {
2662    case QRhiSampler::Nearest:
2663        return MTLSamplerMinMagFilterNearest;
2664    case QRhiSampler::Linear:
2665        return MTLSamplerMinMagFilterLinear;
2666    default:
2667        Q_UNREACHABLE();
2668        return MTLSamplerMinMagFilterNearest;
2669    }
2670}
2671
2672static inline MTLSamplerMipFilter toMetalMipmapMode(QRhiSampler::Filter f)
2673{
2674    switch (f) {
2675    case QRhiSampler::None:
2676        return MTLSamplerMipFilterNotMipmapped;
2677    case QRhiSampler::Nearest:
2678        return MTLSamplerMipFilterNearest;
2679    case QRhiSampler::Linear:
2680        return MTLSamplerMipFilterLinear;
2681    default:
2682        Q_UNREACHABLE();
2683        return MTLSamplerMipFilterNotMipmapped;
2684    }
2685}
2686
2687static inline MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode m)
2688{
2689    switch (m) {
2690    case QRhiSampler::Repeat:
2691        return MTLSamplerAddressModeRepeat;
2692    case QRhiSampler::ClampToEdge:
2693        return MTLSamplerAddressModeClampToEdge;
2694    case QRhiSampler::Mirror:
2695        return MTLSamplerAddressModeMirrorRepeat;
2696    default:
2697        Q_UNREACHABLE();
2698        return MTLSamplerAddressModeClampToEdge;
2699    }
2700}
2701
2702static inline MTLCompareFunction toMetalTextureCompareFunction(QRhiSampler::CompareOp op)
2703{
2704    switch (op) {
2705    case QRhiSampler::Never:
2706        return MTLCompareFunctionNever;
2707    case QRhiSampler::Less:
2708        return MTLCompareFunctionLess;
2709    case QRhiSampler::Equal:
2710        return MTLCompareFunctionEqual;
2711    case QRhiSampler::LessOrEqual:
2712        return MTLCompareFunctionLessEqual;
2713    case QRhiSampler::Greater:
2714        return MTLCompareFunctionGreater;
2715    case QRhiSampler::NotEqual:
2716        return MTLCompareFunctionNotEqual;
2717    case QRhiSampler::GreaterOrEqual:
2718        return MTLCompareFunctionGreaterEqual;
2719    case QRhiSampler::Always:
2720        return MTLCompareFunctionAlways;
2721    default:
2722        Q_UNREACHABLE();
2723        return MTLCompareFunctionNever;
2724    }
2725}
2726
2727bool QMetalSampler::build()
2728{
2729    if (d->samplerState)
2730        release();
2731
2732    MTLSamplerDescriptor *desc = [[MTLSamplerDescriptor alloc] init];
2733    desc.minFilter = toMetalFilter(m_minFilter);
2734    desc.magFilter = toMetalFilter(m_magFilter);
2735    desc.mipFilter = toMetalMipmapMode(m_mipmapMode);
2736    desc.sAddressMode = toMetalAddressMode(m_addressU);
2737    desc.tAddressMode = toMetalAddressMode(m_addressV);
2738    desc.rAddressMode = toMetalAddressMode(m_addressW);
2739    desc.compareFunction = toMetalTextureCompareFunction(m_compareOp);
2740
2741    QRHI_RES_RHI(QRhiMetal);
2742    d->samplerState = [rhiD->d->dev newSamplerStateWithDescriptor: desc];
2743    [desc release];
2744
2745    lastActiveFrameSlot = -1;
2746    generation += 1;
2747    rhiD->registerResource(this);
2748    return true;
2749}
2750
2751// dummy, no Vulkan-style RenderPass+Framebuffer concept here.
2752// We do have MTLRenderPassDescriptor of course, but it will be created on the fly for each pass.
2753QMetalRenderPassDescriptor::QMetalRenderPassDescriptor(QRhiImplementation *rhi)
2754    : QRhiRenderPassDescriptor(rhi)
2755{
2756}
2757
2758QMetalRenderPassDescriptor::~QMetalRenderPassDescriptor()
2759{
2760    release();
2761}
2762
2763void QMetalRenderPassDescriptor::release()
2764{
2765    // nothing to do here
2766}
2767
2768bool QMetalRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
2769{
2770    if (!other)
2771        return false;
2772
2773    const QMetalRenderPassDescriptor *o = QRHI_RES(const QMetalRenderPassDescriptor, other);
2774
2775    if (colorAttachmentCount != o->colorAttachmentCount)
2776        return false;
2777
2778    if (hasDepthStencil != o->hasDepthStencil)
2779         return false;
2780
2781    for (int i = 0; i < colorAttachmentCount; ++i) {
2782        if (colorFormat[i] != o->colorFormat[i])
2783            return false;
2784    }
2785
2786    if (hasDepthStencil) {
2787        if (dsFormat != o->dsFormat)
2788            return false;
2789    }
2790
2791    return true;
2792}
2793
2794QMetalReferenceRenderTarget::QMetalReferenceRenderTarget(QRhiImplementation *rhi)
2795    : QRhiRenderTarget(rhi),
2796      d(new QMetalRenderTargetData)
2797{
2798}
2799
2800QMetalReferenceRenderTarget::~QMetalReferenceRenderTarget()
2801{
2802    release();
2803    delete d;
2804}
2805
2806void QMetalReferenceRenderTarget::release()
2807{
2808    // nothing to do here
2809}
2810
2811QSize QMetalReferenceRenderTarget::pixelSize() const
2812{
2813    return d->pixelSize;
2814}
2815
2816float QMetalReferenceRenderTarget::devicePixelRatio() const
2817{
2818    return d->dpr;
2819}
2820
2821int QMetalReferenceRenderTarget::sampleCount() const
2822{
2823    return d->sampleCount;
2824}
2825
2826QMetalTextureRenderTarget::QMetalTextureRenderTarget(QRhiImplementation *rhi,
2827                                                     const QRhiTextureRenderTargetDescription &desc,
2828                                                     Flags flags)
2829    : QRhiTextureRenderTarget(rhi, desc, flags),
2830      d(new QMetalRenderTargetData)
2831{
2832}
2833
2834QMetalTextureRenderTarget::~QMetalTextureRenderTarget()
2835{
2836    release();
2837    delete d;
2838}
2839
2840void QMetalTextureRenderTarget::release()
2841{
2842    // nothing to do here
2843}
2844
2845QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDescriptor()
2846{
2847    const int colorAttachmentCount = m_desc.cendColorAttachments() - m_desc.cbeginColorAttachments();
2848    QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
2849    rpD->colorAttachmentCount = colorAttachmentCount;
2850    rpD->hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
2851
2852    for (int i = 0; i < colorAttachmentCount; ++i) {
2853        const QRhiColorAttachment *colorAtt = m_desc.colorAttachmentAt(i);
2854        QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAtt->texture());
2855        QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAtt->renderBuffer());
2856        rpD->colorFormat[i] = int(texD ? texD->d->format : rbD->d->format);
2857    }
2858
2859    if (m_desc.depthTexture())
2860        rpD->dsFormat = int(QRHI_RES(QMetalTexture, m_desc.depthTexture())->d->format);
2861    else if (m_desc.depthStencilBuffer())
2862        rpD->dsFormat = int(QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format);
2863
2864    return rpD;
2865}
2866
2867bool QMetalTextureRenderTarget::build()
2868{
2869    const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments();
2870    Q_ASSERT(hasColorAttachments || m_desc.depthTexture());
2871    Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
2872    const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
2873
2874    d->colorAttCount = 0;
2875    int attIndex = 0;
2876    for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
2877        d->colorAttCount += 1;
2878        QMetalTexture *texD = QRHI_RES(QMetalTexture, it->texture());
2879        QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, it->renderBuffer());
2880        Q_ASSERT(texD || rbD);
2881        id<MTLTexture> dst = nil;
2882        if (texD) {
2883            dst = texD->d->tex;
2884            if (attIndex == 0) {
2885                d->pixelSize = texD->pixelSize();
2886                d->sampleCount = texD->samples;
2887            }
2888        } else if (rbD) {
2889            dst = rbD->d->tex;
2890            if (attIndex == 0) {
2891                d->pixelSize = rbD->pixelSize();
2892                d->sampleCount = rbD->samples;
2893            }
2894        }
2895        QMetalRenderTargetData::ColorAtt colorAtt;
2896        colorAtt.tex = dst;
2897        colorAtt.layer = it->layer();
2898        colorAtt.level = it->level();
2899        QMetalTexture *resTexD = QRHI_RES(QMetalTexture, it->resolveTexture());
2900        colorAtt.resolveTex = resTexD ? resTexD->d->tex : nil;
2901        colorAtt.resolveLayer = it->resolveLayer();
2902        colorAtt.resolveLevel = it->resolveLevel();
2903        d->fb.colorAtt[attIndex] = colorAtt;
2904    }
2905    d->dpr = 1;
2906
2907    if (hasDepthStencil) {
2908        if (m_desc.depthTexture()) {
2909            QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture());
2910            d->fb.dsTex = depthTexD->d->tex;
2911            d->fb.hasStencil = false;
2912            d->fb.depthNeedsStore = true;
2913            if (d->colorAttCount == 0) {
2914                d->pixelSize = depthTexD->pixelSize();
2915                d->sampleCount = depthTexD->samples;
2916            }
2917        } else {
2918            QMetalRenderBuffer *depthRbD = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer());
2919            d->fb.dsTex = depthRbD->d->tex;
2920            d->fb.hasStencil = true;
2921            d->fb.depthNeedsStore = false;
2922            if (d->colorAttCount == 0) {
2923                d->pixelSize = depthRbD->pixelSize();
2924                d->sampleCount = depthRbD->samples;
2925            }
2926        }
2927        d->dsAttCount = 1;
2928    } else {
2929        d->dsAttCount = 0;
2930    }
2931
2932    return true;
2933}
2934
2935QSize QMetalTextureRenderTarget::pixelSize() const
2936{
2937    return d->pixelSize;
2938}
2939
2940float QMetalTextureRenderTarget::devicePixelRatio() const
2941{
2942    return d->dpr;
2943}
2944
2945int QMetalTextureRenderTarget::sampleCount() const
2946{
2947    return d->sampleCount;
2948}
2949
2950QMetalShaderResourceBindings::QMetalShaderResourceBindings(QRhiImplementation *rhi)
2951    : QRhiShaderResourceBindings(rhi)
2952{
2953}
2954
2955QMetalShaderResourceBindings::~QMetalShaderResourceBindings()
2956{
2957    release();
2958}
2959
2960void QMetalShaderResourceBindings::release()
2961{
2962    sortedBindings.clear();
2963    maxBinding = -1;
2964}
2965
2966bool QMetalShaderResourceBindings::build()
2967{
2968    if (!sortedBindings.isEmpty())
2969        release();
2970
2971    std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
2972    std::sort(sortedBindings.begin(), sortedBindings.end(),
2973              [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
2974    {
2975        return a.data()->binding < b.data()->binding;
2976    });
2977    if (!sortedBindings.isEmpty())
2978        maxBinding = sortedBindings.last().data()->binding;
2979    else
2980        maxBinding = -1;
2981
2982    boundResourceData.resize(sortedBindings.count());
2983
2984    for (int i = 0, ie = sortedBindings.count(); i != ie; ++i) {
2985        const QRhiShaderResourceBinding::Data *b = sortedBindings.at(i).data();
2986        QMetalShaderResourceBindings::BoundResourceData &bd(boundResourceData[i]);
2987        switch (b->type) {
2988        case QRhiShaderResourceBinding::UniformBuffer:
2989        {
2990            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
2991            bd.ubuf.id = bufD->m_id;
2992            bd.ubuf.generation = bufD->generation;
2993        }
2994            break;
2995        case QRhiShaderResourceBinding::SampledTexture:
2996        {
2997            const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
2998            bd.stex.count = data->count;
2999            for (int elem = 0; elem < data->count; ++elem) {
3000                QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex);
3001                QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler);
3002                bd.stex.d[elem].texId = texD->m_id;
3003                bd.stex.d[elem].texGeneration = texD->generation;
3004                bd.stex.d[elem].samplerId = samplerD->m_id;
3005                bd.stex.d[elem].samplerGeneration = samplerD->generation;
3006            }
3007        }
3008            break;
3009        case QRhiShaderResourceBinding::ImageLoad:
3010        case QRhiShaderResourceBinding::ImageStore:
3011        case QRhiShaderResourceBinding::ImageLoadStore:
3012        {
3013            QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
3014            bd.simage.id = texD->m_id;
3015            bd.simage.generation = texD->generation;
3016        }
3017            break;
3018        case QRhiShaderResourceBinding::BufferLoad:
3019        case QRhiShaderResourceBinding::BufferStore:
3020        case QRhiShaderResourceBinding::BufferLoadStore:
3021        {
3022            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
3023            bd.sbuf.id = bufD->m_id;
3024            bd.sbuf.generation = bufD->generation;
3025        }
3026            break;
3027        default:
3028            Q_UNREACHABLE();
3029            break;
3030        }
3031    }
3032
3033    generation += 1;
3034    return true;
3035}
3036
3037QMetalGraphicsPipeline::QMetalGraphicsPipeline(QRhiImplementation *rhi)
3038    : QRhiGraphicsPipeline(rhi),
3039      d(new QMetalGraphicsPipelineData)
3040{
3041}
3042
3043QMetalGraphicsPipeline::~QMetalGraphicsPipeline()
3044{
3045    release();
3046    delete d;
3047}
3048
3049void QMetalGraphicsPipeline::release()
3050{
3051    QRHI_RES_RHI(QRhiMetal);
3052
3053    d->vs.release();
3054    d->fs.release();
3055
3056    [d->ds release];
3057    d->ds = nil;
3058
3059    if (!d->ps)
3060        return;
3061
3062    [d->ps release];
3063    d->ps = nil;
3064
3065    rhiD->unregisterResource(this);
3066}
3067
3068static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
3069{
3070    switch (format) {
3071    case QRhiVertexInputAttribute::Float4:
3072        return MTLVertexFormatFloat4;
3073    case QRhiVertexInputAttribute::Float3:
3074        return MTLVertexFormatFloat3;
3075    case QRhiVertexInputAttribute::Float2:
3076        return MTLVertexFormatFloat2;
3077    case QRhiVertexInputAttribute::Float:
3078        return MTLVertexFormatFloat;
3079    case QRhiVertexInputAttribute::UNormByte4:
3080        return MTLVertexFormatUChar4Normalized;
3081    case QRhiVertexInputAttribute::UNormByte2:
3082        return MTLVertexFormatUChar2Normalized;
3083    case QRhiVertexInputAttribute::UNormByte:
3084        if (@available(macOS 10.13, iOS 11.0, *))
3085            return MTLVertexFormatUCharNormalized;
3086        else
3087            Q_UNREACHABLE();
3088    default:
3089        Q_UNREACHABLE();
3090        return MTLVertexFormatFloat4;
3091    }
3092}
3093
3094static inline MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
3095{
3096    switch (f) {
3097    case QRhiGraphicsPipeline::Zero:
3098        return MTLBlendFactorZero;
3099    case QRhiGraphicsPipeline::One:
3100        return MTLBlendFactorOne;
3101    case QRhiGraphicsPipeline::SrcColor:
3102        return MTLBlendFactorSourceColor;
3103    case QRhiGraphicsPipeline::OneMinusSrcColor:
3104        return MTLBlendFactorOneMinusSourceColor;
3105    case QRhiGraphicsPipeline::DstColor:
3106        return MTLBlendFactorDestinationColor;
3107    case QRhiGraphicsPipeline::OneMinusDstColor:
3108        return MTLBlendFactorOneMinusDestinationColor;
3109    case QRhiGraphicsPipeline::SrcAlpha:
3110        return MTLBlendFactorSourceAlpha;
3111    case QRhiGraphicsPipeline::OneMinusSrcAlpha:
3112        return MTLBlendFactorOneMinusSourceAlpha;
3113    case QRhiGraphicsPipeline::DstAlpha:
3114        return MTLBlendFactorDestinationAlpha;
3115    case QRhiGraphicsPipeline::OneMinusDstAlpha:
3116        return MTLBlendFactorOneMinusDestinationAlpha;
3117    case QRhiGraphicsPipeline::ConstantColor:
3118        return MTLBlendFactorBlendColor;
3119    case QRhiGraphicsPipeline::ConstantAlpha:
3120        return MTLBlendFactorBlendAlpha;
3121    case QRhiGraphicsPipeline::OneMinusConstantColor:
3122        return MTLBlendFactorOneMinusBlendColor;
3123    case QRhiGraphicsPipeline::OneMinusConstantAlpha:
3124        return MTLBlendFactorOneMinusBlendAlpha;
3125    case QRhiGraphicsPipeline::SrcAlphaSaturate:
3126        return MTLBlendFactorSourceAlphaSaturated;
3127    case QRhiGraphicsPipeline::Src1Color:
3128        return MTLBlendFactorSource1Color;
3129    case QRhiGraphicsPipeline::OneMinusSrc1Color:
3130        return MTLBlendFactorOneMinusSource1Color;
3131    case QRhiGraphicsPipeline::Src1Alpha:
3132        return MTLBlendFactorSource1Alpha;
3133    case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
3134        return MTLBlendFactorOneMinusSource1Alpha;
3135    default:
3136        Q_UNREACHABLE();
3137        return MTLBlendFactorZero;
3138    }
3139}
3140
3141static inline MTLBlendOperation toMetalBlendOp(QRhiGraphicsPipeline::BlendOp op)
3142{
3143    switch (op) {
3144    case QRhiGraphicsPipeline::Add:
3145        return MTLBlendOperationAdd;
3146    case QRhiGraphicsPipeline::Subtract:
3147        return MTLBlendOperationSubtract;
3148    case QRhiGraphicsPipeline::ReverseSubtract:
3149        return MTLBlendOperationReverseSubtract;
3150    case QRhiGraphicsPipeline::Min:
3151        return MTLBlendOperationMin;
3152    case QRhiGraphicsPipeline::Max:
3153        return MTLBlendOperationMax;
3154    default:
3155        Q_UNREACHABLE();
3156        return MTLBlendOperationAdd;
3157    }
3158}
3159
3160static inline uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
3161{
3162    uint f = 0;
3163    if (c.testFlag(QRhiGraphicsPipeline::R))
3164        f |= MTLColorWriteMaskRed;
3165    if (c.testFlag(QRhiGraphicsPipeline::G))
3166        f |= MTLColorWriteMaskGreen;
3167    if (c.testFlag(QRhiGraphicsPipeline::B))
3168        f |= MTLColorWriteMaskBlue;
3169    if (c.testFlag(QRhiGraphicsPipeline::A))
3170        f |= MTLColorWriteMaskAlpha;
3171    return f;
3172}
3173
3174static inline MTLCompareFunction toMetalCompareOp(QRhiGraphicsPipeline::CompareOp op)
3175{
3176    switch (op) {
3177    case QRhiGraphicsPipeline::Never:
3178        return MTLCompareFunctionNever;
3179    case QRhiGraphicsPipeline::Less:
3180        return MTLCompareFunctionLess;
3181    case QRhiGraphicsPipeline::Equal:
3182        return MTLCompareFunctionEqual;
3183    case QRhiGraphicsPipeline::LessOrEqual:
3184        return MTLCompareFunctionLessEqual;
3185    case QRhiGraphicsPipeline::Greater:
3186        return MTLCompareFunctionGreater;
3187    case QRhiGraphicsPipeline::NotEqual:
3188        return MTLCompareFunctionNotEqual;
3189    case QRhiGraphicsPipeline::GreaterOrEqual:
3190        return MTLCompareFunctionGreaterEqual;
3191    case QRhiGraphicsPipeline::Always:
3192        return MTLCompareFunctionAlways;
3193    default:
3194        Q_UNREACHABLE();
3195        return MTLCompareFunctionAlways;
3196    }
3197}
3198
3199static inline MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
3200{
3201    switch (op) {
3202    case QRhiGraphicsPipeline::StencilZero:
3203        return MTLStencilOperationZero;
3204    case QRhiGraphicsPipeline::Keep:
3205        return MTLStencilOperationKeep;
3206    case QRhiGraphicsPipeline::Replace:
3207        return MTLStencilOperationReplace;
3208    case QRhiGraphicsPipeline::IncrementAndClamp:
3209        return MTLStencilOperationIncrementClamp;
3210    case QRhiGraphicsPipeline::DecrementAndClamp:
3211        return MTLStencilOperationDecrementClamp;
3212    case QRhiGraphicsPipeline::Invert:
3213        return MTLStencilOperationInvert;
3214    case QRhiGraphicsPipeline::IncrementAndWrap:
3215        return MTLStencilOperationIncrementWrap;
3216    case QRhiGraphicsPipeline::DecrementAndWrap:
3217        return MTLStencilOperationDecrementWrap;
3218    default:
3219        Q_UNREACHABLE();
3220        return MTLStencilOperationKeep;
3221    }
3222}
3223
3224static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t)
3225{
3226    switch (t) {
3227    case QRhiGraphicsPipeline::Triangles:
3228        return MTLPrimitiveTypeTriangle;
3229    case QRhiGraphicsPipeline::TriangleStrip:
3230        return MTLPrimitiveTypeTriangleStrip;
3231    case QRhiGraphicsPipeline::Lines:
3232        return MTLPrimitiveTypeLine;
3233    case QRhiGraphicsPipeline::LineStrip:
3234        return MTLPrimitiveTypeLineStrip;
3235    case QRhiGraphicsPipeline::Points:
3236        return MTLPrimitiveTypePoint;
3237    default:
3238        Q_UNREACHABLE();
3239        return MTLPrimitiveTypeTriangle;
3240    }
3241}
3242
3243static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
3244{
3245    switch (c) {
3246    case QRhiGraphicsPipeline::None:
3247        return MTLCullModeNone;
3248    case QRhiGraphicsPipeline::Front:
3249        return MTLCullModeFront;
3250    case QRhiGraphicsPipeline::Back:
3251        return MTLCullModeBack;
3252    default:
3253        Q_UNREACHABLE();
3254        return MTLCullModeNone;
3255    }
3256}
3257
3258id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
3259                                             QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
3260{
3261    QShaderKey key = { QShader::MetalLibShader, 20, shaderVariant };
3262    QShaderCode mtllib = shader.shader(key);
3263    if (mtllib.shader().isEmpty()) {
3264        key.setSourceVersion(12);
3265        mtllib = shader.shader(key);
3266    }
3267    if (!mtllib.shader().isEmpty()) {
3268        dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
3269                                                    size_t(mtllib.shader().size()),
3270                                                    dispatch_get_global_queue(0, 0),
3271                                                    DISPATCH_DATA_DESTRUCTOR_DEFAULT);
3272        NSError *err = nil;
3273        id<MTLLibrary> lib = [dev newLibraryWithData: data error: &err];
3274        dispatch_release(data);
3275        if (!err) {
3276            *entryPoint = mtllib.entryPoint();
3277            *activeKey = key;
3278            return lib;
3279        } else {
3280            const QString msg = QString::fromNSString(err.localizedDescription);
3281            qWarning("Failed to load metallib from baked shader: %s", qPrintable(msg));
3282        }
3283    }
3284
3285    key = { QShader::MslShader, 20, shaderVariant };
3286    QShaderCode mslSource = shader.shader(key);
3287    if (mslSource.shader().isEmpty()) {
3288        key.setSourceVersion(12);
3289        mslSource = shader.shader(key);
3290    }
3291    if (mslSource.shader().isEmpty()) {
3292        qWarning() << "No MSL 2.0 or 1.2 code found in baked shader" << shader;
3293        return nil;
3294    }
3295
3296    NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()];
3297    MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
3298    opts.languageVersion = key.sourceVersion() == 20 ? MTLLanguageVersion2_0 : MTLLanguageVersion1_2;
3299    NSError *err = nil;
3300    id<MTLLibrary> lib = [dev newLibraryWithSource: src options: opts error: &err];
3301    [opts release];
3302    // src is autoreleased
3303
3304    // if lib is null and err is non-null, we had errors (fail)
3305    // if lib is non-null and err is non-null, we had warnings (success)
3306    // if lib is non-null and err is null, there were no errors or warnings (success)
3307    if (!lib) {
3308        const QString msg = QString::fromNSString(err.localizedDescription);
3309        *error = msg;
3310        return nil;
3311    }
3312
3313    *entryPoint = mslSource.entryPoint();
3314    *activeKey = key;
3315    return lib;
3316}
3317
3318id<MTLFunction> QRhiMetalData::createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint)
3319{
3320    NSString *name = [NSString stringWithUTF8String: entryPoint.constData()];
3321    id<MTLFunction> f = [lib newFunctionWithName: name];
3322    [name release];
3323    return f;
3324}
3325
3326bool QMetalGraphicsPipeline::build()
3327{
3328    if (d->ps)
3329        release();
3330
3331    QRHI_RES_RHI(QRhiMetal);
3332    if (!rhiD->sanityCheckGraphicsPipeline(this))
3333        return false;
3334
3335    // same binding space for vertex and constant buffers - work it around
3336    const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, m_shaderResourceBindings)->maxBinding + 1;
3337
3338    MTLVertexDescriptor *inputLayout = [MTLVertexDescriptor vertexDescriptor];
3339    for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes();
3340         it != itEnd; ++it)
3341    {
3342        const uint loc = uint(it->location());
3343        inputLayout.attributes[loc].format = toMetalAttributeFormat(it->format());
3344        inputLayout.attributes[loc].offset = NSUInteger(it->offset());
3345        inputLayout.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
3346    }
3347    int bindingIndex = 0;
3348    for (auto it = m_vertexInputLayout.cbeginBindings(), itEnd = m_vertexInputLayout.cendBindings();
3349         it != itEnd; ++it, ++bindingIndex)
3350    {
3351        const uint layoutIdx = uint(firstVertexBinding + bindingIndex);
3352        inputLayout.layouts[layoutIdx].stepFunction =
3353                it->classification() == QRhiVertexInputBinding::PerInstance
3354                ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
3355        inputLayout.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
3356        inputLayout.layouts[layoutIdx].stride = it->stride();
3357    }
3358
3359    MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
3360
3361    rpDesc.vertexDescriptor = inputLayout;
3362
3363    // mutability cannot be determined (slotted buffers could be set as
3364    // MTLMutabilityImmutable, but then we potentially need a different
3365    // descriptor for each buffer combination as this depends on the actual
3366    // buffers not just the resource binding layout) so leave it at the default
3367
3368    for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
3369        auto cacheIt = rhiD->d->shaderCache.constFind(shaderStage);
3370        if (cacheIt != rhiD->d->shaderCache.constEnd()) {
3371            switch (shaderStage.type()) {
3372            case QRhiShaderStage::Vertex:
3373                d->vs = *cacheIt;
3374                [d->vs.lib retain];
3375                [d->vs.func retain];
3376                rpDesc.vertexFunction = d->vs.func;
3377                break;
3378            case QRhiShaderStage::Fragment:
3379                d->fs = *cacheIt;
3380                [d->fs.lib retain];
3381                [d->fs.func retain];
3382                rpDesc.fragmentFunction = d->fs.func;
3383                break;
3384            default:
3385                break;
3386            }
3387        } else {
3388            const QShader shader = shaderStage.shader();
3389            QString error;
3390            QByteArray entryPoint;
3391            QShaderKey activeKey;
3392            id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, shaderStage.shaderVariant(),
3393                                                         &error, &entryPoint, &activeKey);
3394            if (!lib) {
3395                qWarning("MSL shader compilation failed: %s", qPrintable(error));
3396                return false;
3397            }
3398            id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
3399            if (!func) {
3400                qWarning("MSL function for entry point %s not found", entryPoint.constData());
3401                [lib release];
3402                return false;
3403            }
3404            if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
3405                // Use the simplest strategy: too many cached shaders -> drop them all.
3406                for (QMetalShader &s : rhiD->d->shaderCache)
3407                    s.release();
3408                rhiD->d->shaderCache.clear();
3409            }
3410            switch (shaderStage.type()) {
3411            case QRhiShaderStage::Vertex:
3412                d->vs.lib = lib;
3413                d->vs.func = func;
3414                if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey))
3415                    d->vs.nativeResourceBindingMap = *map;
3416                rhiD->d->shaderCache.insert(shaderStage, d->vs);
3417                [d->vs.lib retain];
3418                [d->vs.func retain];
3419                rpDesc.vertexFunction = func;
3420                break;
3421            case QRhiShaderStage::Fragment:
3422                d->fs.lib = lib;
3423                d->fs.func = func;
3424                if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey))
3425                    d->fs.nativeResourceBindingMap = *map;
3426                rhiD->d->shaderCache.insert(shaderStage, d->fs);
3427                [d->fs.lib retain];
3428                [d->fs.func retain];
3429                rpDesc.fragmentFunction = func;
3430                break;
3431            default:
3432                [func release];
3433                [lib release];
3434                break;
3435            }
3436        }
3437    }
3438
3439    QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc);
3440
3441    if (rpD->colorAttachmentCount) {
3442        // defaults when no targetBlends are provided
3443        rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormat(rpD->colorFormat[0]);
3444        rpDesc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
3445        rpDesc.colorAttachments[0].blendingEnabled = false;
3446
3447        Q_ASSERT(m_targetBlends.count() == rpD->colorAttachmentCount
3448                 || (m_targetBlends.isEmpty() && rpD->colorAttachmentCount == 1));
3449
3450        for (uint i = 0, ie = uint(m_targetBlends.count()); i != ie; ++i) {
3451            const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[int(i)]);
3452            rpDesc.colorAttachments[i].pixelFormat = MTLPixelFormat(rpD->colorFormat[i]);
3453            rpDesc.colorAttachments[i].blendingEnabled = b.enable;
3454            rpDesc.colorAttachments[i].sourceRGBBlendFactor = toMetalBlendFactor(b.srcColor);
3455            rpDesc.colorAttachments[i].destinationRGBBlendFactor = toMetalBlendFactor(b.dstColor);
3456            rpDesc.colorAttachments[i].rgbBlendOperation = toMetalBlendOp(b.opColor);
3457            rpDesc.colorAttachments[i].sourceAlphaBlendFactor = toMetalBlendFactor(b.srcAlpha);
3458            rpDesc.colorAttachments[i].destinationAlphaBlendFactor = toMetalBlendFactor(b.dstAlpha);
3459            rpDesc.colorAttachments[i].alphaBlendOperation = toMetalBlendOp(b.opAlpha);
3460            rpDesc.colorAttachments[i].writeMask = toMetalColorWriteMask(b.colorWrite);
3461        }
3462    }
3463
3464    if (rpD->hasDepthStencil) {
3465        // Must only be set when a depth-stencil buffer will actually be bound,
3466        // validation blows up otherwise.
3467        MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat);
3468        rpDesc.depthAttachmentPixelFormat = fmt;
3469#ifdef Q_OS_MACOS
3470        if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float)
3471#else
3472        if (fmt != MTLPixelFormatDepth32Float)
3473#endif
3474            rpDesc.stencilAttachmentPixelFormat = fmt;
3475    }
3476
3477    rpDesc.sampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
3478
3479    NSError *err = nil;
3480    d->ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
3481    if (!d->ps) {
3482        const QString msg = QString::fromNSString(err.localizedDescription);
3483        qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
3484        [rpDesc release];
3485        return false;
3486    }
3487    [rpDesc release];
3488
3489    MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
3490    dsDesc.depthCompareFunction = m_depthTest ? toMetalCompareOp(m_depthOp) : MTLCompareFunctionAlways;
3491    dsDesc.depthWriteEnabled = m_depthWrite;
3492    if (m_stencilTest) {
3493        dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
3494        dsDesc.frontFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilFront.failOp);
3495        dsDesc.frontFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilFront.depthFailOp);
3496        dsDesc.frontFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilFront.passOp);
3497        dsDesc.frontFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilFront.compareOp);
3498        dsDesc.frontFaceStencil.readMask = m_stencilReadMask;
3499        dsDesc.frontFaceStencil.writeMask = m_stencilWriteMask;
3500
3501        dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
3502        dsDesc.backFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilBack.failOp);
3503        dsDesc.backFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilBack.depthFailOp);
3504        dsDesc.backFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilBack.passOp);
3505        dsDesc.backFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilBack.compareOp);
3506        dsDesc.backFaceStencil.readMask = m_stencilReadMask;
3507        dsDesc.backFaceStencil.writeMask = m_stencilWriteMask;
3508    }
3509
3510    d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
3511    [dsDesc release];
3512
3513    d->primitiveType = toMetalPrimitiveType(m_topology);
3514    d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
3515    d->cullMode = toMetalCullMode(m_cullMode);
3516    d->depthBias = float(m_depthBias);
3517    d->slopeScaledDepthBias = m_slopeScaledDepthBias;
3518
3519    lastActiveFrameSlot = -1;
3520    generation += 1;
3521    rhiD->registerResource(this);
3522    return true;
3523}
3524
3525QMetalComputePipeline::QMetalComputePipeline(QRhiImplementation *rhi)
3526    : QRhiComputePipeline(rhi),
3527      d(new QMetalComputePipelineData)
3528{
3529}
3530
3531QMetalComputePipeline::~QMetalComputePipeline()
3532{
3533    release();
3534    delete d;
3535}
3536
3537void QMetalComputePipeline::release()
3538{
3539    QRHI_RES_RHI(QRhiMetal);
3540
3541    d->cs.release();
3542
3543    if (!d->ps)
3544        return;
3545
3546    [d->ps release];
3547    d->ps = nil;
3548
3549    rhiD->unregisterResource(this);
3550}
3551
3552bool QMetalComputePipeline::build()
3553{
3554    if (d->ps)
3555        release();
3556
3557    QRHI_RES_RHI(QRhiMetal);
3558
3559    auto cacheIt = rhiD->d->shaderCache.constFind(m_shaderStage);
3560    if (cacheIt != rhiD->d->shaderCache.constEnd()) {
3561        d->cs = *cacheIt;
3562    } else {
3563        const QShader shader = m_shaderStage.shader();
3564        QString error;
3565        QByteArray entryPoint;
3566        QShaderKey activeKey;
3567        id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(),
3568                                                     &error, &entryPoint, &activeKey);
3569        if (!lib) {
3570            qWarning("MSL shader compilation failed: %s", qPrintable(error));
3571            return false;
3572        }
3573        id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
3574        if (!func) {
3575            qWarning("MSL function for entry point %s not found", entryPoint.constData());
3576            [lib release];
3577            return false;
3578        }
3579        d->cs.lib = lib;
3580        d->cs.func = func;
3581        d->cs.localSize = shader.description().computeShaderLocalSize();
3582        if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey))
3583            d->cs.nativeResourceBindingMap = *map;
3584
3585        if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
3586            for (QMetalShader &s : rhiD->d->shaderCache)
3587                s.release();
3588            rhiD->d->shaderCache.clear();
3589        }
3590        rhiD->d->shaderCache.insert(m_shaderStage, d->cs);
3591    }
3592
3593    [d->cs.lib retain];
3594    [d->cs.func retain];
3595
3596    d->localSize = MTLSizeMake(d->cs.localSize[0], d->cs.localSize[1], d->cs.localSize[2]);
3597
3598    NSError *err = nil;
3599    d->ps = [rhiD->d->dev newComputePipelineStateWithFunction: d->cs.func error: &err];
3600    if (!d->ps) {
3601        const QString msg = QString::fromNSString(err.localizedDescription);
3602        qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
3603        return false;
3604    }
3605
3606    lastActiveFrameSlot = -1;
3607    generation += 1;
3608    rhiD->registerResource(this);
3609    return true;
3610}
3611
3612QMetalCommandBuffer::QMetalCommandBuffer(QRhiImplementation *rhi)
3613    : QRhiCommandBuffer(rhi),
3614      d(new QMetalCommandBufferData)
3615{
3616    resetState();
3617}
3618
3619QMetalCommandBuffer::~QMetalCommandBuffer()
3620{
3621    release();
3622    delete d;
3623}
3624
3625void QMetalCommandBuffer::release()
3626{
3627    // nothing to do here, we do not own the MTL cb object
3628}
3629
3630const QRhiNativeHandles *QMetalCommandBuffer::nativeHandles()
3631{
3632    nativeHandlesStruct.commandBuffer = d->cb;
3633    nativeHandlesStruct.encoder = d->currentRenderPassEncoder;
3634    return &nativeHandlesStruct;
3635}
3636
3637void QMetalCommandBuffer::resetState()
3638{
3639    d->currentRenderPassEncoder = nil;
3640    d->currentComputePassEncoder = nil;
3641    d->currentPassRpDesc = nil;
3642    resetPerPassState();
3643}
3644
3645void QMetalCommandBuffer::resetPerPassState()
3646{
3647    recordingPass = NoPass;
3648    currentTarget = nullptr;
3649    resetPerPassCachedState();
3650}
3651
3652void QMetalCommandBuffer::resetPerPassCachedState()
3653{
3654    currentGraphicsPipeline = nullptr;
3655    currentComputePipeline = nullptr;
3656    currentPipelineGeneration = 0;
3657    currentGraphicsSrb = nullptr;
3658    currentComputeSrb = nullptr;
3659    currentSrbGeneration = 0;
3660    currentResSlot = -1;
3661    currentIndexBuffer = nullptr;
3662    currentIndexOffset = 0;
3663    currentIndexFormat = QRhiCommandBuffer::IndexUInt16;
3664    currentCullMode = -1;
3665    currentFrontFaceWinding = -1;
3666    currentDepthBiasValues = { 0.0f, 0.0f };
3667
3668    d->currentFirstVertexBinding = -1;
3669    d->currentVertexInputsBuffers.clear();
3670    d->currentVertexInputOffsets.clear();
3671}
3672
3673QMetalSwapChain::QMetalSwapChain(QRhiImplementation *rhi)
3674    : QRhiSwapChain(rhi),
3675      rtWrapper(rhi),
3676      cbWrapper(rhi),
3677      d(new QMetalSwapChainData)
3678{
3679    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3680        d->sem[i] = nullptr;
3681        d->msaaTex[i] = nil;
3682    }
3683}
3684
3685QMetalSwapChain::~QMetalSwapChain()
3686{
3687    release();
3688    delete d;
3689}
3690
3691void QMetalSwapChain::release()
3692{
3693#ifdef TARGET_IPHONE_SIMULATOR
3694    if (@available(ios 13.0, *)) {
3695#endif
3696
3697    if (!d->layer)
3698        return;
3699
3700    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3701        if (d->sem[i]) {
3702            // the semaphores cannot be released if they do not have the initial value
3703            dispatch_semaphore_wait(d->sem[i], DISPATCH_TIME_FOREVER);
3704            dispatch_semaphore_signal(d->sem[i]);
3705
3706            dispatch_release(d->sem[i]);
3707            d->sem[i] = nullptr;
3708        }
3709    }
3710
3711    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3712        [d->msaaTex[i] release];
3713        d->msaaTex[i] = nil;
3714    }
3715
3716    d->layer = nullptr;
3717
3718    QRHI_RES_RHI(QRhiMetal);
3719    rhiD->swapchains.remove(this);
3720
3721    QRHI_PROF;
3722    QRHI_PROF_F(releaseSwapChain(this));
3723
3724    rhiD->unregisterResource(this);
3725
3726#ifdef TARGET_IPHONE_SIMULATOR
3727    }
3728#endif
3729}
3730
3731QRhiCommandBuffer *QMetalSwapChain::currentFrameCommandBuffer()
3732{
3733    return &cbWrapper;
3734}
3735
3736QRhiRenderTarget *QMetalSwapChain::currentFrameRenderTarget()
3737{
3738    return &rtWrapper;
3739}
3740
3741QSize QMetalSwapChain::surfacePixelSize()
3742{
3743    Q_ASSERT(m_window);
3744    return m_window->size() * m_window->devicePixelRatio();
3745}
3746
3747QRhiRenderPassDescriptor *QMetalSwapChain::newCompatibleRenderPassDescriptor()
3748{
3749    chooseFormats(); // ensure colorFormat and similar are filled out
3750
3751    QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
3752    rpD->colorAttachmentCount = 1;
3753    rpD->hasDepthStencil = m_depthStencil != nullptr;
3754
3755    rpD->colorFormat[0] = int(d->colorFormat);
3756
3757#ifdef Q_OS_MACOS
3758    // m_depthStencil may not be built yet so cannot rely on computed fields in it
3759    QRHI_RES_RHI(QRhiMetal);
3760    rpD->dsFormat = rhiD->d->dev.depth24Stencil8PixelFormatSupported
3761            ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
3762#else
3763    rpD->dsFormat = MTLPixelFormatDepth32Float_Stencil8;
3764#endif
3765
3766    return rpD;
3767}
3768
3769void QMetalSwapChain::chooseFormats()
3770{
3771    QRHI_RES_RHI(QRhiMetal);
3772    samples = rhiD->effectiveSampleCount(m_sampleCount);
3773    // pick a format that is allowed for CAMetalLayer.pixelFormat
3774    d->colorFormat = m_flags.testFlag(sRGB) ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
3775    d->rhiColorFormat = QRhiTexture::BGRA8;
3776}
3777
3778bool QMetalSwapChain::buildOrResize()
3779{
3780#ifdef TARGET_IPHONE_SIMULATOR
3781    if (@available(ios 13.0, *)) {
3782#endif
3783
3784    Q_ASSERT(m_window);
3785
3786    const bool needsRegistration = !window || window != m_window;
3787
3788    if (window && window != m_window)
3789        release();
3790    // else no release(), this is intentional
3791
3792    QRHI_RES_RHI(QRhiMetal);
3793    if (needsRegistration)
3794        rhiD->swapchains.insert(this);
3795
3796    window = m_window;
3797
3798    if (window->surfaceType() != QSurface::MetalSurface) {
3799        qWarning("QMetalSwapChain only supports MetalSurface windows");
3800        return false;
3801    }
3802
3803#ifdef Q_OS_MACOS
3804    NSView *view = reinterpret_cast<NSView *>(window->winId());
3805#else
3806    UIView *view = reinterpret_cast<UIView *>(window->winId());
3807#endif
3808    Q_ASSERT(view);
3809    d->layer = static_cast<CAMetalLayer *>(view.layer);
3810    Q_ASSERT(d->layer);
3811
3812    chooseFormats();
3813    if (d->colorFormat != d->layer.pixelFormat)
3814        d->layer.pixelFormat = d->colorFormat;
3815
3816    if (m_flags.testFlag(UsedAsTransferSource))
3817        d->layer.framebufferOnly = NO;
3818
3819#ifdef Q_OS_MACOS
3820    if (m_flags.testFlag(NoVSync)) {
3821        if (@available(macOS 10.13, *))
3822            d->layer.displaySyncEnabled = NO;
3823    }
3824#endif
3825
3826    if (m_flags.testFlag(SurfaceHasPreMulAlpha)) {
3827        d->layer.opaque = NO;
3828    } else if (m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
3829        // The CoreAnimation compositor is said to expect premultiplied alpha,
3830        // so this is then wrong when it comes to the blending operations but
3831        // there's nothing we can do. Fortunately Qt Quick always outputs
3832        // premultiplied alpha so it is not a problem there.
3833        d->layer.opaque = NO;
3834    } else {
3835        d->layer.opaque = YES;
3836    }
3837
3838    // Now set the layer's drawableSize which will stay set to the same value
3839    // until the next buildOrResize(), thus ensuring atomicity with regards to
3840    // the drawable size in frames.
3841    CGSize layerSize = d->layer.bounds.size;
3842    layerSize.width *= d->layer.contentsScale;
3843    layerSize.height *= d->layer.contentsScale;
3844    d->layer.drawableSize = layerSize;
3845
3846    m_currentPixelSize = QSizeF::fromCGSize(layerSize).toSize();
3847    pixelSize = m_currentPixelSize;
3848
3849    [d->layer setDevice: rhiD->d->dev];
3850
3851    d->curDrawable = nil;
3852
3853    for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3854        if (!d->sem[i])
3855            d->sem[i] = dispatch_semaphore_create(QMTL_FRAMES_IN_FLIGHT - 1);
3856    }
3857
3858    currentFrameSlot = 0;
3859    frameCount = 0;
3860
3861    ds = m_depthStencil ? QRHI_RES(QMetalRenderBuffer, m_depthStencil) : nullptr;
3862    if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
3863        qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
3864                 m_depthStencil->sampleCount(), m_sampleCount);
3865    }
3866    if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
3867        if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) {
3868            m_depthStencil->setPixelSize(pixelSize);
3869            if (!m_depthStencil->build())
3870                qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d",
3871                         pixelSize.width(), pixelSize.height());
3872        } else {
3873            qWarning("Depth-stencil buffer's size (%dx%d) does not match the layer size (%dx%d). Expect problems.",
3874                     m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
3875                     pixelSize.width(), pixelSize.height());
3876        }
3877    }
3878
3879    rtWrapper.d->pixelSize = pixelSize;
3880    rtWrapper.d->dpr = float(window->devicePixelRatio());
3881    rtWrapper.d->sampleCount = samples;
3882    rtWrapper.d->colorAttCount = 1;
3883    rtWrapper.d->dsAttCount = ds ? 1 : 0;
3884
3885    qCDebug(QRHI_LOG_INFO, "got CAMetalLayer, size %dx%d", pixelSize.width(), pixelSize.height());
3886
3887    if (samples > 1) {
3888        MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
3889        desc.textureType = MTLTextureType2DMultisample;
3890        desc.pixelFormat = d->colorFormat;
3891        desc.width = NSUInteger(pixelSize.width());
3892        desc.height = NSUInteger(pixelSize.height());
3893        desc.sampleCount = NSUInteger(samples);
3894        desc.resourceOptions = MTLResourceStorageModePrivate;
3895        desc.storageMode = MTLStorageModePrivate;
3896        desc.usage = MTLTextureUsageRenderTarget;
3897        for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3898            [d->msaaTex[i] release];
3899            d->msaaTex[i] = [rhiD->d->dev newTextureWithDescriptor: desc];
3900        }
3901        [desc release];
3902    }
3903
3904    QRHI_PROF;
3905    QRHI_PROF_F(resizeSwapChain(this, QMTL_FRAMES_IN_FLIGHT, samples > 1 ? QMTL_FRAMES_IN_FLIGHT : 0, samples));
3906
3907    if (needsRegistration)
3908        rhiD->registerResource(this);
3909
3910    return true;
3911
3912#ifdef TARGET_IPHONE_SIMULATOR
3913    } else {
3914        // Won't ever get here in a normal app because MTLDevice creation would
3915        // fail too. Print a warning, just in case.
3916        qWarning("No CAMetalLayer support in this version of the iOS Simulator");
3917        return false;
3918    }
3919#endif
3920}
3921
3922QT_END_NAMESPACE
3923