1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_layers_GenericFlingAnimation_h_ 8 #define mozilla_layers_GenericFlingAnimation_h_ 9 10 #include "APZUtils.h" 11 #include "AsyncPanZoomAnimation.h" 12 #include "AsyncPanZoomController.h" 13 #include "FrameMetrics.h" 14 #include "Layers.h" 15 #include "LayersLogging.h" 16 #include "Units.h" 17 #include "OverscrollHandoffState.h" 18 #include "mozilla/Assertions.h" 19 #include "mozilla/Monitor.h" 20 #include "mozilla/RefPtr.h" 21 #include "mozilla/StaticPrefs_apz.h" 22 #include "mozilla/TimeStamp.h" 23 #include "nsThreadUtils.h" 24 25 static mozilla::LazyLogModule sApzFlgLog("apz.fling"); 26 #define FLING_LOG(...) MOZ_LOG(sApzFlgLog, LogLevel::Debug, (__VA_ARGS__)) 27 28 namespace mozilla { 29 namespace layers { 30 31 /** 32 * The FlingPhysics template parameter determines the physics model 33 * that the fling animation follows. It must have the following methods: 34 * 35 * - Default constructor. 36 * 37 * - Init(const ParentLayerPoint& aStartingVelocity, float aPLPPI). 38 * Called at the beginning of the fling, with the fling's starting velocity, 39 * and the number of ParentLayer pixels per (Screen) inch at the point of 40 * the fling's start in the fling's direction. 41 * 42 * - Sample(const TimeDuration& aDelta, 43 * ParentLayerPoint* aOutVelocity, 44 * ParentLayerPoint* aOutOffset); 45 * Called on each sample of the fling. 46 * |aDelta| is the time elapsed since the last sample. 47 * |aOutVelocity| should be the desired velocity after the current sample, 48 * in ParentLayer pixels per millisecond. 49 * |aOutOffset| should be the desired _delta_ to the scroll offset after 50 * the current sample. |aOutOffset| should _not_ be clamped to the APZC's 51 * scrollable bounds; the caller will do the clamping, and it needs to 52 * know the unclamped value to handle handoff/overscroll correctly. 53 */ 54 template <typename FlingPhysics> 55 class GenericFlingAnimation : public AsyncPanZoomAnimation, 56 public FlingPhysics { 57 public: GenericFlingAnimation(AsyncPanZoomController & aApzc,const RefPtr<const OverscrollHandoffChain> & aOverscrollHandoffChain,bool aFlingIsHandedOff,const RefPtr<const AsyncPanZoomController> & aScrolledApzc,float aPLPPI)58 GenericFlingAnimation( 59 AsyncPanZoomController& aApzc, 60 const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain, 61 bool aFlingIsHandedOff, 62 const RefPtr<const AsyncPanZoomController>& aScrolledApzc, float aPLPPI) 63 : mApzc(aApzc), 64 mOverscrollHandoffChain(aOverscrollHandoffChain), 65 mScrolledApzc(aScrolledApzc) { 66 MOZ_ASSERT(mOverscrollHandoffChain); 67 TimeStamp now = aApzc.GetFrameTime(); 68 69 // Drop any velocity on axes where we don't have room to scroll anyways 70 // (in this APZC, or an APZC further in the handoff chain). 71 // This ensures that we don't take the 'overscroll' path in Sample() 72 // on account of one axis which can't scroll having a velocity. 73 if (!mOverscrollHandoffChain->CanScrollInDirection( 74 &mApzc, ScrollDirection::eHorizontal)) { 75 RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex); 76 mApzc.mX.SetVelocity(0); 77 } 78 if (!mOverscrollHandoffChain->CanScrollInDirection( 79 &mApzc, ScrollDirection::eVertical)) { 80 RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex); 81 mApzc.mY.SetVelocity(0); 82 } 83 84 ParentLayerPoint velocity = mApzc.GetVelocityVector(); 85 86 // If the last fling was very recent and in the same direction as this one, 87 // boost the velocity to be the sum of the two. Check separate axes 88 // separately because we could have two vertical flings with small 89 // horizontal components on the opposite side of zero, and we still want the 90 // y-fling to get accelerated. Note that the acceleration code is only 91 // applied on the APZC that initiates the fling; the accelerated velocities 92 // are then handed off using the normal DispatchFling codepath. Acceleration 93 // is only applied in the APZC that originated the fling, not in APZCs 94 // further down the handoff chain during handoff. 95 bool applyAcceleration = !aFlingIsHandedOff; 96 if (applyAcceleration && !mApzc.mLastFlingTime.IsNull() && 97 (now - mApzc.mLastFlingTime).ToMilliseconds() < 98 StaticPrefs::apz_fling_accel_interval_ms() && 99 velocity.Length() >= StaticPrefs::apz_fling_accel_min_velocity()) { 100 if (velocity.x != 0 && 101 SameDirection(velocity.x, mApzc.mLastFlingVelocity.x)) { 102 velocity.x = Accelerate(velocity.x, mApzc.mLastFlingVelocity.x); 103 FLING_LOG("%p Applying fling x-acceleration from %f to %f (delta %f)\n", 104 &mApzc, mApzc.mX.GetVelocity(), velocity.x, 105 mApzc.mLastFlingVelocity.x); 106 mApzc.mX.SetVelocity(velocity.x); 107 } 108 if (velocity.y != 0 && 109 SameDirection(velocity.y, mApzc.mLastFlingVelocity.y)) { 110 velocity.y = Accelerate(velocity.y, mApzc.mLastFlingVelocity.y); 111 FLING_LOG("%p Applying fling y-acceleration from %f to %f (delta %f)\n", 112 &mApzc, mApzc.mY.GetVelocity(), velocity.y, 113 mApzc.mLastFlingVelocity.y); 114 mApzc.mY.SetVelocity(velocity.y); 115 } 116 } 117 118 mApzc.mLastFlingTime = now; 119 mApzc.mLastFlingVelocity = velocity; 120 121 FlingPhysics::Init(mApzc.GetVelocityVector(), aPLPPI); 122 } 123 124 /** 125 * Advances a fling by an interpolated amount based on the passed in |aDelta|. 126 * This should be called whenever sampling the content transform for this 127 * frame. Returns true if the fling animation should be advanced by one frame, 128 * or false if there is no fling or the fling has ended. 129 */ DoSample(FrameMetrics & aFrameMetrics,const TimeDuration & aDelta)130 virtual bool DoSample(FrameMetrics& aFrameMetrics, 131 const TimeDuration& aDelta) override { 132 ParentLayerPoint velocity; 133 ParentLayerPoint offset; 134 FlingPhysics::Sample(aDelta, &velocity, &offset); 135 136 mApzc.SetVelocityVector(velocity); 137 138 // If we shouldn't continue the fling, let's just stop and repaint. 139 if (IsZero(velocity)) { 140 FLING_LOG("%p ending fling animation. overscrolled=%d\n", &mApzc, 141 mApzc.IsOverscrolled()); 142 // This APZC or an APZC further down the handoff chain may be be 143 // overscrolled. Start a snap-back animation on the overscrolled APZC. 144 // Note: 145 // This needs to be a deferred task even though it can safely run 146 // while holding mRecursiveMutex, because otherwise, if the overscrolled 147 // APZC is this one, then the SetState(NOTHING) in UpdateAnimation will 148 // stomp on the SetState(SNAP_BACK) it does. 149 mDeferredTasks.AppendElement(NewRunnableMethod<AsyncPanZoomController*>( 150 "layers::OverscrollHandoffChain::SnapBackOverscrolledApzc", 151 mOverscrollHandoffChain.get(), 152 &OverscrollHandoffChain::SnapBackOverscrolledApzc, &mApzc)); 153 return false; 154 } 155 156 // Ordinarily we might need to do a ScheduleComposite if either of 157 // the following AdjustDisplacement calls returns true, but this 158 // is already running as part of a FlingAnimation, so we'll be compositing 159 // per frame of animation anyway. 160 ParentLayerPoint overscroll; 161 ParentLayerPoint adjustedOffset; 162 mApzc.mX.AdjustDisplacement(offset.x, adjustedOffset.x, overscroll.x); 163 mApzc.mY.AdjustDisplacement(offset.y, adjustedOffset.y, overscroll.y); 164 165 mApzc.ScrollBy(adjustedOffset / aFrameMetrics.GetZoom()); 166 167 // The fling may have caused us to reach the end of our scroll range. 168 if (!IsZero(overscroll)) { 169 // Hand off the fling to the next APZC in the overscroll handoff chain. 170 171 // We may have reached the end of the scroll range along one axis but 172 // not the other. In such a case we only want to hand off the relevant 173 // component of the fling. 174 if (FuzzyEqualsAdditive(overscroll.x, 0.0f, COORDINATE_EPSILON)) { 175 velocity.x = 0; 176 } else if (FuzzyEqualsAdditive(overscroll.y, 0.0f, COORDINATE_EPSILON)) { 177 velocity.y = 0; 178 } 179 180 // To hand off the fling, we attempt to find a target APZC and start a new 181 // fling with the same velocity on that APZC. For simplicity, the actual 182 // overscroll of the current sample is discarded rather than being handed 183 // off. The compositor should sample animations sufficiently frequently 184 // that this is not noticeable. The target APZC is chosen by seeing if 185 // there is an APZC further in the handoff chain which is pannable; if 186 // there isn't, we take the new fling ourselves, entering an overscrolled 187 // state. 188 // Note: APZC is holding mRecursiveMutex, so directly calling 189 // HandleFlingOverscroll() (which acquires the tree lock) would violate 190 // the lock ordering. Instead we schedule HandleFlingOverscroll() to be 191 // called after mRecursiveMutex is released. 192 FLING_LOG("%p fling went into overscroll, handing off with velocity %s\n", 193 &mApzc, Stringify(velocity).c_str()); 194 mDeferredTasks.AppendElement( 195 NewRunnableMethod<ParentLayerPoint, 196 RefPtr<const OverscrollHandoffChain>, 197 RefPtr<const AsyncPanZoomController>>( 198 "layers::AsyncPanZoomController::HandleFlingOverscroll", &mApzc, 199 &AsyncPanZoomController::HandleFlingOverscroll, velocity, 200 mOverscrollHandoffChain, mScrolledApzc)); 201 202 // If there is a remaining velocity on this APZC, continue this fling 203 // as well. (This fling and the handed-off fling will run concurrently.) 204 // Note that AdjustDisplacement() will have zeroed out the velocity 205 // along the axes where we're overscrolled. 206 return !IsZero(mApzc.GetVelocityVector()); 207 } 208 209 return true; 210 } 211 HandleScrollOffsetUpdate(const Maybe<CSSPoint> & aRelativeDelta)212 virtual bool HandleScrollOffsetUpdate( 213 const Maybe<CSSPoint>& aRelativeDelta) override { 214 return true; 215 } 216 217 private: SameDirection(float aVelocity1,float aVelocity2)218 static bool SameDirection(float aVelocity1, float aVelocity2) { 219 return (aVelocity1 == 0.0f) || (aVelocity2 == 0.0f) || 220 (IsNegative(aVelocity1) == IsNegative(aVelocity2)); 221 } 222 Accelerate(float aBase,float aSupplemental)223 static float Accelerate(float aBase, float aSupplemental) { 224 return (aBase * StaticPrefs::apz_fling_accel_base_mult()) + 225 (aSupplemental * StaticPrefs::apz_fling_accel_supplemental_mult()); 226 } 227 228 AsyncPanZoomController& mApzc; 229 RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain; 230 RefPtr<const AsyncPanZoomController> mScrolledApzc; 231 }; 232 233 } // namespace layers 234 } // namespace mozilla 235 236 #endif // mozilla_layers_GenericFlingAnimation_h_ 237