diff options
Diffstat (limited to 'src/gui/rhi/qrhimetal.mm')
-rw-r--r-- | src/gui/rhi/qrhimetal.mm | 540 |
1 files changed, 351 insertions, 189 deletions
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index d0eb805c42..887d40c7a5 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -13,6 +13,7 @@ #include <QOperatingSystemVersion> #include <QtCore/private/qcore_mac_p.h> +#include <QtGui/private/qmetallayer_p.h> #ifdef Q_OS_MACOS #include <AppKit/AppKit.h> @@ -20,8 +21,9 @@ #include <UIKit/UIKit.h> #endif +#include <QuartzCore/CATransaction.h> + #include <Metal/Metal.h> -#include <QuartzCore/CAMetalLayer.h> QT_BEGIN_NAMESPACE @@ -40,12 +42,18 @@ QT_BEGIN_NAMESPACE #error ARC not supported #endif -// Note: we expect everything here pass the Metal API validation when running -// in Debug mode in XCode (or with METAL_DEVICE_WRAPPER_TYPE=1). An exception -// is the nextDrawable Called Early blah blah warning, which is plain and -// simply false. This may not be present with newer XCode. There may also be -// warnings about threading (e.g. about accessing view.layer), those are -// expected for now. +// Even though the macOS 13 MTLBinaryArchive problem (QTBUG-106703) seems +// to be solved in later 13.x releases, we have reports from old Intel hardware +// and older macOS versions where this causes problems (QTBUG-114338). +// Thus we no longer do OS version based differentiation, but rather have a +// single toggle that is currently on, and so QRhi::(set)pipelineCache() +// does nothing with Metal. +#define QRHI_METAL_DISABLE_BINARY_ARCHIVE + +// We should be able to operate with command buffers that do not automatically +// retain/release the resources used by them. (since we have logic that mirrors +// other backends such as the Vulkan one anyway) +#define QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES /*! \class QRhiMetalInitParams @@ -53,7 +61,7 @@ QT_BEGIN_NAMESPACE \since 6.6 \brief Metal specific initialization parameters. - \note This an RHI API with limited compatibility guarantees, see \l QRhi + \note This is a RHI API with limited compatibility guarantees, see \l QRhi for details. A Metal-based QRhi needs no special parameters for initialization. @@ -91,16 +99,21 @@ QT_BEGIN_NAMESPACE \since 6.6 \brief Holds the Metal device used by the QRhi. - \note This an RHI API with limited compatibility guarantees, see \l QRhi + \note This is a RHI API with limited compatibility guarantees, see \l QRhi for details. */ /*! \variable QRhiMetalNativeHandles::dev + + Set to a valid MTLDevice to import an existing device. */ /*! \variable QRhiMetalNativeHandles::cmdQueue + + Set to a valid MTLCommandQueue when importing an existing command queue. + When \nullptr, QRhi will create a new command queue. */ /*! @@ -119,7 +132,7 @@ QT_BEGIN_NAMESPACE between \l{QRhiCommandBuffer::beginPass()} - \l{QRhiCommandBuffer::endPass()}. - \note This an RHI API with limited compatibility guarantees, see \l QRhi + \note This is a RHI API with limited compatibility guarantees, see \l QRhi for details. */ @@ -158,8 +171,8 @@ struct QRhiMetalData id<MTLDevice> dev = nil; id<MTLCommandQueue> cmdQueue = nil; API_AVAILABLE(macosx(11.0), ios(14.0)) id<MTLBinaryArchive> binArch = nil; - bool binArchWasEmpty = false; + id<MTLCommandBuffer> newCommandBuffer(); MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, @@ -357,8 +370,11 @@ struct QMetalRenderTargetData struct { ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS]; id<MTLTexture> dsTex = nil; + id<MTLTexture> dsResolveTex = nil; bool hasStencil = false; bool depthNeedsStore = false; + bool preserveColor = false; + bool preserveDs = false; } fb; QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; @@ -377,6 +393,15 @@ struct QMetalGraphicsPipelineData float slopeScaledDepthBias; QMetalShader vs; QMetalShader fs; + struct ExtraBufferManager { + enum class WorkBufType { + DeviceLocal, + HostVisible + }; + QMetalBuffer *acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type = WorkBufType::DeviceLocal); + QVector<QMetalBuffer *> deviceLocalWorkBuffers; + QVector<QMetalBuffer *> hostVisibleWorkBuffers; + } extraBufMgr; struct Tessellation { QMetalGraphicsPipelineData *q = nullptr; bool enabled = false; @@ -410,13 +435,6 @@ struct QMetalGraphicsPipelineData id<MTLComputePipelineState> vsCompPipeline(QRhiMetal *rhiD, QShader::Variant vertexCompVariant); id<MTLComputePipelineState> tescCompPipeline(QRhiMetal *rhiD); id<MTLRenderPipelineState> teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline); - enum class WorkBufType { - DeviceLocal, - HostVisible - }; - QMetalBuffer *acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type = WorkBufType::DeviceLocal); - QVector<QMetalBuffer *> deviceLocalWorkBuffers; - QVector<QMetalBuffer *> hostVisibleWorkBuffers; } tess; void setupVertexInputDescriptor(MTLVertexDescriptor *desc); void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc); @@ -445,11 +463,6 @@ struct QMetalSwapChainData id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT]; QRhiTexture::Format rhiColorFormat; MTLPixelFormat colorFormat; -#ifdef Q_OS_MACOS - bool liveResizeObserverSet = false; - QMacNotificationObserver liveResizeStartObserver; - QMacNotificationObserver liveResizeEndObserver; -#endif }; QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice) @@ -460,7 +473,7 @@ QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *import importedDevice = importDevice != nullptr; if (importedDevice) { - if (d->dev) { + if (importDevice->dev) { d->dev = (id<MTLDevice>) importDevice->dev; importedCmdQueue = importDevice->cmdQueue != nullptr; if (importedCmdQueue) @@ -494,8 +507,24 @@ bool QRhiMetal::probe(QRhiMetalInitParams *params) return false; } +id<MTLCommandBuffer> QRhiMetalData::newCommandBuffer() +{ +#ifdef QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES + // Do not let the command buffer mess with the refcount of objects. We do + // have a proper render loop and will manage lifetimes similarly to other + // backends (Vulkan). + return [cmdQueue commandBufferWithUnretainedReferences]; +#else + return [cmdQueue commandBuffer]; +#endif +} + bool QRhiMetalData::setupBinaryArchive(NSURL *sourceFileUrl) { +#ifdef QRHI_METAL_DISABLE_BINARY_ARCHIVE + return false; +#endif + if (@available(macOS 11.0, iOS 14.0, *)) { [binArch release]; MTLBinaryArchiveDescriptor *binArchDesc = [MTLBinaryArchiveDescriptor new]; @@ -508,7 +537,6 @@ bool QRhiMetalData::setupBinaryArchive(NSURL *sourceFileUrl) qWarning("newBinaryArchiveWithDescriptor failed: %s", qPrintable(msg)); return false; } - binArchWasEmpty = sourceFileUrl == nil; return true; } return false; @@ -536,9 +564,7 @@ bool QRhiMetal::create(QRhi::Flags flags) // suitable as deviceId because it does not seem stable on macOS and can // apparently change when the system is rebooted. -#ifdef Q_OS_IOS - driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice; -#else +#ifdef Q_OS_MACOS if (@available(macOS 10.15, *)) { const MTLDeviceLocation deviceLocation = [d->dev location]; switch (deviceLocation) { @@ -555,6 +581,8 @@ bool QRhiMetal::create(QRhi::Flags flags) break; } } +#else + driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice; #endif const QOperatingSystemVersion ver = QOperatingSystemVersion::current(); @@ -580,6 +608,7 @@ bool QRhiMetal::create(QRhi::Flags flags) if (@available(macOS 10.15, *)) caps.isAppleGPU = [d->dev supportsFamily:MTLGPUFamilyApple7]; caps.maxThreadGroupSize = 1024; + caps.multiView = true; #elif defined(Q_OS_TVOS) if ([d->dev supportsFeatureSet: MTLFeatureSet(30003)]) // MTLFeatureSet_tvOS_GPUFamily2_v1 caps.maxTextureSize = 16384; @@ -606,8 +635,10 @@ bool QRhiMetal::create(QRhi::Flags flags) } caps.isAppleGPU = true; if (@available(iOS 13, *)) { - if ([d->dev supportsFamily:MTLGPUFamilyApple4]) + if ([d->dev supportsFamily: MTLGPUFamilyApple4]) caps.maxThreadGroupSize = 1024; + if ([d->dev supportsFamily: MTLGPUFamilyApple5]) + caps.multiView = true; } #endif @@ -657,17 +688,6 @@ QVector<int> QRhiMetal::supportedSampleCounts() const return caps.supportedSampleCounts; } -int QRhiMetal::effectiveSampleCount(int sampleCount) const -{ - // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1. - const int s = qBound(1, sampleCount, 64); - if (!supportedSampleCounts().contains(s)) { - qWarning("Attempted to set unsupported sample count %d", sampleCount); - return 1; - } - return s; -} - QRhiSwapChain *QRhiMetal::createSwapChain() { return new QMetalSwapChain(this); @@ -833,6 +853,12 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const return false; case QRhi::ThreeDimensionalTextureMipmaps: return true; + case QRhi::MultiView: + return caps.multiView; + case QRhi::TextureViewFormat: + return false; + case QRhi::ResolveDepthStencil: + return true; default: Q_UNREACHABLE(); return false; @@ -1469,11 +1495,11 @@ void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline psD->makeActiveForCurrentRenderPassEncoder(cbD); } else { // mark work buffers that can now be safely reused as reusable - for (QMetalBuffer *workBuf : psD->d->tess.deviceLocalWorkBuffers) { + for (QMetalBuffer *workBuf : psD->d->extraBufMgr.deviceLocalWorkBuffers) { if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot) workBuf->lastActiveFrameSlot = -1; } - for (QMetalBuffer *workBuf : psD->d->tess.hostVisibleWorkBuffers) { + for (QMetalBuffer *workBuf : psD->d->extraBufMgr.hostVisibleWorkBuffers) { if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot) workBuf->lastActiveFrameSlot = -1; } @@ -1965,6 +1991,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) const quint32 vertexOrIndexCount = indexed ? args.drawIndexed.indexCount : args.draw.vertexCount; QMetalGraphicsPipelineData::Tessellation &tess(graphicsPipeline->d->tess); + QMetalGraphicsPipelineData::ExtraBufferManager &extraBufMgr(graphicsPipeline->d->extraBufMgr); const quint32 patchCount = tess.patchCountForDrawCall(vertexOrIndexCount, instanceCount); QMetalBuffer *vertOutBuf = nullptr; QMetalBuffer *tescOutBuf = nullptr; @@ -1998,7 +2025,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) if (outputBufferBinding >= 0) { const quint32 workBufSize = tess.vsCompOutputBufferSize(vertexOrIndexCount, instanceCount); - vertOutBuf = tess.acquireWorkBuffer(this, workBufSize); + vertOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize); if (!vertOutBuf) return; [computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding]; @@ -2046,7 +2073,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) if (outputBufferBinding >= 0) { const quint32 workBufSize = tess.tescCompOutputBufferSize(patchCount); - tescOutBuf = tess.acquireWorkBuffer(this, workBufSize); + tescOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize); if (!tescOutBuf) return; [computeEncoder setBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding]; @@ -2054,14 +2081,14 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) if (patchOutputBufferBinding >= 0) { const quint32 workBufSize = tess.tescCompPatchOutputBufferSize(patchCount); - tescPatchOutBuf = tess.acquireWorkBuffer(this, workBufSize); + tescPatchOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize); if (!tescPatchOutBuf) return; [computeEncoder setBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding]; } if (tessFactorBufferBinding >= 0) { - tescFactorBuf = tess.acquireWorkBuffer(this, patchCount * sizeof(MTLQuadTessellationFactorsHalf)); + tescFactorBuf = extraBufMgr.acquireWorkBuffer(this, patchCount * sizeof(MTLQuadTessellationFactorsHalf)); [computeEncoder setBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding]; } @@ -2070,7 +2097,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) quint32 inControlPointCount; quint32 patchCount; } params; - tescParamsBuf = tess.acquireWorkBuffer(this, sizeof(params), QMetalGraphicsPipelineData::Tessellation::WorkBufType::HostVisible); + tescParamsBuf = extraBufMgr.acquireWorkBuffer(this, sizeof(params), QMetalGraphicsPipelineData::ExtraBufferManager::WorkBufType::HostVisible); if (!tescParamsBuf) return; params.inControlPointCount = tess.inControlPointCount; @@ -2139,6 +2166,39 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) } } +void QRhiMetal::adjustForMultiViewDraw(quint32 *instanceCount, QRhiCommandBuffer *cb) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + const int multiViewCount = cbD->currentGraphicsPipeline->m_multiViewCount; + if (multiViewCount <= 1) + return; + + const QMap<int, int> &ebb(cbD->currentGraphicsPipeline->d->vs.nativeShaderInfo.extraBufferBindings); + const int viewMaskBufBinding = ebb.value(QShaderPrivate::MslMultiViewMaskBufferBinding, -1); + if (viewMaskBufBinding == -1) { + qWarning("No extra buffer for multiview in the vertex shader; was it built with --view-count specified?"); + return; + } + struct { + quint32 viewOffset; + quint32 viewCount; + } multiViewInfo; + multiViewInfo.viewOffset = 0; + multiViewInfo.viewCount = quint32(multiViewCount); + QMetalBuffer *buf = cbD->currentGraphicsPipeline->d->extraBufMgr.acquireWorkBuffer(this, sizeof(multiViewInfo), + QMetalGraphicsPipelineData::ExtraBufferManager::WorkBufType::HostVisible); + if (buf) { + id<MTLBuffer> mtlbuf = buf->d->buf[0]; + char *p = reinterpret_cast<char *>([mtlbuf contents]); + memcpy(p, &multiViewInfo, sizeof(multiViewInfo)); + [cbD->d->currentRenderPassEncoder setVertexBuffer: mtlbuf offset: 0 atIndex: viewMaskBufBinding]; + // The instance count is adjusted for layered rendering. The vertex shader is expected to contain something like: + // uint gl_ViewIndex = spvViewMask[0] + (gl_InstanceIndex - gl_BaseInstance) % spvViewMask[1]; + // where spvViewMask is the buffer with multiViewInfo passed in above. + *instanceCount *= multiViewCount; + } +} + void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { @@ -2157,6 +2217,8 @@ void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount, return; } + adjustForMultiViewDraw(&instanceCount, cb); + if (caps.baseVertexAndInstance) { [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance]; @@ -2195,6 +2257,8 @@ void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, return; } + adjustForMultiViewDraw(&instanceCount, cb); + if (caps.baseVertexAndInstance) { [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType indexCount: indexCount @@ -2297,10 +2361,7 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF [d->captureScope beginScope]; - // Do not let the command buffer mess with the refcount of objects. We do - // have a proper render loop and will manage lifetimes similarly to other - // backends (Vulkan). - swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences]; + swapChainD->cbWrapper.d->cb = d->newCommandBuffer(); QMetalRenderTargetData::ColorAtt colorAtt; if (swapChainD->samples > 1) { @@ -2312,6 +2373,7 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt; swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil; + swapChainD->rtWrapper.d->fb.dsResolveTex = nil; swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false; swapChainD->rtWrapper.d->fb.depthNeedsStore = false; @@ -2331,31 +2393,89 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain); Q_ASSERT(currentSwapChain == swapChainD); + // Keep strong reference to command buffer + id<MTLCommandBuffer> commandBuffer = swapChainD->cbWrapper.d->cb; + __block int thisFrameSlot = currentFrameSlot; - [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer> cb) { + [commandBuffer addCompletedHandler: ^(id<MTLCommandBuffer> cb) { swapChainD->d->lastGpuTime[thisFrameSlot] += cb.GPUEndTime - cb.GPUStartTime; dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]); }]; - const bool needsPresent = !flags.testFlag(QRhi::SkipPresent); - const bool presentsWithTransaction = swapChainD->d->layer.presentsWithTransaction; - if (!presentsWithTransaction && needsPresent) { - // beginFrame-endFrame without a render pass inbetween means there is no drawable. - if (id<CAMetalDrawable> drawable = swapChainD->d->curDrawable) - [swapChainD->cbWrapper.d->cb presentDrawable: drawable]; - } - - [swapChainD->cbWrapper.d->cb commit]; +#ifdef QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES + // When Metal API validation diagnostics is enabled in Xcode the texture is + // released before the command buffer is done with it. Manually keep it alive + // to work around this. + id<MTLTexture> drawableTexture = [swapChainD->d->curDrawable.texture retain]; + [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) { + [drawableTexture release]; + }]; +#endif - if (presentsWithTransaction && needsPresent) { - // beginFrame-endFrame without a render pass inbetween means there is no drawable. + if (flags.testFlag(QRhi::SkipPresent)) { + // Just need to commit, that's it + [commandBuffer commit]; + } else { if (id<CAMetalDrawable> drawable = swapChainD->d->curDrawable) { - // The layer has presentsWithTransaction set to true to avoid flicker on resizing, - // so here it is important to follow what the Metal docs say when it comes to the - // issuing the present. - [swapChainD->cbWrapper.d->cb waitUntilScheduled]; - [drawable present]; + // Got something to present + if (swapChainD->d->layer.presentsWithTransaction) { + [commandBuffer commit]; + // Keep strong reference to Metal layer + auto *metalLayer = swapChainD->d->layer; + auto presentWithTransaction = ^{ + [commandBuffer waitUntilScheduled]; + // If the layer has been resized while we waited to be scheduled we bail out, + // as the drawable is no longer valid for the layer, and we'll get a follow-up + // display with the right size. We know we are on the main thread here, which + // means we can access the layer directly. We also know that the layer is valid, + // since the block keeps a strong reference to it, compared to the QRhiSwapChain + // that can go away under our feet by the time we're scheduled. + const auto surfaceSize = QSizeF::fromCGSize(metalLayer.bounds.size) * metalLayer.contentsScale; + const auto textureSize = QSizeF(drawable.texture.width, drawable.texture.height); + if (textureSize == surfaceSize) { + [drawable present]; + } else { + qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable << "due to texture size" + << textureSize << "not matching surface size" << surfaceSize; + } + }; + + if (NSThread.currentThread == NSThread.mainThread) { + presentWithTransaction(); + } else { + auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer); + Q_ASSERT(qtMetalLayer); + // Let the main thread present the drawable from displayLayer + qtMetalLayer.mainThreadPresentation = presentWithTransaction; + } + } else { + // Keep strong reference to Metal layer so it's valid in the block + auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer); + [commandBuffer addScheduledHandler:^(id<MTLCommandBuffer>) { + if (qtMetalLayer) { + // The schedule handler comes in on the com.Metal.CompletionQueueDispatch + // thread, which means we might be racing against a display cycle on the + // main thread. If the displayLayer is already in progress, we don't want + // to step on its toes. + if (qtMetalLayer.displayLock.tryLockForRead()) { + [drawable present]; + qtMetalLayer.displayLock.unlock(); + } else { + qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable + << "due to" << qtMetalLayer << "needing display"; + } + } else { + [drawable present]; + } + }]; + [commandBuffer commit]; + } + } else { + // Still need to commit, even if we don't have a drawable + [commandBuffer commit]; } + + swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT; } // Must not hold on to the drawable, regardless of needsPresent @@ -2364,9 +2484,6 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame [d->captureScope endScope]; - if (needsPresent) - swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT; - swapChainD->frameCount += 1; currentSwapChain = nullptr; return QRhi::FrameOpSuccess; @@ -2383,7 +2500,7 @@ QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi: d->ofr.active = true; *cb = &d->ofr.cbWrapper; - d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences]; + d->ofr.cbWrapper.d->cb = d->newCommandBuffer(); executeDeferredReleases(); d->ofr.cbWrapper.resetState(d->ofr.lastGpuTime); @@ -2448,10 +2565,10 @@ QRhi::FrameOpResult QRhiMetal::finish() if (inFrame) { if (d->ofr.active) { d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime; - d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences]; + d->ofr.cbWrapper.d->cb = d->newCommandBuffer(); } else { swapChainD->d->lastGpuTime[currentFrameSlot] += cb.GPUEndTime - cb.GPUStartTime; - swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences]; + swapChainD->cbWrapper.d->cb = d->newCommandBuffer(); } } @@ -2514,7 +2631,6 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc int w = img.width(); int h = img.height(); int bpl = img.bytesPerLine(); - int srcOffset = 0; if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) { const int sx = subresDesc.sourceTopLeft().x(); @@ -2523,10 +2639,12 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc w = subresDesc.sourceSize().width(); h = subresDesc.sourceSize().height(); } - if (img.depth() == 32) { - memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(fullImageSizeBytes)); - srcOffset = sy * bpl + sx * 4; - // bpl remains set to the original image's row stride + if (w == img.width()) { + const int bpc = qMax(1, img.depth() / 8); + Q_ASSERT(h * img.bytesPerLine() <= fullImageSizeBytes); + memcpy(reinterpret_cast<char *>(mp) + *curOfs, + img.constBits() + sy * img.bytesPerLine() + sx * bpc, + h * img.bytesPerLine()); } else { img = img.copy(sx, sy, w, h); bpl = img.bytesPerLine(); @@ -2538,7 +2656,7 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc } [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot] - sourceOffset: NSUInteger(*curOfs + srcOffset) + sourceOffset: NSUInteger(*curOfs) sourceBytesPerRow: NSUInteger(bpl) sourceBytesPerImage: 0 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1) @@ -2630,6 +2748,15 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); + id<MTLBlitCommandEncoder> blitEnc = nil; + auto ensureBlit = [&blitEnc, cbD, this]() { + if (!blitEnc) { + blitEnc = [cbD->d->cb blitCommandEncoder]; + if (debugMarkers) + [blitEnc pushDebugGroup: @"Texture upload/copy"]; + } + }; + for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) { const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]); if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { @@ -2668,19 +2795,17 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate readback.readSize = u.readSize; readback.result = u.result; d->activeBufferReadbacks.append(readback); +#ifdef Q_OS_MACOS + if (bufD->d->managed) { + // On non-Apple Silicon, manually synchronize memory from GPU to CPU + ensureBlit(); + [blitEnc synchronizeResource:readback.buf]; + } +#endif } } } - id<MTLBlitCommandEncoder> blitEnc = nil; - auto ensureBlit = [&blitEnc, cbD, this] { - if (!blitEnc) { - blitEnc = [cbD->d->cb blitCommandEncoder]; - if (debugMarkers) - [blitEnc pushDebugGroup: @"Texture upload/copy"]; - } - }; - for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) { const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]); if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { @@ -2891,28 +3016,39 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QMetalTexture, QMetalRenderBuffer>(rtTex->description(), rtD->currentResIdList)) rtTex->create(); cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount); - if (rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents)) { + if (rtD->fb.preserveColor) { for (uint i = 0; i < uint(rtD->colorAttCount); ++i) cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad; } - if (rtD->dsAttCount && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents)) { + if (rtD->dsAttCount && rtD->fb.preserveDs) { cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad; cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad; } + int colorAttCount = 0; for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); it != itEnd; ++it) { - if (it->texture()) + colorAttCount += 1; + if (it->texture()) { QRHI_RES(QMetalTexture, it->texture())->lastActiveFrameSlot = currentFrameSlot; - else if (it->renderBuffer()) + if (it->multiViewCount() >= 2) + cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(it->multiViewCount()); + } else if (it->renderBuffer()) { QRHI_RES(QMetalRenderBuffer, it->renderBuffer())->lastActiveFrameSlot = currentFrameSlot; + } if (it->resolveTexture()) QRHI_RES(QMetalTexture, it->resolveTexture())->lastActiveFrameSlot = currentFrameSlot; } if (rtTex->m_desc.depthStencilBuffer()) QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot; - if (rtTex->m_desc.depthTexture()) - QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture())->lastActiveFrameSlot = currentFrameSlot; + if (rtTex->m_desc.depthTexture()) { + QMetalTexture *depthTexture = QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture()); + depthTexture->lastActiveFrameSlot = currentFrameSlot; + if (colorAttCount == 0 && depthTexture->arraySize() >= 2) + cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(depthTexture->arraySize()); + } + if (rtTex->m_desc.depthResolveTexture()) + QRHI_RES(QMetalTexture, rtTex->m_desc.depthResolveTexture())->lastActiveFrameSlot = currentFrameSlot; } break; default: @@ -2926,7 +3062,8 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, cbD->d->currentPassRpDesc.colorAttachments[i].depthPlane = NSUInteger(rtD->fb.colorAtt[i].slice); cbD->d->currentPassRpDesc.colorAttachments[i].level = NSUInteger(rtD->fb.colorAtt[i].level); if (rtD->fb.colorAtt[i].resolveTex) { - cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve; + cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = rtD->fb.preserveColor ? MTLStoreActionStoreAndMultisampleResolve + : MTLStoreActionMultisampleResolve; cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex; cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = NSUInteger(rtD->fb.colorAtt[i].resolveLayer); cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = NSUInteger(rtD->fb.colorAtt[i].resolveLevel); @@ -2939,6 +3076,15 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil; if (rtD->fb.depthNeedsStore) // Depth/Stencil is set to DontCare by default, override if needed cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore; + if (rtD->fb.dsResolveTex) { + cbD->d->currentPassRpDesc.depthAttachment.storeAction = rtD->fb.depthNeedsStore ? MTLStoreActionStoreAndMultisampleResolve + : MTLStoreActionMultisampleResolve; + cbD->d->currentPassRpDesc.depthAttachment.resolveTexture = rtD->fb.dsResolveTex; + if (rtD->fb.hasStencil) { + cbD->d->currentPassRpDesc.stencilAttachment.resolveTexture = rtD->fb.dsResolveTex; + cbD->d->currentPassRpDesc.stencilAttachment.storeAction = cbD->d->currentPassRpDesc.depthAttachment.storeAction; + } + } } cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc]; @@ -3748,11 +3894,11 @@ bool QMetalTexture::create() desc.textureType = isArray ? MTLTextureType1DArray : MTLTextureType1D; } else if (isArray) { #ifdef Q_OS_IOS - if (samples > 1) { - // would be available on iOS 14.0+ but cannot test for that with a 13 SDK - qWarning("Multisample 2D texture array is not supported on iOS"); + if (@available(iOS 14, *)) { + desc.textureType = samples > 1 ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray; + } else { + desc.textureType = MTLTextureType2DArray; } - desc.textureType = MTLTextureType2DArray; #else desc.textureType = samples > 1 ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray; #endif @@ -4161,8 +4307,9 @@ bool QMetalTextureRenderTarget::create() if (m_desc.depthTexture()) { QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture()); d->fb.dsTex = depthTexD->d->tex; - d->fb.hasStencil = false; - d->fb.depthNeedsStore = true; + d->fb.hasStencil = rhiD->isStencilSupportingFormat(depthTexD->format()); + d->fb.depthNeedsStore = !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture(); + d->fb.preserveDs = m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents); if (d->colorAttCount == 0) { d->pixelSize = depthTexD->pixelSize(); d->sampleCount = depthTexD->samples; @@ -4172,16 +4319,24 @@ bool QMetalTextureRenderTarget::create() d->fb.dsTex = depthRbD->d->tex; d->fb.hasStencil = true; d->fb.depthNeedsStore = false; + d->fb.preserveDs = false; if (d->colorAttCount == 0) { d->pixelSize = depthRbD->pixelSize(); d->sampleCount = depthRbD->samples; } } + if (m_desc.depthResolveTexture()) { + QMetalTexture *depthResolveTexD = QRHI_RES(QMetalTexture, m_desc.depthResolveTexture()); + d->fb.dsResolveTex = depthResolveTexD->d->tex; + } d->dsAttCount = 1; } else { d->dsAttCount = 0; } + if (d->colorAttCount > 0) + d->fb.preserveColor = m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents); + QRhiRenderTargetAttachmentTracker::updateResIdList<QMetalTexture, QMetalRenderBuffer>(m_desc, &d->currentResIdList); rhiD->registerResource(this, false); @@ -4293,10 +4448,10 @@ void QMetalGraphicsPipeline::destroy() d->tess.compTesc.destroy(); d->tess.vertTese.destroy(); - qDeleteAll(d->tess.deviceLocalWorkBuffers); - d->tess.deviceLocalWorkBuffers.clear(); - qDeleteAll(d->tess.hostVisibleWorkBuffers); - d->tess.hostVisibleWorkBuffers.clear(); + qDeleteAll(d->extraBufMgr.deviceLocalWorkBuffers); + d->extraBufMgr.deviceLocalWorkBuffers.clear(); + qDeleteAll(d->extraBufMgr.hostVisibleWorkBuffers); + d->extraBufMgr.hostVisibleWorkBuffers.clear(); delete d->bufferSizeBuffer; d->bufferSizeBuffer = nullptr; @@ -4368,6 +4523,22 @@ static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::F return MTLVertexFormatHalf2; case QRhiVertexInputAttribute::Half: return MTLVertexFormatHalf; + case QRhiVertexInputAttribute::UShort4: + return MTLVertexFormatUShort4; + case QRhiVertexInputAttribute::UShort3: + return MTLVertexFormatUShort3; + case QRhiVertexInputAttribute::UShort2: + return MTLVertexFormatUShort2; + case QRhiVertexInputAttribute::UShort: + return MTLVertexFormatUShort; + case QRhiVertexInputAttribute::SShort4: + return MTLVertexFormatShort4; + case QRhiVertexInputAttribute::SShort3: + return MTLVertexFormatShort3; + case QRhiVertexInputAttribute::SShort2: + return MTLVertexFormatShort2; + case QRhiVertexInputAttribute::SShort: + return MTLVertexFormatShort; default: Q_UNREACHABLE(); return MTLVertexFormatFloat4; @@ -4523,6 +4694,24 @@ static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topolo } } +static inline MTLPrimitiveTopologyClass toMetalPrimitiveTopologyClass(QRhiGraphicsPipeline::Topology t) +{ + switch (t) { + case QRhiGraphicsPipeline::Triangles: + case QRhiGraphicsPipeline::TriangleStrip: + case QRhiGraphicsPipeline::TriangleFan: + return MTLPrimitiveTopologyClassTriangle; + case QRhiGraphicsPipeline::Lines: + case QRhiGraphicsPipeline::LineStrip: + return MTLPrimitiveTopologyClassLine; + case QRhiGraphicsPipeline::Points: + return MTLPrimitiveTopologyClassPoint; + default: + Q_UNREACHABLE(); + return MTLPrimitiveTopologyClassTriangle; + } +} + static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c) { switch (c) { @@ -4710,7 +4899,7 @@ void QMetalGraphicsPipeline::setupAttachmentsInMetalRenderPassDescriptor(void *m } QRHI_RES_RHI(QRhiMetal); - rpDesc.sampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount)); + rpDesc.rasterSampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount)); } void QMetalGraphicsPipeline::setupMetalDepthStencilDescriptor(void *metalDsDesc) @@ -4763,6 +4952,7 @@ void QMetalGraphicsPipelineData::setupVertexInputDescriptor(MTLVertexDescriptor desc.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding()); } int bindingIndex = 0; + const NSUInteger viewCount = qMax<NSUInteger>(1, q->multiViewCount()); for (auto it = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings(); it != itEnd; ++it, ++bindingIndex) { @@ -4771,6 +4961,8 @@ void QMetalGraphicsPipelineData::setupVertexInputDescriptor(MTLVertexDescriptor it->classification() == QRhiVertexInputBinding::PerInstance ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex; desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate()); + if (desc.layouts[layoutIdx].stepFunction == MTLVertexStepFunctionPerInstance) + desc.layouts[layoutIdx].stepRate *= viewCount; desc.layouts[layoutIdx].stride = it->stride(); } } @@ -4819,33 +5011,10 @@ void QRhiMetalData::trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineD } } -static bool canAddToBinaryArchive(QRhiMetalData *d) -{ - if (@available(macOS 11.0, iOS 14.0, *)) { - if (!d->binArch) - return false; - - // ### QTBUG-106703, QTBUG-108216, revisit after 13.0 - if (!d->binArchWasEmpty && d->q->osMajor >= 13) { - static bool logPrinted = false; - if (!logPrinted) { - logPrinted = true; - qCDebug(QRHI_LOG_INFO, "Skipping adding more pipelines to MTLBinaryArchive on this OS version (%d.%d) due to known issues.", - d->q->osMajor, d->q->osMinor); - } - return false; - } - - return true; - } else { - return false; - } -} - void QRhiMetalData::addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc) { if (@available(macOS 11.0, iOS 14.0, *)) { - if (canAddToBinaryArchive(this)) { + if (binArch) { NSError *err = nil; if (![binArch addRenderPipelineFunctionsWithDescriptor: rpDesc error: &err]) { const QString msg = QString::fromNSString(err.localizedDescription); @@ -4947,6 +5116,9 @@ bool QMetalGraphicsPipeline::createVertexFragmentPipeline() QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc); setupAttachmentsInMetalRenderPassDescriptor(rpDesc, rpD); + if (m_multiViewCount >= 2) + rpDesc.inputPrimitiveTopology = toMetalPrimitiveTopologyClass(m_topology); + rhiD->d->trySeedingRenderPipelineFromBinaryArchive(rpDesc); if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) @@ -5469,7 +5641,7 @@ id<MTLRenderPipelineState> QMetalGraphicsPipelineData::Tessellation::teseFragRen return ps; } -QMetalBuffer *QMetalGraphicsPipelineData::Tessellation::acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type) +QMetalBuffer *QMetalGraphicsPipelineData::ExtraBufferManager::acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type) { QVector<QMetalBuffer *> *workBuffers = type == WorkBufType::DeviceLocal ? &deviceLocalWorkBuffers : &hostVisibleWorkBuffers; @@ -5535,6 +5707,9 @@ bool QMetalGraphicsPipeline::createTessellationPipelines(const QShader &tessVert return false; } + if (m_multiViewCount >= 2) + qWarning("Multiview is not supported with tessellation"); + // Now the vertex shader is a compute shader. // It should have three dedicated *VertexAsComputeShader variants. // What the requested variant was (Standard or Batchable) plays no role here. @@ -5825,7 +6000,7 @@ void QRhiMetalData::trySeedingComputePipelineFromBinaryArchive(MTLComputePipelin void QRhiMetalData::addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc) { if (@available(macOS 11.0, iOS 14.0, *)) { - if (canAddToBinaryArchive(this)) { + if (binArch) { NSError *err = nil; if (![binArch addComputePipelineFunctionsWithDescriptor: cpDesc error: &err]) { const QString msg = QString::fromNSString(err.localizedDescription); @@ -6035,13 +6210,8 @@ void QMetalSwapChain::destroy() d->msaaTex[i] = nil; } -#ifdef Q_OS_MACOS - d->liveResizeStartObserver.remove(); - d->liveResizeEndObserver.remove(); - d->liveResizeObserverSet = false; -#endif - d->layer = nullptr; + m_proxyData = {}; [d->curDrawable release]; d->curDrawable = nil; @@ -6105,9 +6275,17 @@ QSize QMetalSwapChain::surfacePixelSize() bool QMetalSwapChain::isFormatSupported(Format f) { -#ifdef Q_OS_MACOS - return f == SDR || f == HDRExtendedSrgbLinear; -#endif + if (f == HDRExtendedSrgbLinear) { + if (@available(macOS 10.11, iOS 16.0, *)) + return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f; + else + return false; + } else if (f == HDRExtendedDisplayP3Linear) { + if (@available(macOS 11.0, iOS 14.0, *)) + return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f; + else + return false; + } return f == SDR; } @@ -6142,7 +6320,7 @@ void QMetalSwapChain::chooseFormats() QRHI_RES_RHI(QRhiMetal); samples = rhiD->effectiveSampleCount(m_sampleCount); // pick a format that is allowed for CAMetalLayer.pixelFormat - if (m_format == HDRExtendedSrgbLinear) { + if (m_format == HDRExtendedSrgbLinear || m_format == HDRExtendedDisplayP3Linear) { d->colorFormat = MTLPixelFormatRGBA16Float; d->rhiColorFormat = QRhiTexture::RGBA16F; return; @@ -6189,13 +6367,18 @@ bool QMetalSwapChain::createOrResize() chooseFormats(); if (d->colorFormat != d->layer.pixelFormat) d->layer.pixelFormat = d->colorFormat; -#ifdef Q_OS_MACOS - // Can't enable this on iOS until wantsExtendedDynamicRangeContent is available + if (m_format == HDRExtendedSrgbLinear) { - d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB); - d->layer.wantsExtendedDynamicRangeContent = YES; + if (@available(macOS 10.11, iOS 16.0, *)) { + d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB); + d->layer.wantsExtendedDynamicRangeContent = YES; + } + } else if (m_format == HDRExtendedDisplayP3Linear) { + if (@available(macOS 11.0, iOS 16.0, *)) { + d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3); + d->layer.wantsExtendedDynamicRangeContent = YES; + } } -#endif if (m_flags.testFlag(UsedAsTransferSource)) d->layer.framebufferOnly = NO; @@ -6233,34 +6416,6 @@ bool QMetalSwapChain::createOrResize() [d->layer setDevice: rhiD->d->dev]; -#ifdef Q_OS_MACOS - // Can only use presentsWithTransaction (to get smooth resizing) when - // presenting from the main (gui) thread. We predict that based on the - // thread this function is called on since if the QRhiSwapChain is - // initialied on a given thread then that's almost certainly the thread on - // which the QRhi renders and presents. - const bool canUsePresentsWithTransaction = NSThread.isMainThread; - - // Have an env.var. just in case it turns out presentsWithTransaction is - // not desired in some specific case. - static bool allowPresentsWithTransaction = !qEnvironmentVariableIntValue("QT_MTL_NO_TRANSACTION"); - - if (allowPresentsWithTransaction && canUsePresentsWithTransaction && !d->liveResizeObserverSet) { - d->liveResizeObserverSet = true; - NSView *view = reinterpret_cast<NSView *>(window->winId()); - NSWindow *window = view.window; - if (window) { - qCDebug(QRHI_LOG_INFO, "will set presentsWithTransaction during live resize"); - d->liveResizeStartObserver = QMacNotificationObserver(window, NSWindowWillStartLiveResizeNotification, [this] { - d->layer.presentsWithTransaction = true; - }); - d->liveResizeEndObserver = QMacNotificationObserver(window, NSWindowDidEndLiveResizeNotification, [this] { - d->layer.presentsWithTransaction = false; - }); - } - } -#endif - [d->curDrawable release]; d->curDrawable = nil; @@ -6328,21 +6483,28 @@ QRhiSwapChainHdrInfo QMetalSwapChain::hdrInfo() { QRhiSwapChainHdrInfo info; info.limitsType = QRhiSwapChainHdrInfo::ColorComponentValue; - if (m_format == SDR) { - info.limits.colorComponentValue.maxColorComponentValue = 1; - return info; - } + info.limits.colorComponentValue.maxColorComponentValue = 1; + info.limits.colorComponentValue.maxPotentialColorComponentValue = 1; + info.luminanceBehavior = QRhiSwapChainHdrInfo::DisplayReferred; // 1.0 = SDR white + info.sdrWhiteLevel = 200; // typical value, but dummy (don't know the real one); won't matter due to being display-referred -#ifdef Q_OS_MACOS - info.isHardCodedDefaults = false; - NSView *view = reinterpret_cast<NSView *>(window->winId()); - info.limits.colorComponentValue.maxColorComponentValue = view.window.screen.maximumExtendedDynamicRangeColorComponentValue; -#else - // ### Fixme: Maybe retrieve the brightness from the screen and if we're not at full brightness we might be able to do more. - // For now, assume 2, in line with iPhone 12 specs that claim 625 nits max brightness and 1200 nits max HDR brightness. - info.isHardCodedDefaults = true; - info.limits.colorComponentValue.maxColorComponentValue = 2; + if (m_window) { + // Must use m_window, not window, given this may be called before createOrResize(). +#if defined(Q_OS_MACOS) + NSView *view = reinterpret_cast<NSView *>(m_window->winId()); + NSScreen *screen = view.window.screen; + info.limits.colorComponentValue.maxColorComponentValue = screen.maximumExtendedDynamicRangeColorComponentValue; + info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.maximumPotentialExtendedDynamicRangeColorComponentValue; +#elif defined(Q_OS_IOS) + if (@available(iOS 16.0, *)) { + UIView *view = reinterpret_cast<UIView *>(m_window->winId()); + UIScreen *screen = view.window.windowScene.screen; + info.limits.colorComponentValue.maxColorComponentValue = view.window.windowScene.screen.currentEDRHeadroom; + info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.potentialEDRHeadroom; + } #endif + } + return info; } |