diff options
Diffstat (limited to 'src/gui/rhi/qrhimetal.mm')
-rw-r--r-- | src/gui/rhi/qrhimetal.mm | 191 |
1 files changed, 106 insertions, 85 deletions
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index e10da4ca0e..05fda2f556 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 @@ -461,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) @@ -567,9 +564,7 @@ bool QRhiMetal::create(QRhi::Flags flags) // suitable as deviceId because it does not seem stable on macOS and can // apparently change when the system is rebooted. -#ifdef Q_OS_IOS - driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice; -#else +#ifdef Q_OS_MACOS if (@available(macOS 10.15, *)) { const MTLDeviceLocation deviceLocation = [d->dev location]; switch (deviceLocation) { @@ -586,6 +581,8 @@ bool QRhiMetal::create(QRhi::Flags flags) break; } } +#else + driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice; #endif const QOperatingSystemVersion ver = QOperatingSystemVersion::current(); @@ -613,23 +610,17 @@ bool QRhiMetal::create(QRhi::Flags flags) 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 { @@ -638,9 +629,9 @@ bool QRhiMetal::create(QRhi::Flags flags) } caps.isAppleGPU = true; if (@available(iOS 13, *)) { - if ([d->dev supportsFamily: MTLGPUFamilyApple4]) + if ([d->dev supportsFamily:MTLGPUFamilyApple4]) caps.maxThreadGroupSize = 1024; - if ([d->dev supportsFamily: MTLGPUFamilyApple5]) + if ([d->dev supportsFamily:MTLGPUFamilyApple5]) caps.multiView = true; } #endif @@ -2396,31 +2387,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 @@ -2429,9 +2478,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; @@ -2579,7 +2625,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(); @@ -2588,10 +2633,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(); @@ -2603,7 +2650,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) @@ -2971,9 +3018,11 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, 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) { + colorAttCount += 1; if (it->texture()) { QRHI_RES(QMetalTexture, it->texture())->lastActiveFrameSlot = currentFrameSlot; if (it->multiViewCount() >= 2) @@ -2986,8 +3035,12 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, } 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; } @@ -3401,6 +3454,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: @@ -4840,7 +4895,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) @@ -6151,12 +6206,6 @@ 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 = {}; @@ -6363,34 +6412,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; @@ -6465,12 +6486,12 @@ QRhiSwapChainHdrInfo QMetalSwapChain::hdrInfo() if (m_window) { // Must use m_window, not window, given this may be called before createOrResize(). -#ifdef Q_OS_MACOS +#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; -#else +#elif defined(Q_OS_IOS) if (@available(iOS 16.0, *)) { UIView *view = reinterpret_cast<UIView *>(m_window->winId()); UIScreen *screen = view.window.windowScene.screen; |