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