mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 07:45:30 +00:00
Bug 1621007 - Rewrite the checking for PrerenderDecision and reset mAllowAsyncAnimation if needed in 3d context. r=mattwoodrow
The basic idea is: In the same 3d context (note: we use AutoPreserves3DContext to keep this info), and we block the async animations of the ancestors of the ones who don't prerender. Of course, and its later silbing frames and child frames. In order to to this, we keep a flag in Preserves3DContext, and set it to false if the decision is No prerender, so we could force to block async animations on its ancestors and descendants (and later silbings). Differential Revision: https://phabricator.services.mozilla.com/D67686 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
3c7aa1ba1c
commit
2eaf9ee039
@ -1377,7 +1377,7 @@ promise_test(async t => {
|
||||
promise_test(async t => {
|
||||
const container = addDiv(t, { style: 'transform-style: preserve-3d;' });
|
||||
const targetA = addDiv(t, { style: 'transform-style: preserve-3d' });
|
||||
const targetB = addDiv(t, { style: 'transform-style: preserve-3d '});
|
||||
const targetB = addDiv(t, { style: 'transform-style: preserve-3d' });
|
||||
const targetC = addDiv(t);
|
||||
container.appendChild(targetA);
|
||||
targetA.append(targetB);
|
||||
@ -1402,5 +1402,93 @@ promise_test(async t => {
|
||||
}, 'Transform-like animations in the 3d rendering context should runs on the ' +
|
||||
'compositor');
|
||||
|
||||
promise_test(async t => {
|
||||
const container = addDiv(t, { style: 'transform-style: preserve-3d;' });
|
||||
const target = addDiv(t, { style: 'transform-style: preserve-3d;' });
|
||||
const innerA = addDiv(t, { style: 'width: 50px; height: 50px;' });
|
||||
// The frame of innerB is too large, so this makes its ancenstors and children
|
||||
// in the 3d context be not allowed the async animations.
|
||||
const innerB = addDiv(t, { style: 'rotate: 0 1 1 100deg; ' +
|
||||
'transform-style: preserve-3d; ' +
|
||||
'text-indent: -9999em' });
|
||||
const innerB2 = addDiv(t, { style: 'rotate: 0 1 1 45deg;' });
|
||||
const innerBText = document.createTextNode("innerB");
|
||||
container.appendChild(target);
|
||||
target.appendChild(innerA);
|
||||
target.appendChild(innerB);
|
||||
innerB.appendChild(innerBText);
|
||||
innerB.appendChild(innerB2);
|
||||
|
||||
const animation1 = target.animate({ rotate: ['0 0 1 0deg', '1 1 1 45deg'] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
const animation2 = innerA.animate({ rotate: ['0 0 1 0deg', '0 1 1 100deg'] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
const animation3 = innerB2.animate({ rotate: ['0 0 1 0deg', '0 1 1 90deg'] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
await waitForAnimationReadyToRestyle(animation1);
|
||||
await waitForAnimationReadyToRestyle(animation2);
|
||||
await waitForAnimationReadyToRestyle(animation3);
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_is_not_running_on_compositor(animation1,
|
||||
'rotate animation in the 3d rendering context should not be running on ' +
|
||||
'the compositor because one of its inner frames is too large');
|
||||
assert_animation_is_running_on_compositor(animation2,
|
||||
'rotate animation in the 3d rendering context is still running on ' +
|
||||
'the compositor because its display item is created earlier');
|
||||
assert_animation_is_not_running_on_compositor(animation3,
|
||||
'rotate animation in the 3d rendering context should not be running on ' +
|
||||
'the compositor because one of its parent frames is too large');
|
||||
|
||||
innerBText.remove();
|
||||
}, 'Transform-like animations in the 3d rendering context should not runs on ' +
|
||||
'the compositor if it is the ancestor of ones whose frames are too large ' +
|
||||
'or its ancestor is not allowed to run on the compositor');
|
||||
|
||||
promise_test(async t => {
|
||||
const container = addDiv(t, { style: 'transform-style: preserve-3d;' });
|
||||
const target = addDiv(t, { style: 'transform-style: preserve-3d;' });
|
||||
// The frame of innerA is too large, so this makes its ancenstors and children
|
||||
// in the 3d context be not allowed the async animations.
|
||||
const innerA = addDiv(t, { style: 'transform-style: preserve-3d; ' +
|
||||
'text-indent: -9999em' });
|
||||
const innerAText = document.createTextNode("innerA");
|
||||
const innerB = addDiv(t, { style: 'width: 50px; height: 50px;' });
|
||||
container.appendChild(target);
|
||||
target.appendChild(innerA);
|
||||
target.appendChild(innerB);
|
||||
innerA.appendChild(innerAText);
|
||||
|
||||
const animation1 = target.animate({ rotate: ['0 0 1 0deg', '1 1 1 45deg'] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
const animation2 = innerA.animate({ rotate: ['0 0 1 0deg', '0 1 1 100deg'] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
const animation3 = innerB.animate({ rotate: ['0 0 1 0deg', '0 1 1 90deg'] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
await waitForAnimationReadyToRestyle(animation1);
|
||||
await waitForAnimationReadyToRestyle(animation2);
|
||||
await waitForAnimationReadyToRestyle(animation3);
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_is_not_running_on_compositor(animation1,
|
||||
'rotate animation in the 3d rendering context should not be running on ' +
|
||||
'the compositor because one of its inner frames is too large');
|
||||
assert_animation_is_not_running_on_compositor(animation2,
|
||||
'rotate animation in the 3d rendering context should not be running on ' +
|
||||
'the compositor because its frame size is too large');
|
||||
assert_animation_is_not_running_on_compositor(animation3,
|
||||
'rotate animation in the 3d rendering context should not be running on ' +
|
||||
'the compositor because its previous sibling frame is too large');
|
||||
|
||||
innerAText.remove();
|
||||
}, 'Transform-like animations in the 3d rendering context should not runs on ' +
|
||||
'the compositor if its previous sibling frame size is too large');
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
@ -3210,10 +3210,11 @@ void nsIFrame::BuildDisplayListForStackingContext(
|
||||
bool allowAsyncAnimation = false;
|
||||
bool inTransform = aBuilder->IsInTransform();
|
||||
if (isTransformed) {
|
||||
nsDisplayTransform::PrerenderDecision decision =
|
||||
nsDisplayTransform::PrerenderInfo decision =
|
||||
nsDisplayTransform::ShouldPrerenderTransformedContent(aBuilder, this,
|
||||
&dirtyRect);
|
||||
switch (decision) {
|
||||
|
||||
switch (decision.mDecision) {
|
||||
case nsDisplayTransform::PrerenderDecision::Full:
|
||||
allowAsyncAnimation = true;
|
||||
visibleRect = dirtyRect;
|
||||
@ -3224,6 +3225,15 @@ void nsIFrame::BuildDisplayListForStackingContext(
|
||||
[[fallthrough]];
|
||||
// fall through to the PrerenderDecision::No case
|
||||
case nsDisplayTransform::PrerenderDecision::No: {
|
||||
// If we didn't prerender an animated frame in a preserve-3d context,
|
||||
// then we want disable async animations for the rest of the preserve-3d
|
||||
// (especially ancestors).
|
||||
if ((extend3DContext || combines3DTransformWithAncestors) &&
|
||||
decision.mDecision == nsDisplayTransform::PrerenderDecision::No &&
|
||||
decision.mHasAnimations) {
|
||||
aBuilder->SavePreserves3DAllowAsyncAnimation(false);
|
||||
}
|
||||
|
||||
const nsRect overflow = GetVisualOverflowRectRelativeToSelf();
|
||||
if (overflow.IsEmpty() && !extend3DContext) {
|
||||
return;
|
||||
@ -3231,8 +3241,6 @@ void nsIFrame::BuildDisplayListForStackingContext(
|
||||
|
||||
// If we're in preserve-3d then grab the dirty rect that was given to
|
||||
// the root and transform using the combined transform.
|
||||
// FIXME: Could we remove this after making transform animations with
|
||||
// preserve-3d run on the compositor?
|
||||
if (combines3DTransformWithAncestors) {
|
||||
visibleRect = dirtyRect = aBuilder->GetPreserves3DRect();
|
||||
}
|
||||
@ -3703,6 +3711,22 @@ void nsIFrame::BuildDisplayListForStackingContext(
|
||||
buildingDisplayList.SetReferenceFrameAndCurrentOffset(
|
||||
outerReferenceFrame, GetOffsetToCrossDoc(outerReferenceFrame));
|
||||
|
||||
// We would like to block async animations for ancestors of ones not
|
||||
// prerendered in the preserve-3d tree. Now that we've finished processing
|
||||
// all descendants, update allowAsyncAnimation to take their prerender
|
||||
// state into account
|
||||
// FIXME: We don't block async animations for previous siblings because
|
||||
// their prerender decisions have been made. We may have to figure out a
|
||||
// better way to rollback their prerender decisions.
|
||||
// Alternatively we could not block animations for later siblings, and only
|
||||
// block them for ancestors of a blocked one.
|
||||
if ((extend3DContext || combines3DTransformWithAncestors) &&
|
||||
allowAsyncAnimation) {
|
||||
// aBuilder->GetPreserves3DAllowAsyncAnimation() means the inner or
|
||||
// previous silbing frames are allowed/disallowed for async animations.
|
||||
allowAsyncAnimation = aBuilder->GetPreserves3DAllowAsyncAnimation();
|
||||
}
|
||||
|
||||
nsDisplayTransform* transformItem = MakeDisplayItem<nsDisplayTransform>(
|
||||
aBuilder, this, &resultList, visibleRect, 0, allowAsyncAnimation);
|
||||
if (transformItem) {
|
||||
|
@ -8293,7 +8293,16 @@ bool nsDisplayBackgroundColor::CanUseAsyncAnimations(
|
||||
/* static */
|
||||
auto nsDisplayTransform::ShouldPrerenderTransformedContent(
|
||||
nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect)
|
||||
-> PrerenderDecision {
|
||||
-> PrerenderInfo {
|
||||
PrerenderInfo result;
|
||||
// If we are in a preserve-3d tree, and we've disallowed async animations, we
|
||||
// return No prerender decision directly.
|
||||
if ((aFrame->Extend3DContext() ||
|
||||
aFrame->Combines3DTransformWithAncestors()) &&
|
||||
!aBuilder->GetPreserves3DAllowAsyncAnimation()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Elements whose transform has been modified recently, or which
|
||||
// have a compositor-animated transform, can be prerendered. An element
|
||||
// might have only just had its transform animated in which case
|
||||
@ -8308,7 +8317,13 @@ auto nsDisplayTransform::ShouldPrerenderTransformedContent(
|
||||
AnimationPerformanceWarning(
|
||||
AnimationPerformanceWarning::Type::TransformFrameInactive));
|
||||
|
||||
return PrerenderDecision::No;
|
||||
// This case happens when we're sure that the frame is not animated and its
|
||||
// preserve-3d ancestors are not, either. So we don't need to pre-render.
|
||||
// However, this decision shouldn't affect the decisions for other frames in
|
||||
// the preserve-3d context. We need this flag to determine whether we should
|
||||
// block async animations on other frames in the current preserve-3d tree.
|
||||
result.mHasAnimations = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
// We should not allow prerender if any ancestor container element has
|
||||
@ -8334,7 +8349,7 @@ auto nsDisplayTransform::ShouldPrerenderTransformedContent(
|
||||
container = nsLayoutUtils::GetCrossDocParentFrame(container)) {
|
||||
const nsStyleSVGReset* svgReset = container->StyleSVGReset();
|
||||
if (svgReset->HasMask() || svgReset->HasClipPath()) {
|
||||
return PrerenderDecision::No;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8342,7 +8357,8 @@ auto nsDisplayTransform::ShouldPrerenderTransformedContent(
|
||||
// we are already rendering the entire content.
|
||||
nsRect overflow = aFrame->GetVisualOverflowRectRelativeToSelf();
|
||||
if (aDirtyRect->Contains(overflow)) {
|
||||
return PrerenderDecision::Full;
|
||||
result.mDecision = PrerenderDecision::Full;
|
||||
return result;
|
||||
}
|
||||
|
||||
float viewportRatioX =
|
||||
@ -8376,29 +8392,15 @@ auto nsDisplayTransform::ShouldPrerenderTransformedContent(
|
||||
uint64_t frameArea = uint64_t(frameSize.width) * frameSize.height;
|
||||
if (frameArea <= maxLimitArea && frameSize <= absoluteLimit) {
|
||||
*aDirtyRect = overflow;
|
||||
return PrerenderDecision::Full;
|
||||
}
|
||||
|
||||
// Frames participating any preserve-3d rendering context are force to
|
||||
// full prerender here if we failed the previous if-conditions.
|
||||
// If the current frame doesn't have animations and any of its parent has
|
||||
// animations, we still hit this path because
|
||||
// ActiveLayerTracker::IsTransformMaybeAnimated() catches the existing
|
||||
// animations on the parent frames in the preserve-3d rendering context.
|
||||
// FIXME: For now, we force all the frame in the 3D rendering context to use
|
||||
// FullPrerender, so this may have an issue if one of the frame is too large
|
||||
// and it has animations (which may make the calculation of visible and dirty
|
||||
// rects not correct.).
|
||||
if (aFrame->Extend3DContext() || aFrame->Combines3DTransformWithAncestors()) {
|
||||
// Use overflow as dirty and force to use full prerender.
|
||||
*aDirtyRect = overflow;
|
||||
return PrerenderDecision::Full;
|
||||
result.mDecision = PrerenderDecision::Full;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (StaticPrefs::layout_animation_prerender_partial()) {
|
||||
*aDirtyRect = nsLayoutUtils::ComputePartialPrerenderArea(*aDirtyRect,
|
||||
overflow, maxSize);
|
||||
return PrerenderDecision::Partial;
|
||||
result.mDecision = PrerenderDecision::Partial;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (frameArea > maxLimitArea) {
|
||||
@ -8426,7 +8428,7 @@ auto nsDisplayTransform::ShouldPrerenderTransformedContent(
|
||||
}));
|
||||
}
|
||||
|
||||
return PrerenderDecision::No;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* If the matrix is singular, or a hidden backface is shown, the frame won't be
|
||||
|
@ -402,13 +402,15 @@ class nsDisplayListBuilder {
|
||||
public:
|
||||
typedef mozilla::gfx::Matrix4x4 Matrix4x4;
|
||||
|
||||
Preserves3DContext() : mAccumulatedRectLevels(0) {}
|
||||
Preserves3DContext()
|
||||
: mAccumulatedRectLevels(0), mAllowAsyncAnimation(true) {}
|
||||
|
||||
Preserves3DContext(const Preserves3DContext& aOther)
|
||||
: mAccumulatedTransform(),
|
||||
mAccumulatedRect(),
|
||||
mAccumulatedRectLevels(0),
|
||||
mVisibleRect(aOther.mVisibleRect) {}
|
||||
mVisibleRect(aOther.mVisibleRect),
|
||||
mAllowAsyncAnimation(aOther.mAllowAsyncAnimation) {}
|
||||
|
||||
// Accmulate transforms of ancestors on the preserves-3d chain.
|
||||
Matrix4x4 mAccumulatedTransform;
|
||||
@ -417,6 +419,8 @@ class nsDisplayListBuilder {
|
||||
// How far this frame is from the root of the current 3d context.
|
||||
int mAccumulatedRectLevels;
|
||||
nsRect mVisibleRect;
|
||||
// Allow async animation for this 3D context.
|
||||
bool mAllowAsyncAnimation;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1716,6 +1720,14 @@ class nsDisplayListBuilder {
|
||||
|
||||
void SavePreserves3DRect() { mPreserves3DCtx.mVisibleRect = mVisibleRect; }
|
||||
|
||||
void SavePreserves3DAllowAsyncAnimation(bool aValue) {
|
||||
mPreserves3DCtx.mAllowAsyncAnimation = aValue;
|
||||
}
|
||||
|
||||
bool GetPreserves3DAllowAsyncAnimation() const {
|
||||
return mPreserves3DCtx.mAllowAsyncAnimation;
|
||||
}
|
||||
|
||||
bool IsBuildingInvisibleItems() const { return mBuildingInvisibleItems; }
|
||||
|
||||
void SetBuildingInvisibleItems(bool aBuildingInvisibleItems) {
|
||||
@ -7105,16 +7117,25 @@ class nsDisplayTransform : public nsDisplayHitTestInfoBase {
|
||||
static Matrix4x4 GetResultingTransformMatrix(
|
||||
const FrameTransformProperties& aProperties, TransformReferenceBox&,
|
||||
const nsPoint& aOrigin, float aAppUnitsPerPixel, uint32_t aFlags);
|
||||
|
||||
struct PrerenderInfo {
|
||||
PrerenderDecision mDecision = PrerenderDecision::No;
|
||||
bool mHasAnimations = true;
|
||||
};
|
||||
/**
|
||||
* Decide whether we should prerender some or all of the contents of the
|
||||
* transformed frame even when it's not completely visible (yet).
|
||||
* Return FullPrerender if the entire contents should be prerendered,
|
||||
* PartialPrerender if some but not all of the contents should be prerendered,
|
||||
* or NoPrerender if only the visible area should be rendered.
|
||||
* Return PrerenderDecision::Full if the entire contents should be
|
||||
* prerendered, PrerenderDecision::Partial if some but not all of the
|
||||
* contents should be prerendered, or PrerenderDecision::No if only the
|
||||
* visible area should be rendered.
|
||||
* |mNoAffectDecisionInPreserve3D| is set if the prerender decision should not
|
||||
* affect the decision on other frames in the preserve 3d tree.
|
||||
* |aDirtyRect| is updated to the area that should be prerendered.
|
||||
*/
|
||||
static PrerenderDecision ShouldPrerenderTransformedContent(
|
||||
static PrerenderInfo ShouldPrerenderTransformedContent(
|
||||
nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect);
|
||||
|
||||
bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
|
||||
|
||||
bool MayBeAnimated(nsDisplayListBuilder* aBuilder,
|
||||
|
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<style>
|
||||
.transform {
|
||||
will-change: transform;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div class="transform">
|
||||
<div>TEST PAGE</div>
|
||||
<div style="text-indent: -9999em">TEST PAGE</div>
|
||||
</div>
|
||||
</body>
|
@ -0,0 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<body>
|
||||
<div style="transform-style: preserve-3d">
|
||||
<div>TEST PAGE</div>
|
||||
</div>
|
||||
</body>
|
@ -96,3 +96,4 @@ fuzzy-if(winWidget,0-150,0-120) == component-alpha-1.html component-alpha-1-ref.
|
||||
!= preserve3d-scale.html about:blank
|
||||
fuzzy-if(webrender,0-1,0-5) == perspective-overflow-1.html perspective-overflow-1-ref.html
|
||||
== 1544995-1.html 1544995-1-ref.html
|
||||
== preserve3d-will-change-large-frame.html preserve3d-will-change-ref.html
|
||||
|
Loading…
Reference in New Issue
Block a user