diff options
Diffstat (limited to 'src/gui/rhi/qrhimetal.mm')
-rw-r--r-- | src/gui/rhi/qrhimetal.mm | 886 |
1 files changed, 481 insertions, 405 deletions
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index d0eb805c42..36a71e5101 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,24 +507,36 @@ 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) { - if (@available(macOS 11.0, iOS 14.0, *)) { - [binArch release]; - MTLBinaryArchiveDescriptor *binArchDesc = [MTLBinaryArchiveDescriptor new]; - binArchDesc.url = sourceFileUrl; - NSError *err = nil; - binArch = [dev newBinaryArchiveWithDescriptor: binArchDesc error: &err]; - [binArchDesc release]; - if (!binArch) { - const QString msg = QString::fromNSString(err.localizedDescription); - qWarning("newBinaryArchiveWithDescriptor failed: %s", qPrintable(msg)); - return false; - } - binArchWasEmpty = sourceFileUrl == nil; - return true; - } +#ifdef QRHI_METAL_DISABLE_BINARY_ARCHIVE return false; +#endif + + [binArch release]; + MTLBinaryArchiveDescriptor *binArchDesc = [MTLBinaryArchiveDescriptor new]; + binArchDesc.url = sourceFileUrl; + NSError *err = nil; + binArch = [dev newBinaryArchiveWithDescriptor: binArchDesc error: &err]; + [binArchDesc release]; + if (!binArch) { + const QString msg = QString::fromNSString(err.localizedDescription); + qWarning("newBinaryArchiveWithDescriptor failed: %s", qPrintable(msg)); + return false; + } + return true; } bool QRhiMetal::create(QRhi::Flags flags) @@ -536,25 +561,23 @@ 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 - if (@available(macOS 10.15, *)) { - const MTLDeviceLocation deviceLocation = [d->dev location]; - switch (deviceLocation) { - case MTLDeviceLocationBuiltIn: - driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice; - break; - case MTLDeviceLocationSlot: - driverInfoStruct.deviceType = QRhiDriverInfo::DiscreteDevice; - break; - case MTLDeviceLocationExternal: - driverInfoStruct.deviceType = QRhiDriverInfo::ExternalDevice; - break; - default: - break; - } +#ifdef Q_OS_MACOS + const MTLDeviceLocation deviceLocation = [d->dev location]; + switch (deviceLocation) { + case MTLDeviceLocationBuiltIn: + driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice; + break; + case MTLDeviceLocationSlot: + driverInfoStruct.deviceType = QRhiDriverInfo::DiscreteDevice; + break; + case MTLDeviceLocationExternal: + driverInfoStruct.deviceType = QRhiDriverInfo::ExternalDevice; + break; + default: + break; } +#else + driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice; #endif const QOperatingSystemVersion ver = QOperatingSystemVersion::current(); @@ -577,27 +600,21 @@ bool QRhiMetal::create(QRhi::Flags flags) #if defined(Q_OS_MACOS) caps.maxTextureSize = 16384; caps.baseVertexAndInstance = true; - if (@available(macOS 10.15, *)) - caps.isAppleGPU = [d->dev supportsFamily:MTLGPUFamilyApple7]; + 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 + if ([d->dev supportsFamily:MTLGPUFamilyApple3]) caps.maxTextureSize = 16384; else caps.maxTextureSize = 8192; caps.baseVertexAndInstance = false; caps.isAppleGPU = true; #elif defined(Q_OS_IOS) - // welcome to feature set hell - if ([d->dev supportsFeatureSet: MTLFeatureSet(16)] // MTLFeatureSet_iOS_GPUFamily5_v1 - || [d->dev supportsFeatureSet: MTLFeatureSet(11)] // MTLFeatureSet_iOS_GPUFamily4_v1 - || [d->dev supportsFeatureSet: MTLFeatureSet(4)]) // MTLFeatureSet_iOS_GPUFamily3_v1 - { + if ([d->dev supportsFamily:MTLGPUFamilyApple3]) { caps.maxTextureSize = 16384; caps.baseVertexAndInstance = true; - } else if ([d->dev supportsFeatureSet: MTLFeatureSet(3)] // MTLFeatureSet_iOS_GPUFamily2_v2 - || [d->dev supportsFeatureSet: MTLFeatureSet(2)]) // MTLFeatureSet_iOS_GPUFamily1_v2 - { + } else if ([d->dev supportsFamily:MTLGPUFamilyApple2]) { caps.maxTextureSize = 8192; caps.baseVertexAndInstance = false; } else { @@ -605,10 +622,10 @@ bool QRhiMetal::create(QRhi::Flags flags) caps.baseVertexAndInstance = false; } caps.isAppleGPU = true; - if (@available(iOS 13, *)) { - if ([d->dev supportsFamily:MTLGPUFamilyApple4]) - caps.maxThreadGroupSize = 1024; - } + if ([d->dev supportsFamily:MTLGPUFamilyApple4]) + caps.maxThreadGroupSize = 1024; + if ([d->dev supportsFamily:MTLGPUFamilyApple5]) + caps.multiView = true; #endif caps.supportedSampleCounts = { 1 }; @@ -638,10 +655,8 @@ void QRhiMetal::destroy() [d->captureScope release]; d->captureScope = nil; - if (@available(macOS 11.0, iOS 14.0, *)) { - [d->binArch release]; - d->binArch = nil; - } + [d->binArch release]; + d->binArch = nil; [d->cmdQueue release]; if (!importedCmdQueue) @@ -657,17 +672,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); @@ -799,12 +803,7 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const case QRhi::ReadBackAnyTextureFormat: return true; case QRhi::PipelineCacheDataLoadSave: - { - if (@available(macOS 11.0, iOS 14.0, *)) - return true; - else - return false; - } + return true; case QRhi::ImageDataStride: return true; case QRhi::RenderBufferImport: @@ -833,6 +832,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; @@ -926,54 +931,52 @@ QByteArray QRhiMetal::pipelineCacheData() { Q_STATIC_ASSERT(sizeof(QMetalPipelineCacheDataHeader) == 256); QByteArray data; - if (@available(macOS 11.0, iOS 14.0, *)) { - if (!d->binArch || !rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) - return data; - - QTemporaryFile tmp; - if (!tmp.open()) { - qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal"); - return data; - } - tmp.close(); // the file exists until the tmp dtor runs + if (!d->binArch || !rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) + return data; - const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath(); - NSURL *url = QUrl::fromLocalFile(fn).toNSURL(); - NSError *err = nil; - if (![d->binArch serializeToURL: url error: &err]) { - const QString msg = QString::fromNSString(err.localizedDescription); - // Some of these "errors" are not actual errors. (think of "Nothing to serialize") - qCDebug(QRHI_LOG_INFO, "Failed to serialize MTLBinaryArchive: %s", qPrintable(msg)); - return data; - } + QTemporaryFile tmp; + if (!tmp.open()) { + qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal"); + return data; + } + tmp.close(); // the file exists until the tmp dtor runs - QFile f(fn); - if (!f.open(QIODevice::ReadOnly)) { - qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to reopen temporary file"); - return data; - } - const QByteArray blob = f.readAll(); - f.close(); + const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath(); + NSURL *url = QUrl::fromLocalFile(fn).toNSURL(); + NSError *err = nil; + if (![d->binArch serializeToURL: url error: &err]) { + const QString msg = QString::fromNSString(err.localizedDescription); + // Some of these "errors" are not actual errors. (think of "Nothing to serialize") + qCDebug(QRHI_LOG_INFO, "Failed to serialize MTLBinaryArchive: %s", qPrintable(msg)); + return data; + } - const size_t headerSize = sizeof(QMetalPipelineCacheDataHeader); - const quint32 dataSize = quint32(blob.size()); + QFile f(fn); + if (!f.open(QIODevice::ReadOnly)) { + qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to reopen temporary file"); + return data; + } + const QByteArray blob = f.readAll(); + f.close(); - data.resize(headerSize + dataSize); + const size_t headerSize = sizeof(QMetalPipelineCacheDataHeader); + const quint32 dataSize = quint32(blob.size()); - QMetalPipelineCacheDataHeader header = {}; - header.rhiId = pipelineCacheRhiId(); - header.arch = quint32(sizeof(void*)); - header.dataSize = quint32(dataSize); - header.osMajor = osMajor; - header.osMinor = osMinor; - const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.length())); - if (driverStrLen) - memcpy(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen); - header.driver[driverStrLen] = '\0'; + data.resize(headerSize + dataSize); - memcpy(data.data(), &header, headerSize); - memcpy(data.data() + headerSize, blob.constData(), dataSize); - } + QMetalPipelineCacheDataHeader header = {}; + header.rhiId = pipelineCacheRhiId(); + header.arch = quint32(sizeof(void*)); + header.dataSize = quint32(dataSize); + header.osMajor = osMajor; + header.osMinor = osMinor; + const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.length())); + if (driverStrLen) + memcpy(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen); + header.driver[driverStrLen] = '\0'; + + memcpy(data.data(), &header, headerSize); + memcpy(data.data() + headerSize, blob.constData(), dataSize); return data; } @@ -1023,22 +1026,20 @@ void QRhiMetal::setPipelineCacheData(const QByteArray &data) return; } - if (@available(macOS 11.0, iOS 14.0, *)) { - const char *p = data.constData() + dataOffset; - - QTemporaryFile tmp; - if (!tmp.open()) { - qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal"); - return; - } - tmp.write(p, header.dataSize); - tmp.close(); // the file exists until the tmp dtor runs + const char *p = data.constData() + dataOffset; - const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath(); - NSURL *url = QUrl::fromLocalFile(fn).toNSURL(); - if (d->setupBinaryArchive(url)) - qCDebug(QRHI_LOG_INFO, "Created MTLBinaryArchive with initial data of %u bytes", header.dataSize); + QTemporaryFile tmp; + if (!tmp.open()) { + qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal"); + return; } + tmp.write(p, header.dataSize); + tmp.close(); // the file exists until the tmp dtor runs + + const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath(); + NSURL *url = QUrl::fromLocalFile(fn).toNSURL(); + if (d->setupBinaryArchive(url)) + qCDebug(QRHI_LOG_INFO, "Created MTLBinaryArchive with initial data of %u bytes", header.dataSize); } QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, @@ -1469,11 +1470,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 +1966,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 +2000,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 +2048,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 +2056,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 +2072,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 +2141,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 +2192,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 +2232,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 +2336,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 +2348,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 +2368,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 +2459,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 +2475,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 +2540,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 +2606,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 +2614,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 +2631,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 +2723,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 +2770,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 +2991,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 +3037,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 +3051,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]; @@ -3314,6 +3435,8 @@ static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QR #endif case QRhiTexture::D32F: return MTLPixelFormatDepth32Float; + case QRhiTexture::D32FS8: + return MTLPixelFormatDepth32Float_Stencil8; #ifdef Q_OS_MACOS case QRhiTexture::BC1: @@ -3381,122 +3504,88 @@ static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QR return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR; #else case QRhiTexture::ETC2_RGB8: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8; qWarning("QRhiMetal: ETC2 compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ETC2_RGB8A1: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1; qWarning("QRhiMetal: ETC2 compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ETC2_RGBA8: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8; qWarning("QRhiMetal: ETC2 compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_4x4: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_5x4: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_5x5: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_6x5: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_6x6: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_8x5: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_8x6: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_8x8: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_10x5: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_10x6: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_10x8: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_10x10: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_12x10: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; case QRhiTexture::ASTC_12x12: - if (d->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) - return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR; - } + if (d->caps.isAppleGPU) + return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR; qWarning("QRhiMetal: ASTC compression not supported on this platform"); return MTLPixelFormatInvalid; #endif @@ -3564,12 +3653,8 @@ bool QMetalRenderBuffer::create() case DepthStencil: #ifdef Q_OS_MACOS if (rhiD->caps.isAppleGPU) { - if (@available(macOS 11.0, *)) { - desc.storageMode = MTLStorageModeMemoryless; - d->format = MTLPixelFormatDepth32Float_Stencil8; - } else { - Q_UNREACHABLE(); - } + desc.storageMode = MTLStorageModeMemoryless; + d->format = MTLPixelFormatDepth32Float_Stencil8; } else { desc.storageMode = MTLStorageModePrivate; d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported @@ -3747,15 +3832,7 @@ bool QMetalTexture::create() } else if (is1D) { 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"); - } - desc.textureType = MTLTextureType2DArray; -#else desc.textureType = samples > 1 ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray; -#endif } else { desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D; } @@ -4161,8 +4238,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 +4250,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 +4379,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 +4454,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 +4625,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) { @@ -4593,13 +4713,7 @@ id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var versions << 30; if (@available(macOS 12, iOS 15, *)) versions << 24; - if (@available(macOS 11, iOS 14, *)) - versions << 23; - if (@available(macOS 10.15, iOS 13, *)) - versions << 22; - if (@available(macOS 10.14, iOS 12, *)) - versions << 21; - versions << 20 << 12; + versions << 23 << 22 << 21 << 20 << 12; const QList<QShaderKey> shaders = shader.availableShaders(); @@ -4710,7 +4824,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 +4877,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 +4886,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(); } } @@ -4811,46 +4928,19 @@ void QMetalGraphicsPipelineData::setupStageInputDescriptor(MTLStageInputOutputDe void QRhiMetalData::trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc) { - if (@available(macOS 11.0, iOS 14.0, *)) { - if (binArch) { - NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil]; - rpDesc.binaryArchives = binArchArray; - } - } -} - -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; + if (binArch) { + NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil]; + rpDesc.binaryArchives = binArchArray; } } void QRhiMetalData::addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc) { - if (@available(macOS 11.0, iOS 14.0, *)) { - if (canAddToBinaryArchive(this)) { - NSError *err = nil; - if (![binArch addRenderPipelineFunctionsWithDescriptor: rpDesc error: &err]) { - const QString msg = QString::fromNSString(err.localizedDescription); - qWarning("Failed to collect render pipeline functions to binary archive: %s", qPrintable(msg)); - } + if (binArch) { + NSError *err = nil; + if (![binArch addRenderPipelineFunctionsWithDescriptor: rpDesc error: &err]) { + const QString msg = QString::fromNSString(err.localizedDescription); + qWarning("Failed to collect render pipeline functions to binary archive: %s", qPrintable(msg)); } } } @@ -4947,6 +5037,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 +5562,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 +5628,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. @@ -5814,23 +5910,19 @@ void QMetalComputePipeline::destroy() void QRhiMetalData::trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc) { - if (@available(macOS 11.0, iOS 14.0, *)) { - if (binArch) { - NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil]; - cpDesc.binaryArchives = binArchArray; - } + if (binArch) { + NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil]; + cpDesc.binaryArchives = binArchArray; } } void QRhiMetalData::addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc) { - if (@available(macOS 11.0, iOS 14.0, *)) { - if (canAddToBinaryArchive(this)) { - NSError *err = nil; - if (![binArch addComputePipelineFunctionsWithDescriptor: cpDesc error: &err]) { - const QString msg = QString::fromNSString(err.localizedDescription); - qWarning("Failed to collect compute pipeline functions to binary archive: %s", qPrintable(msg)); - } + if (binArch) { + NSError *err = nil; + if (![binArch addComputePipelineFunctionsWithDescriptor: cpDesc error: &err]) { + const QString msg = QString::fromNSString(err.localizedDescription); + qWarning("Failed to collect compute pipeline functions to binary archive: %s", qPrintable(msg)); } } } @@ -6035,13 +6127,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 +6192,14 @@ QSize QMetalSwapChain::surfacePixelSize() bool QMetalSwapChain::isFormatSupported(Format f) { -#ifdef Q_OS_MACOS - return f == SDR || f == HDRExtendedSrgbLinear; -#endif + if (f == HDRExtendedSrgbLinear) { + if (@available(iOS 16.0, *)) + return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f; + else + return false; + } else if (f == HDRExtendedDisplayP3Linear) { + return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f; + } return f == SDR; } @@ -6142,7 +6234,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 +6281,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(iOS 16.0, *)) { + d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB); + d->layer.wantsExtendedDynamicRangeContent = YES; + } + } else if (m_format == HDRExtendedDisplayP3Linear) { + if (@available(iOS 16.0, *)) { + d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3); + d->layer.wantsExtendedDynamicRangeContent = YES; + } } -#endif if (m_flags.testFlag(UsedAsTransferSource)) d->layer.framebufferOnly = NO; @@ -6233,34 +6330,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 +6397,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; } |