qrhimetal.mm 113 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt RHI module
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qrhimetal_p.h"
#include <QGuiApplication>
#include <QWindow>
Laszlo Agocs's avatar
Laszlo Agocs committed
40
#include <qmath.h>
41
42
43
44
45
46
47
48
#include <QBakedShader>
#include <AppKit/AppKit.h>
#include <Metal/Metal.h>
#include <QuartzCore/CAMetalLayer.h>

QT_BEGIN_NAMESPACE

/*
Laszlo Agocs's avatar
Laszlo Agocs committed
49
    Metal backend. Double buffers and throttles to vsync. "Dynamic" buffers are
Laszlo Agocs's avatar
Laszlo Agocs committed
50
51
52
    Shared (host visible) and duplicated (due to 2 frames in flight), "static"
    are Managed on macOS and Shared on iOS/tvOS, and still duplicated.
    "Immutable" is like "static" but with only one native buffer underneath.
Laszlo Agocs's avatar
Laszlo Agocs committed
53
    Textures are Private (device local) and a host visible staging buffer is
Laszlo Agocs's avatar
Laszlo Agocs committed
54
55
56
57
    used to upload data to them. Does not rely on strong objects refs from
    command buffers (hence uses commandBufferWithUnretainedReferences), but
    does rely on automatic dependency tracking between encoders (hence no
    MTLResourceHazardTrackingModeUntracked atm).
58
59
60
61
62
63
*/

#if __has_feature(objc_arc)
#error ARC not supported
#endif

Laszlo Agocs's avatar
Laszlo Agocs committed
64
65
66
// Note: we expect everything here pass the Metal API validation when running
// in Debug mode in XCode. Some of the issues that break validation are not
// obvious and not visible when running outside XCode.
Laszlo Agocs's avatar
Laszlo Agocs committed
67
68
69
//
// An exception is the nextDrawable Called Early blah blah warning, which is
// plain and simply false.
Laszlo Agocs's avatar
Laszlo Agocs committed
70

Laszlo Agocs's avatar
Laszlo Agocs committed
71
72
73
/*!
    \class QRhiMetalInitParams
    \inmodule QtRhi
Laszlo Agocs's avatar
Laszlo Agocs committed
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
    \brief Metal specific initialization parameters.

    A Metal-based QRhi needs no special parameters for initialization.

    \badcode
        QRhiMetalInitParams params;
        rhi = QRhi::create(QRhi::Metal, &params);
    \endcode

    \note Metal API validation cannot be enabled by the application. Instead,
    run the debug build of the application in XCode. Generating a
    \c{.xcodeproj} file via \c{qmake -spec macx-xcode} provides a convenient
    way to enable this.

    \note QRhiSwapChain can only target QWindow instances that have their
    surface type set to QSurface::MetalSurface.

    \section2 Working with existing Metal devices

    When interoperating with another graphics engine, it may be necessary to
    get a QRhi instance that uses the same Metal device. This can be achieved
95
96
97
    by passing a pointer to a QRhiMetalNativeHandles to QRhi::create(). The
    device must be set to a non-null value then. Optionally, a command queue
    object can be specified as well.
Laszlo Agocs's avatar
Laszlo Agocs committed
98
99

    The QRhi does not take ownership of any of the external objects.
Laszlo Agocs's avatar
Laszlo Agocs committed
100
101
102
103
104
 */

/*!
    \class QRhiMetalNativeHandles
    \inmodule QtRhi
Laszlo Agocs's avatar
Laszlo Agocs committed
105
    \brief Holds the Metal device used by the QRhi.
106
107
108
109

    \note The class uses \c{void *} as the type since including the Objective C
    headers is not acceptable here. The actual types are \c{id<MTLDevice>} and
    \c{id<MTLCommandQueue>}.
Laszlo Agocs's avatar
Laszlo Agocs committed
110
111
112
113
114
 */

/*!
    \class QRhiMetalTextureNativeHandles
    \inmodule QtRhi
Laszlo Agocs's avatar
Laszlo Agocs committed
115
    \brief Holds the Metal texture object that is backing a QRhiTexture instance.
116
117
118

    \note The class uses \c{void *} as the type since including the Objective C
    headers is not acceptable here. The actual type is \c{id<MTLTexture>}.
Laszlo Agocs's avatar
Laszlo Agocs committed
119
120
 */

121
122
struct QRhiMetalData
{
123
124
    QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }

125
126
    id<MTLDevice> dev = nil;
    id<MTLCommandQueue> cmdQueue = nil;
127
128
129

    MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
                                                     const QRhiColorClearValue &colorClearValue,
130
131
                                                     const QRhiDepthStencilClearValue &depthStencilClearValue,
                                                     int colorAttCount);
132
133
    id<MTLLibrary> createMetalLib(const QBakedShader &shader, QBakedShaderKey::ShaderVariant shaderVariant,
                                  QString *error, QByteArray *entryPoint);
134
135
136
137
138
    id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);

    struct DeferredReleaseEntry {
        enum Type {
            Buffer,
Laszlo Agocs's avatar
Laszlo Agocs committed
139
            RenderBuffer,
Laszlo Agocs's avatar
Laszlo Agocs committed
140
            Texture,
141
142
            Sampler,
            StagingBuffer
143
144
145
146
147
148
149
        };
        Type type;
        int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
        union {
            struct {
                id<MTLBuffer> buffers[QMTL_FRAMES_IN_FLIGHT];
            } buffer;
Laszlo Agocs's avatar
Laszlo Agocs committed
150
151
152
            struct {
                id<MTLTexture> texture;
            } renderbuffer;
Laszlo Agocs's avatar
Laszlo Agocs committed
153
154
            struct {
                id<MTLTexture> texture;
155
                id<MTLBuffer> stagingBuffers[QMTL_FRAMES_IN_FLIGHT];
Laszlo Agocs's avatar
Laszlo Agocs committed
156
            } texture;
Laszlo Agocs's avatar
Laszlo Agocs committed
157
158
159
            struct {
                id<MTLSamplerState> samplerState;
            } sampler;
160
161
162
            struct {
                id<MTLBuffer> buffer;
            } stagingBuffer;
163
164
165
        };
    };
    QVector<DeferredReleaseEntry> releaseQueue;
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

    struct OffscreenFrame {
        OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
        bool active = false;
        QMetalCommandBuffer cbWrapper;
    } ofr;

    struct ActiveReadback {
        int activeFrameSlot = -1;
        QRhiReadbackDescription desc;
        QRhiReadbackResult *result;
        id<MTLBuffer> buf;
        quint32 bufSize;
        QSize pixelSize;
        QRhiTexture::Format format;
    };
    QVector<ActiveReadback> activeReadbacks;
Laszlo Agocs's avatar
Laszlo Agocs committed
183
184
185

    API_AVAILABLE(macos(10.13), ios(11.0)) MTLCaptureManager *captureMgr;
    API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil;
186
187
};

188
189
190
Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE);

191
192
struct QMetalBufferData
{
Laszlo Agocs's avatar
Laszlo Agocs committed
193
    bool managed;
194
    id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT];
Laszlo Agocs's avatar
Laszlo Agocs committed
195
    QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
196
197
};

Laszlo Agocs's avatar
Laszlo Agocs committed
198
199
struct QMetalRenderBufferData
{
200
    MTLPixelFormat format;
Laszlo Agocs's avatar
Laszlo Agocs committed
201
202
203
    id<MTLTexture> tex = nil;
};

Laszlo Agocs's avatar
Laszlo Agocs committed
204
205
struct QMetalTextureData
{
206
    MTLPixelFormat format;
Laszlo Agocs's avatar
Laszlo Agocs committed
207
    id<MTLTexture> tex = nil;
208
    id<MTLBuffer> stagingBuf[QMTL_FRAMES_IN_FLIGHT];
209
    bool owns = true;
Laszlo Agocs's avatar
Laszlo Agocs committed
210
211
};

Laszlo Agocs's avatar
Laszlo Agocs committed
212
213
214
215
216
struct QMetalSamplerData
{
    id<MTLSamplerState> samplerState = nil;
};

217
218
219
struct QMetalCommandBufferData
{
    id<MTLCommandBuffer> cb;
220
221
    id<MTLRenderCommandEncoder> currentPassEncoder;
    MTLRenderPassDescriptor *currentPassRpDesc;
222
223
224
    int currentFirstVertexBinding;
    QRhiBatchedBindings<id<MTLBuffer> > currentVertexInputsBuffers;
    QRhiBatchedBindings<NSUInteger> currentVertexInputOffsets;
225
226
227
228
229
};

struct QMetalRenderTargetData
{
    QSize pixelSize;
Laszlo Agocs's avatar
Laszlo Agocs committed
230
    float dpr = 1;
231
    int sampleCount = 1;
232
233
    int colorAttCount = 0;
    int dsAttCount = 0;
234
235

    struct ColorAtt {
Laszlo Agocs's avatar
Laszlo Agocs committed
236
        bool needsDrawableForTex = false;
237
238
239
        id<MTLTexture> tex = nil;
        int layer = 0;
        int level = 0;
Laszlo Agocs's avatar
Laszlo Agocs committed
240
        bool needsDrawableForResolveTex = false;
241
242
243
244
245
        id<MTLTexture> resolveTex = nil;
        int resolveLayer = 0;
        int resolveLevel = 0;
    };

246
    struct {
247
        ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
248
        id<MTLTexture> dsTex = nil;
Laszlo Agocs's avatar
Laszlo Agocs committed
249
        bool hasStencil = false;
250
        bool depthNeedsStore = false;
251
    } fb;
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
};

struct QMetalGraphicsPipelineData
{
    id<MTLRenderPipelineState> ps = nil;
    id<MTLDepthStencilState> ds = nil;
    MTLPrimitiveType primitiveType;
    MTLWinding winding;
    MTLCullMode cullMode;
    id<MTLLibrary> vsLib = nil;
    id<MTLFunction> vsFunc = nil;
    id<MTLLibrary> fsLib = nil;
    id<MTLFunction> fsFunc = nil;
};

struct QMetalSwapChainData
{
    CAMetalLayer *layer = nullptr;
    id<CAMetalDrawable> curDrawable;
Laszlo Agocs's avatar
Laszlo Agocs committed
271
    dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT];
272
    MTLRenderPassDescriptor *rp = nullptr;
273
    id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
274
    QRhiTexture::Format rhiColorFormat;
275
    MTLPixelFormat colorFormat;
276
277
};

278
QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
279
{
Laszlo Agocs's avatar
Laszlo Agocs committed
280
281
    Q_UNUSED(params);

282
    d = new QRhiMetalData(this);
283

284
    importedDevice = importDevice != nullptr;
285
    if (importedDevice) {
286
287
288
289
290
291
292
293
294
        if (d->dev) {
            d->dev = (id<MTLDevice>) importDevice->dev;
            importedCmdQueue = importDevice->cmdQueue != nullptr;
            if (importedCmdQueue)
                d->cmdQueue = (id<MTLCommandQueue>) importDevice->cmdQueue;
        } else {
            qWarning("No MTLDevice given, cannot import");
            importedDevice = false;
        }
295
296
297
298
299
300
301
302
303
304
305
306
307
    }
}

QRhiMetal::~QRhiMetal()
{
    delete d;
}

static inline uint aligned(uint v, uint byteAlign)
{
    return (v + byteAlign - 1) & ~(byteAlign - 1);
}

308
bool QRhiMetal::create(QRhi::Flags flags)
309
{
310
311
    Q_UNUSED(flags);

312
    if (importedDevice)
313
        [d->dev retain];
314
315
    else
        d->dev = MTLCreateSystemDefaultDevice();
316
317
318

    qDebug("Metal device: %s", qPrintable(QString::fromNSString([d->dev name])));

319
    if (importedCmdQueue)
320
        [d->cmdQueue retain];
321
    else
322
        d->cmdQueue = [d->dev newCommandQueue];
323

Laszlo Agocs's avatar
Laszlo Agocs committed
324
325
    if (@available(macOS 10.13, iOS 11.0, *)) {
        d->captureMgr = [MTLCaptureManager sharedCaptureManager];
326
327
328
        // Have a custom capture scope as well which then shows up in XCode as
        // an option when capturing, and becomes especially useful when having
        // multiple windows with multiple QRhis.
Laszlo Agocs's avatar
Laszlo Agocs committed
329
        d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
330
331
        const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
        d->captureScope.label = label.toNSString();
Laszlo Agocs's avatar
Laszlo Agocs committed
332
333
    }

334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
#if defined(Q_OS_MACOS)
    caps.maxTextureSize = 16384;
#elif defined(Q_OS_TVOS)
    if ([d->dev supportsFeatureSet: MTLFeatureSet(30003)]) // MTLFeatureSet_tvOS_GPUFamily2_v1
        caps.maxTextureSize = 16384;
    else
        caps.maxTextureSize = 8192;
#elif defined(Q_OS_IOS)
    // welcome to feature set hell
    if ([d->dev supportsFeatureSet: MTLFeatureSet(16)] // MTLFeatureSet_iOS_GPUFamily5_v1
            || [d->dev supportsFeatureSet: MTLFeatureSet(11)] // MTLFeatureSet_iOS_GPUFamily4_v1
            || [d->dev supportsFeatureSet: MTLFeatureSet(4)]) // MTLFeatureSet_iOS_GPUFamily3_v1
    {
        caps.maxTextureSize = 16384;
    } else if ([d->dev supportsFeatureSet: MTLFeatureSet(3)] // MTLFeatureSet_iOS_GPUFamily2_v2
            || [d->dev supportsFeatureSet: MTLFeatureSet(2)]) // MTLFeatureSet_iOS_GPUFamily1_v2
    {
        caps.maxTextureSize = 8192;
    } else {
        caps.maxTextureSize = 4096;
    }
#endif

357
358
359
    nativeHandlesStruct.dev = d->dev;
    nativeHandlesStruct.cmdQueue = d->cmdQueue;

360
    return true;
361
362
363
364
365
}

void QRhiMetal::destroy()
{
    executeDeferredReleases(true);
366
    finishActiveReadbacks(true);
367

Laszlo Agocs's avatar
Laszlo Agocs committed
368
    if (@available(macOS 10.13, iOS 11.0, *)) {
369
370
        [d->captureScope release];
        d->captureScope = nil;
Laszlo Agocs's avatar
Laszlo Agocs committed
371
372
    }

373
374
    [d->cmdQueue release];
    if (!importedCmdQueue)
375
376
        d->cmdQueue = nil;

377
378
    [d->dev release];
    if (!importedDevice)
379
380
381
382
383
        d->dev = nil;
}

QVector<int> QRhiMetal::supportedSampleCounts() const
{
384
385
386
387
388
389
390
391
392
393
394
395
    return { 1, 2, 4, 8 };
}

int QRhiMetal::effectiveSampleCount(int sampleCount) const
{
    // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
    const int s = qBound(1, sampleCount, 64);
    if (!supportedSampleCounts().contains(s)) {
        qWarning("Attempted to set unsupported sample count %d", sampleCount);
        return 1;
    }
    return s;
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
}

QRhiSwapChain *QRhiMetal::createSwapChain()
{
    return new QMetalSwapChain(this);
}

QRhiBuffer *QRhiMetal::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
{
    return new QMetalBuffer(this, type, usage, size);
}

int QRhiMetal::ubufAlignment() const
{
    return 256;
}

bool QRhiMetal::isYUpInFramebuffer() const
{
    return false;
}

Laszlo Agocs's avatar
Laszlo Agocs committed
418
419
420
421
422
bool QRhiMetal::isYUpInNDC() const
{
    return true;
}

Laszlo Agocs's avatar
Laszlo Agocs committed
423
424
425
426
427
bool QRhiMetal::isClipDepthZeroToOne() const
{
    return true;
}

428
429
QMatrix4x4 QRhiMetal::clipSpaceCorrMatrix() const
{
Laszlo Agocs's avatar
Laszlo Agocs committed
430
431
432
433
434
435
436
437
438
439
    // depth range 0..1
    static QMatrix4x4 m;
    if (m.isIdentity()) {
        // NB the ctor takes row-major
        m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
                       0.0f, 1.0f, 0.0f, 0.0f,
                       0.0f, 0.0f, 0.5f, 0.5f,
                       0.0f, 0.0f, 0.0f, 1.0f);
    }
    return m;
440
441
}

442
bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
443
{
444
445
    Q_UNUSED(flags);

446
447
448
449
450
#ifdef Q_OS_MACOS
    if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
        return false;
    if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12)
        return false;
Laszlo Agocs's avatar
Laszlo Agocs committed
451
452
453
#else
    if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
        return false;
454
#endif
455
456
457
458

    return true;
}

Laszlo Agocs's avatar
Laszlo Agocs committed
459
460
461
462
bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
{
    switch (feature) {
    case QRhi::MultisampleTexture:
463
        return true;
Laszlo Agocs's avatar
Laszlo Agocs committed
464
    case QRhi::MultisampleRenderBuffer:
465
        return true;
466
    case QRhi::DebugMarkers:
Laszlo Agocs's avatar
Laszlo Agocs committed
467
        return true;
468
469
    case QRhi::Timestamps:
        return false;
470
471
472
473
    case QRhi::Instancing:
        return true;
    case QRhi::CustomInstanceStepRate:
        return true;
474
475
    case QRhi::PrimitiveRestart:
        return true;
476
477
478
479
    case QRhi::GeometryShaders:
        return false;
    case QRhi::TessellationShaders:
        return false; // for now
480
481
    case QRhi::NonDynamicUniformBuffers:
        return true;
482
483
    case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
        return false;
484
485
    case QRhi::NPOTTextureRepeat:
        return true;
486
487
    case QRhi::RedOrAlpha8IsRed:
        return true;
Laszlo Agocs's avatar
Laszlo Agocs committed
488
489
490
491
492
493
    default:
        Q_UNREACHABLE();
        return false;
    }
}

Laszlo Agocs's avatar
Laszlo Agocs committed
494
int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
495
496
497
498
499
{
    switch (limit) {
    case QRhi::TextureSizeMin:
        return 1;
    case QRhi::TextureSizeMax:
500
        return caps.maxTextureSize;
Laszlo Agocs's avatar
Laszlo Agocs committed
501
502
    case QRhi::MaxColorAttachments:
        return 8;
503
504
505
506
507
508
    default:
        Q_UNREACHABLE();
        return 0;
    }
}

509
const QRhiNativeHandles *QRhiMetal::nativeHandles()
510
511
512
513
{
    return &nativeHandlesStruct;
}

514
QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
Laszlo Agocs's avatar
Laszlo Agocs committed
515
                                                int sampleCount, QRhiRenderBuffer::Flags flags)
516
{
Laszlo Agocs's avatar
Laszlo Agocs committed
517
    return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags);
518
519
}

Laszlo Agocs's avatar
Laszlo Agocs committed
520
521
QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
                                      int sampleCount, QRhiTexture::Flags flags)
522
{
Laszlo Agocs's avatar
Laszlo Agocs committed
523
    return new QMetalTexture(this, format, pixelSize, sampleCount, flags);
524
525
526
527
}

QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
                                      QRhiSampler::Filter mipmapMode,
528
                                      QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
529
{
Laszlo Agocs's avatar
Laszlo Agocs committed
530
    return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v);
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
}

QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
                                                              QRhiTextureRenderTarget::Flags flags)
{
    return new QMetalTextureRenderTarget(this, desc, flags);
}

QRhiGraphicsPipeline *QRhiMetal::createGraphicsPipeline()
{
    return new QMetalGraphicsPipeline(this);
}

QRhiShaderResourceBindings *QRhiMetal::createShaderResourceBindings()
{
    return new QMetalShaderResourceBindings(this);
}

549
550
551
void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD,
                                              const QVector<QRhiCommandBuffer::DynamicOffset> &dynamicOffsets,
                                              bool offsetOnlyChange)
Laszlo Agocs's avatar
Laszlo Agocs committed
552
{
553
554
555
556
557
558
559
560
    static const int KNOWN_STAGES = 2;
    struct {
        QRhiBatchedBindings<id<MTLBuffer> > buffers;
        QRhiBatchedBindings<NSUInteger> bufferOffsets;
        QRhiBatchedBindings<id<MTLTexture> > textures;
        QRhiBatchedBindings<id<MTLSamplerState> > samplers;
    } res[KNOWN_STAGES];

561
562
563
    for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
        const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
        switch (b->type) {
Laszlo Agocs's avatar
Laszlo Agocs committed
564
565
        case QRhiShaderResourceBinding::UniformBuffer:
        {
566
            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
Laszlo Agocs's avatar
Laszlo Agocs committed
567
            id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];
568
569
570
571
572
573
574
575
576
            uint offset = b->u.ubuf.offset;
            if (!dynamicOffsets.isEmpty()) {
                for (const QRhiCommandBuffer::DynamicOffset &dynOfs : dynamicOffsets) {
                    if (dynOfs.first == b->binding) {
                        offset = dynOfs.second;
                        break;
                    }
                }
            }
577
578
            if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
                res[0].buffers.feed(b->binding, mtlbuf);
579
                res[0].bufferOffsets.feed(b->binding, offset);
580
            }
581
582
            if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
                res[1].buffers.feed(b->binding, mtlbuf);
583
                res[1].bufferOffsets.feed(b->binding, offset);
584
            }
Laszlo Agocs's avatar
Laszlo Agocs committed
585
586
587
588
        }
            break;
        case QRhiShaderResourceBinding::SampledTexture:
        {
589
590
591
592
593
            QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
            QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
            if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
                res[0].textures.feed(b->binding, texD->d->tex);
                res[0].samplers.feed(b->binding, samplerD->d->samplerState);
Laszlo Agocs's avatar
Laszlo Agocs committed
594
            }
595
596
597
            if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
                res[1].textures.feed(b->binding, texD->d->tex);
                res[1].samplers.feed(b->binding, samplerD->d->samplerState);
Laszlo Agocs's avatar
Laszlo Agocs committed
598
599
600
601
602
603
604
605
            }
        }
            break;
        default:
            Q_UNREACHABLE();
            break;
        }
    }
606
607
608
609

    for (int idx = 0; idx < KNOWN_STAGES; ++idx) {
        res[idx].buffers.finish();
        res[idx].bufferOffsets.finish();
610

611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
        for (int i = 0, ie = res[idx].buffers.batches.count(); i != ie; ++i) {
            const auto &bufferBatch(res[idx].buffers.batches[i]);
            const auto &offsetBatch(res[idx].bufferOffsets.batches[i]);
            switch (idx) {
            case 0:
                [cbD->d->currentPassEncoder setVertexBuffers: bufferBatch.resources.constData()
                  offsets: offsetBatch.resources.constData()
                  withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
                break;
            case 1:
                [cbD->d->currentPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
                  offsets: offsetBatch.resources.constData()
                  withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
                break;
            default:
                Q_UNREACHABLE();
                break;
            }
        }
630
631
632
633
634
635
636

        if (offsetOnlyChange)
            continue;

        res[idx].textures.finish();
        res[idx].samplers.finish();

637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
        for (int i = 0, ie = res[idx].textures.batches.count(); i != ie; ++i) {
            const auto &batch(res[idx].textures.batches[i]);
            switch (idx) {
            case 0:
                [cbD->d->currentPassEncoder setVertexTextures: batch.resources.constData()
                  withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
                break;
            case 1:
                [cbD->d->currentPassEncoder setFragmentTextures: batch.resources.constData()
                  withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
                break;
            default:
                Q_UNREACHABLE();
                break;
            }
        }
        for (int i = 0, ie = res[idx].samplers.batches.count(); i != ie; ++i) {
            const auto &batch(res[idx].samplers.batches[i]);
            switch (idx) {
            case 0:
                [cbD->d->currentPassEncoder setVertexSamplerStates: batch.resources.constData()
                  withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
                break;
            case 1:
                [cbD->d->currentPassEncoder setFragmentSamplerStates: batch.resources.constData()
                  withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
                break;
            default:
                Q_UNREACHABLE();
                break;
            }
        }
    }
Laszlo Agocs's avatar
Laszlo Agocs committed
670
671
}

672
void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
673
674
675
{
    Q_ASSERT(inPass);

676
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
Laszlo Agocs's avatar
Laszlo Agocs committed
677
    QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps);
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698

    if (cbD->currentPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
        cbD->currentPipeline = ps;
        cbD->currentPipelineGeneration = psD->generation;

        [cbD->d->currentPassEncoder setRenderPipelineState: psD->d->ps];
        [cbD->d->currentPassEncoder setDepthStencilState: psD->d->ds];
        [cbD->d->currentPassEncoder setCullMode: psD->d->cullMode];
        [cbD->d->currentPassEncoder setFrontFacingWinding: psD->d->winding];
    }

    psD->lastActiveFrameSlot = currentFrameSlot;
}

void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
                                   const QVector<QRhiCommandBuffer::DynamicOffset> &dynamicOffsets)
{
    Q_ASSERT(inPass);

    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
    Q_ASSERT(cbD->currentPipeline);
699
    if (!srb)
700
        srb = QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->m_shaderResourceBindings;
701
702

    QMetalShaderResourceBindings *srbD = QRHI_RES(QMetalShaderResourceBindings, srb);
Laszlo Agocs's avatar
Laszlo Agocs committed
703
    bool hasSlottedResourceInSrb = false;
704
    bool hasDynamicOffsetInSrb = false;
Laszlo Agocs's avatar
Laszlo Agocs committed
705
706
    bool resNeedsRebind = false;

Laszlo Agocs's avatar
Laszlo Agocs committed
707
    // do buffer writes, figure out if we need to rebind, and mark as in-use
Laszlo Agocs's avatar
Laszlo Agocs committed
708
    for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
709
        const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
Laszlo Agocs's avatar
Laszlo Agocs committed
710
        QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
711
        switch (b->type) {
712
        case QRhiShaderResourceBinding::UniformBuffer:
713
        {
714
            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
Laszlo Agocs's avatar
Laszlo Agocs committed
715
            Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
Laszlo Agocs's avatar
Laszlo Agocs committed
716
717
            executeBufferHostWritesForCurrentFrame(bufD);
            if (bufD->m_type != QRhiBuffer::Immutable)
Laszlo Agocs's avatar
Laszlo Agocs committed
718
                hasSlottedResourceInSrb = true;
719
720
            if (b->u.ubuf.hasDynamicOffset)
                hasDynamicOffsetInSrb = true;
Laszlo Agocs's avatar
Laszlo Agocs committed
721
            if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
Laszlo Agocs's avatar
Laszlo Agocs committed
722
                resNeedsRebind = true;
Laszlo Agocs's avatar
Laszlo Agocs committed
723
                bd.ubuf.id = bufD->m_id;
Laszlo Agocs's avatar
Laszlo Agocs committed
724
725
                bd.ubuf.generation = bufD->generation;
            }
726
727
728
            bufD->lastActiveFrameSlot = currentFrameSlot;
        }
            break;
729
        case QRhiShaderResourceBinding::SampledTexture:
Laszlo Agocs's avatar
Laszlo Agocs committed
730
        {
731
732
            QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
            QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
Laszlo Agocs's avatar
Laszlo Agocs committed
733
            if (texD->generation != bd.stex.texGeneration
Laszlo Agocs's avatar
Laszlo Agocs committed
734
735
736
                    || texD->m_id != bd.stex.texId
                    || samplerD->generation != bd.stex.samplerGeneration
                    || samplerD->m_id != bd.stex.samplerId)
Laszlo Agocs's avatar
Laszlo Agocs committed
737
738
            {
                resNeedsRebind = true;
Laszlo Agocs's avatar
Laszlo Agocs committed
739
                bd.stex.texId = texD->m_id;
Laszlo Agocs's avatar
Laszlo Agocs committed
740
                bd.stex.texGeneration = texD->generation;
Laszlo Agocs's avatar
Laszlo Agocs committed
741
                bd.stex.samplerId = samplerD->m_id;
Laszlo Agocs's avatar
Laszlo Agocs committed
742
                bd.stex.samplerGeneration = samplerD->generation;
Laszlo Agocs's avatar
Laszlo Agocs committed
743
            }
Laszlo Agocs's avatar
Laszlo Agocs committed
744
745
            texD->lastActiveFrameSlot = currentFrameSlot;
            samplerD->lastActiveFrameSlot = currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
746
        }
747
748
749
750
751
752
753
            break;
        default:
            Q_UNREACHABLE();
            break;
        }
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
754
    // make sure the resources for the correct slot get bound
755
    const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
Laszlo Agocs's avatar
Laszlo Agocs committed
756
757
758
    if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
        resNeedsRebind = true;

759
    const bool srbChange = cbD->currentSrb != srb || cbD->currentSrbGeneration != srbD->generation;
760

761
762
    // dynamic uniform buffer offsets always trigger a rebind
    if (hasDynamicOffsetInSrb || resNeedsRebind || srbChange) {
Laszlo Agocs's avatar
Laszlo Agocs committed
763
764
765
        cbD->currentSrb = srb;
        cbD->currentSrbGeneration = srbD->generation;
        cbD->currentResSlot = resSlot;
766

767
768
        const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChange;
        enqueueShaderResourceBindings(srbD, cbD, dynamicOffsets, offsetOnlyChange);
Laszlo Agocs's avatar
Laszlo Agocs committed
769
    }
770
771
}

772
773
void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb, int startBinding, const QVector<QRhiCommandBuffer::VertexInput> &bindings,
                               QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
774
775
776
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
777
    Q_ASSERT(cbD->currentPipeline);
Laszlo Agocs's avatar
Laszlo Agocs committed
778

779
780
    QRhiBatchedBindings<id<MTLBuffer> > buffers;
    QRhiBatchedBindings<NSUInteger> offsets;
781
782
783
    for (int i = 0; i < bindings.count(); ++i) {
        QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, bindings[i].first);
        executeBufferHostWritesForCurrentFrame(bufD);
Laszlo Agocs's avatar
Laszlo Agocs committed
784
        bufD->lastActiveFrameSlot = currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
785
        id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];
786
787
788
789
790
791
792
        buffers.feed(startBinding + i, mtlbuf);
        offsets.feed(startBinding + i, bindings[i].second);
    }
    buffers.finish();
    offsets.finish();

    // same binding space for vertex and constant buffers - work it around
793
794
795
796
797
798
799
    QRhiShaderResourceBindings *srb = cbD->currentSrb;
    // There's nothing guaranteeing setShaderResources() was called before
    // setVertexInput()... but whatever srb will get bound will have to be
    // layout-compatible anyways so maxBinding is the same.
    if (!srb)
        srb = cbD->currentPipeline->shaderResourceBindings();
    const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, srb)->maxBinding + 1;
800

801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
    if (firstVertexBinding != cbD->d->currentFirstVertexBinding
            || buffers != cbD->d->currentVertexInputsBuffers
            || offsets != cbD->d->currentVertexInputOffsets)
    {
        cbD->d->currentFirstVertexBinding = firstVertexBinding;
        cbD->d->currentVertexInputsBuffers = buffers;
        cbD->d->currentVertexInputOffsets = offsets;

        for (int i = 0, ie = buffers.batches.count(); i != ie; ++i) {
            const auto &bufferBatch(buffers.batches[i]);
            const auto &offsetBatch(offsets.batches[i]);
            [cbD->d->currentPassEncoder setVertexBuffers:
                bufferBatch.resources.constData()
              offsets: offsetBatch.resources.constData()
              withRange: NSMakeRange(firstVertexBinding + bufferBatch.startBinding, bufferBatch.resources.count())];
        }
817
    }
Laszlo Agocs's avatar
Laszlo Agocs committed
818
819
820
821
822

    if (indexBuf) {
        QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
        executeBufferHostWritesForCurrentFrame(ibufD);
        ibufD->lastActiveFrameSlot = currentFrameSlot;
823
824
825
        cbD->currentIndexBuffer = indexBuf;
        cbD->currentIndexOffset = indexOffset;
        cbD->currentIndexFormat = indexFormat;
Laszlo Agocs's avatar
Laszlo Agocs committed
826
    } else {
827
        cbD->currentIndexBuffer = nullptr;
Laszlo Agocs's avatar
Laszlo Agocs committed
828
    }
829
830
831
832
833
834
}

static inline MTLViewport toMetalViewport(const QRhiViewport &viewport, const QSize &outputSize)
{
    // x,y is top-left in MTLViewport but bottom-left in QRhiViewport
    MTLViewport vp;
Laszlo Agocs's avatar
Laszlo Agocs committed
835
836
837
838
839
840
841
    const QVector4D r = viewport.viewport();
    vp.originX = r.x();
    vp.originY = outputSize.height() - (r.y() + r.w());
    vp.width = r.z();
    vp.height = r.w();
    vp.znear = viewport.minDepth();
    vp.zfar = viewport.maxDepth();
842
843
844
845
846
847
848
    return vp;
}

static inline MTLScissorRect toMetalScissor(const QRhiScissor &scissor, const QSize &outputSize)
{
    // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
    MTLScissorRect s;
Laszlo Agocs's avatar
Laszlo Agocs committed
849
850
851
852
853
    const QVector4D r = scissor.scissor();
    s.x = r.x();
    s.y = outputSize.height() - (r.y() + r.w());
    s.width = r.z();
    s.height = r.w();
854
855
856
    return s;
}

857
858
859
860
861
void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
    Q_ASSERT(cbD->currentPipeline && cbD->currentTarget);
862
    const QSize outputSize = cbD->currentTarget->pixelSize();
863
864
865
866
867
868
869
870
871
872
    const MTLViewport vp = toMetalViewport(viewport, outputSize);
    [cbD->d->currentPassEncoder setViewport: vp];

    if (!QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
        const QVector4D v = viewport.viewport();
        const MTLScissorRect s = toMetalScissor(QRhiScissor(v.x(), v.y(), v.z(), v.w()), outputSize);
        [cbD->d->currentPassEncoder setScissorRect: s];
    }
}

873
874
875
876
877
void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
    Q_ASSERT(cbD->currentPipeline && cbD->currentTarget);
878
    Q_ASSERT(QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
879
    const QSize outputSize = cbD->currentTarget->pixelSize();
880
    const MTLScissorRect s = toMetalScissor(scissor, outputSize);
881
    [cbD->d->currentPassEncoder setScissorRect: s];
882
883
884
885
886
887
}

void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QVector4D &c)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
888
    [cbD->d->currentPassEncoder setBlendColorRed: c.x() green: c.y() blue: c.z() alpha: c.w()];
889
890
891
892
893
894
}

void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
895
    [cbD->d->currentPassEncoder setStencilReferenceValue: refValue];
896
897
898
899
900
901
902
}

void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
                     quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
903
    [cbD->d->currentPassEncoder drawPrimitives:
904
905
906
907
908
909
910
911
912
        QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->d->primitiveType
      vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
}

void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
                            quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
913
    if (!cbD->currentIndexBuffer)
Laszlo Agocs's avatar
Laszlo Agocs committed
914
915
        return;

916
    const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
Laszlo Agocs's avatar
Laszlo Agocs committed
917
918
    Q_ASSERT(indexOffset == aligned(indexOffset, 4));

919
    QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, cbD->currentIndexBuffer);
Laszlo Agocs's avatar
Laszlo Agocs committed
920
921
    id<MTLBuffer> mtlbuf = ibufD->d->buf[ibufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];

922
    [cbD->d->currentPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->d->primitiveType
Laszlo Agocs's avatar
Laszlo Agocs committed
923
      indexCount: indexCount
924
      indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
Laszlo Agocs's avatar
Laszlo Agocs committed
925
926
927
928
929
      indexBuffer: mtlbuf
      indexBufferOffset: indexOffset
      instanceCount: instanceCount
      baseVertex: vertexOffset
      baseInstance: firstInstance];
930
931
}

932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
void QRhiMetal::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
{
    if (!debugMarkers)
        return;

    NSString *str = [NSString stringWithUTF8String: name.constData()];
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
    if (inPass) {
        [cbD->d->currentPassEncoder pushDebugGroup: str];
    } else {
        if (@available(macOS 10.13, iOS 11.0, *))
            [cbD->d->cb pushDebugGroup: str];
    }
}

void QRhiMetal::debugMarkEnd(QRhiCommandBuffer *cb)
{
    if (!debugMarkers)
        return;

    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
    if (inPass) {
        [cbD->d->currentPassEncoder popDebugGroup];
    } else {
        if (@available(macOS 10.13, iOS 11.0, *))
            [cbD->d->cb popDebugGroup];
    }
}

void QRhiMetal::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
{
    if (!debugMarkers)
        return;

    if (inPass) {
        QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
        [cbD->d->currentPassEncoder insertDebugSignpost: [NSString stringWithUTF8String: msg.constData()]];
    }
}

972
QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
973
{
974
    Q_UNUSED(flags);
975
976
977
978
979
    Q_ASSERT(!inFrame);
    inFrame = true;

    QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);

980
    // This is a bit messed up since for this swapchain we want to wait for the
Laszlo Agocs's avatar
Laszlo Agocs committed
981
    // commands+present to complete, while for others just for the commands
982
983
    // (for this same frame slot) but not sure how to do that in a sane way so
    // wait for full cb completion for now.
Laszlo Agocs's avatar
Laszlo Agocs committed
984
    for (QMetalSwapChain *sc : qAsConst(swapchains)) {
985
        dispatch_semaphore_t sem = sc->d->sem[swapChainD->currentFrameSlot];
Laszlo Agocs's avatar
Laszlo Agocs committed
986
987
988
989
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        if (sc != swapChainD)
            dispatch_semaphore_signal(sem);
    }
990

991
    currentSwapChain = swapChainD;
992
    currentFrameSlot = swapChainD->currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
993
994
    if (swapChainD->ds)
        swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
995

Laszlo Agocs's avatar
Laszlo Agocs committed
996
997
998
    if (@available(macOS 10.13, iOS 11.0, *))
        [d->captureScope beginScope];

999
1000
1001
    // Do not let the command buffer mess with the refcount of objects. We do
    // have a proper render loop and will manage lifetimes similarly to other
    // backends (Vulkan).
Laszlo Agocs's avatar
Laszlo Agocs committed
1002
    swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
1003

Laszlo Agocs's avatar
Laszlo Agocs committed
1004
    QMetalRenderTargetData::ColorAtt colorAtt;
1005
    if (swapChainD->samples > 1) {
Laszlo Agocs's avatar
Laszlo Agocs committed
1006
1007
1008
1009
        colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
        colorAtt.needsDrawableForResolveTex = true;
    } else {
        colorAtt.needsDrawableForTex = true;
1010
1011
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
1012
    swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
1013
    swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
Laszlo Agocs's avatar
Laszlo Agocs committed
1014
    swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
1015
    swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
1016

1017
1018
1019
    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
    QRHI_PROF_F(beginSwapChainFrame(swapChain));

1020
    executeDeferredReleases();
1021
1022
    swapChainD->cbWrapper.resetState();
    finishActiveReadbacks();
1023
1024
1025
1026

    return QRhi::FrameOpSuccess;
}

1027
QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
1028
1029
1030
1031
1032
{
    Q_ASSERT(inFrame);
    inFrame = false;

    QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
1033
    Q_ASSERT(currentSwapChain == swapChainD);
1034

1035
1036
1037
    const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
    if (needsPresent)
        [swapChainD->cbWrapper.d->cb presentDrawable: swapChainD->d->curDrawable];
1038

1039
    __block int thisFrameSlot = currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
1040
    [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer>) {
1041
        dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
1042
1043
    }];

Laszlo Agocs's avatar
Laszlo Agocs committed
1044
    [swapChainD->cbWrapper.d->cb commit];
1045

1046
1047
1048
    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
    QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));

Laszlo Agocs's avatar
Laszlo Agocs committed
1049
1050
1051
    if (@available(macOS 10.13, iOS 11.0, *))
        [d->captureScope endScope];

1052
1053
    if (needsPresent)
        swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
1054

1055
    swapChainD->frameCount += 1;
1056
    currentSwapChain = nullptr;
1057
1058
1059
    return QRhi::FrameOpSuccess;
}

1060
1061
QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb)
{
1062
1063
1064
1065
    Q_ASSERT(!inFrame);
    inFrame = true;

    currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
Laszlo Agocs's avatar
Laszlo Agocs committed
1066
1067
1068
1069
1070
1071
1072
    if (swapchains.count() > 1) {
        for (QMetalSwapChain *sc : qAsConst(swapchains)) {
            dispatch_semaphore_t sem = sc->d->sem[currentFrameSlot];
            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_signal(sem);
        }
    }
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082

    d->ofr.active = true;
    *cb = &d->ofr.cbWrapper;
    d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];

    executeDeferredReleases();
    d->ofr.cbWrapper.resetState();
    finishActiveReadbacks();

    return QRhi::FrameOpSuccess;
1083
1084
}

1085
QRhi::FrameOpResult QRhiMetal::endOffscreenFrame()
1086
{
1087
1088
1089
1090
1091
    Q_ASSERT(d->ofr.active);
    d->ofr.active = false;
    Q_ASSERT(inFrame);
    inFrame = false;

Laszlo Agocs's avatar
Laszlo Agocs committed
1092
    [d->ofr.cbWrapper.d->cb commit];
1093
1094
1095
1096
1097
1098
1099

    // offscreen frames wait for completion, unlike swapchain ones
    [d->ofr.cbWrapper.d->cb waitUntilCompleted];

    finishActiveReadbacks(true);

    return QRhi::FrameOpSuccess;
1100
1101
}

1102
1103
1104
1105
QRhi::FrameOpResult QRhiMetal::finish()
{
    Q_ASSERT(!inPass);

Laszlo Agocs's avatar
Laszlo Agocs committed
1106
1107
1108
1109
1110
1111
    QMetalSwapChain *swapChainD = nullptr;
    if (inFrame) {
        id<MTLCommandBuffer> cb;
        if (d->ofr.active) {
            Q_ASSERT(!currentSwapChain);
            cb = d->ofr.cbWrapper.d->cb;
Laszlo Agocs's avatar
Laszlo Agocs committed
1112
1113
            [cb commit];
            [cb waitUntilCompleted];
Laszlo Agocs's avatar
Laszlo Agocs committed
1114
1115
1116
1117
        } else {
            Q_ASSERT(currentSwapChain);
            swapChainD = currentSwapChain;
            cb = swapChainD->cbWrapper.d->cb;
Laszlo Agocs's avatar
Laszlo Agocs committed
1118
            [cb commit];
Laszlo Agocs's avatar
Laszlo Agocs committed
1119
1120
1121
        }
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
    for (QMetalSwapChain *sc : qAsConst(swapchains)) {
        for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
            dispatch_semaphore_t sem = sc->d->sem[i];
            // wait+signal is the general pattern to ensure the commands for a
            // given frame slot have completed (if sem is 1, we go 0 then 1; if
            // sem is 0 we go -1, block, completion increments to 0, then us to 1)
            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_signal(sem);
        }
    }
Laszlo Agocs's avatar
Laszlo Agocs committed
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143

    if (inFrame) {
        if (d->ofr.active)
            d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
        else
            swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
    }

    executeDeferredReleases(true);

    finishActiveReadbacks(true);

1144
1145
1146
    return QRhi::FrameOpSuccess;
}

1147
1148
MTLRenderPassDescriptor *QRhiMetalData::createDefaultRenderPass(bool hasDepthStencil,
                                                                const QRhiColorClearValue &colorClearValue,
1149
1150
                                                                const QRhiDepthStencilClearValue &depthStencilClearValue,
                                                                int colorAttCount)
1151
1152
{
    MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];
Laszlo Agocs's avatar
Laszlo Agocs committed
1153
1154
    const QVector4D rgba = colorClearValue.rgba();
    MTLClearColor c = MTLClearColorMake(rgba.x(), rgba.y(), rgba.z(), rgba.w());
1155
1156
1157
1158
1159
1160

    for (int i = 0; i < colorAttCount; ++i) {
        rp.colorAttachments[i].loadAction = MTLLoadActionClear;
        rp.colorAttachments[i].storeAction = MTLStoreActionStore;
        rp.colorAttachments[i].clearColor = c;
    }
1161
1162
1163
1164
1165
1166

    if (hasDepthStencil) {
        rp.depthAttachment.loadAction = MTLLoadActionClear;
        rp.depthAttachment.storeAction = MTLStoreActionDontCare;
        rp.stencilAttachment.loadAction = MTLLoadActionClear;
        rp.stencilAttachment.storeAction = MTLStoreActionDontCare;
Laszlo Agocs's avatar
Laszlo Agocs committed
1167
1168
        rp.depthAttachment.clearDepth = depthStencilClearValue.depthClearValue();
        rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
1169
1170
1171
1172
1173
    }

    return rp;
}

1174
void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1175
{
Laszlo Agocs's avatar
Laszlo Agocs committed
1176
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1177
    QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
1178
    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1179
1180
1181

    for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
        QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
Laszlo Agocs's avatar
Laszlo Agocs committed
1182
        Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);