From 1eda179306c2ac86546e0e6eef8325b5d373f27d Mon Sep 17 00:00:00 2001 From: Benoit Girard Date: Tue, 21 Oct 2014 21:55:55 -0400 Subject: [PATCH] Bug 961871 - Part 4: Fix budget calculation by adding Maybe prerenderer and animate. r=roc --- layout/base/ActiveLayerTracker.cpp | 10 ++++-- layout/base/ActiveLayerTracker.h | 5 +++ layout/base/FrameLayerBuilder.cpp | 2 +- layout/base/nsDisplayList.cpp | 56 +++++++++++++++++++++++------ layout/base/nsDisplayList.h | 18 ++++++++-- layout/generic/nsGfxScrollFrame.cpp | 36 +++++++++++++------ layout/generic/nsGfxScrollFrame.h | 1 + 7 files changed, 103 insertions(+), 25 deletions(-) diff --git a/layout/base/ActiveLayerTracker.cpp b/layout/base/ActiveLayerTracker.cpp index bac5f3edec26..d0bf4eb7cefa 100644 --- a/layout/base/ActiveLayerTracker.cpp +++ b/layout/base/ActiveLayerTracker.cpp @@ -259,6 +259,12 @@ ActiveLayerTracker::NotifyInlineStyleRuleModified(nsIFrame* aFrame, NotifyAnimated(aFrame, aProperty); } +/* static */ bool +ActiveLayerTracker::IsStyleMaybeAnimated(nsIFrame* aFrame, nsCSSProperty aProperty) +{ + return IsStyleAnimated(nullptr, aFrame, aProperty); +} + /* static */ bool ActiveLayerTracker::IsStyleAnimated(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsCSSProperty aProperty) @@ -266,12 +272,12 @@ ActiveLayerTracker::IsStyleAnimated(nsDisplayListBuilder* aBuilder, // TODO: Add some abuse restrictions if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) && aProperty == eCSSProperty_transform && - aBuilder->IsInWillChangeBudget(aFrame)) { + (!aBuilder || aBuilder->IsInWillChangeBudget(aFrame))) { return true; } if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) && aProperty == eCSSProperty_opacity && - aBuilder->IsInWillChangeBudget(aFrame)) { + (!aBuilder || aBuilder->IsInWillChangeBudget(aFrame))) { return true; } diff --git a/layout/base/ActiveLayerTracker.h b/layout/base/ActiveLayerTracker.h index e5c6af447a0c..d81677214890 100644 --- a/layout/base/ActiveLayerTracker.h +++ b/layout/base/ActiveLayerTracker.h @@ -56,6 +56,11 @@ public: * style will trigger this. */ static void NotifyInlineStyleRuleModified(nsIFrame* aFrame, nsCSSProperty aProperty); + /** + * Return true if aFrame's aProperty style should be considered as being animated + * for pre-rendering. + */ + static bool IsStyleMaybeAnimated(nsIFrame* aFrame, nsCSSProperty aProperty); /** * Return true if aFrame's aProperty style should be considered as being animated * for constructing active layers. diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index 06bfaa15ffd8..54f27f1f0693 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -2804,7 +2804,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) nsIntRect itemDrawRect = ScaleToOutsidePixels(itemContent, snap); nsDisplayItem::Type itemType = item->GetType(); bool prerenderedTransform = itemType == nsDisplayItem::TYPE_TRANSFORM && - static_cast(item)->ShouldPrerender(); + static_cast(item)->ShouldPrerender(mBuilder); nsIntRect clipRect; const DisplayItemClip& itemClip = item->GetClip(); if (itemClip.HasClip()) { diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 670596d6dd31..2c11f433d7c6 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -525,6 +525,7 @@ nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame, mCurrentTableItem(nullptr), mCurrentFrame(aReferenceFrame), mCurrentReferenceFrame(aReferenceFrame), + mWillChangeBudgetCalculated(false), mDirtyRect(-1,-1,-1,-1), mGlassDisplayItem(nullptr), mMode(aMode), @@ -1111,6 +1112,11 @@ nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame) void nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame, const nsSize& aRect) { + // Make sure that we don't query the budget before the display list is fully + // built and that the will change budget is locked in. + MOZ_ASSERT(!mWillChangeBudgetCalculated, + "Can't modify the budget once it's been used."); + DocumentWillChangeBudget budget; nsPresContext* key = aFrame->PresContext(); @@ -1133,6 +1139,8 @@ nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame, const nsSize& aRec bool nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame) const { + mWillChangeBudgetCalculated = true; + nsPresContext* key = aFrame->PresContext(); if (!mWillChangeBudget.Contains(key)) { MOZ_ASSERT(false, "If we added nothing to our budget then this " @@ -4544,8 +4552,15 @@ nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder) { mStoredList.SetClip(aBuilder, DisplayItemClip::NoClip()); mStoredList.SetVisibleRect(mChildrenVisibleRect); - mPrerender = ShouldPrerenderTransformedContent(aBuilder, mFrame); - if (mPrerender) { + mMaybePrerender = ShouldPrerenderTransformedContent(aBuilder, mFrame); + + const nsStyleDisplay* disp = mFrame->StyleDisplay(); + if ((disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM)) { + // We will only pre-render if this will-change is on budget. + mMaybePrerender = true; + } + + if (mMaybePrerender) { bool snap; mVisibleRect = GetBounds(aBuilder, &snap); } @@ -4905,10 +4920,31 @@ nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) return false; } +bool +nsDisplayTransform::ShouldPrerender(nsDisplayListBuilder* aBuilder) { + if (!mMaybePrerender) { + return false; + } + + if (ShouldPrerenderTransformedContent(aBuilder, mFrame)) { + return true; + } + + const nsStyleDisplay* disp = mFrame->StyleDisplay(); + if ((disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) && + aBuilder->IsInWillChangeBudget(mFrame)) { + return true; + } + + return false; +} + bool nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) { - if (mPrerender) { + if (mMaybePrerender) { + // TODO We need to make sure that if we use async animation we actually + // pre-render even if we're out of will change budget. return true; } DebugOnly prerender = ShouldPrerenderTransformedContent(aBuilder, mFrame, true); @@ -4925,7 +4961,7 @@ nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBui // have a compositor-animated transform, can be prerendered. An element // might have only just had its transform animated in which case // the ActiveLayerManager may not have been notified yet. - if (!ActiveLayerTracker::IsStyleAnimated(aBuilder, aFrame, eCSSProperty_transform) && + if (!ActiveLayerTracker::IsStyleMaybeAnimated(aFrame, eCSSProperty_transform) && (!aFrame->GetContent() || !nsLayoutUtils::HasAnimationsForCompositor(aFrame->GetContent(), eCSSProperty_transform))) { @@ -5023,7 +5059,7 @@ nsDisplayTransform::GetTransform() bool nsDisplayTransform::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) { - return ShouldPrerender(); + return ShouldPrerender(aBuilder); } already_AddRefed nsDisplayTransform::BuildLayer(nsDisplayListBuilder *aBuilder, @@ -5037,7 +5073,7 @@ already_AddRefed nsDisplayTransform::BuildLayer(nsDisplayListBuilder *aBu return nullptr; } - uint32_t flags = ShouldPrerender() ? + uint32_t flags = ShouldPrerender(aBuilder) ? FrameLayerBuilder::CONTAINER_NOT_CLIPPED_BY_ANCESTORS : 0; nsRefPtr container = aManager->GetLayerBuilder()-> BuildContainerLayerFor(aBuilder, aManager, mFrame, this, mStoredList.GetChildren(), @@ -5058,7 +5094,7 @@ already_AddRefed nsDisplayTransform::BuildLayer(nsDisplayListBuilder *aBu nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(container, aBuilder, this, mFrame, eCSSProperty_transform); - if (ShouldPrerender()) { + if (ShouldPrerender(aBuilder)) { container->SetUserData(nsIFrame::LayerIsPrerenderedDataKey(), /*the value is irrelevant*/nullptr); container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_MAY_CHANGE_TRANSFORM); @@ -5111,7 +5147,7 @@ bool nsDisplayTransform::ComputeVisibility(nsDisplayListBuilder *aBuilder, * think that it's painting in its original rectangular coordinate space. * If we can't untransform, take the entire overflow rect */ nsRect untransformedVisibleRect; - if (ShouldPrerender() || + if (ShouldPrerender(aBuilder) || !UntransformVisibleRect(aBuilder, &untransformedVisibleRect)) { untransformedVisibleRect = mFrame->GetVisualOverflowRectRelativeToSelf(); @@ -5241,7 +5277,7 @@ nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsP */ nsRect nsDisplayTransform::GetBounds(nsDisplayListBuilder *aBuilder, bool* aSnap) { - nsRect untransformedBounds = ShouldPrerender() ? + nsRect untransformedBounds = MaybePrerender() ? mFrame->GetVisualOverflowRectRelativeToSelf() : mStoredList.GetBounds(aBuilder, aSnap); *aSnap = false; @@ -5279,7 +5315,7 @@ nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder, // covers the entire window, but it allows our transform to be // updated extremely cheaply, without invalidating any other // content. - if (ShouldPrerender() || + if (MaybePrerender() || !UntransformVisibleRect(aBuilder, &untransformedVisible)) { return nsRegion(); } diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index 18a2bb9388ae..84dec547b275 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -14,6 +14,7 @@ #define NSDISPLAYLIST_H_ #include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" #include "nsCOMPtr.h" #include "nsContainerFrame.h" #include "nsPoint.h" @@ -778,6 +779,8 @@ private: // will-change budget tracker nsDataHashtable, DocumentWillChangeBudget> mWillChangeBudget; + // Assert that we never check the budget before its fully calculated. + mutable mozilla::DebugOnly mWillChangeBudgetCalculated; // Relative to mCurrentFrame. nsRect mDirtyRect; nsRegion mWindowOpaqueRegion; @@ -3464,7 +3467,16 @@ public: bool aLogAnimations = false); bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE; - bool ShouldPrerender() const { return mPrerender; } + /** + * This will return if it's possible for this element to be prerendered. + * This should never return false if we're going to prerender. + */ + bool MaybePrerender() const { return mMaybePrerender; } + /** + * Check if this element will be prerendered. This must be done after the + * display list has been fully built. + */ + bool ShouldPrerender(nsDisplayListBuilder* aBuilder); #ifdef MOZ_DUMP_PAINTING virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE; @@ -3486,7 +3498,9 @@ private: ComputeTransformFunction mTransformGetter; nsRect mChildrenVisibleRect; uint32_t mIndex; - bool mPrerender; + // We wont know if we pre-render until the layer building phase where we can + // check layers will-change budget. + bool mMaybePrerender; }; /** diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 6556772c8347..4fa51cd20e9c 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -2546,7 +2546,10 @@ ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder, return; } - mozilla::layers::FrameMetrics::ViewID scrollTargetId = IsScrollingActive(aBuilder) + // We can't check will-change budget during display list building phase. + // This means that we will build scroll bar layers for out of budget + // will-change: scroll position. + mozilla::layers::FrameMetrics::ViewID scrollTargetId = IsMaybeScrollingActive() ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent()) : mozilla::layers::FrameMetrics::NULL_SCROLL_ID; @@ -2765,10 +2768,10 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, if (aBuilder->IsPaintingToWindow()) { mScrollPosAtLastPaint = GetScrollPosition(); - if (IsScrollingActive(aBuilder) && NeedToInvalidateOnScroll(mOuter)) { + if (IsMaybeScrollingActive() && NeedToInvalidateOnScroll(mOuter)) { MarkNotRecentlyScrolled(); } - if (IsScrollingActive(aBuilder)) { + if (IsMaybeScrollingActive()) { if (mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)) { mScrollPosForLayerPixelAlignment = mScrollPosAtLastPaint; } @@ -2875,6 +2878,11 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, dirtyRect = ExpandRectToNearlyVisible(dirtyRect); } + const nsStyleDisplay* disp = mOuter->StyleDisplay(); + if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL)) { + aBuilder->AddToWillChangeBudget(mOuter, GetScrollPositionClampingScrollPortSize()); + } + // Since making new layers is expensive, only use nsDisplayScrollLayer // if the area is scrollable and we're the content process (unless we're on // B2G, where we support async scrolling for scrollable elements in the @@ -2896,11 +2904,6 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, // info layer to make a ComputeFrameMetrics call for us as // nsDisplayList::PaintForFrame already calls ComputeFrameMetrics for us. (!mIsRoot || aBuilder->RootReferenceFrame()->PresContext() != mOuter->PresContext()); - - const nsStyleDisplay* disp = mOuter->StyleDisplay(); - bool willScroll = disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL) && - aBuilder->IsInWillChangeBudget(mOuter); - shouldBuildLayer |= willScroll; } mScrollParentID = aBuilder->GetCurrentScrollParentId(); @@ -4125,6 +4128,19 @@ ScrollFrameHelper::IsScrollbarOnRight() const } } +bool +ScrollFrameHelper::IsMaybeScrollingActive() const +{ + const nsStyleDisplay* disp = mOuter->StyleDisplay(); + if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL)) { + return true; + } + + return mHasBeenScrolledRecently || + IsAlwaysActive() || + mShouldBuildScrollableLayer; +} + bool ScrollFrameHelper::IsScrollingActive(nsDisplayListBuilder* aBuilder) const { @@ -4135,8 +4151,8 @@ ScrollFrameHelper::IsScrollingActive(nsDisplayListBuilder* aBuilder) const } return mHasBeenScrolledRecently || - IsAlwaysActive() || - mShouldBuildScrollableLayer; + IsAlwaysActive() || + mShouldBuildScrollableLayer; } /** diff --git a/layout/generic/nsGfxScrollFrame.h b/layout/generic/nsGfxScrollFrame.h index 92d20d7790ce..447828b94e81 100644 --- a/layout/generic/nsGfxScrollFrame.h +++ b/layout/generic/nsGfxScrollFrame.h @@ -285,6 +285,7 @@ public: bool IsLTR() const; bool IsScrollbarOnRight() const; bool IsScrollingActive(nsDisplayListBuilder* aBuilder) const; + bool IsMaybeScrollingActive() const; bool IsProcessingAsyncScroll() const { return mAsyncScroll != nullptr || mAsyncSmoothMSDScroll != nullptr; }