Bug 1404181 - Part 17: Track will-change contributions per-frame so that can remove contributions from invalidated frames. r?mstange draft
authorMatt Woodrow <mwoodrow@mozilla.com>, Miko Mynttinen <mikokm@gmail.com>, Timothy Nikkel <tnikkel@gmail.com>
Thu, 28 Sep 2017 13:53:25 +1300
changeset 684528 9975d8374e55af116261d3e6a84db2281f65555b
parent 684527 7eb23adee994f854c354f37f51ff26aa914fb70b
child 684529 c36fae7110d8d66ec584d4cc60d4a339b46702d5
push id85633
push usermwoodrow@mozilla.com
push dateSun, 22 Oct 2017 23:03:02 +0000
reviewersmstange
bugs1404181
milestone58.0a1
Bug 1404181 - Part 17: Track will-change contributions per-frame so that can remove contributions from invalidated frames. r?mstange MozReview-Commit-ID: 54LwXKkjfFX
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/painting/nsDisplayList.cpp
layout/painting/nsDisplayList.h
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -3105,16 +3105,18 @@ nsIFrame::BuildDisplayListForChild(nsDis
       return;
     }
   }
 
   nsIFrame* child = aChild;
   if (child->GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE)
     return;
 
+  aBuilder->ClearWillChangeBudget(child);
+
   const bool doingShortcut =
     (child->GetStateBits() & NS_FRAME_SIMPLE_DISPLAYLIST) &&
     aBuilder->IsPaintingToWindow() &&
     // This would be changed by the change of preference.
     aBuilder->IsBuildingLayerEventRegions() &&
     // Animations may change the value of |HasOpacity()|.
     !(child->GetContent() &&
       child->GetContent()->MayHaveAnimations());
@@ -3182,16 +3184,17 @@ nsIFrame::BuildDisplayListForChild(nsDis
   nsRect dirty = aBuilder->GetDirtyRect() - offset;
 
   nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData = nullptr;
   bool isPlaceholder = false;
   if (child->IsPlaceholderFrame()) {
     isPlaceholder = true;
     nsPlaceholderFrame* placeholder = static_cast<nsPlaceholderFrame*>(child);
     child = placeholder->GetOutOfFlowFrame();
+    aBuilder->ClearWillChangeBudget(child);
     NS_ASSERTION(child, "No out of flow frame?");
     // If 'child' is a pushed float then it's owned by a block that's not an
     // ancestor of the placeholder, and it will be painted by that block and
     // should not be painted through the placeholder.
     if (!child || nsLayoutUtils::IsPopup(child) ||
         (child->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT))
       return;
     MOZ_ASSERT(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW);
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -622,16 +622,17 @@ public:
     , mHasImageRequest(false)
     , mHasFirstLetterChild(false)
     , mParentIsWrapperAnonBox(false)
     , mIsWrapperBoxNeedingRestyle(false)
     , mReflowRequestedForCharDataChange(false)
     , mForceDescendIntoIfVisible(false)
     , mBuiltDisplayList(false)
     , mFrameIsModified(false)
+    , mMayHaveWillChangeBudget(false)
     , mIsPrimaryFrame(false)
   {
     mozilla::PodZero(&mOverflow);
   }
 
   nsPresContext* PresContext() const {
     return StyleContext()->PresContext();
   }
@@ -4101,16 +4102,19 @@ public:
   void SetForceDescendIntoIfVisible(bool aForce) { mForceDescendIntoIfVisible = aForce; }
 
   bool BuiltDisplayList() { return mBuiltDisplayList; }
   void SetBuiltDisplayList(bool aBuilt) { mBuiltDisplayList = aBuilt; }
 
   bool IsFrameModified() { return mFrameIsModified; }
   void SetFrameIsModified(bool aFrameIsModified) { mFrameIsModified = aFrameIsModified; }
 
+  bool MayHaveWillChangeBudget() { return mMayHaveWillChangeBudget; }
+  void SetMayHaveWillChangeBudget(bool aHasBudget) { mMayHaveWillChangeBudget = aHasBudget; }
+
 protected:
 
   /**
    * Reparent this frame's view if it has one.
    */
   void ReparentFrameViewTo(nsViewManager* aViewManager,
                            nsView*        aNewParentView,
                            nsView*        aOldParentView);
@@ -4279,25 +4283,31 @@ protected:
    * the last call to CheckAndClearDisplayListState, false
    * otherwise. Used for the reftest harness to verify minimal
    * display list building.
    */
   bool mBuiltDisplayList : 1;
 
   bool mFrameIsModified : 1;
 
+  /**
+   * True if frame has will-change, and currently has display
+   * items consuming some of the will-change budget.
+   */
+  bool mMayHaveWillChangeBudget : 1;
+
 private:
   /**
    * True if this is the primary frame for mContent.
    */
   bool mIsPrimaryFrame : 1;
 
 protected:
 
-  // There is a 7-bit gap left here.
+  // There is a 5-bit gap left here.
 
   // Helpers
   /**
    * Can we stop inside this frame when we're skipping non-rendered whitespace?
    * @param  aForward [in] Are we moving forward (or backward) in content order.
    * @param  aOffset [in/out] At what offset into the frame to start looking.
    *         on output - what offset was reached (whether or not we found a place to stop).
    * @return STOP: An appropriate offset was found within this frame,
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -1917,17 +1917,17 @@ static uint32_t GetLayerizationCost(cons
       nsPresContext::AppUnitsToIntCSSPixels(aSize.height));
 
   return budgetCost;
 }
 
 bool
 nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame,
                                             const nsSize& aSize) {
-  if (mWillChangeBudgetSet.Contains(aFrame)) {
+  if (mWillChangeBudgetSet.Get(aFrame, nullptr)) {
     return true; // Already accounted
   }
 
   nsPresContext* key = aFrame->PresContext();
   DocumentWillChangeBudget budget;
   auto willChangeBudgetEntry = mWillChangeBudget.LookupForAdd(key);
   if (willChangeBudgetEntry) {
     // We have an existing entry.
@@ -1943,17 +1943,18 @@ nsDisplayListBuilder::AddToWillChangeBud
 
   uint32_t cost = GetLayerizationCost(aSize);
   bool onBudget = (budget.mBudget + cost) /
                     gWillChangeAreaMultiplier < budgetLimit;
 
   if (onBudget) {
     budget.mBudget += cost;
     willChangeBudgetEntry.Data() = budget;
-    mWillChangeBudgetSet.PutEntry(aFrame);
+    mWillChangeBudgetSet.Put(aFrame, cost);
+    aFrame->SetMayHaveWillChangeBudget(true);
   }
 
   return onBudget;
 }
 
 bool
 nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame,
                                            const nsSize& aSize) {
@@ -1975,16 +1976,36 @@ nsDisplayListBuilder::IsInWillChangeBudg
     const char16_t* params[] = { multiplierStr.get(), limitStr.get() };
     aFrame->PresContext()->Document()->WarnOnceAbout(
       nsIDocument::eIgnoringWillChangeOverBudget, false,
       params, ArrayLength(params));
   }
   return onBudget;
 }
 
+void
+nsDisplayListBuilder::ClearWillChangeBudget(nsIFrame* aFrame)
+{
+  if (!aFrame->MayHaveWillChangeBudget()) {
+    return;
+  }
+  aFrame->SetMayHaveWillChangeBudget(false);
+
+  uint32_t cost = 0;
+  if (!mWillChangeBudgetSet.Get(aFrame, &cost)) {
+    return;
+  }
+  mWillChangeBudgetSet.Remove(aFrame);
+
+  DocumentWillChangeBudget& budget =
+    mWillChangeBudget.GetOrInsert(aFrame->PresContext());
+  MOZ_ASSERT(budget.mBudget >= cost);
+  budget.mBudget -= cost;
+}
+
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
 const float gAGRBudgetAreaMultiplier = 0.3;
 #else
 const float gAGRBudgetAreaMultiplier = 3.0;
 #endif
 
 bool
 nsDisplayListBuilder::AddToAGRBudget(nsIFrame* aFrame)
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -1524,16 +1524,18 @@ public:
   /**
    * This will add the current frame to the will-change budget the first
    * time it is seen. On subsequent calls this will return the same
    * answer. This effectively implements a first-come, first-served
    * allocation of the will-change budget.
    */
   bool IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize);
 
+  void ClearWillChangeBudget(nsIFrame* aFrame);
+
   void EnterSVGEffectsContents(nsDisplayList* aHoistedItemsStorage);
   void ExitSVGEffectsContents();
 
   /**
    * Note: if changing the conditions under which scroll info layers
    * are created, make a corresponding change to
    * ScrollFrameWillBuildScrollInfoLayer() in nsSliderFrame.cpp.
    */
@@ -1666,16 +1668,26 @@ private:
   struct DocumentWillChangeBudget {
     DocumentWillChangeBudget()
       : mBudget(0)
     {}
 
     uint32_t mBudget;
   };
 
+  struct FrameWillChangeBudget {
+    FrameWillChangeBudget(nsIFrame* aFrame, uint32_t aUsage)
+      : mFrame(aFrame)
+      , mUsage(aUsage)
+    {}
+
+    nsIFrame* mFrame;
+    uint32_t mUsage;
+  };
+
   nsIFrame* const                mReferenceFrame;
   nsIFrame*                      mIgnoreScrollFrame;
   nsDisplayLayerEventRegions*    mLayerEventRegions;
 
   static const size_t kArenaAlignment =
       mozilla::tl::Max<NS_ALIGNMENT_OF(void*), NS_ALIGNMENT_OF(double)>::value;
   mozilla::ArenaAllocator<4096, kArenaAlignment> mPool;
 
@@ -1699,17 +1711,17 @@ private:
   RefPtr<AnimatedGeometryRoot>   mCurrentAGR;
 
   // will-change budget tracker
   nsDataHashtable<nsPtrHashKey<nsPresContext>, DocumentWillChangeBudget>
                                  mWillChangeBudget;
 
   // Any frame listed in this set is already counted in the budget
   // and thus is in-budget.
-  nsTHashtable<nsPtrHashKey<nsIFrame> > mWillChangeBudgetSet;
+  nsDataHashtable<nsPtrHashKey<nsIFrame>, uint32_t> mWillChangeBudgetSet;
 
   // Area of animated geometry root budget already allocated
   uint32_t mUsedAGRBudget;
   // Set of frames already counted in budget
   nsTHashtable<nsPtrHashKey<nsIFrame> > mAGRBudgetSet;
 
   nsTArray<nsIFrame*>           mModifiedFramesDuringBuilding;