diff --git a/src/gui/kernel/qplatformwindow_p.h b/src/gui/kernel/qplatformwindow_p.h index 2bbdfd5bf9f..e240da5838c 100644 --- a/src/gui/kernel/qplatformwindow_p.h +++ b/src/gui/kernel/qplatformwindow_p.h @@ -28,6 +28,10 @@ struct wl_surface; #endif +#if defined(Q_OS_MACOS) +Q_FORWARD_DECLARE_OBJC_CLASS(CALayer); +#endif + QT_BEGIN_NAMESPACE class QMargins; @@ -55,9 +59,10 @@ struct Q_GUI_EXPORT QWasmWindow #if defined(Q_OS_MACOS) || defined(Q_QDOC) struct Q_GUI_EXPORT QCocoaWindow { - QT_DECLARE_NATIVE_INTERFACE(QCocoaWindow, 1, QWindow) + QT_DECLARE_NATIVE_INTERFACE(QCocoaWindow, 2, QWindow) virtual void setContentBorderEnabled(bool enable) = 0; virtual QPoint bottomLeftClippedByNSWindowOffset() const = 0; + virtual CALayer *contentLayer() const = 0; }; #endif diff --git a/src/gui/platform/darwin/qmetallayer.mm b/src/gui/platform/darwin/qmetallayer.mm index 082bde95b5b..569ba69b53b 100644 --- a/src/gui/platform/darwin/qmetallayer.mm +++ b/src/gui/platform/darwin/qmetallayer.mm @@ -73,5 +73,4 @@ QT_USE_NAMESPACE return [super nextDrawable]; } - @end diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 4375f123e93..42ebd9a5415 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -14,6 +14,7 @@ #include #include +#include #ifdef Q_OS_MACOS #include @@ -6321,13 +6322,15 @@ QRhiRenderTarget *QMetalSwapChain::currentFrameRenderTarget() static inline CAMetalLayer *layerForWindow(QWindow *window) { Q_ASSERT(window); + CALayer *layer = nullptr; #ifdef Q_OS_MACOS - NSView *view = reinterpret_cast(window->winId()); + if (auto *cocoaWindow = window->nativeInterface()) + layer = cocoaWindow->contentLayer(); #else - UIView *view = reinterpret_cast(window->winId()); + layer = reinterpret_cast(window->winId()).layer; #endif - Q_ASSERT(view); - return static_cast(view.layer); + Q_ASSERT(layer); + return static_cast(layer); } // If someone calls this, it is hopefully from the main thread, and they will diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 64794408c90..78d23b01dea 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -350,6 +350,7 @@ void QCALayerBackingStore::flush(QWindow *flushedWindow, const QRegion ®ion, QMacAutoReleasePool pool; NSView *flushedView = static_cast(flushedWindow->handle())->view(); + CALayer *layer = static_cast(flushedWindow->handle())->contentLayer(); // If the backingstore is just flushed, without being painted to first, then we may // end in a situation where the backingstore is flushed to a layer with a different @@ -360,11 +361,11 @@ void QCALayerBackingStore::flush(QWindow *flushedWindow, const QRegion ®ion, // we at least cover the whole layer. This is necessary since we set the view's // contents placement policy to NSViewLayerContentsPlacementTopLeft, which means // AppKit will not do any scaling on our behalf. - if (m_buffers.back()->devicePixelRatio() != flushedView.layer.contentsScale) { + if (m_buffers.back()->devicePixelRatio() != layer.contentsScale) { qCWarning(lcQpaBackingStore) << "Back buffer dpr of" << m_buffers.back()->devicePixelRatio() - << "doesn't match" << flushedView.layer << "contents scale of" << flushedView.layer.contentsScale + << "doesn't match" << layer << "contents scale of" << layer.contentsScale << "- updating layer to match."; - flushedView.layer.contentsScale = m_buffers.back()->devicePixelRatio(); + layer.contentsScale = m_buffers.back()->devicePixelRatio(); } const bool isSingleBuffered = window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer; @@ -380,13 +381,13 @@ void QCALayerBackingStore::flush(QWindow *flushedWindow, const QRegion ®ion, if (isSingleBuffered) { // The private API [CALayer reloadValueForKeyPath:@"contents"] would be preferable, // but barring any side effects or performance issues we opt for the hammer for now. - flushedView.layer.contents = nil; + layer.contents = nil; } qCInfo(lcQpaBackingStore) << "Flushing" << backBufferSurface - << "to" << flushedView.layer << "of" << flushedView; + << "to" << layer << "of" << flushedView; - flushedView.layer.contents = backBufferSurface; + layer.contents = backBufferSurface; if (!isSingleBuffered) { // Mark the surface as in use, so that we don't end up rendering @@ -428,7 +429,8 @@ void QCALayerBackingStore::flushSubWindow(QWindow *subWindow) NSView *backingStoreView = static_cast(window()->handle())->view(); NSView *flushedView = static_cast(subWindow->handle())->view(); auto subviewRect = [flushedView convertRect:flushedView.bounds toView:backingStoreView]; - auto scale = flushedView.layer.contentsScale; + CALayer *layer = static_cast(subWindow->handle())->contentLayer(); + auto scale = layer.contentsScale; subviewRect = CGRectApplyAffineTransform(subviewRect, CGAffineTransformMakeScale(scale, scale)); m_buffers.back()->lock(QPlatformGraphicsBuffer::SWReadAccess); diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index 765b7e6619f..a3ce192e6e3 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -225,6 +225,8 @@ public: // for QNSView static void setupPopupMonitor(); static void removePopupMonitor(); + CALayer *contentLayer() const override; + NSView *m_view = nil; QCocoaNSWindow *m_nsWindow = nil; diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 47a09f2e90d..9a8e5246b80 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -1732,7 +1732,7 @@ void QCocoaWindow::deliverUpdateRequest() { qCDebug(lcQpaDrawing) << "Delivering update request to" << window(); - if (auto *qtMetalLayer = qt_objc_cast(m_view.layer)) { + if (auto *qtMetalLayer = qt_objc_cast(contentLayer())) { // We attempt a read lock here, so that the animation/render thread is // prioritized lower than the main thread's displayLayer processing. // Without this the two threads might fight over the next drawable, @@ -2216,6 +2216,14 @@ void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled) m_frameStrutEventsEnabled = enabled; } +CALayer *QCocoaWindow::contentLayer() const +{ + auto *layer = m_view.layer; + if (auto *containerLayer = qt_objc_cast(layer)) + layer = containerLayer.contentLayer; + return layer; +} + #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug debug, const QCocoaWindow *window) { diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index 7f845a5c3bc..d8858b38875 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -5,6 +5,7 @@ #define QNSVIEW_H #include +#include #include @@ -41,7 +42,12 @@ Q_FORWARD_DECLARE_OBJC_CLASS(NSColorSpace); @interface QNSView (QtExtras) @property (nonatomic, readonly) QCocoaWindow *platformWindow; @end + +QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QContainerLayer, CALayer +- (instancetype)initWithContentLayer:(CALayer *)contentLayer; +@property (readonly) CALayer *contentLayer; +) + #endif // __OBJC__ - #endif //QNSVIEW_H diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm index cfb7873eeab..b954a48e71a 100644 --- a/src/plugins/platforms/cocoa/qnsview_drawing.mm +++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm @@ -3,6 +3,36 @@ // This file is included from qnsview.mm, and only used to organize the code +@implementation QContainerLayer { + CALayer *m_contentLayer; +} +- (instancetype)initWithContentLayer:(CALayer *)contentLayer +{ + if ((self = [super init])) { + m_contentLayer = contentLayer; + [self addSublayer:contentLayer]; + contentLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; + } + return self; +} + +- (CALayer*)contentLayer +{ + return m_contentLayer; +} + +- (void)setNeedsDisplay +{ + [self setNeedsDisplayInRect:CGRectInfinite]; +} + +- (void)setNeedsDisplayInRect:(CGRect)rect +{ + [super setNeedsDisplayInRect:rect]; + [self.contentLayer setNeedsDisplayInRect:rect]; +} +@end + @implementation QNSView (Drawing) - (void)initDrawing @@ -107,6 +137,17 @@ layer.delegate = self; } + layer.name = @"Qt content layer"; + + static const bool containerLayerOptOut = qEnvironmentVariableIsSet("QT_MAC_NO_CONTAINER_LAYER"); + if (m_platformWindow->window()->surfaceType() != QSurface::OpenGLSurface && !containerLayerOptOut) { + qCDebug(lcQpaDrawing) << "Wrapping content layer" << layer << "in container layer"; + auto *containerLayer = [[QContainerLayer alloc] initWithContentLayer:layer]; + containerLayer.name = @"Qt container layer"; + containerLayer.delegate = self; + layer = containerLayer; + } + [super setLayer:layer]; [self propagateBackingProperties]; @@ -117,7 +158,6 @@ // where it doesn't. layer.backgroundColor = NSColor.magentaColor.CGColor; } - } // ----------------------- Layer updates ----------------------- @@ -172,11 +212,12 @@ // to NO. In this case the window will have a backingScaleFactor of 2, // but the QWindow will have a devicePixelRatio of 1. auto devicePixelRatio = m_platformWindow->devicePixelRatio(); - qCDebug(lcQpaDrawing) << "Updating" << self.layer << "content scale to" << devicePixelRatio; - self.layer.contentsScale = devicePixelRatio; + auto *contentLayer = m_platformWindow->contentLayer(); + qCDebug(lcQpaDrawing) << "Updating" << contentLayer << "content scale to" << devicePixelRatio; + contentLayer.contentsScale = devicePixelRatio; - if ([self.layer isKindOfClass:CAMetalLayer.class]) { - CAMetalLayer *metalLayer = static_cast(self.layer); + if ([contentLayer isKindOfClass:CAMetalLayer.class]) { + CAMetalLayer *metalLayer = static_cast(contentLayer); metalLayer.colorspace = self.colorSpace.CGColorSpace; qCDebug(lcQpaDrawing) << "Set" << metalLayer << "color space to" << metalLayer.colorspace; } @@ -205,8 +246,11 @@ */ - (void)displayLayer:(CALayer *)layer { - Q_ASSERT_X(self.layer && layer == self.layer, "QNSView", - "The displayLayer code path should only be hit for our own layer"); + if (auto *containerLayer = qt_objc_cast(layer)) { + qCDebug(lcQpaDrawing) << "Skipping display of" << containerLayer + << "as display is handled by content layer" << containerLayer.contentLayer; + return; + } if (!m_platformWindow) return; @@ -226,7 +270,7 @@ m_platformWindow->handleExposeEvent(bounds); }; - if (auto *qtMetalLayer = qt_objc_cast(self.layer)) { + if (auto *qtMetalLayer = qt_objc_cast(layer)) { const bool presentedWithTransaction = qtMetalLayer.presentsWithTransaction; qtMetalLayer.presentsWithTransaction = YES;