vcl/inc/osx/salframeview.h   |    7 +++
 vcl/inc/skia/osx/gdiimpl.hxx |    4 -
 vcl/osx/salframeview.mm      |   91 ++++++++++++++++++++++++++++++++++++++++---
 vcl/skia/osx/gdiimpl.cxx     |   52 ------------------------
 4 files changed, 93 insertions(+), 61 deletions(-)

New commits:
commit 5913201efff027e683b2ff15349943d99b726414
Author:     Patrick Luby <guibmac...@gmail.com>
AuthorDate: Tue May 13 09:34:35 2025 -0400
Commit:     Patrick Luby <guibomac...@gmail.com>
CommitDate: Wed May 14 21:45:09 2025 +0200

    tdf#163945 Coalesce mouse dragged events
    
    Previously, the rate of flushes when using Skia/Metal were limited.
    While the various approaches to limiting flushes fixed tdf#166258,
    they caused tdf#163945 to reoccur on some machines.
    
    So instead of trying to reduce the number of Skia/Metal flushes per
    second, coalesce mouse dragged events when macOS floods the native
    event queue with mouse dragged events.
    
    Change-Id: Ie355c9da705d7ff7d045b924ba0b3d10c093e780
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185258
    Tested-by: Jenkins
    Reviewed-by: Patrick Luby <guibomac...@gmail.com>

diff --git a/vcl/inc/osx/salframeview.h b/vcl/inc/osx/salframeview.h
index 420e8a25b324..eae401fc8841 100644
--- a/vcl/inc/osx/salframeview.h
+++ b/vcl/inc/osx/salframeview.h
@@ -114,9 +114,14 @@ enum class SalEvent;
     NSTrackingArea* mpLastTrackingArea;
 
     BOOL            mbInViewDidChangeEffectiveAppearance;
+
+    NSTimer*        mpMouseDraggedTimer;
+    NSEvent*        mpPendingMouseDraggedEvent;
 }
 +(void)unsetMouseFrame: (AquaSalFrame*)pFrame;
 -(id)initWithSalFrame: (AquaSalFrame*)pFrame;
+-(void)clearMouseDraggedTimer;
+-(void)clearPendingMouseDraggedEvent;
 -(void)dealloc;
 -(AquaSalFrame*)getSalFrame;
 -(BOOL)acceptsFirstResponder;
@@ -274,6 +279,8 @@ enum class SalEvent;
 
 -(void)viewDidChangeEffectiveAppearance;
 
+-(void)mouseDraggedWithTimer:(NSTimer *)pTimer;
+
 @end
 
 @interface SalFrameViewA11yWrapper : AquaA11yWrapper
diff --git a/vcl/inc/skia/osx/gdiimpl.hxx b/vcl/inc/skia/osx/gdiimpl.hxx
index f1e2214d3924..6e1656e59f1c 100644
--- a/vcl/inc/skia/osx/gdiimpl.hxx
+++ b/vcl/inc/skia/osx/gdiimpl.hxx
@@ -49,10 +49,6 @@ private:
     virtual void createWindowSurfaceInternal(bool forceRaster = false) 
override;
     virtual void flushSurfaceToWindowContext() override;
     static inline sk_sp<SkFontMgr> fontManager;
-    static inline AquaSkiaSalGraphicsImpl* lastFlushedGraphicsImpl = nullptr;
-    static inline CFAbsoluteTime lastFlushedGraphicsImplTime = 0;
-
-    bool mbInFlushSurfaceToWindowContext;
 };
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm
index c4afc0eced21..7ea26a3d4fad 100644
--- a/vcl/osx/salframeview.mm
+++ b/vcl/osx/salframeview.mm
@@ -1061,13 +1061,39 @@ static void updateWindowCollectionBehavior( const 
SalFrameStyleFlags nStyle, con
         mpLastTrackingArea = nil;
 
         mbInViewDidChangeEffectiveAppearance = NO;
+
+        mpMouseDraggedTimer = nil;
+        mpPendingMouseDraggedEvent = nil;
     }
 
     return self;
 }
 
+-(void)clearMouseDraggedTimer
+{
+    if ( mpMouseDraggedTimer )
+    {
+        [mpMouseDraggedTimer invalidate];
+        [mpMouseDraggedTimer release];
+        mpMouseDraggedTimer = nil;
+    }
+
+    // Clear the pending mouse dragged event as well
+    [self clearPendingMouseDraggedEvent];
+}
+
+-(void)clearPendingMouseDraggedEvent
+{
+    if ( mpPendingMouseDraggedEvent )
+    {
+        [mpPendingMouseDraggedEvent release];
+        mpPendingMouseDraggedEvent = nil;
+    }
+}
+
 -(void)dealloc
 {
+    [self clearMouseDraggedTimer];
     [self clearLastEvent];
     [self clearLastMarkedText];
     [self clearLastTrackingArea];
@@ -1237,17 +1263,47 @@ static void updateWindowCollectionBehavior( const 
SalFrameStyleFlags nStyle, con
 
 -(void)mouseDragged: (NSEvent*)pEvent
 {
-    if ( mpMouseEventListener != nil &&
-         [mpMouseEventListener respondsToSelector: @selector(mouseDragged:)])
-    {
-        [mpMouseEventListener mouseDragged: [pEvent copyWithZone: nullptr]];
+    // tdf#163945 Coalesce mouse dragged events
+    // When dragging a selection box on an empty background in Impress
+    // while using Skia/Metal, the selection box would not keep up with
+    // the pointer. The selection box would repaint sporadically or not
+    // at all if the pointer was dragged rapidly and the status bar was
+    // visible.
+    // Apparently, flushing a graphics doesn't actually do much of
+    // anything with Skia/Raster and Skia disabled so the selection box
+    // repaints without any noticeable delay.
+    // However, with Skia/Metal every flush of a graphics creates and
+    // queues a new CAMetalLayer drawable. During rapid dragging, this
+    // can lead to creating and queueing up to 200 drawables per second
+    // leaving no spare time for the Impress selection box painting
+    // timer to fire. So coalesce mouse dragged events so that only
+    // a maximum of 50 mouse dragged events are dispatched per second.
+    [self clearPendingMouseDraggedEvent];
+    mpPendingMouseDraggedEvent = [pEvent retain];
+    if ( !mpMouseDraggedTimer )
+    {
+        mpMouseDraggedTimer = [NSTimer scheduledTimerWithTimeInterval:0.025f 
target:self selector:@selector(mouseDraggedWithTimer:) userInfo:nil 
repeats:YES];
+        if ( mpMouseDraggedTimer )
+        {
+            [mpMouseDraggedTimer retain];
+
+            // The timer won't fire without a call to
+            // Application::Reschedule() unless we copy the fix for
+            // #i84055# from vcl/osx/saltimer.cxx and add the timer
+            // to the NSEventTrackingRunLoopMode run loop mode
+            [[NSRunLoop currentRunLoop] addTimer:mpMouseDraggedTimer 
forMode:NSEventTrackingRunLoopMode];
+        }
     }
-    s_nLastButton = MOUSE_LEFT;
-    [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT 
eventtype:SalEvent::MouseMove];
 }
 
 -(void)mouseUp: (NSEvent*)pEvent
 {
+    // Dispatch any pending mouse dragged event before dispatching the
+    // mouse up event
+    if ( mpPendingMouseDraggedEvent )
+        [self mouseDraggedWithTimer: nil];
+    [self clearMouseDraggedTimer];
+
     s_nLastButton = 0;
     [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT 
eventtype:SalEvent::MouseButtonUp];
 }
@@ -2984,6 +3040,29 @@ static void updateWindowCollectionBehavior( const 
SalFrameStyleFlags nStyle, con
     mbInViewDidChangeEffectiveAppearance = NO;
 }
 
+-(void)mouseDraggedWithTimer: (NSTimer *)pTimer
+{
+    (void)pTimer;
+
+    if ( mpPendingMouseDraggedEvent )
+    {
+        if ( mpMouseEventListener != nil &&
+             [mpMouseEventListener respondsToSelector: 
@selector(mouseDragged:)])
+        {
+            [mpMouseEventListener mouseDragged: [mpPendingMouseDraggedEvent 
copyWithZone: nullptr]];
+        }
+
+        s_nLastButton = MOUSE_LEFT;
+        [self sendMouseEventToFrame:mpPendingMouseDraggedEvent 
button:MOUSE_LEFT eventtype:SalEvent::MouseMove];
+
+        [self clearPendingMouseDraggedEvent];
+    }
+    else
+    {
+        [self clearMouseDraggedTimer];
+    }
+}
+
 @end
 
 @implementation SalFrameViewA11yWrapper
diff --git a/vcl/skia/osx/gdiimpl.cxx b/vcl/skia/osx/gdiimpl.cxx
index 7c0789bfe89f..8043fb2e2b72 100644
--- a/vcl/skia/osx/gdiimpl.cxx
+++ b/vcl/skia/osx/gdiimpl.cxx
@@ -59,15 +59,11 @@ 
AquaSkiaSalGraphicsImpl::AquaSkiaSalGraphicsImpl(AquaSalGraphics& rParent,
                                                  AquaSharedAttributes& rShared)
     : SkiaSalGraphicsImpl(rParent, rShared.mpFrame)
     , AquaGraphicsBackendBase(rShared, this)
-    , mbInFlushSurfaceToWindowContext(false)
 {
 }
 
 AquaSkiaSalGraphicsImpl::~AquaSkiaSalGraphicsImpl()
 {
-    if (lastFlushedGraphicsImpl == this)
-        lastFlushedGraphicsImpl = nullptr;
-
     DeInit(); // mac code doesn't call DeInit()
 }
 
@@ -129,53 +125,7 @@ void AquaSkiaSalGraphicsImpl::flushSurfaceToWindowContext()
     }
     else
     {
-        // Related: tdf#163945 don't directly flush graphics with Skia/Metal
-        // When dragging a selection box on an empty background in
-        // Impress and only with Skia/Metal, the selection box would
-        // not keep up with the pointer. The selection box would
-        // repaint sporadically or not at all if the pointer was
-        // dragged rapidly and the status bar was visible.
-        // Apparently, flushing a graphics doesn't actually do much of
-        // anything with Skia/Raster and Skia disabled so the selection
-        // box repaints without any noticeable delay.
-        // However, with Skia/Metal every flush of a graphics creates
-        // and queues a new CAMetalLayer drawable. During rapid
-        // dragging, this can lead to creating and queueing up to 200
-        // drawables per second leaving no spare time for the Impress
-        // selection box painting timer to fire. So with Skia/Metal,
-        // throttle the rate of flushing.
-        // tdf#166258 Assume a certain maximum flushing rate with Skia/Metal
-        // Previously, the fix for tdf#163945 was done in the
-        // AquaSalFrame::Flush() methods, but that caused tdf#166258
-        // so include flushes done by the Skia timer in the throttling
-        // of the maximum flushing rate. Currently, the maximum flushing
-        // rate is 100 flushes per second.
-        static const CFAbsoluteTime fMinFlushInterval = 0.01;
-
-        // Related: tdf#166258 prevent recursion as calling scheduleFlush()
-        // may immediately recurse into performFlush() leading to infinite
-        // recursion. If scheduleFlush() is recursing, assume that a flush
-        // is necessary.
-        // Also assume that a flush is necessary if the current graphics
-        // is different than the last flushed graphics. Otherwise, the
-        // "tip of the day" dialog or other windows will fail to draw
-        // immediately after the window opens.
-        CFAbsoluteTime fInterval = CFAbsoluteTimeGetCurrent() - 
lastFlushedGraphicsImplTime;
-        if (!mbInFlushSurfaceToWindowContext && lastFlushedGraphicsImpl == 
this && fInterval >= 0.0f
-            && fInterval < fMinFlushInterval)
-        {
-            // Just to be safe, enable the Skia timer so that the surface
-            // will eventually be flushed.
-            mbInFlushSurfaceToWindowContext = true;
-            scheduleFlush();
-            mbInFlushSurfaceToWindowContext = false;
-        }
-        else
-        {
-            lastFlushedGraphicsImpl = this;
-            SkiaSalGraphicsImpl::flushSurfaceToWindowContext();
-            lastFlushedGraphicsImplTime = CFAbsoluteTimeGetCurrent();
-        }
+        SkiaSalGraphicsImpl::flushSurfaceToWindowContext();
     }
 }
 

Reply via email to