qrhimetal.mm 101 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 54
    Textures are Private (device local) and a host visible staging buffer is
    used to upload data to them.
55 56 57 58 59 60
*/

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

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

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

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

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

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

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

    \section2 Working with existing Metal devices

    When interoperating with another graphics engine, it may be necessary to
    get a QRhi instance that uses the same Metal device. This can be achieved
    by setting importExistingDevice to \c true and providing dev.

    \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<MTLDevice>} or
    \c{MTLDevice *}.

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

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

/*!
    \class QRhiMetalTextureNativeHandles
    \inmodule QtRhi
110
    \brief Holds the Metal texture object that is backing a QRhiTexture instance.
Laszlo Agocs's avatar
Laszlo Agocs committed
111 112
 */

113 114
struct QRhiMetalData
{
115 116
    QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }

117 118 119 120 121 122
    id<MTLDevice> dev;
    id<MTLCommandQueue> cmdQueue;

    MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
                                                     const QRhiColorClearValue &colorClearValue,
                                                     const QRhiDepthStencilClearValue &depthStencilClearValue);
Laszlo Agocs's avatar
Laszlo Agocs committed
123
    id<MTLLibrary> createMetalLib(const QBakedShader &shader, QString *error, QByteArray *entryPoint);
124 125 126 127 128
    id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);

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

    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
173 174 175

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

178 179 180
Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE);

181 182
struct QMetalBufferData
{
Laszlo Agocs's avatar
Laszlo Agocs committed
183
    bool managed;
184
    id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT];
Laszlo Agocs's avatar
Laszlo Agocs committed
185
    QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
186 187
};

Laszlo Agocs's avatar
Laszlo Agocs committed
188 189
struct QMetalRenderBufferData
{
190
    MTLPixelFormat format;
Laszlo Agocs's avatar
Laszlo Agocs committed
191 192 193
    id<MTLTexture> tex = nil;
};

Laszlo Agocs's avatar
Laszlo Agocs committed
194 195
struct QMetalTextureData
{
196
    MTLPixelFormat format;
Laszlo Agocs's avatar
Laszlo Agocs committed
197
    id<MTLTexture> tex = nil;
198
    id<MTLBuffer> stagingBuf[QMTL_FRAMES_IN_FLIGHT];
199
    bool owns = true;
Laszlo Agocs's avatar
Laszlo Agocs committed
200 201
};

Laszlo Agocs's avatar
Laszlo Agocs committed
202 203 204 205 206
struct QMetalSamplerData
{
    id<MTLSamplerState> samplerState = nil;
};

207 208 209
struct QMetalCommandBufferData
{
    id<MTLCommandBuffer> cb;
210 211
    id<MTLRenderCommandEncoder> currentPassEncoder;
    MTLRenderPassDescriptor *currentPassRpDesc;
212
    bool shaderResourceBindingsValid;
213 214 215
    int currentFirstVertexBinding;
    QRhiBatchedBindings<id<MTLBuffer> > currentVertexInputsBuffers;
    QRhiBatchedBindings<NSUInteger> currentVertexInputOffsets;
216 217 218 219 220
};

struct QMetalRenderTargetData
{
    QSize pixelSize;
Laszlo Agocs's avatar
Laszlo Agocs committed
221
    float dpr = 1;
222 223
    int colorAttCount = 0;
    int dsAttCount = 0;
224 225

    struct ColorAtt {
Laszlo Agocs's avatar
Laszlo Agocs committed
226
        bool needsDrawableForTex = false;
227 228 229
        id<MTLTexture> tex = nil;
        int layer = 0;
        int level = 0;
Laszlo Agocs's avatar
Laszlo Agocs committed
230
        bool needsDrawableForResolveTex = false;
231 232 233 234 235
        id<MTLTexture> resolveTex = nil;
        int resolveLayer = 0;
        int resolveLevel = 0;
    };

236
    struct {
237
        ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
238
        id<MTLTexture> dsTex = nil;
Laszlo Agocs's avatar
Laszlo Agocs committed
239
        bool hasStencil = false;
240
    } fb;
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
};

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
260
    dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT];
261
    MTLRenderPassDescriptor *rp = nullptr;
262
    id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
263
    QRhiTexture::Format rhiColorFormat;
264
    MTLPixelFormat colorFormat;
265 266 267 268
};

QRhiMetal::QRhiMetal(QRhiInitParams *params)
{
269
    d = new QRhiMetalData(this);
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288

    QRhiMetalInitParams *metalparams = static_cast<QRhiMetalInitParams *>(params);
    importedDevice = metalparams->importExistingDevice;
    if (importedDevice) {
        d->dev = (id<MTLDevice>) metalparams->dev;
        [d->dev retain];
    }
}

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

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

289
bool QRhiMetal::create(QRhi::Flags flags)
290
{
291 292
    Q_UNUSED(flags);

293 294 295 296 297 298
    if (!importedDevice)
        d->dev = MTLCreateSystemDefaultDevice();

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

    d->cmdQueue = [d->dev newCommandQueue];
299

Laszlo Agocs's avatar
Laszlo Agocs committed
300 301 302 303 304 305
    if (@available(macOS 10.13, iOS 11.0, *)) {
        d->captureMgr = [MTLCaptureManager sharedCaptureManager];
        d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
        d->captureScope.label = @"Qt capture scope";
    }

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
#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

329 330 331
    nativeHandlesStruct.dev = d->dev;
    nativeHandlesStruct.cmdQueue = d->cmdQueue;

332
    return true;
333 334 335 336 337
}

void QRhiMetal::destroy()
{
    executeDeferredReleases(true);
338
    finishActiveReadbacks(true);
339

Laszlo Agocs's avatar
Laszlo Agocs committed
340 341 342 343 344 345 346
    if (@available(macOS 10.13, iOS 11.0, *)) {
        if (d->captureScope) {
            [d->captureScope release];
            d->captureScope = nil;
        }
    }

347 348 349 350 351 352 353 354 355 356 357 358 359
    if (d->cmdQueue) {
        [d->cmdQueue release];
        d->cmdQueue = nil;
    }

    if (d->dev) {
        [d->dev release];
        d->dev = nil;
    }
}

QVector<int> QRhiMetal::supportedSampleCounts() const
{
360 361 362 363 364 365 366 367 368 369 370 371
    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;
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
}

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
396 397 398 399 400 401 402 403 404 405
    // 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;
406 407
}

408
bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
409
{
410 411
    Q_UNUSED(flags);

412 413 414 415 416
#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
417 418 419
#else
    if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
        return false;
420
#endif
421 422 423 424

    return true;
}

425 426 427 428
bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
{
    switch (feature) {
    case QRhi::MultisampleTexture:
429
        return true;
430
    case QRhi::MultisampleRenderBuffer:
431
        return true;
432
    case QRhi::DebugMarkers:
433
        return true;
434 435
    case QRhi::Timestamps:
        return false;
436 437 438 439
    case QRhi::Instancing:
        return true;
    case QRhi::CustomInstanceStepRate:
        return true;
440 441
    case QRhi::PrimitiveRestart:
        return true;
442 443 444 445 446 447
    default:
        Q_UNREACHABLE();
        return false;
    }
}

448 449 450 451 452 453
int QRhiMetal::resourceSizeLimit(QRhi::ResourceSizeLimit limit) const
{
    switch (limit) {
    case QRhi::TextureSizeMin:
        return 1;
    case QRhi::TextureSizeMax:
454
        return caps.maxTextureSize;
455 456 457 458 459 460
    default:
        Q_UNREACHABLE();
        return 0;
    }
}

461
const QRhiNativeHandles *QRhiMetal::nativeHandles()
462 463 464 465
{
    return &nativeHandlesStruct;
}

466
QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
Laszlo Agocs's avatar
Laszlo Agocs committed
467
                                                int sampleCount, QRhiRenderBuffer::Flags flags)
468
{
Laszlo Agocs's avatar
Laszlo Agocs committed
469
    return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags);
470 471
}

Laszlo Agocs's avatar
Laszlo Agocs committed
472 473
QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
                                      int sampleCount, QRhiTexture::Flags flags)
474
{
Laszlo Agocs's avatar
Laszlo Agocs committed
475
    return new QMetalTexture(this, format, pixelSize, sampleCount, flags);
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
}

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

501 502
void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD)
{
503 504 505 506 507 508 509 510
    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];

511 512 513 514 515 516
    for (const QRhiShaderResourceBinding &b : qAsConst(srbD->sortedBindings)) {
        switch (b.type) {
        case QRhiShaderResourceBinding::UniformBuffer:
        {
            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b.ubuf.buf);
            id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];
517 518 519 520 521 522 523 524
            if (b.stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
                res[0].buffers.feed(b.binding, mtlbuf);
                res[0].bufferOffsets.feed(b.binding, b.ubuf.offset);
            }
            if (b.stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
                res[1].buffers.feed(b.binding, mtlbuf);
                res[1].bufferOffsets.feed(b.binding, b.ubuf.offset);
            }
525 526 527 528 529 530 531
        }
            break;
        case QRhiShaderResourceBinding::SampledTexture:
        {
            QMetalTexture *texD = QRHI_RES(QMetalTexture, b.stex.tex);
            QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b.stex.sampler);
            if (b.stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
532 533
                res[0].textures.feed(b.binding, texD->d->tex);
                res[0].samplers.feed(b.binding, samplerD->d->samplerState);
534 535
            }
            if (b.stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
536 537
                res[1].textures.feed(b.binding, texD->d->tex);
                res[1].samplers.feed(b.binding, samplerD->d->samplerState);
538 539 540 541 542 543 544 545
            }
        }
            break;
        default:
            Q_UNREACHABLE();
            break;
        }
    }
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603

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

606 607 608 609
void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps, QRhiShaderResourceBindings *srb)
{
    Q_ASSERT(inPass);

Laszlo Agocs's avatar
Laszlo Agocs committed
610
    QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps);
611
    if (!srb)
Laszlo Agocs's avatar
Laszlo Agocs committed
612
        srb = psD->m_shaderResourceBindings;
613 614

    QMetalShaderResourceBindings *srbD = QRHI_RES(QMetalShaderResourceBindings, srb);
615 616 617
    bool hasSlottedResourceInSrb = false;
    bool resNeedsRebind = false;

Laszlo Agocs's avatar
Laszlo Agocs committed
618
    // do buffer writes, figure out if we need to rebind, and mark as in-use
619 620 621
    for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
        const QRhiShaderResourceBinding &b(srbD->sortedBindings[i]);
        QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
622
        switch (b.type) {
623
        case QRhiShaderResourceBinding::UniformBuffer:
624 625
        {
            QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b.ubuf.buf);
Laszlo Agocs's avatar
Laszlo Agocs committed
626
            Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
Laszlo Agocs's avatar
Laszlo Agocs committed
627 628
            executeBufferHostWritesForCurrentFrame(bufD);
            if (bufD->m_type != QRhiBuffer::Immutable)
629 630 631 632 633
                hasSlottedResourceInSrb = true;
            if (bufD->generation != bd.ubuf.generation) {
                resNeedsRebind = true;
                bd.ubuf.generation = bufD->generation;
            }
634 635 636
            bufD->lastActiveFrameSlot = currentFrameSlot;
        }
            break;
637
        case QRhiShaderResourceBinding::SampledTexture:
Laszlo Agocs's avatar
Laszlo Agocs committed
638
        {
639 640 641 642 643 644 645 646
            QMetalTexture *texD = QRHI_RES(QMetalTexture, b.stex.tex);
            QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b.stex.sampler);
            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
647
            }
648 649
            texD->lastActiveFrameSlot = currentFrameSlot;
            samplerD->lastActiveFrameSlot = currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
650
        }
651 652 653 654 655 656 657
            break;
        default:
            Q_UNREACHABLE();
            break;
        }
    }

658 659
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
    // make sure the resources for the correct slot get bound
660
    const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
661 662 663 664
    if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
        resNeedsRebind = true;

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

668 669 670 671
        [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];
672
    }
673

674
    if (!cbD->d->shaderResourceBindingsValid)
675
        resNeedsRebind = true;
676 677 678 679 680

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

682
        enqueueShaderResourceBindings(srbD, cbD);
683
        cbD->d->shaderResourceBindingsValid = true;
684 685
    }

686 687 688
    psD->lastActiveFrameSlot = currentFrameSlot;
}

689 690
void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb, int startBinding, const QVector<QRhiCommandBuffer::VertexInput> &bindings,
                               QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
691 692 693
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
Laszlo Agocs's avatar
Laszlo Agocs committed
694

695 696
    QRhiBatchedBindings<id<MTLBuffer> > buffers;
    QRhiBatchedBindings<NSUInteger> offsets;
697 698 699
    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
700
        bufD->lastActiveFrameSlot = currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
701
        id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];
702 703 704 705 706 707 708 709 710
        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;

711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
    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())];
        }
727
    }
Laszlo Agocs's avatar
Laszlo Agocs committed
728 729 730 731 732

    if (indexBuf) {
        QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
        executeBufferHostWritesForCurrentFrame(ibufD);
        ibufD->lastActiveFrameSlot = currentFrameSlot;
733 734 735
        cbD->currentIndexBuffer = indexBuf;
        cbD->currentIndexOffset = indexOffset;
        cbD->currentIndexFormat = indexFormat;
Laszlo Agocs's avatar
Laszlo Agocs committed
736
    } else {
737
        cbD->currentIndexBuffer = nullptr;
Laszlo Agocs's avatar
Laszlo Agocs committed
738
    }
739 740 741 742 743 744
}

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
745 746 747 748 749 750 751
    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();
752 753 754 755 756 757 758 759 760 761
    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);
762
    [cbD->d->currentPassEncoder setViewport: vp];
763 764 765 766 767 768
}

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
769 770 771 772 773
    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();
774 775 776 777 778 779 780 781 782 783
    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);
784
    [cbD->d->currentPassEncoder setScissorRect: s];
785 786 787 788 789 790
}

void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QVector4D &c)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
791
    [cbD->d->currentPassEncoder setBlendColorRed: c.x() green: c.y() blue: c.z() alpha: c.w()];
792 793 794 795 796 797
}

void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
798
    [cbD->d->currentPassEncoder setStencilReferenceValue: refValue];
799 800 801 802 803 804 805
}

void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
                     quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
{
    Q_ASSERT(inPass);
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
806
    [cbD->d->currentPassEncoder drawPrimitives:
807 808 809 810 811 812 813 814 815
        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);
816
    if (!cbD->currentIndexBuffer)
Laszlo Agocs's avatar
Laszlo Agocs committed
817 818
        return;

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

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

825
    [cbD->d->currentPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->d->primitiveType
Laszlo Agocs's avatar
Laszlo Agocs committed
826
      indexCount: indexCount
827
      indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
Laszlo Agocs's avatar
Laszlo Agocs committed
828 829 830 831 832
      indexBuffer: mtlbuf
      indexBufferOffset: indexOffset
      instanceCount: instanceCount
      baseVertex: vertexOffset
      baseInstance: firstInstance];
833 834
}

835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
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()]];
    }
}

875 876 877 878 879 880 881
QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain)
{
    Q_ASSERT(!inFrame);
    inFrame = true;

    QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);

882
    // This is a bit messed up since for this swapchain we want to wait for the
Laszlo Agocs's avatar
Laszlo Agocs committed
883
    // commands+present to complete, while for others just for the commands
884 885
    // (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
886
    for (QMetalSwapChain *sc : qAsConst(swapchains)) {
887
        dispatch_semaphore_t sem = sc->d->sem[swapChainD->currentFrameSlot];
Laszlo Agocs's avatar
Laszlo Agocs committed
888 889 890 891
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        if (sc != swapChainD)
            dispatch_semaphore_signal(sem);
    }
892

893
    currentSwapChain = swapChainD;
894
    currentFrameSlot = swapChainD->currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
895 896
    if (swapChainD->ds)
        swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
897

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

901 902 903
    // 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
904
    swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
905

Laszlo Agocs's avatar
Laszlo Agocs committed
906
    QMetalRenderTargetData::ColorAtt colorAtt;
907
    if (swapChainD->samples > 1) {
Laszlo Agocs's avatar
Laszlo Agocs committed
908 909 910 911
        colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
        colorAtt.needsDrawableForResolveTex = true;
    } else {
        colorAtt.needsDrawableForTex = true;
912 913
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
914
    swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
915
    swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
Laszlo Agocs's avatar
Laszlo Agocs committed
916
    swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
917

918 919 920
    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
    QRHI_PROF_F(beginSwapChainFrame(swapChain));

921
    executeDeferredReleases();
922 923
    swapChainD->cbWrapper.resetState();
    finishActiveReadbacks();
924 925 926 927 928 929 930 931 932 933

    return QRhi::FrameOpSuccess;
}

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

    QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
934
    Q_ASSERT(currentSwapChain == swapChainD);
935

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

938
    __block int thisFrameSlot = currentFrameSlot;
Laszlo Agocs's avatar
Laszlo Agocs committed
939
    [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer>) {
940
        dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
941 942
    }];

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

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

948 949
    swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
    swapChainD->frameCount += 1;
950 951 952 953

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

954
    currentSwapChain = nullptr;
955 956 957 958

    return QRhi::FrameOpSuccess;
}

959 960
QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb)
{
961 962 963 964
    Q_ASSERT(!inFrame);
    inFrame = true;

    currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
Laszlo Agocs's avatar
Laszlo Agocs committed
965 966 967 968 969 970 971
    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);
        }
    }
972 973 974 975 976 977 978 979 980 981

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

984
QRhi::FrameOpResult QRhiMetal::endOffscreenFrame()
985
{
986 987 988 989 990
    Q_ASSERT(d->ofr.active);
    d->ofr.active = false;
    Q_ASSERT(inFrame);
    inFrame = false;

Laszlo Agocs's avatar
Laszlo Agocs committed
991
    [d->ofr.cbWrapper.d->cb commit];
992 993 994 995 996 997 998

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

    finishActiveReadbacks(true);

    return QRhi::FrameOpSuccess;
999 1000
}

1001 1002 1003 1004
QRhi::FrameOpResult QRhiMetal::finish()
{
    Q_ASSERT(!inPass);

Laszlo Agocs's avatar
Laszlo Agocs committed
1005 1006 1007 1008 1009 1010
    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
1011 1012
            [cb commit];
            [cb waitUntilCompleted];
Laszlo Agocs's avatar
Laszlo Agocs committed
1013 1014 1015 1016
        } else {
            Q_ASSERT(currentSwapChain);
            swapChainD = currentSwapChain;
            cb = swapChainD->cbWrapper.d->cb;
Laszlo Agocs's avatar
Laszlo Agocs committed
1017
            [cb commit];
Laszlo Agocs's avatar
Laszlo Agocs committed
1018 1019 1020
        }
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
    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
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042

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

1043 1044 1045
    return QRhi::FrameOpSuccess;
}

1046 1047 1048 1049 1050 1051 1052 1053
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
1054 1055
    const QVector4D rgba = colorClearValue.rgba();
    MTLClearColor c = MTLClearColorMake(rgba.x(), rgba.y(), rgba.z(), rgba.w());
1056 1057 1058 1059 1060 1061 1062
    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
1063 1064
        rp.depthAttachment.clearDepth = depthStencilClearValue.depthClearValue();
        rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
1065 1066 1067 1068 1069
    }

    return rp;
}

1070
void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1071
{
Laszlo Agocs's avatar
Laszlo Agocs committed
1072
    QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1073
    QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
1074
    QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1075 1076 1077

    for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
        QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
Laszlo Agocs's avatar
Laszlo Agocs committed
1078
        Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
1079
        for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
Laszlo Agocs's avatar
Laszlo Agocs committed
1080
            bufD->d->pendingUpdates[i].append(u);
1081 1082 1083 1084
    }

    for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
        QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
Laszlo Agocs's avatar
Laszlo Agocs committed
1085
        Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
1086
        Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
Laszlo Agocs's avatar
Laszlo Agocs committed
1087
        for (int i = 0, ie = bufD->m_type == QRhiBuffer::Immutable ? 1 : QMTL_FRAMES_IN_FLIGHT; i != ie; ++i)
1088
            bufD->d->pendingUpdates[i].append({ u.buf, u.offset, u.data.size(), u.data.constData() });
1089 1090
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
1091
    id<MTLBlitCommandEncoder> blitEnc = nil;
1092 1093
    auto ensureBlit = [&blitEnc, cbD, this] {
        if (!blitEnc) {
Laszlo Agocs's avatar
Laszlo Agocs committed
1094
            blitEnc = [cbD->d->cb blitCommandEncoder];
1095 1096 1097
            if (debugMarkers)
                [blitEnc pushDebugGroup: @"Texture upload/copy"];
        }
Laszlo Agocs's avatar
Laszlo Agocs committed
1098
    };
Laszlo Agocs's avatar
Laszlo Agocs committed
1099

1100
    for (const QRhiResourceUpdateBatchPrivate::TextureUpload &u : ud->textureUploads) {
1101
        const QVector<QRhiTextureLayer> layers = u.desc.layers();
Laszlo Agocs's avatar
Laszlo Agocs committed
1102
        if (layers.isEmpty() || layers[0].mipImages().isEmpty())
Laszlo Agocs's avatar
Laszlo Agocs committed
1103 1104 1105 1106 1107 1108
            continue;

        QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.tex);
        qsizetype stagingSize = 0;
        const int texbufAlign = 256; // probably not needed

1109 1110 1111 1112 1113 1114
        for (int layer = 0, layerCount = layers.count(); layer != layerCount; ++layer) {
            const QRhiTextureLayer &layerDesc(layers[layer]);
            const QVector<QRhiTextureMipLevel> mipImages = layerDesc.mipImages();
            Q_ASSERT(mipImages.count() == 1 || utexD->m_flags.testFlag(QRhiTexture::MipMapped));
            for (int level = 0, levelCount = mipImages.count(); level != levelCount; ++level) {
                const QRhiTextureMipLevel &mipDesc(mipImages[level]);
Laszlo Agocs's avatar
Laszlo Agocs committed
1115 1116
                const qsizetype imageSizeBytes = mipDesc.image().isNull() ?
                            mipDesc.compressedData().size() : mipDesc.image().sizeInBytes();
Laszlo Agocs's avatar
Laszlo Agocs committed
1117 1118 1119 1120 1121
                if (imageSizeBytes > 0)
                    stagingSize += aligned(imageSizeBytes, texbufAlign);
            }
        }

Laszlo Agocs's avatar
Laszlo Agocs committed
1122
        ensureBlit();
1123
        if (!utexD->d->stagingBuf[currentFrameSlot]) {
1124
            utexD->d->stagingBuf[currentFrameSlot] = [d->dev newBufferWithLength: stagingSize options: MTLResourceStorageModeShared];
1125 1126
            QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, stagingSize));
        }
1127

1128
        void *mp = [utexD->d->stagingBuf[currentFrameSlot] contents];
Laszlo Agocs's avatar
Laszlo Agocs committed
1129
        qsizetype curOfs = 0;
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
        for (int layer = 0, layerCount = layers.count(); layer != layerCount; ++layer) {
            const QRhiTextureLayer &layerDesc(layers[layer]);
            const QVector<QRhiTextureMipLevel> mipImages = layerDesc.mipImages();
            for (int level = 0, levelCount = mipImages.count(); level != levelCount; ++level) {
                const QRhiTextureMipLevel &mipDesc(mipImages[level]);
                const QPoint dp = mipDesc.destinationTopLeft();
                const QByteArray compressedData = mipDesc.compressedData();
                QImage img = mipDesc.image();

                if (!img.isNull()) {
                    const qsizetype fullImageSizeBytes = img.sizeInBytes();
1141 1142 1143 1144 1145
                    int w = img.width();
                    int h = img.height();
                    int bpl = img.bytesPerLine();
                    int srcOffset = 0;

1146 1147 1148 1149 1150 1151
                    if (!mipDesc.sourceSize().isEmpty() || !mipDesc.sourceTopLeft().isNull()) {
                        const int sx = mipDesc.sourceTopLeft().x();
                        const int sy = mipDesc.sourceTopLeft().y();
                        if (!mipDesc.sourceSize().isEmpty()) {
                            w = mipDesc.sourceSize().width();
                            h = mipDesc.sourceSize().height();
1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
                        }
                        if (img.depth() == 32) {
                            memcpy(reinterpret_cast<char *>(mp) + curOfs, img.constBits(), fullImageSizeBytes);
                            srcOffset = sy * bpl + sx * 4;
                            // bpl remains set to the original image's row stride
                        } else {
                            img = img.copy(sx, sy, w, h);
                            bpl = img.bytesPerLine();
                            Q_ASSERT(img.sizeInBytes() <= fullImageSizeBytes);
                            memcpy(reinterpret_cast<char *>(mp) + curOfs, img.constBits(), img.sizeInBytes());
                        }
                    } else {
                        memcpy(reinterpret_cast<char *>(mp) + curOfs, img.constBits(), fullImageSizeBytes);
                    }
1166

1167
                    [blitEnc copyFromBuffer: utexD->d->stagingBuf[currentFrameSlot]
1168 1169
                                             sourceOffset: curOfs + srcOffset
                                             sourceBytesPerRow: bpl
Laszlo Agocs's avatar
Laszlo Agocs committed
1170
                                             sourceBytesPerImage: 0
1171
                                             sourceSize: MTLSizeMake(w, h, 1)
1172 1173 1174
                      toTexture: utexD->d->tex
                      destinationSlice: layer
                      destinationLevel: level
1175
                      destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)
1176
                      options: MTLBlitOptionNone];
1177

1178
                    curOfs += aligned(fullImageSizeBytes, texbufAlign);
1179
                } else if (!compressedData.isEmpty() && isCompressedFormat(utexD->m_format)) {
1180 1181
                    const int subresw = qFloor(float(qMax(1, utexD->m_pixelSize.width() >> level)));
                    const int subresh = qFloor(float(qMax(1, utexD->m_pixelSize.height() >> level)));
1182
                    int w, h;
1183
                    if (mipDesc.sourceSize().isEmpty()) {
1184 1185
                        w = subresw;
                        h = subresh;
1186
                    } else {
1187 1188
                        w = mipDesc.sourceSize().width();
                        h = mipDesc.sourceSize().height();
1189
                    }
1190

1191 1192 1193
                    quint32 bpl = 0;
                    QSize blockDim;
                    compressedFormatInfo(utexD->m_format, QSize(w, h), &bpl, nullptr, &blockDim);
1194

1195 1196
                    const int dx = aligned(dp.x(), blockDim.width());
                    const int dy = aligned(dp.y(), blockDim.height());
1197 1198 1199 1200 1201
                    if (dx + w != subresw)
                        w = aligned(w, blockDim.width());
                    if (dy + h != subresh)
                        h = aligned(h, blockDim.height());

1202
                    memcpy(reinterpret_cast<char *>(mp) + curOfs, compressedData.constData(), compressedData.size());
1203

1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
                    [blitEnc copyFromBuffer: utexD->d->stagingBuf[currentFrameSlot]
                                             sourceOffset: curOfs
                                             sourceBytesPerRow: bpl
                                             sourceBytesPerImage: 0
                                             sourceSize: MTLSizeMake(w, h, 1)
                      toTexture: utexD->d->tex
                      destinationSlice: layer
                      destinationLevel: level
                      destinationOrigin: MTLOriginMake(dx, dy, 0)
                      options: MTLBlitOptionNone];
1214

1215
                    curOfs += aligned(compressedData.size(), texbufAlign);
Laszlo Agocs's avatar
Laszlo Agocs committed
1216 1217 1218 1219 1220 1221 1222
                }
            }
        }

        utexD->lastActiveFrameSlot = currentFrameSlot;

        if (!utexD->m_flags.testFlag(QRhiTexture::ChangesFrequently)) {
1223 1224 1225 1226 1227 1228
            QRhiMetalData::DeferredReleaseEntry e;
            e.type = QRhiMetalData::DeferredReleaseEntry::StagingBuffer;
            e.lastActiveFrameSlot = currentFrameSlot;
            e.stagingBuffer.buffer = utexD->d->stagingBuf[currentFrameSlot];
            utexD->d->stagingBuf[currentFrameSlot] = nil;
            d->releaseQueue.append(e);
1229
            QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
Laszlo Agocs's avatar
Laszlo Agocs committed
1230
        }
1231 1232
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
1233 1234 1235 1236
    for (const QRhiResourceUpdateBatchPrivate::TextureCopy &u : ud->textureCopies) {
        Q_ASSERT(u.src && u.dst);
        QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.src);
        QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.dst);
1237 1238 1239
        const QPoint dp = u.desc.destinationTopLeft();
        const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize();
        const QPoint sp = u.desc.sourceTopLeft();
Laszlo Agocs's avatar
Laszlo Agocs committed
1240 1241 1242

        ensureBlit();
        [blitEnc copyFromTexture: srcD->d->tex
1243 1244 1245
                                  sourceSlice: u.desc.sourceLayer()
                                  sourceLevel: u.desc.sourceLevel()
                                  sourceOrigin: MTLOriginMake(sp.x(), sp.y(), 0)
Laszlo Agocs's avatar
Laszlo Agocs committed
1246 1247
                                  sourceSize: MTLSizeMake(size.width(), size.height(), 1)
                                  toTexture: dstD->d->tex
1248 1249 1250
                                  destinationSlice: u.desc.destinationLayer()
                                  destinationLevel: u.desc.destinationLevel()
                                  destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)];
Laszlo Agocs's avatar
Laszlo Agocs committed
1251 1252
    }

1253 1254 1255 1256 1257 1258
    for (const QRhiResourceUpdateBatchPrivate::TextureRead &u : ud->textureReadbacks) {
        QRhiMetalData::ActiveReadback aRb;
        aRb.activeFrameSlot = currentFrameSlot;
        aRb.desc = u.rb;
        aRb.result = u.result;

1259
        QMetalTexture *texD = QRHI_RES(QMetalTexture, u.rb.texture());
1260 1261 1262 1263 1264 1265 1266 1267 1268
        QMetalSwapChain *swapChainD = nullptr;
        id<MTLTexture> src;
        QSize srcSize;
        if (texD) {
            if (texD->samples > 1) {
                qWarning("Multisample texture cannot be read back");
                continue;
            }
            aRb.pixelSize = texD->m_pixelSize;
1269 1270 1271
            if (u.rb.level() > 0) {
                aRb.pixelSize.setWidth(qFloor(float(qMax(1, aRb.pixelSize.width() >> u.rb.level()))));
                aRb.pixelSize.setHeight(qFloor(float(qMax(1, aRb.pixelSize.height() >> u.rb.level()))));
1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291
            }
            aRb.format = texD->m_format;
            src = texD->d->tex;
            srcSize = texD->m_pixelSize;
        } else {
            Q_ASSERT(currentSwapChain);
            swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
            aRb.pixelSize = swapChainD->pixelSize;
            aRb.format = swapChainD->d->rhiColorFormat;
            // Multisample swapchains need nothing special since resolving
            // happens when ending a renderpass.
            const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
            src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
            srcSize = swapChainD->rtWrapper.d->pixelSize;
        }

        quint32 bpl = 0;
        textureFormatInfo(aRb.format, aRb.pixelSize, &bpl, &aRb.bufSize);
        aRb.buf = [d->dev newBufferWithLength: aRb.bufSize options: MTLResourceStorageModeShared];

1292 1293 1294 1295
        QRHI_PROF_F(newReadbackBuffer(quint64(quintptr(aRb.buf)),
                                      texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
                                      aRb.bufSize));

1296 1297
        ensureBlit();
        [blitEnc copyFromTexture: src
1298 1299
                                  sourceSlice: u.rb.layer()
                                  sourceLevel: u.rb.level()
1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310
                                  sourceOrigin: MTLOriginMake(0, 0, 0)
                                  sourceSize: MTLSizeMake(srcSize.width(), srcSize.height(), 1)
                                  toBuffer: aRb.buf
                                  destinationOffset: 0
                                  destinationBytesPerRow: bpl
                                  destinationBytesPerImage: 0
                                  options: MTLBlitOptionNone];

        d->activeReadbacks.append(aRb);
    }

Laszlo Agocs's avatar
Laszlo Agocs committed
1311 1312 1313 1314 1315
    for (const QRhiResourceUpdateBatchPrivate::TextureMipGen &u : ud->textureMipGens) {
        ensureBlit();
        [blitEnc generateMipmapsForTexture: QRHI_RES(QMetalTexture, u.tex)->d->tex];
    }

1316 1317 1318
    if (blitEnc) {
        if (debugMarkers)
            [blitEnc popDebugGroup];
Laszlo Agocs's avatar
Laszlo Agocs committed
1319
        [blitEnc endEncoding];
1320
    }
Laszlo Agocs's avatar
Laszlo Agocs committed
1321

1322 1323 1324 1325 1326
    ud->free();
}

void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD)
{
Laszlo Agocs's avatar
Laszlo Agocs committed
1327 1328
    const int idx = bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot;
    QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->d->pendingUpdates[idx]);
1329 1330 1331
    if (updates.isEmpty())
        return;

Laszlo Agocs's avatar
Laszlo Agocs committed
1332 1333 1334
    void *p = [bufD->d->buf[idx] contents];
    int changeBegin = -1;
    int changeEnd = -1;
1335 1336 1337
    for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) {
        Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf));
        memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), u.data.size());
Laszlo Agocs's avatar
Laszlo Agocs committed
1338 1339 1340 1341
        if (changeBegin == -1 || u.offset < changeBegin)
            changeBegin = u.offset;
        if (changeEnd == -1 || u.offset + u.data.size() > changeEnd)
            changeEnd = u.offset + u.data.size();
1342 <