Bug 1267438 - During AlignFixedAndStickyLayers, only un-adjust the fixed portion of a layer's clip rect. r=mstange draft
authorBotond Ballo <botond@mozilla.com>
Wed, 04 May 2016 21:05:27 -0400
changeset 364619 4746e1cebf87e6eeda886041c745cfcce985db80
parent 364618 d3ad1e0ab2690bb9e55a593ea6d979e234e71e9f
child 364620 508f14af8caca19e296fd90b3bd19b6d16902d1c
push id17504
push userbballo@mozilla.com
push dateFri, 06 May 2016 23:44:30 +0000
reviewersmstange
bugs1267438
milestone49.0a1
Bug 1267438 - During AlignFixedAndStickyLayers, only un-adjust the fixed portion of a layer's clip rect. r=mstange MozReview-Commit-ID: 62JVHL5AVhP
gfx/layers/composite/AsyncCompositionManager.cpp
gfx/layers/composite/AsyncCompositionManager.h
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -210,16 +210,30 @@ TransformClipRect(Layer* aLayer,
   MOZ_ASSERT(aTransform.Is2D());
   const Maybe<ParentLayerIntRect>& clipRect = aLayer->AsLayerComposite()->GetShadowClipRect();
   if (clipRect) {
     ParentLayerIntRect transformed = TransformBy(aTransform, *clipRect);
     aLayer->AsLayerComposite()->SetShadowClipRect(Some(transformed));
   }
 }
 
+// Similar to TransformFixedClip(), but only transforms the fixed part of the
+// clip.
+static void
+TransformFixedClip(Layer* aLayer,
+                   const ParentLayerToParentLayerMatrix4x4& aTransform,
+                   AsyncCompositionManager::ClipParts& aClipParts)
+{
+  MOZ_ASSERT(aTransform.Is2D());
+  if (aClipParts.mFixedClip) {
+    *aClipParts.mFixedClip = TransformBy(aTransform, *aClipParts.mFixedClip);
+    aLayer->AsLayerComposite()->SetShadowClipRect(aClipParts.Intersect());
+  }
+}
+
 /**
  * Set the given transform as the shadow transform on the layer, assuming
  * that the given transform already has the pre- and post-scales applied.
  * That is, this function cancels out the pre- and post-scales from aTransform
  * before setting it as the shadow transform on the layer, so that when
  * the layer's effective transform is computed, the pre- and post-scales will
  * only be applied once.
  */
@@ -235,40 +249,49 @@ SetShadowTransform(Layer* aLayer, LayerT
                        1.0f / aLayer->GetPostYScale(),
                        1);
   aLayer->AsLayerComposite()->SetShadowBaseTransform(aTransform.ToUnknownMatrix());
 }
 
 static void
 TranslateShadowLayer(Layer* aLayer,
                      const gfxPoint& aTranslation,
-                     bool aAdjustClipRect)
+                     bool aAdjustClipRect,
+                     AsyncCompositionManager::ClipPartsCache* aClipPartsCache = nullptr)
 {
   // This layer might also be a scrollable layer and have an async transform.
   // To make sure we don't clobber that, we start with the shadow transform.
   // (i.e. GetLocalTransform() instead of GetTransform()).
   // Note that the shadow transform is reset on every frame of composition so
   // we don't have to worry about the adjustments compounding over successive
   // frames.
   LayerToParentLayerMatrix4x4 layerTransform = aLayer->GetLocalTransformTyped();
 
   // Apply the translation to the layer transform.
   layerTransform.PostTranslate(aTranslation.x, aTranslation.y, 0);
 
   SetShadowTransform(aLayer, layerTransform);
   aLayer->AsLayerComposite()->SetShadowTransformSetByAnimation(false);
 
   if (aAdjustClipRect) {
-    TransformClipRect(aLayer,
-        ParentLayerToParentLayerMatrix4x4::Translation(aTranslation.x, aTranslation.y, 0));
+    auto transform = ParentLayerToParentLayerMatrix4x4::Translation(aTranslation.x, aTranslation.y, 0);
+    // If we're passed a clip parts cache, only transform the fixed part of
+    // the clip.
+    if (aClipPartsCache) {
+      auto iter = aClipPartsCache->find(aLayer);
+      MOZ_ASSERT(iter != aClipPartsCache->end());
+      TransformFixedClip(aLayer, transform, iter->second);
+    } else {
+      TransformClipRect(aLayer, transform);
+    }
 
     // If a fixed- or sticky-position layer has a mask layer, that mask should
     // move along with the layer, so apply the translation to the mask layer too.
     if (Layer* maskLayer = aLayer->GetMaskLayer()) {
-      TranslateShadowLayer(maskLayer, aTranslation, false);
+      TranslateShadowLayer(maskLayer, aTranslation, false, aClipPartsCache);
     }
   }
 }
 
 static void
 AccumulateLayerTransforms(Layer* aLayer,
                           Layer* aAncestor,
                           Matrix4x4& aMatrix)
@@ -504,17 +527,18 @@ AsyncCompositionManager::AlignFixedAndSt
   // where the async transform on |aTransformedSubtreeRoot| affects this layer's
   // clip rect, we need to apply the same translation to said clip rect, so
   // that the effective transform on the clip rect takes it back to where it was
   // originally, had there been no async scroll.
   // Also, some layers want async scrolling to move their clip rect
   // (IsClipFixed() = false), so we don't make a compensating adjustment for
   // those.
   bool adjustClipRect = aTransformAffectsLayerClip && aLayer->IsClipFixed();
-  TranslateShadowLayer(aLayer, ThebesPoint(translation.ToUnknownPoint()), adjustClipRect);
+  TranslateShadowLayer(aLayer, ThebesPoint(translation.ToUnknownPoint()),
+      adjustClipRect, &mClipPartsCache);
 }
 
 static void
 SampleValue(float aPortion, Animation& aAnimation, StyleAnimationValue& aStart,
             StyleAnimationValue& aEnd, Animatable* aValue, Layer* aLayer)
 {
   StyleAnimationValue interpolatedValue;
   NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() ||
@@ -804,28 +828,42 @@ AsyncCompositionManager::ApplyAsyncConte
 
   LayerToParentLayerMatrix4x4 oldTransform = aLayer->GetTransformTyped() *
       AsyncTransformMatrix();
 
   AsyncTransformComponentMatrix combinedAsyncTransform;
   bool hasAsyncTransform = false;
   ScreenMargin fixedLayerMargins;
 
-  // Each layer has multiple clips. Its local clip, which must move with async
-  // transforms, and its scrollframe clips, which are the clips between each
-  // scrollframe and its ancestor scrollframe. Scrollframe clips include the
-  // composition bounds and any other clips induced by layout.
-  //
-  // The final clip for the layer is the intersection of these clips.
-  Maybe<ParentLayerIntRect> asyncClip = aLayer->GetClipRect();
+  // Each layer has multiple clips:
+  //  - Its local clip, which is fixed to the layer contents, i.e. it moves
+  //    with those async transforms which the layer contents move with.
+  //  - For each ScrollMetadata on the layer, a scroll clip. This includes
+  //    the composition bounds and any other clips induced by layout. This
+  //    moves with async transforms from ScrollMetadatas above it.
+  // In this function, these clips are combined into two shadow clip parts:
+  //  - The fixed clip, which consists of the local clip only, initially
+  //    transformed by all async transforms.
+  //  - The scrolled clip, which consists of the scroll clips, transformed by
+  //    the appropriate transforms.
+  // These two parts are kept separate for now, because for fixed layers, we
+  // need to adjust the fixed clip (to cancel out some async transforms).
+  // The parts are kept in a cache which is cleared at the beginning of every
+  // composite.
+  // The final shadow clip for the layer is the intersection of the (possibly
+  // adjusted) fixed clip and the scrolled clip.
+  ClipParts& clipParts = mClipPartsCache[aLayer];
+  clipParts.mFixedClip = aLayer->GetClipRect();
 
   // If we are a perspective transform ContainerLayer, apply the clip deferred
   // from our child (if there is any) before we iterate over our frame metrics,
   // because this clip is subject to all async transforms of this layer.
-  asyncClip = IntersectMaybeRects(asyncClip, clipDeferredFromChildren);
+  // Since this clip came from the a scroll clip on the child, it becomes part
+  // of our scrolled clip.
+  clipParts.mScrolledClip = clipDeferredFromChildren;
 
   // The transform of a mask layer is relative to the masked layer's parent
   // layer. So whenever we apply an async transform to a layer, we need to
   // apply that same transform to the layer's own mask layer.
   // A layer can also have "ancestor" mask layers for any rounded clips from
   // its ancestor scroll frames. A scroll frame mask layer only needs to be
   // async transformed for async scrolls of this scroll frame's ancestor
   // scroll frames, not for async scrolls of this scroll frame itself.
@@ -897,24 +935,31 @@ AsyncCompositionManager::ApplyAsyncConte
       }
     }
 #else
     // Non-Android platforms still care about this flag being cleared after
     // the first call to TransformShadowTree().
     mIsFirstPaint = false;
 #endif
 
-    // Transform the current local clip by this APZC's async transform. If we're
+    // Transform the current local clips by this APZC's async transform. If we're
     // using containerful scrolling, then the clip is not part of the scrolled
     // frame and should not be transformed.
-    if (asyncClip && !scrollMetadata.UsesContainerScrolling()) {
+    if (!scrollMetadata.UsesContainerScrolling()) {
       MOZ_ASSERT(asyncTransform.Is2D());
-      asyncClip = Some(TransformBy(asyncTransform, *asyncClip));
+      if (clipParts.mFixedClip) {
+        clipParts.mFixedClip = Some(TransformBy(asyncTransform, *clipParts.mFixedClip));
+      }
+      if (clipParts.mScrolledClip) {
+        clipParts.mScrolledClip = Some(TransformBy(asyncTransform, *clipParts.mScrolledClip));
+      }
     }
-    aLayer->AsLayerComposite()->SetShadowClipRect(asyncClip);
+    // Note: we don't set the layer's shadow clip rect property yet;
+    // AlignFixedAndStickyLayers will use the clip parts from the clip parts
+    // cache.
 
     combinedAsyncTransform *= asyncTransform;
 
     // For the purpose of aligning fixed and sticky layers, we disregard
     // the overscroll transform as well as any OMTA transform when computing the
     // 'aCurrentTransformForRoot' parameter. This ensures that the overscroll
     // and OMTA transforms are not unapplied, and therefore that the visual
     // effects apply to fixed and sticky layers. We do this by using
@@ -924,42 +969,38 @@ AsyncCompositionManager::ApplyAsyncConte
         aLayer->GetTransformTyped()
       * CompleteAsyncTransform(
           AdjustForClip(asyncTransformWithoutOverscroll, aLayer));
 
     // Since fixed/sticky layers are relative to their nearest scrolling ancestor,
     // we use the ViewID from the bottommost scrollable metrics here.
     AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform,
                               transformWithoutOverscrollOrOmta, fixedLayerMargins,
-                              asyncClip.isSome());
+                              clipParts.IsSome());
 
-    // AlignFixedAndStickyLayers may have changed the clip rect, so we have to
-    // read it from the layer again.
-    asyncClip = aLayer->AsLayerComposite()->GetShadowClipRect();
-
-    // Combine the local clip with the ancestor scrollframe clip. This is not
-    // included in the async transform above, since the ancestor clip should not
-    // move with this APZC.
+    // Combine the scrolled portion of the local clip with the ancestor
+    // scroll clip. This is not included in the async transform above, since
+    // the ancestor clip should not move with this APZC.
     if (scrollMetadata.HasScrollClip()) {
       ParentLayerIntRect clip = scrollMetadata.ScrollClip().GetClipRect();
       if (aLayer->GetParent() && aLayer->GetParent()->GetTransformIsPerspective()) {
         // If our parent layer has a perspective transform, we want to apply
         // our scroll clip to it instead of to this layer (see bug 1168263).
         // A layer with a perspective transform shouldn't have multiple
         // children with FrameMetrics, nor a child with multiple FrameMetrics.
         // (A child with multiple FrameMetrics would mean that there's *another*
         // scrollable element between the one with the CSS perspective and the
         // transformed element. But you'd have to use preserve-3d on the inner
         // scrollable element in order to have the perspective apply to the
         // transformed child, and preserve-3d is not supported on scrollable
         // elements, so this case can't occur.)
         MOZ_ASSERT(!aClipDeferredToParent);
         aClipDeferredToParent = Some(clip);
       } else {
-        asyncClip = IntersectMaybeRects(Some(clip), asyncClip);
+        clipParts.mScrolledClip = IntersectMaybeRects(Some(clip), clipParts.mScrolledClip);
       }
     }
 
     // Do the same for the ancestor mask layers: ancestorMaskLayers contains
     // the ancestor mask layers for scroll frames *inside* the current scroll
     // frame, so these are the ones we need to shift by our async transform.
     for (Layer* ancestorMaskLayer : ancestorMaskLayers) {
       SetShadowTransform(ancestorMaskLayer,
@@ -972,18 +1013,24 @@ AsyncCompositionManager::ApplyAsyncConte
       if (scrollClip.GetMaskLayerIndex()) {
         size_t maskLayerIndex = scrollClip.GetMaskLayerIndex().value();
         Layer* ancestorMaskLayer = aLayer->GetAncestorMaskLayerAt(maskLayerIndex);
         ancestorMaskLayers.AppendElement(ancestorMaskLayer);
       }
     }
   }
 
-  if (hasAsyncTransform || clipDeferredFromChildren) {
-    aLayer->AsLayerComposite()->SetShadowClipRect(asyncClip);
+  bool clipChanged = (hasAsyncTransform || clipDeferredFromChildren);
+  if (clipChanged) {
+    // Intersect the two clip parts and apply them to the layer.
+    // During ApplyAsyncContentTransformTree on an ancestor layer,
+    // AlignFixedAndStickyLayers may overwrite this with a new clip it
+    // computes from the clip parts, but if that doesn't happen, this
+    // is the layer's final clip rect.
+    aLayer->AsLayerComposite()->SetShadowClipRect(clipParts.Intersect());
   }
 
   if (hasAsyncTransform) {
     // Apply the APZ transform on top of GetLocalTransform() here (rather than
     // GetTransform()) in case the OMTA code in SampleAnimations already set a
     // shadow transform; in that case we want to apply ours on top of that one
     // rather than clobber it.
     SetShadowTransform(aLayer,
@@ -1426,16 +1473,19 @@ AsyncCompositionManager::TransformShadow
 
       for (uint32_t i = 0; i < scrollableLayers.Length(); i++) {
         if (scrollableLayers[i]) {
           TransformScrollableLayer(scrollableLayers[i]);
         }
       }
     }
 
+    // Clear the cache of clip parts that was used by ApplyAsyncContentTransformToTree().
+    mClipPartsCache.clear();
+
     // Advance APZ animations to the next expected vsync timestamp, if we can
     // get it.
     TimeStamp nextFrame = aCurrentFrame;
     TimeDuration vsyncrate = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay().GetVsyncRate();
     if (vsyncrate != TimeDuration::Forever()) {
       nextFrame += vsyncrate;
     }
     wantNextFrame |= SampleAPZAnimations(LayerMetricsWrapper(root), nextFrame);
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -114,16 +114,34 @@ public:
   // Returns true if the next composition will be the first for a
   // particular document.
   bool IsFirstPaint() { return mIsFirstPaint; }
 
   // GetFrameUniformity will return the frame uniformity for each layer attached to an APZ
   // from the recorded data in RecordShadowTransform
   void GetFrameUniformity(FrameUniformityData* aFrameUniformityData);
 
+  // Stores the clip rect of a layer in two parts: a fixed part and a scrolled
+  // part. When a layer is fixed, the clip needs to be adjusted to account for
+  // async transforms. Only the fixed part needs to be adjusted, so we need
+  // to store the two parts separately.
+  struct ClipParts {
+    Maybe<ParentLayerIntRect> mFixedClip;
+    Maybe<ParentLayerIntRect> mScrolledClip;
+
+    Maybe<ParentLayerIntRect> Intersect() const {
+      return IntersectMaybeRects(mFixedClip, mScrolledClip);
+    }
+
+    bool IsSome() const {
+      return mFixedClip.isSome() || mScrolledClip.isSome();
+    }
+  };
+
+  typedef std::map<Layer*, ClipParts> ClipPartsCache;
 private:
   void TransformScrollableLayer(Layer* aLayer);
   // Return true if an AsyncPanZoomController content transform was
   // applied for |aLayer|. |*aOutFoundRoot| is set to true on Android only, if
   // one of the metrics on one of the layers was determined to be the "root"
   // and its state was synced to the Java front-end. |aOutFoundRoot| must be
   // non-null. As the function recurses over the layer tree, a layer may
   // populate |aClipDeferredToParent| a clip rect it wants to set on its parent.
@@ -224,16 +242,22 @@ private:
 
   int32_t mPaintSyncId;
 
   bool mReadyForCompose;
 
   gfx::Matrix mWorldTransform;
   LayerTransformRecorder mLayerTransformRecorder;
 
+  // Maps layers to their ClipParts during ApplyAsyncContentTransformToTree.
+  // The parts are not stored individually on the layer, but during
+  // AlignFixedAndStickyLayers we need access to the individual parts for
+  // descendant layers.
+  ClipPartsCache mClipPartsCache;
+
 #ifdef MOZ_ANDROID_APZ
   // The following two fields are only needed on Fennec with C++ APZ, because
   // then we need to reposition the gecko scrollbar to deal with the
   // dynamic toolbar shifting content around.
   FrameMetrics::ViewID mRootScrollableId;
   ScreenMargin mFixedLayerMargins;
 #endif
 };