external/skia/macosmetal.patch.1 | 14 +++- vcl/Library_vclplug_osx.mk | 3 vcl/osx/salframe.cxx | 34 ++++++---- vcl/osx/salframeview.mm | 126 +++++++++++++++++++++++++++++---------- 4 files changed, 128 insertions(+), 49 deletions(-)
New commits: commit 0fabcd369fc2c9a79b86b634b2e048223f9d8697 Author: Patrick Luby <guibmac...@gmail.com> AuthorDate: Tue Mar 25 11:36:05 2025 -0400 Commit: Patrick Luby <guibomac...@gmail.com> CommitDate: Sat Mar 29 14:57:12 2025 +0100 Related: tdf#128186 stop Skia/Metal flicker in full screen mode transitions Let vcl use the CAMetalLayer's hidden property to skip the fix for tdf#152703 in external/skia/macosmetal.patch.1 and create a new CAMetalLayer when the window's frame changes. When using Skia/Metal, flushing to an NSWindow during transitions into or out of native full screen mode causes the Skia/Metal surface to be drawn at the wrong window position which results in a noticeable flicker. Also, enable the standard macOS animation by only suppressing animation when in internal full screen mode. LibreOffice's internal full screen mode fills the screen with a regular window so skipping the macOS animation makes sense for that case, but it appears very abrupt when transitioning to a window that is smaller than the screen. Lastly, fix several incorrect conversions between window content and frame rectangles during full screen mode transitions. Change-Id: I0e620ea2f727977921c0fdb63b9995b4e77eb161 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/183307 Reviewed-by: Patrick Luby <guibomac...@gmail.com> Tested-by: Jenkins diff --git a/external/skia/macosmetal.patch.1 b/external/skia/macosmetal.patch.1 index 48f7a2400421..acd17a85eab1 100644 --- a/external/skia/macosmetal.patch.1 +++ b/external/skia/macosmetal.patch.1 @@ -70,7 +70,7 @@ diff -ur skia.org/tools/window/mac/GaneshMetalWindowContext_mac.mm skia/tools/wi #import <Cocoa/Cocoa.h> #import <QuartzCore/CAConstraintLayoutManager.h> -@@ -52,7 +54,23 @@ +@@ -52,7 +54,29 @@ SkASSERT(nil != fMainView); @@ -80,11 +80,17 @@ diff -ur skia.org/tools/window/mac/GaneshMetalWindowContext_mac.mm skia/tools/wi + // resize event repaints the window's background which causes a + // noticeable flicker. So reuse any existing CAMetalLayer already + // assigned to the native view. ++ // Related: tdf#128186 Let vcl use the CAMetalLayer's hidden property ++ // to skip the fix for tdf#152703 and create a new CAMetalLayer when ++ // the window resizes. When using Skia/Metal, flushing to an NSWindow ++ // during transitions into or out of native full screen mode causes ++ // the Skia/Metal surface to be drawn at the wrong window position ++ // which results in a noticeable flicker. + BOOL reuseMetalLayer = NO; + if (fMainView.wantsLayer) + { + CALayer *pLayer = fMainView.layer; -+ if (pLayer && [pLayer isKindOfClass:[CAMetalLayer class]]) ++ if (pLayer && [pLayer isKindOfClass:[CAMetalLayer class]] && ![pLayer isHidden]) + { + fMetalLayer = (__bridge CAMetalLayer*)pLayer; + reuseMetalLayer = YES; @@ -95,7 +101,7 @@ diff -ur skia.org/tools/window/mac/GaneshMetalWindowContext_mac.mm skia/tools/wi fMetalLayer.device = fShared->fDevice.get(); fMetalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm; -@@ -65,10 +83,10 @@ +@@ -65,10 +89,10 @@ fMetalLayer.autoresizingMask = kCALayerHeightSizable | kCALayerWidthSizable; fMetalLayer.contentsGravity = kCAGravityTopLeft; fMetalLayer.magnificationFilter = kCAFilterNearest; @@ -109,7 +115,7 @@ diff -ur skia.org/tools/window/mac/GaneshMetalWindowContext_mac.mm skia/tools/wi fMainView.wantsLayer = YES; return true; -@@ -85,6 +103,18 @@ +@@ -85,6 +109,18 @@ fMetalLayer.drawableSize = backingSize; fMetalLayer.contentsScale = backingScaleFactor; diff --git a/vcl/Library_vclplug_osx.mk b/vcl/Library_vclplug_osx.mk index 0545af8dd33a..4f3de16a5a74 100644 --- a/vcl/Library_vclplug_osx.mk +++ b/vcl/Library_vclplug_osx.mk @@ -154,6 +154,9 @@ $(eval $(call gb_Library_use_system_darwin_frameworks,vclplug_osx,\ Cocoa \ Carbon \ CoreFoundation \ + $(if $(filter SKIA,$(BUILD_TYPE)), \ + QuartzCore \ + ) \ )) ifneq ($(ENABLE_MACOSX_SANDBOX),TRUE) diff --git a/vcl/osx/salframe.cxx b/vcl/osx/salframe.cxx index d0bdd2b85ac7..20d1efd65b25 100644 --- a/vcl/osx/salframe.cxx +++ b/vcl/osx/salframe.cxx @@ -919,16 +919,9 @@ void AquaSalFrame::doShowFullScreen( bool bFullScreen, sal_Int32 nDisplay ) aNewFrameRect = [pScreen frame]; } - // Show the menubar if application is in native full screen mode - // since hiding the menubar in that mode will cause the window's - // titlebar to fail to display or hide as expected. - if( [NSApp presentationOptions] & NSApplicationPresentationFullScreen ) - { - [NSMenu setMenuBarVisible: YES]; - } // Hide the dock and the menubar if this or one of its child // windows are the key window - else if( AquaSalFrame::isAlive( this ) ) + if( AquaSalFrame::isAlive( this ) ) { bool bNativeFullScreen = false; const AquaSalFrame *pParentFrame = this; @@ -951,13 +944,19 @@ void AquaSalFrame::doShowFullScreen( bool bFullScreen, sal_Int32 nDisplay ) if( mbNativeFullScreen && !NSIsEmptyRect( maNativeFullScreenRestoreRect ) ) { maInternalFullScreenRestoreRect = maNativeFullScreenRestoreRect; + + // Show the menubar if application is in native full screen mode + // since hiding the menubar in that mode will cause the window's + // titlebar to fail to display or hide as expected. + [NSMenu setMenuBarVisible: YES]; } else { // Related: tdf#128186 restore rectangles are in VCL coordinates NSRect aFrame = [mpNSWindow frame]; - CocoaToVCL( aFrame ); - maInternalFullScreenRestoreRect = aFrame; + NSRect aContentRect = [NSWindow contentRectForFrameRect: aFrame styleMask: [mpNSWindow styleMask] & ~NSWindowStyleMaskFullScreen]; + CocoaToVCL( aContentRect ); + maInternalFullScreenRestoreRect = aContentRect; } // Related: tdf#161623 do not add the window's titlebar height @@ -992,10 +991,19 @@ void AquaSalFrame::doShowFullScreen( bool bFullScreen, sal_Int32 nDisplay ) if( !NSIsEmptyRect( maInternalFullScreenRestoreRect ) ) { - if( !mbNativeFullScreen || NSIsEmptyRect( maNativeFullScreenRestoreRect ) ) + if( mbNativeFullScreen && !NSIsEmptyRect( maNativeFullScreenRestoreRect ) ) + { + // Related: tdf#128186 force window to unzoom + // If we exit LibreOffice's internal full screen mode while + // the window is in native full screen mode, the window will + // be zoomed after exiting native full screen mode. + [mpNSWindow setIsZoomed: NO]; + } + else { - NSRect aFrame = maInternalFullScreenRestoreRect; - VCLToCocoa( aFrame ); + NSRect aContentRect = maInternalFullScreenRestoreRect; + VCLToCocoa( aContentRect ); + NSRect aFrame = [NSWindow frameRectForContentRect: aContentRect styleMask: [mpNSWindow styleMask] & ~NSWindowStyleMaskFullScreen]; [mpNSWindow setFrame: aFrame display: mbShown ? YES : NO]; } diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm index a2b80ce97349..e43ff781f310 100644 --- a/vcl/osx/salframeview.mm +++ b/vcl/osx/salframeview.mm @@ -42,6 +42,9 @@ #if HAVE_FEATURE_SKIA #include <vcl/skia/SkiaHelper.hxx> +#include <premac.h> +#include <QuartzCore/QuartzCore.h> +#include <postmac.h> #endif #define WHEEL_EVENT_FACTOR 1.5 @@ -566,25 +569,6 @@ static void updateWindowCollectionBehavior( const AquaSalFrame *pFrame ) updateWinDataInLiveResize( [self inLiveResize] ); if ( ImplGetSVData()->mpWinData->mbIsLiveResize ) { -#if HAVE_FEATURE_SKIA - // Related: tdf#152703 Eliminate empty window with Skia/Metal while resizing - // The window will clear its background so when Skia/Metal is - // enabled, explicitly flush the Skia graphics to the window - // during live resizing or else nothing will be drawn until after - // live resizing has ended. - // Also, flushing during [self windowDidResize:] eliminates flicker - // by forcing this window's SkSurface to recreate its underlying - // CAMetalLayer with the new size. Flushing in - // [self displayIfNeeded] does not eliminate flicker so apparently - // [self windowDidResize:] is called earlier. - if ( SkiaHelper::isVCLSkiaEnabled() ) - { - AquaSalGraphics* pGraphics = mpFrame->mpGraphics; - if ( pGraphics ) - pGraphics->Flush(); - } -#endif - // tdf#152703 Force relayout during live resizing of window // During a live resize, macOS floods the application with // windowDidResize: notifications so sending a paint event does @@ -629,6 +613,26 @@ static void updateWindowCollectionBehavior( const AquaSalFrame *pFrame ) // When using Skia/Metal, the window content will flicker while // live resizing a window if we don't send a paint event. mpFrame->SendPaintEvent(); + +#if HAVE_FEATURE_SKIA + // Related: tdf#152703 Eliminate empty window with Skia/Metal while resizing + // The window will clear its background so when Skia/Metal is + // enabled, explicitly flush the Skia graphics to the window + // during live resizing or else nothing will be drawn until after + // live resizing has ended. + // Also, flushing during [self windowDidResize:] eliminates flicker + // by forcing this window's SkSurface to recreate its underlying + // CAMetalLayer with the new size. Flushing in + // [self displayIfNeeded] does not eliminate flicker so apparently + // [self windowDidResize:] is called earlier. + // Lastly, flush after calling AquaSalFrame::SendPaintEvent(). + if ( SkiaHelper::isVCLSkiaEnabled() ) + { + AquaSalGraphics* pGraphics = mpFrame->mpGraphics; + if ( pGraphics ) + pGraphics->Flush(); + } +#endif } mbInWindowDidResize = NO; @@ -700,11 +704,31 @@ static void updateWindowCollectionBehavior( const AquaSalFrame *pFrame ) { // Related: tdf#128186 restore rectangles are in VCL coordinates NSRect aFrame = [mpFrame->getNSWindow() frame]; - mpFrame->CocoaToVCL( aFrame ); - mpFrame->maNativeFullScreenRestoreRect = aFrame; + NSRect aContentRect = [mpFrame->getNSWindow() contentRectForFrameRect: aFrame]; + mpFrame->CocoaToVCL( aContentRect ); + mpFrame->maNativeFullScreenRestoreRect = aContentRect; } updateMenuBarVisibility( mpFrame ); + +#if HAVE_FEATURE_SKIA + // Related: tdf#128186 Let vcl use the CAMetalLayer's hidden property + // to skip the fix for tdf#152703 in external/skia/macosmetal.patch.1 + // and create a new CAMetalLayer when the window resizes. When using + // Skia/Metal, flushing to an NSWindow during transitions into or out + // of native full screen mode causes the Skia/Metal surface to be + // drawn at the wrong window position which results in a noticeable + // flicker. + if( SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster ) + { + if( [mpFrame->getNSView() wantsLayer] ) + { + CALayer *pLayer = [mpFrame->getNSView() layer]; + if( pLayer && [pLayer isKindOfClass:[CAMetalLayer class]] ) + [pLayer setHidden: YES]; + } + } +#endif } } @@ -723,23 +747,55 @@ static void updateWindowCollectionBehavior( const AquaSalFrame *pFrame ) } } +-(void)windowWillExitFullScreen: (NSNotification*)pNotification +{ + (void)pNotification; + SolarMutexGuard aGuard; + +#if HAVE_FEATURE_SKIA + // Related: tdf#128186 Let vcl use the CAMetalLayer's hidden property + // to skip the fix for tdf#152703 in external/skia/macosmetal.patch.1 + // and create a new CAMetalLayer when the window resizes. When using + // Skia/Metal, flushing to an NSWindow during transitions into or out + // of native full screen mode causes the Skia/Metal surface to be + // drawn at the wrong window position which results in a noticeable + // flicker. + if( AquaSalFrame::isAlive( mpFrame ) && SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster ) + { + if( [mpFrame->getNSView() wantsLayer] ) + { + CALayer *pLayer = [mpFrame->getNSView() layer]; + if( pLayer && [pLayer isKindOfClass:[CAMetalLayer class]] ) + [pLayer setHidden: YES]; + } + } +#endif +} + -(void)windowDidExitFullScreen: (NSNotification*)pNotification { (void)pNotification; SolarMutexGuard aGuard; - if( AquaSalFrame::isAlive( mpFrame) ) + if( AquaSalFrame::isAlive( mpFrame) && mpFrame->mbNativeFullScreen ) { mpFrame->mbNativeFullScreen = false; if( !NSIsEmptyRect( mpFrame->maNativeFullScreenRestoreRect ) ) { - if ( !mpFrame->mbInternalFullScreen || NSIsEmptyRect( mpFrame->maInternalFullScreenRestoreRect ) ) - { - NSRect aFrame = mpFrame->maNativeFullScreenRestoreRect; - mpFrame->VCLToCocoa( aFrame ); - [mpFrame->getNSWindow() setFrame: aFrame display: mpFrame->mbShown ? YES : NO]; - } + // Related: tdf#128186 set window frame before exiting native full screen mode + // Setting the window frame just before exiting native full + // screen mode appears to set the desired non-full screen + // window frame without causing a noticeable flicker during + // the macOS default "exit full screen" animation. + NSRect aContentRect; + if( mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenRestoreRect ) ) + aContentRect = mpFrame->maInternalFullScreenRestoreRect; + else + aContentRect = mpFrame->maNativeFullScreenRestoreRect; + mpFrame->VCLToCocoa( aContentRect ); + NSRect aFrame = [NSWindow frameRectForContentRect: aContentRect styleMask: [mpFrame->getNSWindow() styleMask] & ~NSWindowStyleMaskFullScreen]; + [mpFrame->getNSWindow() setFrame: aFrame display: mpFrame->mbShown ? YES : NO]; mpFrame->maNativeFullScreenRestoreRect = NSZeroRect; } @@ -942,10 +998,16 @@ static void updateWindowCollectionBehavior( const AquaSalFrame *pFrame ) - (NSArray<NSWindow *> *)customWindowsToExitFullScreenForWindow: (NSWindow *)pWindow { - // Related: tdf#161623 our code will reset the frame immediately after - // native full screen mode has exited so suppress animation when exiting - // native full screen mode. - return [NSArray arrayWithObject: self]; + SolarMutexGuard aGuard; + + // Related: tdf#161623 suppress animation when in internal full screen mode + // LibreOffice's internal full screen mode fills the screen with a + // regular window so suppress animation when exiting native full + // screen mode. + if( AquaSalFrame::isAlive( mpFrame) && mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenExpectedRect ) ) + return [NSArray arrayWithObject: self]; + + return nil; } @end