summaryrefslogtreecommitdiffstats
path: root/src/gui/rhi/qrhimetal.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/rhi/qrhimetal.mm')
-rw-r--r--src/gui/rhi/qrhimetal.mm1004
1 files changed, 572 insertions, 432 deletions
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index 196016d9ca..36a71e5101 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -1,8 +1,8 @@
-// Copyright (C) 2019 The Qt Company Ltd.
+// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include "qrhimetal_p_p.h"
-#include <QtGui/private/qshader_p_p.h>
+#include "qrhimetal_p.h"
+#include "qshader_p.h"
#include <QGuiApplication>
#include <QWindow>
#include <QUrl>
@@ -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,19 +42,28 @@ 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
\inmodule QtRhi
- \internal
+ \since 6.6
\brief Metal specific initialization parameters.
+ \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.
\badcode
@@ -85,14 +96,30 @@ QT_BEGIN_NAMESPACE
/*!
\class QRhiMetalNativeHandles
\inmodule QtRhi
- \internal
+ \since 6.6
\brief Holds the Metal device used by the 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.
+*/
+
+/*!
\class QRhiMetalCommandBufferNativeHandles
\inmodule QtRhi
- \internal
+ \since 6.6
\brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer.
\note The command buffer object is only guaranteed to be valid while
@@ -104,8 +131,19 @@ QT_BEGIN_NAMESPACE
\note The command encoder is only valid while recording a pass, that is,
between \l{QRhiCommandBuffer::beginPass()} -
\l{QRhiCommandBuffer::endPass()}.
+
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
+ for details.
*/
+/*!
+ \variable QRhiMetalCommandBufferNativeHandles::commandBuffer
+*/
+
+/*!
+ \variable QRhiMetalCommandBufferNativeHandles::encoder
+*/
+
struct QMetalShader
{
id<MTLLibrary> lib = nil;
@@ -133,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,
@@ -194,6 +232,7 @@ struct QRhiMetalData
struct OffscreenFrame {
OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
bool active = false;
+ double lastGpuTime = 0;
QMetalCommandBuffer cbWrapper;
} ofr;
@@ -211,7 +250,7 @@ struct QRhiMetalData
struct BufferReadback
{
int activeFrameSlot = -1;
- QRhiBufferReadbackResult *result;
+ QRhiReadbackResult *result;
quint32 offset;
quint32 readSize;
id<MTLBuffer> buf;
@@ -296,6 +335,7 @@ struct QMetalShaderResourceBindingsData {
struct QMetalCommandBufferData
{
id<MTLCommandBuffer> cb;
+ double lastGpuTime = 0;
id<MTLRenderCommandEncoder> currentRenderPassEncoder;
id<MTLComputeCommandEncoder> currentComputePassEncoder;
id<MTLComputeCommandEncoder> tessellationComputeEncoder;
@@ -330,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;
@@ -350,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;
@@ -383,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);
@@ -413,15 +458,11 @@ struct QMetalSwapChainData
CAMetalLayer *layer = nullptr;
id<CAMetalDrawable> curDrawable = nil;
dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT];
+ double lastGpuTime[QMTL_FRAMES_IN_FLIGHT];
MTLRenderPassDescriptor *rp = nullptr;
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)
@@ -432,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)
@@ -466,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)
@@ -508,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();
@@ -549,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 {
@@ -577,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 };
@@ -610,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)
@@ -629,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);
@@ -727,7 +759,7 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
case QRhi::DebugMarkers:
return true;
case QRhi::Timestamps:
- return false;
+ return true;
case QRhi::Instancing:
return true;
case QRhi::CustomInstanceStepRate:
@@ -771,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:
@@ -805,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;
@@ -898,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;
}
@@ -995,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,
@@ -1441,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;
}
@@ -1937,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;
@@ -1970,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];
@@ -2018,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];
@@ -2026,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];
}
@@ -2042,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;
@@ -2111,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)
{
@@ -2129,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];
@@ -2167,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
@@ -2237,6 +2304,12 @@ void QRhiMetal::endExternal(QRhiCommandBuffer *cb)
cbD->resetPerPassCachedState();
}
+double QRhiMetal::lastCompletedGpuTime(QRhiCommandBuffer *cb)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ return cbD->d->lastGpuTime;
+}
+
QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
{
Q_UNUSED(flags);
@@ -2263,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) {
@@ -2278,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;
@@ -2285,7 +2356,8 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
executeDeferredReleases();
- swapChainD->cbWrapper.resetState();
+ swapChainD->cbWrapper.resetState(swapChainD->d->lastGpuTime[currentFrameSlot]);
+ swapChainD->d->lastGpuTime[currentFrameSlot] = 0;
finishActiveReadbacks();
return QRhi::FrameOpSuccess;
@@ -2296,30 +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>) {
+ [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
@@ -2328,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;
@@ -2347,10 +2475,11 @@ 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.cbWrapper.resetState(d->ofr.lastGpuTime);
+ d->ofr.lastGpuTime = 0;
finishActiveReadbacks();
return QRhi::FrameOpSuccess;
@@ -2362,10 +2491,13 @@ QRhi::FrameOpResult QRhiMetal::endOffscreenFrame(QRhi::EndFrameFlags flags)
Q_ASSERT(d->ofr.active);
d->ofr.active = false;
- [d->ofr.cbWrapper.d->cb commit];
+ id<MTLCommandBuffer> cb = d->ofr.cbWrapper.d->cb;
+ [cb commit];
// offscreen frames wait for completion, unlike swapchain ones
- [d->ofr.cbWrapper.d->cb waitUntilCompleted];
+ [cb waitUntilCompleted];
+
+ d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime;
finishActiveReadbacks(true);
@@ -2406,10 +2538,13 @@ QRhi::FrameOpResult QRhiMetal::finish()
}
if (inFrame) {
- if (d->ofr.active)
- d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
- else
- swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ if (d->ofr.active) {
+ d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime;
+ d->ofr.cbWrapper.d->cb = d->newCommandBuffer();
+ } else {
+ swapChainD->d->lastGpuTime[currentFrameSlot] += cb.GPUEndTime - cb.GPUStartTime;
+ swapChainD->cbWrapper.d->cb = d->newCommandBuffer();
+ }
}
executeDeferredReleases(true);
@@ -2471,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();
@@ -2480,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();
@@ -2495,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)
@@ -2587,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) {
@@ -2625,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) {
@@ -2848,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:
@@ -2883,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);
@@ -2896,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];
@@ -3271,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:
@@ -3338,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
@@ -3521,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
@@ -3666,12 +3794,10 @@ bool QMetalTexture::prepareCreate(QSize *adjustedSize)
qWarning("Texture cannot be both 1D and cube");
return false;
}
- m_depth = qMax(1, m_depth);
if (m_depth > 1 && !is3D) {
qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
return false;
}
- m_arraySize = qMax(0, m_arraySize);
if (m_arraySize > 0 && !isArray) {
qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
return false;
@@ -3706,27 +3832,19 @@ 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;
}
desc.pixelFormat = d->format;
desc.width = NSUInteger(size.width());
desc.height = NSUInteger(size.height());
- desc.depth = is3D ? m_depth : 1;
+ desc.depth = is3D ? qMax(1, m_depth) : 1;
desc.mipmapLevelCount = NSUInteger(mipLevelCount);
if (samples > 1)
desc.sampleCount = NSUInteger(samples);
if (isArray)
- desc.arrayLength = NSUInteger(m_arraySize);
+ desc.arrayLength = NSUInteger(qMax(0, m_arraySize));
desc.resourceOptions = MTLResourceStorageModePrivate;
desc.storageMode = MTLStorageModePrivate;
desc.usage = MTLTextureUsageShaderRead;
@@ -3785,7 +3903,8 @@ id<MTLTexture> QMetalTextureData::viewForLevel(int level)
const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
const bool isArray = q->m_flags.testFlag(QRhiTexture::TextureArray);
id<MTLTexture> view = [tex newTextureViewWithPixelFormat: format textureType: type
- levels: NSMakeRange(NSUInteger(level), 1) slices: NSMakeRange(0, isCube ? 6 : (isArray ? q->m_arraySize : 1))];
+ levels: NSMakeRange(NSUInteger(level), 1)
+ slices: NSMakeRange(0, isCube ? 6 : (isArray ? qMax(0, q->m_arraySize) : 1))];
perLevelViews[level] = view;
return view;
@@ -3930,7 +4049,9 @@ QMetalRenderPassDescriptor::~QMetalRenderPassDescriptor()
void QMetalRenderPassDescriptor::destroy()
{
- // nothing to do here
+ QRHI_RES_RHI(QRhiMetal);
+ if (rhiD)
+ rhiD->unregisterResource(this);
}
bool QMetalRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
@@ -3973,13 +4094,17 @@ void QMetalRenderPassDescriptor::updateSerializedFormat()
QRhiRenderPassDescriptor *QMetalRenderPassDescriptor::newCompatibleRenderPassDescriptor() const
{
- QMetalRenderPassDescriptor *rp = new QMetalRenderPassDescriptor(m_rhi);
- rp->colorAttachmentCount = colorAttachmentCount;
- rp->hasDepthStencil = hasDepthStencil;
- memcpy(rp->colorFormat, colorFormat, sizeof(colorFormat));
- rp->dsFormat = dsFormat;
- rp->updateSerializedFormat();
- return rp;
+ QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
+ rpD->colorAttachmentCount = colorAttachmentCount;
+ rpD->hasDepthStencil = hasDepthStencil;
+ memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat));
+ rpD->dsFormat = dsFormat;
+
+ rpD->updateSerializedFormat();
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->registerResource(rpD, false);
+ return rpD;
}
QVector<quint32> QMetalRenderPassDescriptor::serializedFormat() const
@@ -4035,7 +4160,9 @@ QMetalTextureRenderTarget::~QMetalTextureRenderTarget()
void QMetalTextureRenderTarget::destroy()
{
- // nothing to do here
+ QRHI_RES_RHI(QRhiMetal);
+ if (rhiD)
+ rhiD->unregisterResource(this);
}
QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDescriptor()
@@ -4058,6 +4185,9 @@ QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDesc
rpD->dsFormat = int(QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format);
rpD->updateSerializedFormat();
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->registerResource(rpD, false);
return rpD;
}
@@ -4108,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;
@@ -4119,18 +4250,27 @@ 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);
return true;
}
@@ -4166,6 +4306,10 @@ void QMetalShaderResourceBindings::destroy()
{
sortedBindings.clear();
maxBinding = -1;
+
+ QRHI_RES_RHI(QRhiMetal);
+ if (rhiD)
+ rhiD->unregisterResource(this);
}
bool QMetalShaderResourceBindings::create()
@@ -4192,6 +4336,7 @@ bool QMetalShaderResourceBindings::create()
memset(&bd, 0, sizeof(BoundResourceData));
generation += 1;
+ rhiD->registerResource(this, false);
return true;
}
@@ -4234,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;
@@ -4309,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;
@@ -4464,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) {
@@ -4534,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();
@@ -4651,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)
@@ -4704,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)
{
@@ -4712,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();
}
}
@@ -4752,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));
}
}
}
@@ -4888,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))
@@ -5410,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;
@@ -5476,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.
@@ -5755,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));
}
}
}
@@ -5897,8 +6048,9 @@ const QRhiNativeHandles *QMetalCommandBuffer::nativeHandles()
return &nativeHandlesStruct;
}
-void QMetalCommandBuffer::resetState()
+void QMetalCommandBuffer::resetState(double lastGpuTime)
{
+ d->lastGpuTime = lastGpuTime;
d->currentRenderPassEncoder = nil;
d->currentComputePassEncoder = nil;
d->tessellationComputeEncoder = nil;
@@ -5975,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;
@@ -6045,14 +6192,21 @@ 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;
}
QRhiRenderPassDescriptor *QMetalSwapChain::newCompatibleRenderPassDescriptor()
{
+ QRHI_RES_RHI(QRhiMetal);
+
chooseFormats(); // ensure colorFormat and similar are filled out
QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
@@ -6063,7 +6217,6 @@ QRhiRenderPassDescriptor *QMetalSwapChain::newCompatibleRenderPassDescriptor()
#ifdef Q_OS_MACOS
// m_depthStencil may not be built yet so cannot rely on computed fields in it
- QRHI_RES_RHI(QRhiMetal);
rpD->dsFormat = rhiD->d->dev.depth24Stencil8PixelFormatSupported
? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
#else
@@ -6071,6 +6224,8 @@ QRhiRenderPassDescriptor *QMetalSwapChain::newCompatibleRenderPassDescriptor()
#endif
rpD->updateSerializedFormat();
+
+ rhiD->registerResource(rpD, false);
return rpD;
}
@@ -6079,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;
@@ -6126,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;
@@ -6170,38 +6330,11 @@ 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;
for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ d->lastGpuTime[i] = 0;
if (!d->sem[i])
d->sem[i] = dispatch_semaphore_create(QMTL_FRAMES_IN_FLIGHT - 1);
}
@@ -6264,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;
}