qrhimetal.mm 107 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
/****************************************************************************
**
** 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"
Laszlo Agocs's avatar
Laszlo Agocs committed
38
#include "qrhirsh_p.h"
39 40
#include <QGuiApplication>
#include <QWindow>
Laszlo Agocs's avatar
Laszlo Agocs committed
41
#include <qmath.h>
42 43 44 45 46 47 48 49
#include <QBakedShader>
#include <AppKit/AppKit.h>
#include <Metal/Metal.h>
#include <QuartzCore/CAMetalLayer.h>

QT_BEGIN_NAMESPACE

/*
Laszlo Agocs's avatar
Laszlo Agocs committed
50
    Metal backend. Double buffers and throttles to vsync. "Dynamic" buffers are
Laszlo Agocs's avatar
Laszlo Agocs committed
51 52 53
    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
54 55
    Textures are Private (device local) and a host visible staging buffer is
    used to upload data to them.
56 57 58 59 60 61
*/

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

Laszlo Agocs's avatar
Laszlo Agocs committed
62 63 64
// 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
65 66 67
//
// An exception is the nextDrawable Called Early blah blah warning, which is
// plain and simply false.
Laszlo Agocs's avatar
Laszlo Agocs committed
68

Laszlo Agocs's avatar
Laszlo Agocs committed
69 70 71
/*!
    \class QRhiMetalInitParams
    \inmodule QtRhi
Laszlo Agocs's avatar
Laszlo Agocs committed
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    \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
93 94 95
    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
96 97

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

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

    \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
108 109 110 111 112
 */

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

    \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
117 118
 */

119 120
struct QRhiMetalData
{
121 122
    QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }

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

    MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
                                                     const QRhiColorClearValue &colorClearValue,
                                                     const QRhiDepthStencilClearValue &depthStencilClearValue);
Laszlo Agocs's avatar
Laszlo Agocs committed
129
    id<MTLLibrary> createMetalLib(const QBakedShader &shader, QString *error, QByteArray *entryPoint);
130 131 132 133 134
    id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);

    struct DeferredReleaseEntry {
        enum Type {
            Buffer,
Laszlo Agocs's avatar
Laszlo Agocs committed
135
            RenderBuffer,
Laszlo Agocs's avatar
Laszlo Agocs committed
136
            Texture,
137 138
            Sampler,
            StagingBuffer
139 140 141 142 143 144 145
        };
        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
146 147 148
            struct {
                id<MTLTexture> texture;
            } renderbuffer;
Laszlo Agocs's avatar
Laszlo Agocs committed
149 150
            struct {
                id<MTLTexture> texture;
151
                id<MTLBuffer> stagingBuffers[QMTL_FRAMES_IN_FLIGHT];
Laszlo Agocs's avatar
Laszlo Agocs committed
152
            } texture;
Laszlo Agocs's avatar
Laszlo Agocs committed
153 154 155
            struct {
                id<MTLSamplerState> samplerState;
            } sampler;
156 157 158
            struct {
                id<MTLBuffer> buffer;
            } stagingBuffer;
159 160 161
        };
    };
    QVector<DeferredReleaseEntry> releaseQueue;
162

163 164
    static void executeDeferredReleasesOnRshNow(QVector<DeferredReleaseEntry> *rshRelQueue);

165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    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
181 182 183

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

186 187 188
Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE);

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

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

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

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

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

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

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

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

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
268
    dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT];
269
    MTLRenderPassDescriptor *rp = nullptr;
270
    id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
271
    QRhiTexture::Format rhiColorFormat;
272
    MTLPixelFormat colorFormat;
273 274
};

275
QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
276
{
277
    d = new QRhiMetalData(this);
278

Laszlo Agocs's avatar
Laszlo Agocs committed
279 280
    if (params->resourceSharingHost)
        rsh = QRhiResourceSharingHostPrivate::get(params->resourceSharingHost);
281 282

    importedDevice = importDevice != nullptr;
283
    if (importedDevice) {
284 285 286 287 288 289 290 291 292
        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;
        }
293 294 295 296 297 298 299 300 301 302 303 304 305
    }
}

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

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

306
bool QRhiMetal::create(QRhi::Flags flags)
307
{
308 309
    Q_UNUSED(flags);

Laszlo Agocs's avatar
Laszlo Agocs committed
310 311 312
    QMutexLocker lock(rsh ? &rsh->mtx : nullptr);

    if (importedDevice) {
313
        [d->dev retain];
Laszlo Agocs's avatar
Laszlo Agocs committed
314 315 316 317 318 319 320 321 322 323 324 325
    } else {
        if (!rsh || !rsh->d_metal.dev) {
            d->dev = MTLCreateSystemDefaultDevice();
            if (rsh) {
                rsh->d_metal.dev = d->dev;
                [d->dev retain];
            }
        } else {
            d->dev = (id<MTLDevice>) rsh->d_metal.dev;
            [d->dev retain];
        }
    }
326 327 328

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

Laszlo Agocs's avatar
Laszlo Agocs committed
329
    if (importedCmdQueue) {
330
        [d->cmdQueue retain];
Laszlo Agocs's avatar
Laszlo Agocs committed
331
    } else {
332 333 334 335 336
        // We could use the existing MTLCommandQueue when rsh is enabled. We
        // choose not to share the queue however, in order avoid performance
        // surprises with multiple windows and QRhis severly limiting each
        // other's rendering rate.
        d->cmdQueue = [d->dev newCommandQueue];
Laszlo Agocs's avatar
Laszlo Agocs committed
337
    }
338

Laszlo Agocs's avatar
Laszlo Agocs committed
339 340
    if (@available(macOS 10.13, iOS 11.0, *)) {
        d->captureMgr = [MTLCaptureManager sharedCaptureManager];
341 342 343
        // 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
344
        d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
345 346
        const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
        d->captureScope.label = label.toNSString();
Laszlo Agocs's avatar
Laszlo Agocs committed
347 348
    }

349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
#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

372 373 374
    nativeHandlesStruct.dev = d->dev;
    nativeHandlesStruct.cmdQueue = d->cmdQueue;

Laszlo Agocs's avatar
Laszlo Agocs committed
375 376 377 378 379 380
    if (rsh) {
        qDebug("Attached to QRhiResourceSharingHost %p, currently %d other QRhi instances on MTLDevice %p",
               rsh, rsh->rhiCount, (void *) d->dev);
        rsh->rhiCount += 1;
    }

381
    return true;
382 383 384 385 386
}

void QRhiMetal::destroy()
{
    executeDeferredReleases(true);
387
    finishActiveReadbacks(true);
388

Laszlo Agocs's avatar
Laszlo Agocs committed
389 390
    QMutexLocker lock(rsh ? &rsh->mtx : nullptr);

Laszlo Agocs's avatar
Laszlo Agocs committed
391
    if (@available(macOS 10.13, iOS 11.0, *)) {
392 393
        [d->captureScope release];
        d->captureScope = nil;
Laszlo Agocs's avatar
Laszlo Agocs committed
394 395
    }

396 397
    [d->cmdQueue release];
    if (!importedCmdQueue)
398 399
        d->cmdQueue = nil;

400 401
    [d->dev release];
    if (!importedDevice)
402
        d->dev = nil;
Laszlo Agocs's avatar
Laszlo Agocs committed
403 404 405

    if (rsh) {
        if (--rsh->rhiCount == 0) {
406 407 408 409 410
            if (rsh->d_metal.releaseQueue) {
                auto rshRelQueue = static_cast<QVector<QRhiMetalData::DeferredReleaseEntry> *>(rsh->d_metal.releaseQueue);
                QRhiMetalData::executeDeferredReleasesOnRshNow(rshRelQueue);
                delete rshRelQueue;
            }
Laszlo Agocs's avatar
Laszlo Agocs committed
411 412
            [(id<MTLDevice>) rsh->d_metal.dev release];
            rsh->d_metal.dev = nullptr;
413
            rsh->d_metal.releaseQueue = nullptr;
Laszlo Agocs's avatar
Laszlo Agocs committed
414 415
        }
    }
416 417 418 419
}

QVector<int> QRhiMetal::supportedSampleCounts() const
{
420 421 422 423 424 425 426 427 428 429 430 431
    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;
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
}

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;
}

QMatrix4x4 QRhiMetal::clipSpaceCorrMatrix() const
{
Laszlo Agocs's avatar
Laszlo Agocs committed
456 457 458 459 460 461 462 463 464 465
    // 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;
466 467
}

468
bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
469
{
470 471
    Q_UNUSED(flags);

472 473 474 475 476
#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
477 478 479
#else
    if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
        return false;
480
#endif
481 482 483 484

    return true;
}

Laszlo Agocs's avatar
Laszlo Agocs committed
485 486 487 488
bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
{
    switch (feature) {
    case QRhi::MultisampleTexture:
489
        return true;
Laszlo Agocs's avatar
Laszlo Agocs committed
490
    case QRhi::MultisampleRenderBuffer:
491
        return true;
492
    case QRhi::DebugMarkers:
Laszlo Agocs's avatar
Laszlo Agocs committed
493
        return true;
494 495
    case QRhi::Timestamps:
        return false;
496 497 498 499
    case QRhi::Instancing:
        return true;
    case QRhi::CustomInstanceStepRate:
        return true;
500 501
    case QRhi::PrimitiveRestart:
        return true;
Laszlo Agocs's avatar
Laszlo Agocs committed
502 503 504 505 506 507
    default:
        Q_UNREACHABLE();
        return false;
    }
}

508 509 510 511 512 513
int QRhiMetal::resourceSizeLimit(QRhi::ResourceSizeLimit limit) const
{
    switch (limit) {
    case QRhi::TextureSizeMin:
        return 1;
    case QRhi::TextureSizeMax:
514
        return caps.maxTextureSize;
515 516 517 518 519 520
    default:
        Q_UNREACHABLE();
        return 0;
    }
}

521
const QRhiNativeHandles *QRhiMetal::nativeHandles()
522 523 524 525
{
    return &nativeHandlesStruct;
}

526
QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
Laszlo Agocs's avatar
Laszlo Agocs committed
527
                                                int sampleCount, QRhiRenderBuffer::Flags flags)
528
{
Laszlo Agocs's avatar
Laszlo Agocs committed
529
    return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags);
530 531
}

Laszlo Agocs's avatar
Laszlo Agocs committed
532 533
QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
                                      int sampleCount, QRhiTexture::Flags flags)
534
{
Laszlo Agocs's avatar
Laszlo Agocs committed
535
    return new QMetalTexture(this, format, pixelSize, sampleCount, flags);
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
}

QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
                                      QRhiSampler::Filter mipmapMode,
                                      QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
{
    return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v, w);
}

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);
}

Laszlo Agocs's avatar
Laszlo Agocs committed
561 562
void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD)
{
563 564 565 566 567 568 569 570
    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];

571 572 573
    for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
        const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
        switch (b->type) {
Laszlo Agocs's avatar
Laszlo Agocs committed
574 575
        case QRhiShaderResourceBinding::UniformBuffer:
        {
576
            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
Laszlo Agocs's avatar
Laszlo Agocs committed
577
            id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];
578 579 580
            if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
                res[0].buffers.feed(b->binding, mtlbuf);
                res[0].bufferOffsets.feed(b->binding, b->u.ubuf.offset);
581
            }
582 583 584
            if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
                res[1].buffers.feed(b->binding, mtlbuf);
                res[1].bufferOffsets.feed(b->binding, b->u.ubuf.offset);
585
            }
Laszlo Agocs's avatar
Laszlo Agocs committed
586 587 588 589
        }
            break;
        case QRhiShaderResourceBinding::SampledTexture:
        {
590 591 592 593 594
            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
595
            }
596 597 598
            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
599 600 601 602 603 604 605 606
            }
        }
            break;
        default:
            Q_UNREACHABLE();
            break;
        }
    }
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 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

    for (int idx = 0; idx < KNOWN_STAGES; ++idx) {
        res[idx].buffers.finish();
        res[idx].bufferOffsets.finish();
        res[idx].textures.finish();
        res[idx].samplers.finish();
        for (int i = 0, ie = res[idx].buffers.batches.count(); i != ie; ++i) {
            const auto &bufferBatch(res[idx].buffers.batches[i]);
            const auto &offsetBatch(res[idx].bufferOffsets.batches[i]);
            switch (idx) {
            case 0:
                [cbD->d->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;
            }
        }
        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
665 666
}

667 668 669 670
void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps, QRhiShaderResourceBindings *srb)
{
    Q_ASSERT(inPass);

Laszlo Agocs's avatar
Laszlo Agocs committed
671
    QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps);
672
    if (!srb)
Laszlo Agocs's avatar
Laszlo Agocs committed
673
        srb = psD->m_shaderResourceBindings;
674 675

    QMetalShaderResourceBindings *srbD = QRHI_RES(QMetalShaderResourceBindings, srb);
Laszlo Agocs's avatar
Laszlo Agocs committed
676 677 678
    bool hasSlottedResourceInSrb = false;
    bool resNeedsRebind = false;

Laszlo Agocs's avatar
Laszlo Agocs committed
679
    // do buffer writes, figure out if we need to rebind, and mark as in-use
Laszlo Agocs's avatar
Laszlo Agocs committed
680
    for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
681
        const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
Laszlo Agocs's avatar
Laszlo Agocs committed
682
        QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
683
        switch (b->type) {
684
        case QRhiShaderResourceBinding::UniformBuffer:
685
        {
686
            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
Laszlo Agocs's avatar
Laszlo Agocs committed
687
            Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
Laszlo Agocs's avatar
Laszlo Agocs committed
688 689
            executeBufferHostWritesForCurrentFrame(bufD);
            if (bufD->m_type != QRhiBuffer::Immutable)
Laszlo Agocs's avatar
Laszlo Agocs committed
690 691 692 693 694
                hasSlottedResourceInSrb = true;
            if (bufD->generation != bd.ubuf.generation) {
                resNeedsRebind = true;
                bd.ubuf.generation = bufD->generation;
            }
695 696 697
            bufD->lastActiveFrameSlot = currentFrameSlot;
        }
            break;
698
        case QRhiShaderResourceBinding::SampledTexture:
Laszlo Agocs's avatar
Laszlo Agocs committed
699
        {
700 701
            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
702 703 704 705 706 707
            if (texD->generation != bd.stex.texGeneration
                    || samplerD->generation != bd.stex.samplerGeneration)
            {
                resNeedsRebind = true;
                bd.stex.texGeneration = texD->generation;
                bd.stex.samplerGeneration = samplerD->generation;
Laszlo Agocs's avatar
Laszlo Agocs committed
708
            }
Laszlo Agocs's avatar
Laszlo Agocs committed
709 710
            texD->lastActiveFrameSlot = currentFrameSlot;
            samplerD->lastActiveFrameSlot = currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
711
        }
712 713 714 715 716 717 718
            break;
        default:
            Q_UNREACHABLE();
            break;
        }
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
719 720
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
    // make sure the resources for the correct slot get bound
721
    const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
Laszlo Agocs's avatar
Laszlo Agocs committed
722 723 724 725
    if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
        resNeedsRebind = true;

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

729 730 731 732
        [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];
733
    }
734

735
    if (!cbD->d->shaderResourceBindingsValid)
736
        resNeedsRebind = true;
Laszlo Agocs's avatar
Laszlo Agocs committed
737 738 739 740 741

    if (resNeedsRebind || cbD->currentSrb != srb || cbD->currentSrbGeneration != srbD->generation) {
        cbD->currentSrb = srb;
        cbD->currentSrbGeneration = srbD->generation;
        cbD->currentResSlot = resSlot;
742

Laszlo Agocs's avatar
Laszlo Agocs committed
743
        enqueueShaderResourceBindings(srbD, cbD);
744
        cbD->d->shaderResourceBindingsValid = true;
Laszlo Agocs's avatar
Laszlo Agocs committed
745 746
    }

747 748 749
    psD->lastActiveFrameSlot = currentFrameSlot;
}

750 751
void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb, int startBinding, const QVector<QRhiCommandBuffer::VertexInput> &bindings,
                               QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
752 753 754
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
Laszlo Agocs's avatar
Laszlo Agocs committed
755

756 757
    QRhiBatchedBindings<id<MTLBuffer> > buffers;
    QRhiBatchedBindings<NSUInteger> offsets;
758 759 760
    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
761
        bufD->lastActiveFrameSlot = currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
762
        id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];
763 764 765 766 767 768 769 770 771
        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
    const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, cbD->currentSrb)->maxBinding + 1;

772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
    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())];
        }
788
    }
Laszlo Agocs's avatar
Laszlo Agocs committed
789 790 791 792 793

    if (indexBuf) {
        QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
        executeBufferHostWritesForCurrentFrame(ibufD);
        ibufD->lastActiveFrameSlot = currentFrameSlot;
794 795 796
        cbD->currentIndexBuffer = indexBuf;
        cbD->currentIndexOffset = indexOffset;
        cbD->currentIndexFormat = indexFormat;
Laszlo Agocs's avatar
Laszlo Agocs committed
797
    } else {
798
        cbD->currentIndexBuffer = nullptr;
Laszlo Agocs's avatar
Laszlo Agocs committed
799
    }
800 801 802 803 804 805
}

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
806 807 808 809 810 811 812
    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();
813 814 815 816 817 818 819 820 821 822
    return vp;
}

void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
    Q_ASSERT(cbD->currentPipeline && cbD->currentTarget);
    const QSize outputSize = cbD->currentTarget->sizeInPixels();
    const MTLViewport vp = toMetalViewport(viewport, outputSize);
823
    [cbD->d->currentPassEncoder setViewport: vp];
824 825 826 827 828 829
}

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
830 831 832 833 834
    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();
835 836 837 838 839 840 841 842 843 844
    return s;
}

void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
    Q_ASSERT(cbD->currentPipeline && cbD->currentTarget);
    const QSize outputSize = cbD->currentTarget->sizeInPixels();
    const MTLScissorRect s = toMetalScissor(scissor, outputSize);
845
    [cbD->d->currentPassEncoder setScissorRect: s];
846 847 848 849 850 851
}

void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QVector4D &c)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
852
    [cbD->d->currentPassEncoder setBlendColorRed: c.x() green: c.y() blue: c.z() alpha: c.w()];
853 854 855 856 857 858
}

void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
859
    [cbD->d->currentPassEncoder setStencilReferenceValue: refValue];
860 861 862 863 864 865 866
}

void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
                     quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
867
    [cbD->d->currentPassEncoder drawPrimitives:
868 869 870 871 872 873 874 875 876
        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);
877
    if (!cbD->currentIndexBuffer)
Laszlo Agocs's avatar
Laszlo Agocs committed
878 879
        return;

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

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

886
    [cbD->d->currentPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->d->primitiveType
Laszlo Agocs's avatar
Laszlo Agocs committed
887
      indexCount: indexCount
888
      indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
Laszlo Agocs's avatar
Laszlo Agocs committed
889 890 891 892 893
      indexBuffer: mtlbuf
      indexBufferOffset: indexOffset
      instanceCount: instanceCount
      baseVertex: vertexOffset
      baseInstance: firstInstance];
894 895
}

896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
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()]];
    }
}

936 937 938 939 940 941 942
QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain)
{
    Q_ASSERT(!inFrame);
    inFrame = true;

    QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);

943
    // This is a bit messed up since for this swapchain we want to wait for the
Laszlo Agocs's avatar
Laszlo Agocs committed
944
    // commands+present to complete, while for others just for the commands
945 946
    // (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
947
    for (QMetalSwapChain *sc : qAsConst(swapchains)) {
948
        dispatch_semaphore_t sem = sc->d->sem[swapChainD->currentFrameSlot];
Laszlo Agocs's avatar
Laszlo Agocs committed
949 950 951 952
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        if (sc != swapChainD)
            dispatch_semaphore_signal(sem);
    }
953

954
    currentSwapChain = swapChainD;
955
    currentFrameSlot = swapChainD->currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
956 957
    if (swapChainD->ds)
        swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
958

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

962 963 964
    // 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
965
    swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
966

Laszlo Agocs's avatar
Laszlo Agocs committed
967
    QMetalRenderTargetData::ColorAtt colorAtt;
968
    if (swapChainD->samples > 1) {
Laszlo Agocs's avatar
Laszlo Agocs committed
969 970 971 972
        colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
        colorAtt.needsDrawableForResolveTex = true;
    } else {
        colorAtt.needsDrawableForTex = true;
973 974
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
975
    swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
976
    swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
Laszlo Agocs's avatar
Laszlo Agocs committed
977
    swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
978

979 980 981
    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
    QRHI_PROF_F(beginSwapChainFrame(swapChain));

982
    executeDeferredReleases();
983 984
    swapChainD->cbWrapper.resetState();
    finishActiveReadbacks();
985 986 987 988 989 990 991 992 993 994

    return QRhi::FrameOpSuccess;
}

QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain)
{
    Q_ASSERT(inFrame);
    inFrame = false;

    QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
995
    Q_ASSERT(currentSwapChain == swapChainD);
996

Laszlo Agocs's avatar
Laszlo Agocs committed
997
    [swapChainD->cbWrapper.d->cb presentDrawable: swapChainD->d->curDrawable];
998

999
    __block int thisFrameSlot = currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
1000
    [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer>) {
1001
        dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
1002 1003
    }];

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

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

1009 1010
    swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
    swapChainD->frameCount += 1;
1011 1012 1013 1014

    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
    QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount));

1015
    currentSwapChain = nullptr;
1016 1017 1018 1019

    return QRhi::FrameOpSuccess;
}

1020 1021
QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb)
{
1022 1023 1024 1025
    Q_ASSERT(!inFrame);
    inFrame = true;

    currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
Laszlo Agocs's avatar
Laszlo Agocs committed
1026 1027 1028 1029 1030 1031 1032
    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);
        }
    }
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042

    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;
1043 1044
}

1045
QRhi::FrameOpResult QRhiMetal::endOffscreenFrame()
1046
{
1047 1048 1049 1050 1051
    Q_ASSERT(d->ofr.active);
    d->ofr.active = false;
    Q_ASSERT(inFrame);
    inFrame = false;

Laszlo Agocs's avatar
Laszlo Agocs committed
1052
    [d->ofr.cbWrapper.d->cb commit];
1053 1054 1055 1056 1057 1058 1059

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

    finishActiveReadbacks(true);

    return QRhi::FrameOpSuccess;
1060 1061
}

1062 1063 1064 1065
QRhi::FrameOpResult QRhiMetal::finish()
{
    Q_ASSERT(!inPass);

Laszlo Agocs's avatar
Laszlo Agocs committed
1066 1067 1068 1069 1070 1071
    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
1072 1073
            [cb commit];
            [cb waitUntilCompleted];
Laszlo Agocs's avatar
Laszlo Agocs committed
1074 1075 1076 1077
        } else {
            Q_ASSERT(currentSwapChain);
            swapChainD = currentSwapChain;
            cb = swapChainD->cbWrapper.d->cb;
Laszlo Agocs's avatar
Laszlo Agocs committed
1078
            [cb commit];
Laszlo Agocs's avatar
Laszlo Agocs committed
1079 1080 1081
        }
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
    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
1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103

    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);

1104 1105 1106
    return QRhi::FrameOpSuccess;
}

1107 1108 1109 1110 1111 1112 1113 1114
MTLRenderPassDescriptor *QRhiMetalData::createDefaultRenderPass(bool hasDepthStencil,
                                                                const QRhiColorClearValue &colorClearValue,
                                                                const QRhiDepthStencilClearValue &depthStencilClearValue)
{
    MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];

    rp.colorAttachments[0].loadAction = MTLLoadActionClear;
    rp.colorAttachments[0].storeAction = MTLStoreActionStore;
Laszlo Agocs's avatar
Laszlo Agocs committed
1115 1116
    const QVector4D rgba = colorClearValue.rgba();
    MTLClearColor c = MTLClearColorMake(rgba.x(), rgba.y(), rgba.z(), rgba.w());
1117 1118 1119 1120 1121 1122 1123
    rp.colorAttachments[0].clearColor = c;

    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
1124 1125
        rp.depthAttachment.clearDepth = depthStencilClearValue.depthClearValue();
        rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
1126 1127 1128 1129 1130
    }

    return rp;
}

1131
void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1132
{
Laszlo Agocs's avatar
Laszlo Agocs committed
1133
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1134
    QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
1135
    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();