Bug 1402498 - Add ScrollAnimationMSDPhysics, can be enabled using general.smoothScroll.msdPhysics.enabled. r?rhunt draft
authorMarkus Stange <mstange@themasta.com>
Tue, 26 Sep 2017 20:55:35 -0400
changeset 672909 9ea693749cf82995d46f52be50eca490c0d8ba04
parent 672908 d2b8c4f1811f4b86719379154ac8e52e9f2e08a4
child 673070 9a9fb58a42a859e13070b5e18d741d4a0fd5d7ba
push id82420
push userbmo:mstange@themasta.com
push dateFri, 29 Sep 2017 22:38:50 +0000
reviewersrhunt
bugs1402498
milestone58.0a1
Bug 1402498 - Add ScrollAnimationMSDPhysics, can be enabled using general.smoothScroll.msdPhysics.enabled. r?rhunt MozReview-Commit-ID: fr8Q9iod5k
gfx/layers/apz/src/GenericScrollAnimation.cpp
gfx/thebes/gfxPrefs.h
layout/generic/ScrollAnimationMSDPhysics.cpp
layout/generic/ScrollAnimationMSDPhysics.h
layout/generic/moz.build
layout/generic/nsGfxScrollFrame.cpp
modules/libpref/init/all.js
--- a/gfx/layers/apz/src/GenericScrollAnimation.cpp
+++ b/gfx/layers/apz/src/GenericScrollAnimation.cpp
@@ -6,28 +6,33 @@
 
 #include "GenericScrollAnimation.h"
 
 #include "AsyncPanZoomController.h"
 #include "gfxPrefs.h"
 #include "nsPoint.h"
 #include "ScrollAnimationPhysics.h"
 #include "ScrollAnimationBezierPhysics.h"
+#include "ScrollAnimationMSDPhysics.h"
 
 namespace mozilla {
 namespace layers {
 
 GenericScrollAnimation::GenericScrollAnimation(AsyncPanZoomController& aApzc,
                                                const nsPoint& aInitialPosition,
                                                const ScrollAnimationBezierPhysicsSettings& aSettings)
   : mApzc(aApzc)
-  , mAnimationPhysics(MakeUnique<ScrollAnimationBezierPhysics>(aInitialPosition, aSettings))
   , mFinalDestination(aInitialPosition)
   , mForceVerticalOverscroll(false)
 {
+  if (gfxPrefs::SmoothScrollMSDPhysicsEnabled()) {
+    mAnimationPhysics = MakeUnique<ScrollAnimationMSDPhysics>(aInitialPosition);
+  } else {
+    mAnimationPhysics = MakeUnique<ScrollAnimationBezierPhysics>(aInitialPosition, aSettings);
+  }
 }
 
 void
 GenericScrollAnimation::UpdateDelta(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity)
 {
   mFinalDestination += aDelta;
 
   Update(aTime, aCurrentVelocity);
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -399,16 +399,31 @@ private:
   DECL_GFX_PREF(Live, "general.smoothScroll.pixels",           PixelSmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.pixels.durationMaxMS",
                 PixelSmoothScrollMaxDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.pixels.durationMinMS",
                 PixelSmoothScrollMinDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.stopDecelerationWeighting",
                 SmoothScrollStopDecelerationWeighting, float, 0.4f);
 
+  DECL_GFX_PREF(Live, "general.smoothScroll.msdPhysics.enabled",
+                SmoothScrollMSDPhysicsEnabled, bool, false);
+  DECL_GFX_PREF(Live, "general.smoothScroll.msdPhysics.continuousMotionMaxDeltaMS",
+                SmoothScrollMSDPhysicsContinuousMotionMaxDeltaMS, int32_t, 120);
+  DECL_GFX_PREF(Live, "general.smoothScroll.msdPhysics.motionBeginSpringConstant",
+                SmoothScrollMSDPhysicsMotionBeginSpringConstant, int32_t, 1250);
+  DECL_GFX_PREF(Live, "general.smoothScroll.msdPhysics.slowdownMinDeltaMS",
+                SmoothScrollMSDPhysicsSlowdownMinDeltaMS, int32_t, 12);
+  DECL_GFX_PREF(Live, "general.smoothScroll.msdPhysics.slowdownMinDeltaRatio",
+                SmoothScrollMSDPhysicsSlowdownMinDeltaRatio, float, 1.3f);
+  DECL_GFX_PREF(Live, "general.smoothScroll.msdPhysics.slowdownSpringConstant",
+                SmoothScrollMSDPhysicsSlowdownSpringConstant, int32_t, 2000);
+  DECL_GFX_PREF(Live, "general.smoothScroll.msdPhysics.regularSpringConstant",
+                SmoothScrollMSDPhysicsRegularSpringConstant, int32_t, 1000);
+
   DECL_GFX_PREF(Once, "gfx.android.rgb16.force",               AndroidRGB16Force, bool, false);
 #if defined(ANDROID)
   DECL_GFX_PREF(Once, "gfx.apitrace.enabled",                  UseApitrace, bool, false);
 #endif
 #if defined(RELEASE_OR_BETA)
   // "Skip" means this is locked to the default value in beta and release.
   DECL_GFX_PREF(Skip, "gfx.blocklist.all",                     BlocklistAll, int32_t, 0);
 #else
new file mode 100644
--- /dev/null
+++ b/layout/generic/ScrollAnimationMSDPhysics.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollAnimationMSDPhysics.h"
+#include "gfxPrefs.h"
+
+using namespace mozilla;
+
+ScrollAnimationMSDPhysics::ScrollAnimationMSDPhysics(const nsPoint& aStartPos)
+ : mStartPos(aStartPos)
+ , mModelX(0, 0, 0, gfxPrefs::SmoothScrollMSDPhysicsRegularSpringConstant(), 1)
+ , mModelY(0, 0, 0, gfxPrefs::SmoothScrollMSDPhysicsRegularSpringConstant(), 1)
+ , mIsFirstIteration(true)
+{
+}
+
+void
+ScrollAnimationMSDPhysics::Update(const TimeStamp& aTime,
+                                  const nsPoint& aDestination,
+                                  const nsSize& aCurrentVelocity)
+{
+  double springConstant = ComputeSpringConstant(aTime);
+
+  // mLastSimulatedTime is the most recent time that this animation has been
+  // "observed" at. We don't want to update back to a state in the past, so we
+  // set mStartTime to the more recent of mLastSimulatedTime and aTime.
+  // aTime can be in the past if we're processing an input event whose internal
+  // timestamp is in the past.
+  if (mLastSimulatedTime && aTime < mLastSimulatedTime) {
+    mStartTime = mLastSimulatedTime;
+  } else {
+    mStartTime = aTime;
+  }
+
+  if (!mIsFirstIteration) {
+    mStartPos = PositionAt(mStartTime);
+  }
+
+  mLastSimulatedTime = mStartTime;
+  mDestination = aDestination;
+  mModelX = AxisPhysicsMSDModel(mStartPos.x, aDestination.x,
+                                aCurrentVelocity.width, springConstant, 1);
+  mModelY = AxisPhysicsMSDModel(mStartPos.y, aDestination.y,
+                                aCurrentVelocity.height, springConstant, 1);
+  mIsFirstIteration = false;
+}
+
+double
+ScrollAnimationMSDPhysics::ComputeSpringConstant(const TimeStamp& aTime)
+{
+  if (!mPreviousEventTime) {
+    mPreviousEventTime = aTime;
+    mPreviousDelta = TimeDuration();
+    return gfxPrefs::SmoothScrollMSDPhysicsMotionBeginSpringConstant();
+  }
+
+  TimeDuration delta = aTime - mPreviousEventTime;
+  TimeDuration previousDelta = mPreviousDelta;
+
+  mPreviousEventTime = aTime;
+  mPreviousDelta = delta;
+
+  double deltaMS = delta.ToMilliseconds();
+  if (deltaMS >= gfxPrefs::SmoothScrollMSDPhysicsContinuousMotionMaxDeltaMS()) {
+    return gfxPrefs::SmoothScrollMSDPhysicsMotionBeginSpringConstant();
+  }
+
+  if (previousDelta &&
+      deltaMS >= gfxPrefs::SmoothScrollMSDPhysicsSlowdownMinDeltaMS() &&
+      deltaMS >= previousDelta.ToMilliseconds() * gfxPrefs::SmoothScrollMSDPhysicsSlowdownMinDeltaRatio()) {
+    // The rate of events has slowed (the time delta between events has
+    // increased) enough that we think that the current scroll motion is coming
+    // to a stop. Use a stiffer spring in order to reach the destination more
+    // quickly.
+    return gfxPrefs::SmoothScrollMSDPhysicsSlowdownSpringConstant();
+  }
+
+  return gfxPrefs::SmoothScrollMSDPhysicsRegularSpringConstant();
+}
+
+void
+ScrollAnimationMSDPhysics::SimulateUntil(const TimeStamp& aTime)
+{
+  if (!mLastSimulatedTime || aTime < mLastSimulatedTime) {
+    return;
+  }
+  TimeDuration delta = aTime - mLastSimulatedTime;
+  mModelX.Simulate(delta);
+  mModelY.Simulate(delta);
+  mLastSimulatedTime = aTime;
+}
+
+nsPoint
+ScrollAnimationMSDPhysics::PositionAt(const TimeStamp& aTime)
+{
+  SimulateUntil(aTime);
+  return nsPoint(NSToCoordRound(mModelX.GetPosition()),
+                 NSToCoordRound(mModelY.GetPosition()));
+}
+
+nsSize
+ScrollAnimationMSDPhysics::VelocityAt(const TimeStamp& aTime)
+{
+  SimulateUntil(aTime);
+  return nsSize(NSToCoordRound(mModelX.GetVelocity()),
+                NSToCoordRound(mModelY.GetVelocity()));
+}
new file mode 100644
--- /dev/null
+++ b/layout/generic/ScrollAnimationMSDPhysics.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layout_ScrollAnimationMSDPhysics_h_
+#define mozilla_layout_ScrollAnimationMSDPhysics_h_
+
+#include "ScrollAnimationPhysics.h"
+#include "mozilla/layers/AxisPhysicsMSDModel.h"
+
+namespace mozilla {
+
+// This class implements a cubic MSD timing function and automatically
+// adapts the animation duration based on the scrolling rate.
+class ScrollAnimationMSDPhysics : public ScrollAnimationPhysics
+{
+public:
+  typedef mozilla::layers::AxisPhysicsMSDModel AxisPhysicsMSDModel;
+
+  explicit ScrollAnimationMSDPhysics(const nsPoint& aStartPos);
+
+  void Update(const TimeStamp& aTime,
+              const nsPoint& aDestination,
+              const nsSize& aCurrentVelocity) override;
+
+  // Get the velocity at a point in time in nscoords/sec.
+  nsSize VelocityAt(const TimeStamp& aTime) override;
+
+  // Returns the expected scroll position at a given point in time, in app
+  // units, relative to the scroll frame.
+  nsPoint PositionAt(const TimeStamp& aTime) override;
+
+  bool IsFinished(const TimeStamp& aTime) override {
+    SimulateUntil(aTime);
+    return mModelX.IsFinished(1) && mModelY.IsFinished(1);
+  }
+
+protected:
+  double ComputeSpringConstant(const TimeStamp& aTime);
+  void SimulateUntil(const TimeStamp& aTime);
+
+  TimeStamp mPreviousEventTime;
+  TimeDuration mPreviousDelta;
+
+  TimeStamp mStartTime;
+
+  nsPoint mStartPos;
+  nsPoint mDestination;
+  TimeStamp mLastSimulatedTime;
+  AxisPhysicsMSDModel mModelX;
+  AxisPhysicsMSDModel mModelY;
+  bool mIsFirstIteration;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_layout_ScrollAnimationMSDPhysics_h_
--- a/layout/generic/moz.build
+++ b/layout/generic/moz.build
@@ -96,16 +96,17 @@ EXPORTS += [
     'nsRubyTextContainerFrame.h',
     'nsRubyTextFrame.h',
     'nsSplittableFrame.h',
     'nsSubDocumentFrame.h',
     'nsTextFrameUtils.h',
     'nsTextRunTransformations.h',
     'RubyUtils.h',
     'ScrollAnimationBezierPhysics.h',
+    'ScrollAnimationMSDPhysics.h',
     'ScrollAnimationPhysics.h',
     'ScrollbarActivity.h',
     'ScrollSnap.h',
     'TextDrawTarget.h',
     'Visibility.h',
 ]
 
 EXPORTS.mozilla += [
@@ -173,16 +174,17 @@ UNIFIED_SOURCES += [
     'nsTextFrame.cpp',
     'nsTextFrameUtils.cpp',
     'nsTextRunTransformations.cpp',
     'nsVideoFrame.cpp',
     'ReflowInput.cpp',
     'ReflowOutput.cpp',
     'RubyUtils.cpp',
     'ScrollAnimationBezierPhysics.cpp',
+    'ScrollAnimationMSDPhysics.cpp',
     'ScrollbarActivity.cpp',
     'ScrollSnap.cpp',
     'ScrollVelocityQueue.cpp',
     'StickyScrollContainer.cpp',
     'TextOverflow.cpp',
     'ViewportFrame.cpp',
 ]
 
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -54,16 +54,17 @@
 #include "nsSVGIntegrationUtils.h"
 #include "nsIScrollPositionListener.h"
 #include "StickyScrollContainer.h"
 #include "nsIFrameInlines.h"
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "ScrollAnimationPhysics.h"
 #include "ScrollAnimationBezierPhysics.h"
+#include "ScrollAnimationMSDPhysics.h"
 #include "ScrollSnap.h"
 #include "UnitTransforms.h"
 #include "nsPluginFrame.h"
 #include "nsSliderFrame.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include <mozilla/layers/AxisPhysicsModel.h>
 #include <mozilla/layers/AxisPhysicsMSDModel.h>
 #include "mozilla/layers/LayerTransactionChild.h"
@@ -1907,17 +1908,17 @@ private:
   }
 };
 
 /*
  * Calculate duration, possibly dynamically according to events rate and event origin.
  * (also maintain previous timestamps - which are only used here).
  */
 static ScrollAnimationBezierPhysicsSettings
-ComputeAnimationSettingsForOrigin(nsIAtom *aOrigin)
+ComputeBezierAnimationSettingsForOrigin(nsIAtom *aOrigin)
 {
   int32_t minMS = 0;
   int32_t maxMS = 0;
   bool isOriginSmoothnessEnabled = false;
   double intervalRatio = 1;
 
   // Default values for all preferences are defined in all.js
   static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150;
@@ -1967,19 +1968,24 @@ ScrollFrameHelper::AsyncScroll::InitSmoo
   }
   // Likewise we should never get APZ-triggered scrolls here, and if that changes
   // something is likely broken somewhere.
   MOZ_ASSERT(aOrigin != nsGkAtoms::apz);
 
   // Read preferences only on first iteration or for a different event origin.
   if (!mAnimationPhysics || aOrigin != mOrigin) {
     mOrigin = aOrigin;
-    ScrollAnimationBezierPhysicsSettings settings = ComputeAnimationSettingsForOrigin(mOrigin);
-    mAnimationPhysics =
-      MakeUnique<ScrollAnimationBezierPhysics>(aInitialPosition, settings);
+    if (gfxPrefs::SmoothScrollMSDPhysicsEnabled()) {
+      mAnimationPhysics = MakeUnique<ScrollAnimationMSDPhysics>(aInitialPosition);
+    } else {
+      ScrollAnimationBezierPhysicsSettings settings =
+        ComputeBezierAnimationSettingsForOrigin(mOrigin);
+      mAnimationPhysics =
+        MakeUnique<ScrollAnimationBezierPhysics>(aInitialPosition, settings);
+    }
   }
 
   mRange = aRange;
 
   mAnimationPhysics->Update(aTime, aDestination, aCurrentVelocity);
 }
 
 bool
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2805,16 +2805,24 @@ pref("general.smoothScroll.other", true)
 // should be longer than scroll events intervals (or else the scroll will stop
 // before the next event arrives - we're guessing next interval by averaging recent
 // intervals).
 // This defines how longer is the duration compared to events interval (percentage)
 pref("general.smoothScroll.durationToIntervalRatio", 200);
 // These two prefs determine the timing function.
 pref("general.smoothScroll.currentVelocityWeighting", "0.25");
 pref("general.smoothScroll.stopDecelerationWeighting", "0.4");
+// Alternative smooth scroll physics ("MSD" = Mass-Spring-Damper)
+pref("general.smoothScroll.msdPhysics.enabled", false);
+pref("general.smoothScroll.msdPhysics.continuousMotionMaxDeltaMS", 120);
+pref("general.smoothScroll.msdPhysics.motionBeginSpringConstant", 1250);
+pref("general.smoothScroll.msdPhysics.slowdownMinDeltaMS", 12);
+pref("general.smoothScroll.msdPhysics.slowdownMinDeltaRatio", "1.3");
+pref("general.smoothScroll.msdPhysics.slowdownSpringConstant", 2000);
+pref("general.smoothScroll.msdPhysics.regularSpringConstant", 1000);
 
 pref("profile.confirm_automigration",true);
 // profile.migration_behavior determines how the profiles root is set
 // 0 - use NS_APP_USER_PROFILES_ROOT_DIR
 // 1 - create one based on the NS4.x profile root
 // 2 - use, if not empty, profile.migration_directory otherwise same as 0
 pref("profile.migration_behavior",0);
 pref("profile.migration_directory", "");