Bug 1598156 - Part 8: Support Compositor animations for all basic shapes. r=emilio,hiro

This patch sends the information of border-radius to the compositor as
well, so we can use it if offset-path uses coord-box only.

Note:
We repaint the frame if border-radius property gets changed, and rebuild
the display item. In this case, we resend the transaction of compositor
animation as well. Therefore, we don't have to block the compositor animations
if they depends on border-radius (i.e. use coord-box only).

We may need a better way to check what should we pass to the compositor,
for motion path, in Bug 1838977.

Differential Revision: https://phabricator.services.mozilla.com/D179631
This commit is contained in:
Boris Chiou 2023-06-26 23:23:55 +00:00
parent 4bec0b7d55
commit 3523b747c5
6 changed files with 234 additions and 12 deletions

View File

@ -1153,8 +1153,73 @@ promise_test(async t => {
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'offset-path animation should be running on the compositor');
}, 'offset-path animation runs on the compositor');
'offset-path:path() animation should be running on the compositor');
}, 'offset-path:path() animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ offsetPath: ['ray(0deg)',
'ray(180deg)'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'offset-path:ray() animation should be running on the compositor');
}, 'offset-path:ray() animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ offsetPath: ['inset(0px)',
'inset(10px)'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'offset-path:inset() animation should be running on the compositor');
}, 'offset-path:inset() animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ offsetPath: ['circle(10px)',
'circle(20px)'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'offset-path:circle() animation should be running on the compositor');
}, 'offset-path:circle() animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ offsetPath: ['ellipse(10px 20px)',
'ellipse(20px 40px)'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'offset-path:ellipse() animation should be running on the compositor');
}, 'offset-path:ellipse() animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ offsetPath: ['polygon(0px 0px)',
'polygon(50px 50px)'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'offset-path:polygon() animation should be running on the compositor');
}, 'offset-path:polygon() animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);

View File

@ -791,13 +791,17 @@ static Maybe<TransformData> CreateAnimationData(
nsRect coordBox;
const nsIFrame* containingBlockFrame =
MotionPathUtils::GetOffsetPathReferenceBox(aFrame, coordBox);
CSSCoord rayContainReferenceLength =
MotionPathUtils::GetRayContainReferenceSize(aFrame);
motionPathData = Some(layers::MotionPathData(
motionPathOrigin, anchorAdjustment, coordBox,
nsTArray<nscoord> radii;
if (containingBlockFrame) {
radii = MotionPathUtils::ComputeBorderRadii(
containingBlockFrame->StyleBorder()->mBorderRadius, coordBox);
}
motionPathData.emplace(
std::move(motionPathOrigin), std::move(anchorAdjustment),
std::move(coordBox),
containingBlockFrame ? aFrame->GetOffsetTo(containingBlockFrame)
: aFrame->GetPosition(),
rayContainReferenceLength));
MotionPathUtils::GetRayContainReferenceSize(aFrame), std::move(radii));
}
Maybe<PartialPrerenderData> partialPrerenderData;

View File

@ -133,6 +133,9 @@ struct AnimationSegment {
nsPoint currentPosition;
// The reference length for computing ray(contain) (in css pixels).
CSSCoord rayContainReferenceLength;
// The resolved border-radius of its containing block. If the array size is
// zero, we don't have radius.
nscoord[] coordBoxInsetRadii;
};
[Comparable] struct PartialPrerenderData {

View File

@ -88,6 +88,22 @@ CSSCoord MotionPathUtils::GetRayContainReferenceSize(nsIFrame* aFrame) {
return std::max(size.width, size.height);
}
/* static */
nsTArray<nscoord> MotionPathUtils::ComputeBorderRadii(
const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox) {
const nsRect insetRect = ShapeUtils::ComputeInsetRect(
StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
aCoordBox);
nsTArray<nscoord> result(8);
result.SetLength(8);
if (!nsIFrame::ComputeBorderRadii(aBorderRadius, aCoordBox.Size(),
insetRect.Size(), Sides(),
result.Elements())) {
result.Clear();
}
return result;
}
// The distance is measured between the origin and the intersection of the ray
// with the reference box of the containing block.
// Note: |aOrigin| and |aContaingBlock| should be in the same coordinate system
@ -471,6 +487,7 @@ Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
// Generate data for motion path on the compositor thread.
static OffsetPathData GenerateOffsetPathData(
const StyleOffsetPath& aOffsetPath,
const StyleOffsetPosition& aOffsetPosition,
const layers::MotionPathData& aMotionPathData,
gfx::Path* aCachedMotionPath) {
if (aOffsetPath.IsNone()) {
@ -488,6 +505,7 @@ static OffsetPathData GenerateOffsetPathData(
}
// Handle path().
// FIXME: Bug 1837042, cache gfx::Path for shapes other than path().
if (aOffsetPath.IsPath()) {
const StyleSVGPathData& pathData = aOffsetPath.AsSVGPathData();
// If aCachedMotionPath is valid, we have a fixed path.
@ -505,8 +523,37 @@ static OffsetPathData GenerateOffsetPathData(
// The rest part is to handle "<basic-shape> || <coord-box>".
MOZ_ASSERT(aOffsetPath.IsBasicShapeOrCoordBox());
// TODO: This is for OMTA. Implement this in the following patches.
return OffsetPathData::None();
const nsRect& coordBox = aMotionPathData.coordBox();
if (coordBox.IsEmpty()) {
return OffsetPathData::None();
}
RefPtr<gfx::PathBuilder> builder =
MotionPathUtils::GetCompositorPathBuilder();
if (!builder) {
return OffsetPathData::None();
}
RefPtr<gfx::Path> path;
if (aOffsetPath.IsCoordBox()) {
const nsRect insetRect = ShapeUtils::ComputeInsetRect(
StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
coordBox);
const nsTArray<nscoord>& radii = aMotionPathData.coordBoxInsetRadii();
path = ShapeUtils::BuildInsetPath(
insetRect, radii.IsEmpty() ? nullptr : radii.Elements(), coordBox,
AppUnitsPerCSSPixel(), builder);
} else {
path = MotionPathUtils::BuildPath(
aOffsetPath.AsOffsetPath().path->AsShape(), aOffsetPosition, coordBox,
aMotionPathData.currentPosition(), builder);
}
return path ? OffsetPathData::Shape(
path.forget(), nsPoint(aMotionPathData.currentPosition()),
true)
: OffsetPathData::None();
}
/* static */
@ -527,7 +574,9 @@ Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
auto autoOffsetAnchor = StylePositionOrAuto::Auto();
auto autoOffsetPosition = StyleOffsetPosition::Auto();
return ResolveMotionPath(
GenerateOffsetPathData(*aPath, *aMotionPathData, aCachedMotionPath),
GenerateOffsetPathData(*aPath,
aPosition ? *aPosition : autoOffsetPosition,
*aMotionPathData, aCachedMotionPath),
aDistance ? *aDistance : zeroOffsetDistance,
aRotate ? *aRotate : autoOffsetRotate,
aAnchor ? *aAnchor : autoOffsetAnchor,

View File

@ -191,6 +191,15 @@ class MotionPathUtils final {
*/
static CSSCoord GetRayContainReferenceSize(nsIFrame* aFrame);
/**
* Get the resolved radius for inset(0 round X), where X is the parameter of
* |aRadius|.
* This returns an empty array if we cannot compute the radii; otherwise, it
* returns an array with 8 elements.
*/
static nsTArray<nscoord> ComputeBorderRadii(
const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox);
/**
* Generate the motion path transform result. This function may be called on
* the compositor thread.

View File

@ -2724,9 +2724,74 @@ addAsyncAnimTest(async function() {
done_div();
});
// FIXME: Add test for ray() after we fix Bug 1590542.
// Normal offset-path animation with ray().
addAsyncAnimTest(async function() {
new_div("offset-path: ray(90deg); " +
"offset-distance: 0%; " +
"transition: offset-path 10s linear");
await waitForPaintsFlushed();
// Normal offset-distance animation.
gDiv.style.offsetPath = "ray(180deg)";
await waitForPaintsFlushed();
// At 0%, it's ray(90deg), so there is no rotation and only movement because
// the default offset-anchor is 50%.
omta_is("offset-path",
{ compositorValue: { tx: -50, ty: -50 },
computed: 'ray(90deg)' },
RunningOn.Compositor,
"offset-path transition runs on compositor thread");
advance_clock(5000);
// At 50%, ray() is 135deg. so the matrix is kind of rotate -45 deg:
// [cos(-1/4 * pi), -sin(-1/4 * pi), sin(-1/4 * pi), cos(-1/4 * pi), -50, -50]
// Note: the movement of (-50 -50) is from the default offset-anchor.
omta_is("offset-path",
{
compositorValue: [
Math.cos(-Math.PI * 1/4), -Math.sin(-Math.PI * 1/4),
Math.sin(-Math.PI * 1/4), Math.cos(-Math.PI * 1/4),
-50, -50
],
computed: 'ray(135deg)'
},
RunningOn.Compositor,
"offset-path on compositor at 5s");
done_div();
});
// Normal offset-path animation with polygon().
addAsyncAnimTest(async function() {
new_div("offset-path: polygon(0px 0px, 100px 0px, 50px 100px); " +
"offset-distance: 0%; " +
"offset-rotate: 0deg; " +
"offset-anchor: left top; " +
"transition: offset-path 10s linear");
await waitForPaintsFlushed();
gDiv.style.offsetPath = "polygon(50px 0px, 100px 0px, 50px 100px)";
await waitForPaintsFlushed();
omta_is("offset-path",
{ compositorValue: { tx: 0 },
computed: 'polygon(0px 0px, 100px 0px, 50px 100px)' },
RunningOn.Compositor,
"offset-path transition runs on compositor thread");
advance_clock(5000);
omta_is("offset-path",
{ compositorValue: { tx: 25 },
computed: 'polygon(25px 0px, 100px 0px, 50px 100px)' },
RunningOn.Compositor,
"offset-path on compositor at 5s");
done_div();
});
// Normal offset-distance animation with path().
addAsyncAnimTest(async function() {
new_div("offset-path: path('M50 50v100'); " +
"offset-distance: 0%; " +
@ -2752,6 +2817,33 @@ addAsyncAnimTest(async function() {
done_div();
});
// Normal offset-distance animation with polygon().
addAsyncAnimTest(async function() {
new_div("offset-path: polygon(0px 0px, 100px 0px, 100px 50px, 0px 50px); " +
"offset-distance: 0%; " +
"offset-rotate: 0deg; " +
"offset-anchor: left top; " +
"transition: offset-distance 10s linear");
await waitForPaintsFlushed();
gDiv.style.offsetDistance = "100%";
await waitForPaintsFlushed();
omta_is("offset-distance",
{ compositorValue: { tx: 0 }, computed: '0%' },
RunningOn.Compositor,
"offset-distance transition runs on compositor thread");
advance_clock(5000);
omta_is("offset-distance",
{ compositorValue: { tx: 100, ty: 50 }, computed: '50%' },
RunningOn.Compositor,
"offset-distance on compositor at 5s");
done_div();
});
// Normal offset-rotate animation.
addAsyncAnimTest(async function() {
new_div("offset-path: path('M50 50v100'); " +