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

Reply via email to