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 480617 9c988c2e4459c13381595c4fcc0875861a362660
parent 479651 af8a2573d0f1e9cc6f2ba0ab67d7a702a197f177
child 545007 b8c74e5eb693bd0c11ac6b5700e041e853dbac5f
push id44599
push userbmo:jnicol@mozilla.com
push dateWed, 08 Feb 2017 16:36:57 +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. Use 64-bit calculations for this budget and existing ones to avoid overflow for large frames. MozReview-Commit-ID: FLIhBVmLttt
gfx/layers/apz/test/mochitest/test_layerization.html
gfx/thebes/gfxPrefs.h
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
@@ -10,28 +10,23 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <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;
   }
   .outer-frame {
     height: 500px;
     overflow: scroll;
     flex-basis: 100%;
     background: repeating-linear-gradient(#CCC, #CCC 100px, #BBB 100px, #BBB 200px);
   }
-  #container-content {
-    height: 200%;
-  }
   </style>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173580">APZ layerization tests</a>
 <p id="display"></p>
 <div id="container">
   <div id="outer1" class="outer-frame">
     <div id="inner1" class="inner-frame">
@@ -40,20 +35,16 @@ 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. -->
-  <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);
 }
@@ -195,19 +186,21 @@ if (isApzEnabled()) {
   SimpleTest.requestFlakyTimeout("we are testing code that measures an actual timeout");
   SimpleTest.expectAssertions(0, 8); // we get a bunch of "ASSERTION: Bounds computation mismatch" sometimes (bug 1232856)
 
   // Disable smooth scrolling, because it results in long-running scroll
   // animations that can result in a 'scroll' event triggered by an earlier
   // wheel event as corresponding to a later wheel event.
   // Also enable APZ test logging, since we use that data to determine whether
   // a scroll frame was layerized.
+  // Set the display port budget to zero so that nothing is initially layerized.
   pushPrefs([["general.smoothScroll", false],
              ["apz.displayport_expiry_ms", 0],
-             ["apz.test.logging_enabled", true]])
+             ["apz.test.logging_enabled", true],
+             ["layout.display-port-budget-multiplier", "0.0"]])
   .then(waitUntilApzStable)
   .then(runContinuation(test))
   .then(SimpleTest.finish);
 }
 
 </script>
 </pre>
 </body>
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -541,16 +541,17 @@ private:
   DECL_GFX_PREF(Live, "layout.css.scroll-behavior.enabled",    ScrollBehaviorEnabled, bool, true);
   DECL_GFX_PREF(Live, "layout.css.scroll-behavior.spring-constant", ScrollBehaviorSpringConstant, float, 250.0f);
   DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-max-velocity", ScrollSnapPredictionMaxVelocity, int32_t, 2000);
   DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-sensitivity", ScrollSnapPredictionSensitivity, float, 0.750f);
   DECL_GFX_PREF(Live, "layout.css.scroll-snap.proximity-threshold", ScrollSnapProximityThreshold, int32_t, 200);
   DECL_GFX_PREF(Live, "layout.css.touch_action.enabled",       TouchActionEnabled, bool, false);
   DECL_GFX_PREF(Live, "layout.display-list.dump",              LayoutDumpDisplayList, bool, false);
   DECL_GFX_PREF(Live, "layout.display-list.dump-content",      LayoutDumpDisplayListContent, bool, false);
+  DECL_GFX_PREF(Live, "layout.display-port-budget-multiplier", LayoutDisplayPortBudgetMultiplier, float, 1.0f);
   DECL_GFX_PREF(Live, "layout.event-regions.enabled",          LayoutEventRegionsEnabledDoNotUseDirectly, bool, false);
   DECL_GFX_PREF(Once, "layout.frame_rate",                     LayoutFrameRate, int32_t, -1);
   DECL_GFX_PREF(Once, "layout.paint_rects_separately",         LayoutPaintRectsSeparately, bool, true);
 
   // This and code dependent on it should be removed once containerless scrolling looks stable.
   DECL_GFX_PREF(Once, "layout.scroll.root-frame-containers",   LayoutUseContainersForRootFrames, bool, true);
   DECL_GFX_PREF(Live, "layout.smaller-painted-layers",         LayoutSmallerPaintedLayers, bool, false);
 
--- 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);
@@ -1642,23 +1642,23 @@ LayoutDeviceIntRegion
 nsDisplayListBuilder::GetWindowDraggingRegion() const
 {
   LayoutDeviceIntRegion result;
   result.Sub(mWindowDraggingRegion, mWindowNoDraggingRegion);;
   return result;
 }
 
 const uint32_t gWillChangeAreaMultiplier = 3;
-static uint32_t GetLayerizationCost(const nsSize& aSize) {
+static uint64_t GetLayerizationCost(const nsSize& aSize) {
   // There's significant overhead for each layer created from Gecko
   // (IPC+Shared Objects) and from the backend (like an OpenGL texture).
   // Therefore we set a minimum cost threshold of a 64x64 area.
   int minBudgetCost = 64 * 64;
 
-  uint32_t budgetCost =
+  uint64_t budgetCost =
     std::max(minBudgetCost,
       nsPresContext::AppUnitsToIntCSSPixels(aSize.width) *
       nsPresContext::AppUnitsToIntCSSPixels(aSize.height));
 
   return budgetCost;
 }
 
 bool
@@ -1672,20 +1672,20 @@ nsDisplayListBuilder::AddToWillChangeBud
   if (!mWillChangeBudget.Contains(key)) {
     mWillChangeBudget.Put(key, DocumentWillChangeBudget());
   }
 
   DocumentWillChangeBudget budget;
   mWillChangeBudget.Get(key, &budget);
 
   nsRect area = aFrame->PresContext()->GetVisibleArea();
-  uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+  uint64_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
     nsPresContext::AppUnitsToIntCSSPixels(area.height);
 
-  uint32_t cost = GetLayerizationCost(aSize);
+  uint64_t cost = GetLayerizationCost(aSize);
   bool onBudget = (budget.mBudget + cost) /
                     gWillChangeAreaMultiplier < budgetLimit;
 
   if (onBudget) {
     budget.mBudget += cost;
     mWillChangeBudget.Put(key, budget);
     mWillChangeBudgetSet.PutEntry(aFrame);
   }
@@ -1702,28 +1702,50 @@ nsDisplayListBuilder::IsInWillChangeBudg
     nsString usageStr;
     usageStr.AppendInt(GetLayerizationCost(aSize));
 
     nsString multiplierStr;
     multiplierStr.AppendInt(gWillChangeAreaMultiplier);
 
     nsString limitStr;
     nsRect area = aFrame->PresContext()->GetVisibleArea();
-    uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+    uint64_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
       nsPresContext::AppUnitsToIntCSSPixels(area.height);
     limitStr.AppendInt(budgetLimit);
 
     const char16_t* params[] = { multiplierStr.get(), limitStr.get() };
     aFrame->PresContext()->Document()->WarnOnceAbout(
       nsIDocument::eIgnoringWillChangeOverBudget, false,
       params, ArrayLength(params));
   }
   return onBudget;
 }
 
+bool
+nsDisplayListBuilder::AddToDisplayPortBudget(nsIFrame* aFrame)
+{
+  const nsPresContext* presContext = aFrame->PresContext()->GetRootPresContext();
+  if (!presContext) {
+    return false;
+  }
+  const nsRect area = presContext->GetVisibleArea();
+  const uint64_t budgetLimit = gfxPrefs::LayoutDisplayPortBudgetMultiplier() *
+    nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+    nsPresContext::AppUnitsToIntCSSPixels(area.height);
+
+  const uint64_t cost = GetLayerizationCost(aFrame->GetSize());
+  const bool onBudget = (mUsedDisplayPortBudget + cost) <= budgetLimit;
+
+  if (onBudget) {
+    mUsedDisplayPortBudget += cost;
+  }
+
+  return onBudget;
+}
+
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
 const float gAGRBudgetAreaMultiplier = 0.3;
 #else
 const float gAGRBudgetAreaMultiplier = 3.0;
 #endif
 
 bool
 nsDisplayListBuilder::AddToAGRBudget(nsIFrame* aFrame)
@@ -1733,21 +1755,21 @@ nsDisplayListBuilder::AddToAGRBudget(nsI
   }
 
   const nsPresContext* presContext = aFrame->PresContext()->GetRootPresContext();
   if (!presContext) {
     return false;
   }
 
   const nsRect area = presContext->GetVisibleArea();
-  const uint32_t budgetLimit = gAGRBudgetAreaMultiplier *
+  const uint64_t budgetLimit = gAGRBudgetAreaMultiplier *
     nsPresContext::AppUnitsToIntCSSPixels(area.width) *
     nsPresContext::AppUnitsToIntCSSPixels(area.height);
 
-  const uint32_t cost = GetLayerizationCost(aFrame->GetSize());
+  const uint64_t cost = GetLayerizationCost(aFrame->GetSize());
   const bool onBudget = mUsedAGRBudget + cost < budgetLimit;
 
   if (onBudget) {
     mUsedAGRBudget += cost;
     mAGRBudgetSet.PutEntry(aFrame);
   }
 
   return onBudget;
--- 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,23 @@ 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.
+   * This is used to limit the area of async-scrollable frames for
+   * which we create a display port.
+   */
+  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 +1483,19 @@ 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;
+
   // 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 +1531,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;
 };