Bug 1375949 - Delay application of async scroll offset by one composite, to give content a chance to remain in sync. r=kats
With this in place, scroll-linked effects will remain in sync with async
scrolling if they can be processed and painted within the frame budget.
This change is currently behind a pref that's off by default.
MozReview-Commit-ID: 6GEJTKZh6ON
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -268,16 +268,23 @@ typedef GenericFlingAnimation FlingAnima
* \li\b apz.fling_stopped_threshold
* When flinging, if the velocity goes below this number, we just stop the
* animation completely. This is to prevent asymptotically approaching 0
* velocity and rerendering unnecessarily.\n
* Units: screen pixels per millisecond.\n
* NOTE: Should not be set to anything
* other than 0.0 for Android except for tests to disable flings.
*
+ * \li\b apz.frame_delay.enabled
+ * If this is set to true, changes to the async scroll offset and async zoom
+ * will not be immediately reflected in GetCurrentAsyncTransform() when called
+ * with |AsyncTransformConsumer::eForCompositing|. Rather, the transform will
+ * reflect the value of the async scroll offset and async zoom at the last time
+ * SampleCompositedAsyncTransform() was called.
+ *
* \li\b apz.max_velocity_inches_per_ms
* Maximum velocity. Velocity will be capped at this value if a faster fling
* occurs. Negative values indicate unlimited velocity.\n
* Units: (real-world, i.e. screen) inches per millisecond
*
* \li\b apz.max_velocity_queue_size
* Maximum size of velocity queue. The queue contains last N velocity records.
* On touch end we calculate the average velocity in order to compensate
@@ -3239,16 +3246,22 @@ bool AsyncPanZoomController::UpdateAnima
// This function may get called multiple with the same sample time, because
// there may be multiple layers with this APZC, and each layer invokes this
// function during composition. However we only want to do one animation step
// per composition so we need to deduplicate these calls first.
if (mLastSampleTime == aSampleTime) {
return false;
}
+
+ // Sample the composited async transform once per composite. Note that we
+ // call this after the |mLastSampleTime == aSampleTime| check, to ensure
+ // it's only called once per APZC on each composite.
+ SampleCompositedAsyncTransform();
+
TimeDuration sampleTimeDelta = aSampleTime - mLastSampleTime;
mLastSampleTime = aSampleTime;
if (mAnimation) {
bool continueAnimation = mAnimation->Sample(mFrameMetrics, sampleTimeDelta);
bool wantsRepaints = mAnimation->WantsRepaints();
*aOutDeferredTasks = mAnimation->TakeDeferredTasks();
if (!continueAnimation) {
@@ -3340,46 +3353,46 @@ ParentLayerPoint
AsyncPanZoomController::GetCurrentAsyncScrollOffset(AsyncTransformConsumer aMode) const
{
ReentrantMonitorAutoEnter lock(mMonitor);
if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
return mLastContentPaintMetrics.GetScrollOffset() * mLastContentPaintMetrics.GetZoom();
}
- return (mFrameMetrics.GetScrollOffset() + mTestAsyncScrollOffset)
- * mFrameMetrics.GetZoom() * mTestAsyncZoom.scale;
+ return (GetEffectiveScrollOffset(aMode) + mTestAsyncScrollOffset)
+ * GetEffectiveZoom(aMode) * mTestAsyncZoom.scale;
}
CSSPoint
AsyncPanZoomController::GetCurrentAsyncScrollOffsetInCssPixels(AsyncTransformConsumer aMode) const {
ReentrantMonitorAutoEnter lock(mMonitor);
if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
return mLastContentPaintMetrics.GetScrollOffset();
}
- return mFrameMetrics.GetScrollOffset() + mTestAsyncScrollOffset;
+ return GetEffectiveScrollOffset(aMode) + mTestAsyncScrollOffset;
}
AsyncTransform
AsyncPanZoomController::GetCurrentAsyncTransform(AsyncTransformConsumer aMode) const
{
ReentrantMonitorAutoEnter lock(mMonitor);
if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
return AsyncTransform();
}
CSSPoint lastPaintScrollOffset;
if (mLastContentPaintMetrics.IsScrollable()) {
lastPaintScrollOffset = mLastContentPaintMetrics.GetScrollOffset();
}
- CSSPoint currentScrollOffset = mFrameMetrics.GetScrollOffset() +
+ CSSPoint currentScrollOffset = GetEffectiveScrollOffset(aMode) +
mTestAsyncScrollOffset;
// If checkerboarding has been disallowed, clamp the scroll position to stay
// within rendered content.
if (!gfxPrefs::APZAllowCheckerboarding() &&
!mLastContentPaintMetrics.GetDisplayPort().IsEmpty()) {
CSSSize compositedSize = mLastContentPaintMetrics.CalculateCompositedSizeInCssPixels();
CSSPoint maxScrollOffset = lastPaintScrollOffset +
@@ -3390,24 +3403,54 @@ AsyncPanZoomController::GetCurrentAsyncT
if (minScrollOffset.x < maxScrollOffset.x) {
currentScrollOffset.x = clamped(currentScrollOffset.x, minScrollOffset.x, maxScrollOffset.x);
}
if (minScrollOffset.y < maxScrollOffset.y) {
currentScrollOffset.y = clamped(currentScrollOffset.y, minScrollOffset.y, maxScrollOffset.y);
}
}
+ CSSToParentLayerScale2D effectiveZoom = GetEffectiveZoom(aMode);
+
ParentLayerPoint translation = (currentScrollOffset - lastPaintScrollOffset)
- * mFrameMetrics.GetZoom() * mTestAsyncZoom.scale;
-
+ * effectiveZoom * mTestAsyncZoom.scale;
+
+ LayerToParentLayerScale compositedAsyncZoom =
+ (effectiveZoom / mFrameMetrics.LayersPixelsPerCSSPixel()).ToScaleFactor();
return AsyncTransform(
- LayerToParentLayerScale(mFrameMetrics.GetAsyncZoom().scale * mTestAsyncZoom.scale),
+ LayerToParentLayerScale(compositedAsyncZoom.scale * mTestAsyncZoom.scale),
-translation);
}
+CSSPoint
+AsyncPanZoomController::GetEffectiveScrollOffset(AsyncTransformConsumer aMode) const
+{
+ if (gfxPrefs::APZFrameDelayEnabled() && aMode == eForCompositing) {
+ return mCompositedScrollOffset;
+ }
+ return mFrameMetrics.GetScrollOffset();
+}
+
+CSSToParentLayerScale2D
+AsyncPanZoomController::GetEffectiveZoom(AsyncTransformConsumer aMode) const
+{
+ if (gfxPrefs::APZFrameDelayEnabled() && aMode == eForCompositing) {
+ return mCompositedZoom;
+ }
+ return mFrameMetrics.GetZoom();
+}
+
+void
+AsyncPanZoomController::SampleCompositedAsyncTransform()
+{
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ mCompositedScrollOffset = mFrameMetrics.GetScrollOffset();
+ mCompositedZoom = mFrameMetrics.GetZoom();
+}
+
AsyncTransformComponentMatrix
AsyncPanZoomController::GetCurrentAsyncTransformWithOverscroll(AsyncTransformConsumer aMode) const
{
return AsyncTransformComponentMatrix(GetCurrentAsyncTransform(aMode))
* GetOverscrollTransform(aMode);
}
Matrix4x4 AsyncPanZoomController::GetTransformToLastDispatchedPaint() const {
@@ -3649,16 +3692,19 @@ void AsyncPanZoomController::NotifyLayer
// Initialize our internal state to something sane when the content
// that was just painted is something we knew nothing about previously
CancelAnimation();
mScrollMetadata = aScrollMetadata;
mExpectedGeckoMetrics = aLayerMetrics;
ShareCompositorFrameMetrics();
+ mCompositedScrollOffset = mFrameMetrics.GetScrollOffset();
+ mCompositedZoom = mFrameMetrics.GetZoom();
+
if (mFrameMetrics.GetDisplayPortMargins() != ScreenMargin()) {
// A non-zero display port margin here indicates a displayport has
// been set by a previous APZC for the content at this guid. The
// scrollable rect may have changed since then, making the margins
// wrong, so we need to calculate a new display port.
APZC_LOG("%p detected non-empty margins which probably need updating\n", this);
needContentRepaint = true;
}
@@ -3680,21 +3726,24 @@ void AsyncPanZoomController::NotifyLayer
gfxSize totalResolutionChange = aLayerMetrics.GetCumulativeResolution()
/ mFrameMetrics.GetCumulativeResolution();
float presShellResolutionChange = aLayerMetrics.GetPresShellResolution()
/ mFrameMetrics.GetPresShellResolution();
if (presShellResolutionChange != 1.0f) {
needContentRepaint = true;
}
mFrameMetrics.ZoomBy(totalResolutionChange / presShellResolutionChange);
+ mCompositedZoom.xScale *= (totalResolutionChange / presShellResolutionChange).width;
+ mCompositedZoom.yScale *= (totalResolutionChange / presShellResolutionChange).height;
} else {
// Take the new zoom as either device scale or composition width or
// viewport size got changed (e.g. due to orientation change, or content
// changing the meta-viewport tag).
mFrameMetrics.SetZoom(aLayerMetrics.GetZoom());
+ mCompositedZoom = aLayerMetrics.GetZoom();
mFrameMetrics.SetDevPixelsPerCSSPixel(aLayerMetrics.GetDevPixelsPerCSSPixel());
}
bool scrollableRectChanged = false;
if (!mFrameMetrics.GetScrollableRect().IsEqualEdges(aLayerMetrics.GetScrollableRect())) {
mFrameMetrics.SetScrollableRect(aLayerMetrics.GetScrollableRect());
needContentRepaint = true;
scrollableRectChanged = true;
}
@@ -3723,16 +3772,17 @@ void AsyncPanZoomController::NotifyLayer
// Send an acknowledgement with the new scroll generation so that any
// repaint requests later in this function go through.
// Because of the scroll generation update, any inflight paint requests are
// going to be ignored by layout, and so mExpectedGeckoMetrics
// becomes incorrect for the purposes of calculating the LD transform. To
// correct this we need to update mExpectedGeckoMetrics to be the
// last thing we know was painted by Gecko.
mFrameMetrics.CopyScrollInfoFrom(aLayerMetrics);
+ mCompositedScrollOffset = mFrameMetrics.GetScrollOffset();
mExpectedGeckoMetrics = aLayerMetrics;
// Cancel the animation (which might also trigger a repaint request)
// after we update the scroll offset above. Otherwise we can be left
// in a state where things are out of sync.
CancelAnimation();
// Since the scroll offset has changed, we need to recompute the
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -726,16 +726,21 @@ private:
FrameMetrics& mLastContentPaintMetrics; // for convenience, refers to mLastContentPaintMetadata.mMetrics
// The last metrics used for a content repaint request.
FrameMetrics mLastPaintRequestMetrics;
// The metrics that we expect content to have. This is updated when we
// request a content repaint, and when we receive a shadow layers update.
// This allows us to transform events into Gecko's coordinate space.
FrameMetrics mExpectedGeckoMetrics;
+ // These variables cache the scroll offset and zoom stored in |mFrameMetrics|
+ // the last time SampleCompositedAsyncTransform() was called.
+ CSSPoint mCompositedScrollOffset;
+ CSSToParentLayerScale2D mCompositedZoom;
+
AxisX mX;
AxisY mY;
// This flag is set to true when we are in a axis-locked pan as a result of
// the touch-action CSS property.
bool mPanDirRestricted;
// Most up-to-date constraints on zooming. These should always be reasonable
@@ -834,16 +839,36 @@ public:
AsyncTransform GetCurrentAsyncTransform(AsyncTransformConsumer aMode) const;
/**
* Returns the same transform as GetCurrentAsyncTransform(), but includes
* any transform due to axis over-scroll.
*/
AsyncTransformComponentMatrix GetCurrentAsyncTransformWithOverscroll(AsyncTransformConsumer aMode) const;
+private:
+ /**
+ * Samples the composited async transform, making the result of
+ * |GetCurrentAsyncTransform(eForCompositing)| and similar functions reflect
+ * the async scroll offset and zoom stored in |mFrameMetrics|.
+ *
+ * (This is only relevant when |gfxPrefs::APZFrameDelayEnabled() == true|.
+ * Otherwise, GetCurrentAsyncTransform() always reflects what's stored in
+ * |mFrameMetrics| immediately, without any delay.)
+ */
+ void SampleCompositedAsyncTransform();
+
+ /*
+ * Helper functions to query the async scroll offset and zoom either
+ * directly from |mFrameMetrics|, or from cached variables that store
+ * the scroll offset and zoom from the last time it was sampled by
+ * calling SampleCompositedAsyncTransform(), depending on who is asking.
+ */
+ CSSPoint GetEffectiveScrollOffset(AsyncTransformConsumer aMode) const;
+ CSSToParentLayerScale2D GetEffectiveZoom(AsyncTransformConsumer aMode) const;
/* ===================================================================
* The functions and members in this section are used to manage
* the state that tracks what this APZC is doing with the input events.
*/
protected:
enum PanZoomState {
NOTHING, /* no touch-start events received */
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -306,16 +306,17 @@ private:
DECL_GFX_PREF(Once, "apz.fling_curve_function_x2", APZCurveFunctionX2, float, 1.0f);
DECL_GFX_PREF(Once, "apz.fling_curve_function_y1", APZCurveFunctionY1, float, 0.0f);
DECL_GFX_PREF(Once, "apz.fling_curve_function_y2", APZCurveFunctionY2, float, 1.0f);
DECL_GFX_PREF(Live, "apz.fling_curve_threshold_inches_per_ms", APZCurveThreshold, float, -1.0f);
DECL_GFX_PREF(Live, "apz.fling_friction", APZFlingFriction, float, 0.002f);
DECL_GFX_PREF(Live, "apz.fling_min_velocity_threshold", APZFlingMinVelocityThreshold, float, 0.5f);
DECL_GFX_PREF(Live, "apz.fling_stop_on_tap_threshold", APZFlingStopOnTapThreshold, float, 0.05f);
DECL_GFX_PREF(Live, "apz.fling_stopped_threshold", APZFlingStoppedThreshold, float, 0.01f);
+ DECL_GFX_PREF(Live, "apz.frame_delay.enabled", APZFrameDelayEnabled, bool, false);
DECL_GFX_PREF(Live, "apz.highlight_checkerboarded_areas", APZHighlightCheckerboardedAreas, bool, false);
DECL_GFX_PREF(Once, "apz.keyboard.enabled", APZKeyboardEnabled, bool, false);
DECL_GFX_PREF(Live, "apz.max_velocity_inches_per_ms", APZMaxVelocity, float, -1.0f);
DECL_GFX_PREF(Once, "apz.max_velocity_queue_size", APZMaxVelocityQueueSize, uint32_t, 5);
DECL_GFX_PREF(Live, "apz.min_skate_speed", APZMinSkateSpeed, float, 1.0f);
DECL_GFX_PREF(Live, "apz.minimap.enabled", APZMinimap, bool, false);
DECL_GFX_PREF(Live, "apz.minimap.visibility.enabled", APZMinimapVisibilityEnabled, bool, false);
DECL_GFX_PREF(Live, "apz.overscroll.enabled", APZOverscrollEnabled, bool, false);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -694,16 +694,17 @@ pref("apz.fling_curve_function_x1", "0.0
pref("apz.fling_curve_function_y1", "0.0");
pref("apz.fling_curve_function_x2", "1.0");
pref("apz.fling_curve_function_y2", "1.0");
pref("apz.fling_curve_threshold_inches_per_ms", "-1.0");
pref("apz.fling_friction", "0.002");
pref("apz.fling_min_velocity_threshold", "0.5");
pref("apz.fling_stop_on_tap_threshold", "0.05");
pref("apz.fling_stopped_threshold", "0.01");
+pref("apz.frame_delay.enabled", false);
pref("apz.highlight_checkerboarded_areas", false);
pref("apz.keyboard.enabled", false);
pref("apz.max_velocity_inches_per_ms", "-1.0");
pref("apz.max_velocity_queue_size", 5);
pref("apz.min_skate_speed", "1.0");
pref("apz.minimap.enabled", false);
pref("apz.minimap.visibility.enabled", false);
pref("apz.overscroll.enabled", false);