Bug 1449608 - Avoid calling ComputeScrollMetadata repeatedly for same scroll frame and clip. r=mstange
☠☠ backed out by 7b4cbe358d21 ☠ ☠
authorJamie Nicol <jnicol@mozilla.com>
Thu, 19 Apr 2018 14:48:04 +0100
changeset 468961 544e2832e78306fc04c7cbaea81a40b0189ae155
parent 468960 e5a4d5c29bb93a75597aecbe6f7d4d94dc0c85f5
child 468962 b6f7de464d7f6fc953aaa4022c6c48cf879fcb10
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1449608
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1449608 - Avoid calling ComputeScrollMetadata repeatedly for same scroll frame and clip. r=mstange Currently in ContainerState::SetupScrollingMetadata we call ComputeScrollMetadata for every layer and for each ASR in the layer's clip chain. If there are many sibling layers with the same clip then this is largely wasted work. This change makes us cache the most recently calculated result, and only recalculate if the ASR or clip is different. There was a small portion of ComputeScrollMetadata that must actually be executed for every layer and ASR in its clip chain. This has been moved to a separate function, ClipLayerToDisplayPort, that is still called every time. MozReview-Commit-ID: 7Zzblmimtc5
gfx/layers/wr/ScrollingLayersHelper.cpp
gfx/layers/wr/WebRenderScrollData.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsIScrollableFrame.h
layout/painting/FrameLayerBuilder.cpp
--- a/gfx/layers/wr/ScrollingLayersHelper.cpp
+++ b/gfx/layers/wr/ScrollingLayersHelper.cpp
@@ -398,20 +398,18 @@ ScrollingLayersHelper::RecurseAndDefineA
     return ids;
   }
 
   // If not, recurse to ensure all the ancestors are defined
   auto ancestorIds = DefineClipChain(
       aItem, aAsr->mParent, aChain, aAppUnitsPerDevPixel, aSc);
   ids = ancestorIds;
 
-  // Ok to pass nullptr for aLayer here (first arg) because aClip (last arg) is
-  // also nullptr.
   Maybe<ScrollMetadata> metadata = aAsr->mScrollableFrame->ComputeScrollMetadata(
-      nullptr, mManager, aItem->ReferenceFrame(), ContainerLayerParameters(), nullptr);
+      mManager, aItem->ReferenceFrame(), ContainerLayerParameters(), nullptr);
   MOZ_ASSERT(metadata);
   FrameMetrics& metrics = metadata->GetMetrics();
 
   if (!metrics.IsScrollable()) {
     // This item in the chain is a no-op, skip over it
     return ids;
   }
 
--- a/gfx/layers/wr/WebRenderScrollData.cpp
+++ b/gfx/layers/wr/WebRenderScrollData.cpp
@@ -59,17 +59,17 @@ WebRenderLayerScrollData::Initialize(Web
        asr && asr != aStopAtAsr;
        asr = asr->mParent) {
     MOZ_ASSERT(aOwner.GetManager());
     FrameMetrics::ViewID scrollId = asr->GetViewId();
     if (Maybe<size_t> index = aOwner.HasMetadataFor(scrollId)) {
       mScrollIds.AppendElement(index.ref());
     } else {
       Maybe<ScrollMetadata> metadata = asr->mScrollableFrame->ComputeScrollMetadata(
-          nullptr, aOwner.GetManager(), aItem->ReferenceFrame(),
+          aOwner.GetManager(), aItem->ReferenceFrame(),
           ContainerLayerParameters(), nullptr);
       MOZ_ASSERT(metadata);
       MOZ_ASSERT(metadata->GetMetrics().GetScrollId() == scrollId);
       mScrollIds.AppendElement(aOwner.AddMetadata(metadata.ref()));
     }
   }
 }
 
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3893,69 +3893,83 @@ ScrollFrameHelper::DecideScrollableLayer
     mIsScrollableLayerInRootContainer = true;
   }
 
   return mWillBuildScrollableLayer;
 }
 
 
 Maybe<ScrollMetadata>
-ScrollFrameHelper::ComputeScrollMetadata(Layer* aLayer,
-                                         LayerManager* aLayerManager,
+ScrollFrameHelper::ComputeScrollMetadata(LayerManager* aLayerManager,
                                          const nsIFrame* aContainerReferenceFrame,
                                          const ContainerLayerParameters& aParameters,
                                          const DisplayItemClip* aClip) const
 {
   if (!mWillBuildScrollableLayer || mIsScrollableLayerInRootContainer) {
     return Nothing();
   }
 
+  if (!nsLayoutUtils::UsesAsyncScrolling(mOuter)) {
+    // Return early, since if we don't use APZ we don't need FrameMetrics.
+    return Nothing();
+  }
+
   nsPoint toReferenceFrame = mOuter->GetOffsetToCrossDoc(aContainerReferenceFrame);
 
   Maybe<nsRect> parentLayerClip;
   // For containerful frames, the clip is on the container layer.
   if (aClip &&
       (!gfxPrefs::LayoutUseContainersForRootFrames() || mAddClipRectToLayer)) {
     parentLayerClip = Some(aClip->GetClipRect());
   }
 
   bool isRootContent = mIsRoot && mOuter->PresContext()->IsRootContentDocument();
-  bool thisScrollFrameUsesAsyncScrolling = nsLayoutUtils::UsesAsyncScrolling(mOuter);
-  if (!thisScrollFrameUsesAsyncScrolling) {
+
+  MOZ_ASSERT(mScrolledFrame->GetContent());
+
+  nsRect scrollport = mScrollPort + toReferenceFrame;
+
+  return Some(nsLayoutUtils::ComputeScrollMetadata(
+    mScrolledFrame, mOuter, mOuter->GetContent(),
+    aContainerReferenceFrame, aLayerManager, mScrollParentID,
+    scrollport, parentLayerClip, isRootContent, aParameters));
+}
+
+void
+ScrollFrameHelper::ClipLayerToDisplayPort(Layer* aLayer,
+                                          const DisplayItemClip* aClip,
+                                          const ContainerLayerParameters& aParameters) const
+{
+  // If APZ is not enabled, we still need the displayport to be clipped
+  // in the compositor.
+  if (!nsLayoutUtils::UsesAsyncScrolling(mOuter)) {
+    Maybe<nsRect> parentLayerClip;
+    // For containerful frames, the clip is on the container layer.
+    if (aClip &&
+        (!gfxPrefs::LayoutUseContainersForRootFrames() || mAddClipRectToLayer)) {
+      parentLayerClip = Some(aClip->GetClipRect());
+    }
+
     if (parentLayerClip) {
-      // If APZ is not enabled, we still need the displayport to be clipped
-      // in the compositor.
       ParentLayerIntRect displayportClip =
         ViewAs<ParentLayerPixel>(
           parentLayerClip->ScaleToNearestPixels(
             aParameters.mXScale,
             aParameters.mYScale,
             mScrolledFrame->PresContext()->AppUnitsPerDevPixel()));
 
       ParentLayerIntRect layerClip;
       if (const ParentLayerIntRect* origClip = aLayer->GetClipRect().ptrOr(nullptr)) {
         layerClip = displayportClip.Intersect(*origClip);
       } else {
         layerClip = displayportClip;
       }
       aLayer->SetClipRect(Some(layerClip));
     }
-
-    // Return early, since if we don't use APZ we don't need FrameMetrics.
-    return Nothing();
-  }
-
-  MOZ_ASSERT(mScrolledFrame->GetContent());
-
-  nsRect scrollport = mScrollPort + toReferenceFrame;
-
-  return Some(nsLayoutUtils::ComputeScrollMetadata(
-    mScrolledFrame, mOuter, mOuter->GetContent(),
-    aContainerReferenceFrame, aLayerManager, mScrollParentID,
-    scrollport, parentLayerClip, isRootContent, aParameters));
+  }
 }
 
 bool
 ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const
 {
   // Use the right rect depending on if a display port is set.
   nsRect displayPort;
   bool usingDisplayport =
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -459,21 +459,23 @@ public:
   void ResetScrollInfoIfGeneration(uint32_t aGeneration) {
     if (aGeneration == mScrollGeneration) {
       mLastScrollOrigin = nullptr;
       mLastSmoothScrollOrigin = nullptr;
     }
   }
   bool WantAsyncScroll() const;
   Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
-    Layer* aLayer,
     LayerManager* aLayerManager,
     const nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const;
+  void ClipLayerToDisplayPort(Layer* aLayer,
+                              const mozilla::DisplayItemClip* aClip,
+                              const ContainerLayerParameters& aParameters) const;
 
   // nsIScrollbarMediator
   void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
                     nsIScrollbarMediator::ScrollSnapMode aSnap
                       = nsIScrollbarMediator::DISABLE_SNAP);
   void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
                      nsIScrollbarMediator::ScrollSnapMode aSnap
                        = nsIScrollbarMediator::DISABLE_SNAP);
@@ -609,17 +611,17 @@ public:
   bool mIsScrollParent:1;
 
   // Whether we are the root scroll frame that is used for containerful
   // scrolling with a display port. If true, the scrollable frame
   // shouldn't attach frame metrics to its layers because the container
   // will already have the necessary frame metrics.
   bool mIsScrollableLayerInRootContainer:1;
 
-  // If true, add clipping in ScrollFrameHelper::ComputeFrameMetrics.
+  // If true, add clipping in ScrollFrameHelper::ClipLayerToDisplayPort.
   bool mAddClipRectToLayer:1;
 
   // True if this frame has been scrolled at least once
   bool mHasBeenScrolled:1;
 
   // True if the events synthesized by OSX to produce momentum scrolling should
   // be ignored.  Reset when the next real, non-synthesized scroll event occurs.
   bool mIgnoreMomentumScroll:1;
@@ -965,26 +967,31 @@ public:
   }
   virtual void ResetScrollInfoIfGeneration(uint32_t aGeneration) override {
     mHelper.ResetScrollInfoIfGeneration(aGeneration);
   }
   virtual bool WantAsyncScroll() const override {
     return mHelper.WantAsyncScroll();
   }
   virtual mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
-    Layer* aLayer,
     LayerManager* aLayerManager,
     const nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const override
   {
-    return mHelper.ComputeScrollMetadata(aLayer, aLayerManager, aContainerReferenceFrame, aParameters, aClip);
+    return mHelper.ComputeScrollMetadata(aLayerManager, aContainerReferenceFrame, aParameters, aClip);
   }
-  virtual bool IsIgnoringViewportClipping() const override {
-    return mHelper.IsIgnoringViewportClipping();
+  virtual void ClipLayerToDisplayPort(Layer* aLayer,
+                                      const mozilla::DisplayItemClip* aClip,
+                                      const ContainerLayerParameters& aParameters) const override
+  {
+    mHelper.ClipLayerToDisplayPort(aLayer, aClip, aParameters);
+  }
+virtual bool IsIgnoringViewportClipping() const override {
+return mHelper.IsIgnoringViewportClipping();
   }
   virtual void MarkScrollbarsDirtyForReflow() const override {
     mHelper.MarkScrollbarsDirtyForReflow();
   }
   virtual bool UsesContainerScrolling() const override {
     return mHelper.UsesContainerScrolling();
   }
   virtual bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
@@ -1408,26 +1415,30 @@ public:
   }
   virtual void ResetScrollInfoIfGeneration(uint32_t aGeneration) override {
     mHelper.ResetScrollInfoIfGeneration(aGeneration);
   }
   virtual bool WantAsyncScroll() const override {
     return mHelper.WantAsyncScroll();
   }
   virtual mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
-    Layer* aLayer,
     LayerManager* aLayerManager,
     const nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const override
   {
-    return mHelper.ComputeScrollMetadata(aLayer, aLayerManager, aContainerReferenceFrame, aParameters, aClip);
+    return mHelper.ComputeScrollMetadata(aLayerManager, aContainerReferenceFrame, aParameters, aClip);
   }
-  virtual bool IsIgnoringViewportClipping() const override {
-    return mHelper.IsIgnoringViewportClipping();
+  virtual void ClipLayerToDisplayPort(Layer* aLayer,
+                                      const mozilla::DisplayItemClip* aClip,
+                                      const ContainerLayerParameters& aParameters) const override {
+    mHelper.ClipLayerToDisplayPort(aLayer, aClip, aParameters);
+  }
+virtual bool IsIgnoringViewportClipping() const override {
+return mHelper.IsIgnoringViewportClipping();
   }
   virtual void MarkScrollbarsDirtyForReflow() const override {
     mHelper.MarkScrollbarsDirtyForReflow();
   }
 
   // nsIStatefulFrame
   mozilla::UniquePtr<mozilla::PresState> SaveState() override {
     return mHelper.SaveState();
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -406,26 +406,29 @@ public:
    */
   virtual void ResetScrollInfoIfGeneration(uint32_t aGeneration) = 0;
   /**
    * Determine whether it is desirable to be able to asynchronously scroll this
    * scroll frame.
    */
   virtual bool WantAsyncScroll() const = 0;
   /**
-   * aLayer's animated geometry root is this frame. Returns the ScrollMetadata
-   * contributed by this frame, if there is one.
-   * If aClip is non-null, then aLayer must also be non-null.
+   * Returns the ScrollMetadata contributed by this frame, if there is one.
    */
   virtual mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
-    mozilla::layers::Layer* aLayer,
     mozilla::layers::LayerManager* aLayerManager,
     const nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const = 0;
+  /**
+   * Ensure's aLayer is clipped to the display port.
+   */
+  virtual void ClipLayerToDisplayPort(mozilla::layers::Layer* aLayer,
+                                      const mozilla::DisplayItemClip* aClip,
+                                      const ContainerLayerParameters& aParameters) const;
 
   /**
    * If this scroll frame is ignoring viewporting clipping
    */
   virtual bool IsIgnoringViewportClipping() const = 0;
 
   /**
    * Mark the scrollbar frames for reflow.
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -1569,16 +1569,29 @@ protected:
   };
 
   nsDataHashtable<nsGenericHashKey<MaskLayerKey>, RefPtr<ImageLayer>>
     mRecycledMaskImageLayers;
   // Keep display port of AGR to avoid wasting time on doing the same
   // thing repeatly.
   AnimatedGeometryRoot* mLastDisplayPortAGR;
   nsRect mLastDisplayPortRect;
+
+  // Cache ScrollMetadata so it doesn't need recomputed if the ASR and clip are unchanged.
+  // If mASR == nullptr then mMetadata is not valid.
+  struct CachedScrollMetadata {
+    const ActiveScrolledRoot* mASR;
+    const DisplayItemClip* mClip;
+    Maybe<ScrollMetadata> mMetadata;
+
+    CachedScrollMetadata()
+      : mASR(nullptr), mClip(nullptr)
+    {}
+  };
+  CachedScrollMetadata mCachedScrollMetadata;
 };
 
 bool
 FLBDisplayItemIterator::ShouldFlattenNextItem() const
 {
   if (!mNext) {
     return false;
   }
@@ -5444,19 +5457,30 @@ ContainerState::SetupScrollingMetadata(N
     if (clipChain && clipChain->mASR == asr) {
       clipChain = clipChain->mParent;
     }
 
     nsIScrollableFrame* scrollFrame = asr->mScrollableFrame;
     const DisplayItemClip* clip =
       (clipChain && clipChain->mASR == asr->mParent) ? &clipChain->mClip : nullptr;
 
-    Maybe<ScrollMetadata> metadata =
-      scrollFrame->ComputeScrollMetadata(aEntry->mLayer, aEntry->mLayer->Manager(),
+    scrollFrame->ClipLayerToDisplayPort(aEntry->mLayer, clip, mParameters);
+
+    Maybe<ScrollMetadata> metadata;
+    if (mCachedScrollMetadata.mASR == asr &&
+        mCachedScrollMetadata.mClip == clip) {
+      metadata = mCachedScrollMetadata.mMetadata;
+    } else {
+      metadata = scrollFrame->ComputeScrollMetadata(aEntry->mLayer->Manager(),
             mContainerReferenceFrame, mParameters, clip);
+      mCachedScrollMetadata.mASR = asr;
+      mCachedScrollMetadata.mClip = clip;
+      mCachedScrollMetadata.mMetadata = metadata;
+    }
+
     if (!metadata) {
       continue;
     }
 
     if (clip &&
         clip->HasClip() &&
         clip->GetRoundedRectCount() > 0)
     {