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