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