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(); } }