Bug 1329968 - Allow multiple async-scrollable frames to be given displayports. draft
authorJamie Nicol <jnicol@mozilla.com>
Tue, 10 Jan 2017 13:40:01 +0000
changeset 479850 1243724a93f1c8b7f80eca4ddbe80cf2af9b5a05
parent 479651 af8a2573d0f1e9cc6f2ba0ab67d7a702a197f177
child 544809 b0396b793819d29cbf0cca0faddf7ec3ec9ebdab
push id44387
push userbmo:jnicol@mozilla.com
push dateTue, 07 Feb 2017 12:39:28 +0000
bugs1329968
milestone54.0a1
Bug 1329968 - Allow multiple async-scrollable frames to be given displayports. To avoid excessive memory usage and over-painting, limit by the scroll frames' area, so that the total area of the scroll frames which are given displayports does not exceed the PresContext's area. Implement in a similar way to the will change budget (for layerising items with properties marked as will-change), and the AGR budget (for layerising items with animated properties) - first come first served until the budget is exceeded. MozReview-Commit-ID: FLIhBVmLttt
gfx/layers/apz/test/mochitest/test_layerization.html
layout/base/nsLayoutUtils.cpp
layout/painting/nsDisplayList.cpp
layout/painting/nsDisplayList.h
--- a/gfx/layers/apz/test/mochitest/test_layerization.html
+++ b/gfx/layers/apz/test/mochitest/test_layerization.html
@@ -11,17 +11,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <link rel="stylesheet" type="text/css" href="helper_subframe_style.css"/>
   <style>
   #container {
     display: flex;
     overflow: scroll;
-    height: 500px;
+    height: 100vh;
   }
   .outer-frame {
     height: 500px;
     overflow: scroll;
     flex-basis: 100%;
     background: repeating-linear-gradient(#CCC, #CCC 100px, #BBB 100px, #BBB 200px);
   }
   #container-content {
@@ -40,19 +40,20 @@ https://bugzilla.mozilla.org/show_bug.cg
   </div>
   <div id="outer2" class="outer-frame">
     <div id="inner2" class="inner-frame">
       <div class="inner-content"></div>
     </div>
   </div>
   <iframe id="outer3" class="outer-frame" src="helper_iframe1.html"></iframe>
   <iframe id="outer4" class="outer-frame" src="helper_iframe2.html"></iframe>
-<!-- The container-content div ensures 'container' is scrollable, so the
-     optimization that layerizes the primary async-scrollable frame on page
-     load layerizes it rather than its child subframes. -->
+<!-- The container-content div ensures 'container' is scrollable and will
+     therefore be initially layerized. This uses up the budget of
+     async-scrollable frames that are initially layerized, meaning that its
+     child subframes are not. -->
   <div id="container-content"></div>
 </div>
 <pre id="test">
 <script type="application/javascript;version=1.7">
 
 // Scroll the mouse wheel over |element|.
 function scrollWheelOver(element, waitForScroll, testDriver) {
   moveMouseAndScrollWheelOver(element, 10, 10, testDriver, waitForScroll);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3314,37 +3314,32 @@ nsLayoutUtils::MaybeCreateDisplayPort(ns
                                       nsIFrame* aScrollFrame) {
   nsIContent* content = aScrollFrame->GetContent();
   nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollFrame);
   if (!content || !scrollableFrame) {
     return;
   }
 
   bool haveDisplayPort = HasDisplayPort(content);
-
-  // We perform an optimization where we ensure that at least one
-  // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a displayport.
-  // If that's not the case yet, and we are async-scrollable, we will get a
-  // displayport.
   if (aBuilder.IsPaintingToWindow() &&
       nsLayoutUtils::AsyncPanZoomEnabled(aScrollFrame) &&
-      !aBuilder.HaveScrollableDisplayPort() &&
       scrollableFrame->WantAsyncScroll()) {
 
-    // If we don't already have a displayport, calculate and set one.
-    if (!haveDisplayPort) {
-      CalculateAndSetDisplayPortMargins(scrollableFrame, nsLayoutUtils::RepaintMode::DoNotRepaint);
+    // Only create the display port if we fall within the budget.
+    bool inBudget = aBuilder.AddToDisplayPortBudget(aScrollFrame);
+    if (inBudget) {
+      // If we don't already have a displayport, calculate and set one.
+      if (!haveDisplayPort) {
+        CalculateAndSetDisplayPortMargins(scrollableFrame, nsLayoutUtils::RepaintMode::DoNotRepaint);
 #ifdef DEBUG
-      haveDisplayPort = HasDisplayPort(content);
-      MOZ_ASSERT(haveDisplayPort, "should have a displayport after having just set it");
+        haveDisplayPort = HasDisplayPort(content);
+        MOZ_ASSERT(haveDisplayPort, "should have a displayport after having just set it");
 #endif
-    }
-
-    // Record that the we now have a scrollable display port.
-    aBuilder.SetHaveScrollableDisplayPort();
+      }
+    }
   }
 }
 
 nsIScrollableFrame*
 nsLayoutUtils::GetAsyncScrollableAncestorFrame(nsIFrame* aTarget)
 {
   uint32_t flags = nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT
                  | nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -856,16 +856,17 @@ nsDisplayListBuilder::nsDisplayListBuild
       mCurrentTableItem(nullptr),
       mCurrentActiveScrolledRoot(nullptr),
       mCurrentContainerASR(nullptr),
       mCurrentFrame(aReferenceFrame),
       mCurrentReferenceFrame(aReferenceFrame),
       mCurrentAGR(&mRootAGR),
       mRootAGR(aReferenceFrame, nullptr),
       mUsedAGRBudget(0),
+      mUsedDisplayPortBudget(0),
       mDirtyRect(-1,-1,-1,-1),
       mGlassDisplayItem(nullptr),
       mScrollInfoItemsForHoisting(nullptr),
       mActiveScrolledRootForRootScrollframe(nullptr),
       mMode(aMode),
       mCurrentScrollParentId(FrameMetrics::NULL_SCROLL_ID),
       mCurrentScrollbarTarget(FrameMetrics::NULL_SCROLL_ID),
       mCurrentScrollbarFlags(0),
@@ -885,17 +886,16 @@ nsDisplayListBuilder::nsDisplayListBuild
       mWillComputePluginGeometry(false),
       mInTransform(false),
       mIsInChromePresContext(false),
       mSyncDecodeImages(false),
       mIsPaintingToWindow(false),
       mIsCompositingCheap(false),
       mContainsPluginItem(false),
       mAncestorHasApzAwareEventHandler(false),
-      mHaveScrollableDisplayPort(false),
       mWindowDraggingAllowed(false),
       mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)),
       mForceLayerForScrollParent(false),
       mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)),
       mBuildingInvisibleItems(false),
       mHitTestShouldStopAtFirstOpaque(false)
 {
   MOZ_COUNT_CTOR(nsDisplayListBuilder);
@@ -1714,16 +1714,42 @@ nsDisplayListBuilder::IsInWillChangeBudg
     const char16_t* params[] = { multiplierStr.get(), limitStr.get() };
     aFrame->PresContext()->Document()->WarnOnceAbout(
       nsIDocument::eIgnoringWillChangeOverBudget, false,
       params, ArrayLength(params));
   }
   return onBudget;
 }
 
+
+const float gDisplayPortBudgetAreaMultiplier = 1.0;
+
+bool
+nsDisplayListBuilder::AddToDisplayPortBudget(nsIFrame* aFrame)
+{
+  if (mDisplayPortBudgetSet.Contains(aFrame)) {
+    return true;
+  }
+
+  const nsRect area = aFrame->PresContext()->GetVisibleArea();
+  const uint32_t budgetLimit = gDisplayPortBudgetAreaMultiplier *
+    nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+    nsPresContext::AppUnitsToIntCSSPixels(area.height);
+
+  const uint32_t cost = GetLayerizationCost(aFrame->GetSize());
+  const bool onBudget = mUsedDisplayPortBudget + cost <= budgetLimit;
+
+  if (onBudget) {
+    mUsedDisplayPortBudget += cost;
+    mDisplayPortBudgetSet.PutEntry(aFrame);
+  }
+
+  return onBudget;
+}
+
 #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
@@ -539,19 +539,16 @@ public:
   }
 
   bool GetAncestorHasApzAwareEventHandler() { return mAncestorHasApzAwareEventHandler; }
   void SetAncestorHasApzAwareEventHandler(bool aValue)
   {
     mAncestorHasApzAwareEventHandler = aValue;
   }
 
-  bool HaveScrollableDisplayPort() const { return mHaveScrollableDisplayPort; }
-  void SetHaveScrollableDisplayPort() { mHaveScrollableDisplayPort = true; }
-
   bool SetIsCompositingCheap(bool aCompositingCheap) { 
     bool temp = mIsCompositingCheap; 
     mIsCompositingCheap = aCompositingCheap;
     return temp;
   }
   bool IsCompositingCheap() const { return mIsCompositingCheap; }
   /**
    * Display the caret if needed.
@@ -1317,16 +1314,24 @@ 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);
 
+  /**
+   * Add the current frame to the display port budget if possible and remember
+   * the outcome. This is used to limit the area of async-scrollable frames
+   * for which we create a display port. Subsequent calls will return the same
+   * value as returned here.
+   */
+  bool AddToDisplayPortBudget(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.
    */
@@ -1479,16 +1484,21 @@ private:
   // and thus is in-budget.
   nsTHashtable<nsPtrHashKey<nsIFrame> > mWillChangeBudgetSet;
 
   // Area of animated geometry root budget already allocated
   uint32_t mUsedAGRBudget;
   // Set of frames already counted in budget
   nsTHashtable<nsPtrHashKey<nsIFrame> > mAGRBudgetSet;
 
+  // Area of display port budget already allocated
+  uint32_t mUsedDisplayPortBudget;
+  // Set of frames already counted in display port budget
+  nsTHashtable<nsPtrHashKey<nsIFrame> > mDisplayPortBudgetSet;
+
   // Relative to mCurrentFrame.
   nsRect                         mDirtyRect;
   nsRegion                       mWindowExcludeGlassRegion;
   nsRegion                       mWindowOpaqueRegion;
   LayoutDeviceIntRegion          mWindowDraggingRegion;
   LayoutDeviceIntRegion          mWindowNoDraggingRegion;
   // The display item for the Windows window glass background, if any
   nsDisplayItem*                 mGlassDisplayItem;
@@ -1524,20 +1534,16 @@ private:
   // under an nsDisplayTransform
   bool                           mInTransform;
   bool                           mIsInChromePresContext;
   bool                           mSyncDecodeImages;
   bool                           mIsPaintingToWindow;
   bool                           mIsCompositingCheap;
   bool                           mContainsPluginItem;
   bool                           mAncestorHasApzAwareEventHandler;
-  // True when the first async-scrollable scroll frame for which we build a
-  // display list has a display port. An async-scrollable scroll frame is one
-  // which WantsAsyncScroll().
-  bool                           mHaveScrollableDisplayPort;
   bool                           mWindowDraggingAllowed;
   bool                           mIsBuildingForPopup;
   bool                           mForceLayerForScrollParent;
   bool                           mAsyncPanZoomEnabled;
   bool                           mBuildingInvisibleItems;
   bool                           mHitTestShouldStopAtFirstOpaque;
 };