Bug 1247554 - Budget creation of AGRs by frame area; r=mattwoodrow
In nsDisplayListBuilder keep track of the total area of frames which
have been made animated geometry roots due to having animated offset or
margin properties. This is in order to reduce the total area of created
layers. Once the area has reached a certain limit do not allow any more
frames to be AGRs for this reason.
Currently this only applies to AGRs created due to frames having
animated offset or margin properties. This is because we believe this is
a major source of over-layerisation and too high memory usage. This
could easily be extended to include the creation of AGRs due to other
reasons too.
Note that this technically limits the area of AGR frames, not the area
of layers, though it will have a fairly direct impact.
MozReview-Commit-ID: BvgRrC8KMIp
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -600,16 +600,17 @@ nsDisplayListBuilder::nsDisplayListBuild
: mReferenceFrame(aReferenceFrame),
mIgnoreScrollFrame(nullptr),
mLayerEventRegions(nullptr),
mCurrentTableItem(nullptr),
mCurrentFrame(aReferenceFrame),
mCurrentReferenceFrame(aReferenceFrame),
mCurrentAGR(&mRootAGR),
mRootAGR(aReferenceFrame, nullptr),
+ mUsedAGRBudget(0),
mDirtyRect(-1,-1,-1,-1),
mGlassDisplayItem(nullptr),
mPendingScrollInfoItems(nullptr),
mCommittedScrollInfoItems(nullptr),
mMode(aMode),
mCurrentScrollParentId(FrameMetrics::NULL_SCROLL_ID),
mCurrentScrollbarTarget(FrameMetrics::NULL_SCROLL_ID),
mCurrentScrollbarFlags(0),
@@ -1082,18 +1083,22 @@ nsDisplayListBuilder::IsAnimatedGeometry
if (aParent) {
*aParent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
}
return false;
}
if (nsLayoutUtils::IsPopup(aFrame))
return true;
- if (ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(aFrame))
- return true;
+ if (ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(aFrame)) {
+ const bool inBudget = AddToAGRBudget(aFrame);
+ if (inBudget) {
+ return true;
+ }
+ }
if (!aFrame->GetParent() &&
nsLayoutUtils::ViewportHasDisplayPort(aFrame->PresContext())) {
// Viewport frames in a display port need to be animated geometry roots
// for background-attachment:fixed elements.
return true;
}
if (aFrame->IsTransformed()) {
return true;
@@ -1250,70 +1255,70 @@ LayoutDeviceIntRegion
nsDisplayListBuilder::GetWindowDraggingRegion() const
{
LayoutDeviceIntRegion result;
result.Sub(mWindowDraggingRegion, mWindowNoDraggingRegion);;
return result;
}
const uint32_t gWillChangeAreaMultiplier = 3;
-static uint32_t GetWillChangeCost(const nsSize& aSize) {
+static uint32_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 =
std::max(minBudgetCost,
nsPresContext::AppUnitsToIntCSSPixels(aSize.width) *
nsPresContext::AppUnitsToIntCSSPixels(aSize.height));
return budgetCost;
}
bool
nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame,
const nsSize& aSize) {
- if (mBudgetSet.Contains(aFrame)) {
+ if (mWillChangeBudgetSet.Contains(aFrame)) {
return true; // Already accounted
}
nsPresContext* key = aFrame->PresContext();
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) *
nsPresContext::AppUnitsToIntCSSPixels(area.height);
- uint32_t cost = GetWillChangeCost(aSize);
+ uint32_t cost = GetLayerizationCost(aSize);
bool onBudget = (budget.mBudget + cost) /
gWillChangeAreaMultiplier < budgetLimit;
if (onBudget) {
budget.mBudget += cost;
mWillChangeBudget.Put(key, budget);
- mBudgetSet.PutEntry(aFrame);
+ mWillChangeBudgetSet.PutEntry(aFrame);
}
return onBudget;
}
bool
nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame,
const nsSize& aSize) {
bool onBudget = AddToWillChangeBudget(aFrame, aSize);
if (!onBudget) {
nsString usageStr;
- usageStr.AppendInt(GetWillChangeCost(aSize));
+ usageStr.AppendInt(GetLayerizationCost(aSize));
nsString multiplierStr;
multiplierStr.AppendInt(gWillChangeAreaMultiplier);
nsString limitStr;
nsRect area = aFrame->PresContext()->GetVisibleArea();
uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
nsPresContext::AppUnitsToIntCSSPixels(area.height);
@@ -1322,16 +1327,50 @@ nsDisplayListBuilder::IsInWillChangeBudg
const char16_t* params[] = { multiplierStr.get(), limitStr.get() };
aFrame->PresContext()->Document()->WarnOnceAbout(
nsIDocument::eIgnoringWillChangeOverBudget, false,
params, ArrayLength(params));
}
return onBudget;
}
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+const float gAGRBudgetAreaMultiplier = 0.3;
+#else
+const float gAGRBudgetAreaMultiplier = 3.0;
+#endif
+
+bool
+nsDisplayListBuilder::AddToAGRBudget(nsIFrame* aFrame)
+{
+ if (mAGRBudgetSet.Contains(aFrame)) {
+ return true;
+ }
+
+ const nsPresContext* presContext = aFrame->PresContext()->GetRootPresContext();
+ if (!presContext) {
+ return false;
+ }
+
+ const nsRect area = presContext->GetVisibleArea();
+ const uint32_t budgetLimit = gAGRBudgetAreaMultiplier *
+ nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+ nsPresContext::AppUnitsToIntCSSPixels(area.height);
+
+ const uint32_t cost = GetLayerizationCost(aFrame->GetSize());
+ const bool onBudget = mUsedAGRBudget + cost < budgetLimit;
+
+ if (onBudget) {
+ mUsedAGRBudget += cost;
+ mAGRBudgetSet.PutEntry(aFrame);
+ }
+
+ return onBudget;
+}
+
nsDisplayList*
nsDisplayListBuilder::EnterScrollInfoItemHoisting(nsDisplayList* aScrollInfoItemStorage)
{
MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting());
nsDisplayList* old = mPendingScrollInfoItems;
mPendingScrollInfoItems = aScrollInfoItemStorage;
return old;
}
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -1159,16 +1159,23 @@ private:
AnimatedGeometryRoot* WrapAGRForFrame(nsIFrame* aAnimatedGeometryRoot,
AnimatedGeometryRoot* aParent = nullptr);
friend class nsDisplayItem;
AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsIFrame* aFrame);
nsDataHashtable<nsPtrHashKey<nsIFrame>, AnimatedGeometryRoot*> mFrameToAnimatedGeometryRootMap;
+ /**
+ * Add the current frame to the AGR budget if possible and remember
+ * the outcome. Subsequent calls will return the same value as
+ * returned here.
+ */
+ bool AddToAGRBudget(nsIFrame* aFrame);
+
struct PresShellState {
nsIPresShell* mPresShell;
nsIFrame* mCaretFrame;
nsRect mCaretRect;
uint32_t mFirstFrameMarkedForDisplay;
bool mIsBackgroundOnly;
// This is a per-document flag turning off event handling for all content
// in the document, and is set when we enter a subdocument for a pointer-
@@ -1212,17 +1219,22 @@ private:
AnimatedGeometryRoot mRootAGR;
// 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> > mBudgetSet;
+ 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;
// rects are relative to the frame's reference frame
nsDataHashtable<nsPtrHashKey<nsIFrame>, nsRect> mDirtyRectForScrolledContents;
// Relative to mCurrentFrame.
nsRect mDirtyRect;
nsRegion mWindowExcludeGlassRegion;
nsRegion mWindowOpaqueRegion;