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
--- 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;
};