Skip to content

Commit

Permalink
[threaded-animation-resolution] add support for CSS Motion Path prope…
Browse files Browse the repository at this point in the history
…rties

https://bugs.webkit.org/show_bug.cgi?id=269503

Reviewed by Dean Jackson.

With threaded animation resolution we can now add support for accelerated interpolation
of CSS Motion Path properties, the `offset` shorthand and its longhands.

To do this, we need to add two new members to `AcceleratedEffectValues`:

    std::optional<TransformOperationData> transformOperationData;
    TransformBox transformBox;

These two new members are required to be able to call into an alternate version of the
`MotionPath::applyMotionPathTransform()` method which works without a RenderStyle, but
rather using individual properties read from RenderStyle. We also adjust the `AcceleratedEffectValues`
constructor to take in a renderer to be able to set up those two new members.

This also required a small refactor related to `BlendingContext` since we need to be
able to adjust the context for discrete interpolation in the case of the `offset-rotate`
property which has an ASSERT() to check we only call its blending function for a discrete
interpolation if the context reflects that.

* Source/WebCore/animation/CSSPropertyAnimation.cpp:
(WebCore::blendStandardProperty):
* Source/WebCore/animation/WebAnimationTypes.h:
* Source/WebCore/platform/animation/AcceleratedEffect.cpp:
(WebCore::blend):
(WebCore::AcceleratedEffect::apply):
(WebCore::AcceleratedEffect::animatesTransformRelatedProperty const):
* Source/WebCore/platform/animation/AcceleratedEffectValues.cpp:
(WebCore::AcceleratedEffectValues::AcceleratedEffectValues):
(WebCore::AcceleratedEffectValues::clone const):
(WebCore::AcceleratedEffectValues::computedTransformationMatrix const):
* Source/WebCore/platform/animation/AcceleratedEffectValues.h:
(WebCore::AcceleratedEffectValues::AcceleratedEffectValues):
* Source/WebCore/platform/animation/AnimationUtilities.h:
(WebCore::BlendingContext::normalizeProgress):
* Source/WebCore/rendering/MotionPath.cpp:
(WebCore::MotionPath::applyMotionPathTransform):
* Source/WebCore/rendering/MotionPath.h:
* Source/WebCore/rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::updateAcceleratedEffectsAndBaseValues):
* Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in:

Canonical link: https://commits.webkit.org/274803@main
  • Loading branch information
graouts committed Feb 16, 2024
1 parent 72c98dc commit 6e1a463
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 52 deletions.
13 changes: 4 additions & 9 deletions Source/WebCore/animation/CSSPropertyAnimation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4212,16 +4212,11 @@ static void blendStandardProperty(const CSSPropertyBlendingClient& client, CSSPr

AnimationPropertyWrapperBase* wrapper = CSSPropertyAnimationWrapperMap::singleton().wrapperForProperty(property);
if (wrapper) {
// https://drafts.csswg.org/web-animations-1/#discrete
// The property's values cannot be meaningfully combined, thus it is not additive and
// interpolation swaps from Va to Vb at 50% (p=0.5).
auto isDiscrete = !wrapper->canInterpolate(from, to, compositeOperation);
if (isDiscrete && wrapper->normalizesProgressForDiscreteInterpolation()) {
// If we want additive, we should specify progress at 0 actually and return from.
progress = progress < 0.5 ? 0 : 1;
compositeOperation = CompositeOperation::Replace;
}
wrapper->blend(destination, from, to, { progress, isDiscrete, compositeOperation, client, property, iterationCompositeOperation, currentIteration });
CSSPropertyBlendingContext context { progress, isDiscrete, compositeOperation, client, property, iterationCompositeOperation, currentIteration };
if (wrapper->normalizesProgressForDiscreteInterpolation())
context.normalizeProgress();
wrapper->blend(destination, from, to, context);
#if !LOG_DISABLED
wrapper->logBlend(from, to, destination, progress);
#endif
Expand Down
7 changes: 6 additions & 1 deletion Source/WebCore/animation/WebAnimationTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ constexpr OptionSet<AcceleratedEffectProperty> transformRelatedAcceleratedProper
AcceleratedEffectProperty::Transform,
AcceleratedEffectProperty::Translate,
AcceleratedEffectProperty::Rotate,
AcceleratedEffectProperty::Scale
AcceleratedEffectProperty::Scale,
AcceleratedEffectProperty::OffsetAnchor,
AcceleratedEffectProperty::OffsetDistance,
AcceleratedEffectProperty::OffsetPath,
AcceleratedEffectProperty::OffsetPosition,
AcceleratedEffectProperty::OffsetRotate
};

struct CSSPropertiesBitSet {
Expand Down
46 changes: 30 additions & 16 deletions Source/WebCore/platform/animation/AcceleratedEffect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "FloatRect.h"
#include "KeyframeEffect.h"
#include "LayoutSize.h"
#include "OffsetRotation.h"
#include "StyleOriginatedAnimation.h"
#include "WebAnimation.h"
#include "WebAnimationTypes.h"
Expand Down Expand Up @@ -290,7 +291,7 @@ AcceleratedEffect::AcceleratedEffect(const AcceleratedEffect& source, OptionSet<
}
}

static void blend(AcceleratedEffectProperty property, AcceleratedEffectValues& output, const AcceleratedEffectValues& from, const AcceleratedEffectValues& to, const BlendingContext& blendingContext, const FloatRect& bounds)
static void blend(AcceleratedEffectProperty property, AcceleratedEffectValues& output, const AcceleratedEffectValues& from, const AcceleratedEffectValues& to, BlendingContext& blendingContext, const FloatRect& bounds)
{
switch (property) {
case AcceleratedEffectProperty::Opacity:
Expand All @@ -313,6 +314,26 @@ static void blend(AcceleratedEffectProperty property, AcceleratedEffectValues& o
if (auto toScale = to.scale)
output.scale = toScale->blend(from.scale.get(), blendingContext);
break;
case AcceleratedEffectProperty::OffsetAnchor:
output.offsetDistance = blend(from.offsetDistance, to.offsetDistance, blendingContext);
break;
case AcceleratedEffectProperty::OffsetDistance:
output.offsetDistance = blend(from.offsetDistance, to.offsetDistance, blendingContext);
break;
case AcceleratedEffectProperty::OffsetPath:
if (auto& fromOffsetPath = from.offsetPath)
output.offsetPath = fromOffsetPath->blend(to.offsetPath.get(), blendingContext);
break;
case AcceleratedEffectProperty::OffsetPosition:
output.offsetPosition = blend(from.offsetPosition, to.offsetPosition, blendingContext);
break;
case AcceleratedEffectProperty::OffsetRotate:
if (!from.offsetRotate.canBlend(to.offsetRotate)) {
blendingContext.isDiscrete = true;
blendingContext.normalizeProgress();
}
output.offsetRotate = from.offsetRotate.blend(to.offsetRotate, blendingContext);
break;
case AcceleratedEffectProperty::Invalid:
ASSERT_NOT_REACHED();
break;
Expand Down Expand Up @@ -342,7 +363,8 @@ void AcceleratedEffect::apply(Seconds currentTime, AcceleratedEffectValues& valu
// progress which already accounts for the transition's timing function.
if (m_animationType == WebAnimationType::CSSTransition) {
ASSERT(m_animatedProperties.hasExactlyOneBitSet());
blend(*m_animatedProperties.begin(), values, m_keyframes.first().values(), m_keyframes.last().values(), { progress, false, m_compositeOperation }, bounds);
BlendingContext context { progress, false, m_compositeOperation };
blend(*m_animatedProperties.begin(), values, m_keyframes.first().values(), m_keyframes.last().values(), context, bounds);
return;
}

Expand All @@ -362,10 +384,11 @@ void AcceleratedEffect::apply(Seconds currentTime, AcceleratedEffectValues& valu
KeyframeInterpolation::CompositionCallback composeProperty = [&](const KeyframeInterpolation::Keyframe& keyframe, CompositeOperation compositeOperation) {
ASSERT(is<AcceleratedEffect::Keyframe>(keyframe));
auto& acceleratedKeyframe = downcast<AcceleratedEffect::Keyframe>(keyframe);
BlendingContext context { 1, false, compositeOperation };
if (acceleratedKeyframe.offset() == startKeyframe->offset())
blend(animatedProperty, startKeyframeValues, propertySpecificKeyframeWithZeroOffset.values(), acceleratedKeyframe.values(), { 1, false, compositeOperation }, bounds);
blend(animatedProperty, startKeyframeValues, propertySpecificKeyframeWithZeroOffset.values(), acceleratedKeyframe.values(), context, bounds);
else
blend(animatedProperty, endKeyframeValues, propertySpecificKeyframeWithZeroOffset.values(), acceleratedKeyframe.values(), { 1, false, compositeOperation }, bounds);
blend(animatedProperty, endKeyframeValues, propertySpecificKeyframeWithZeroOffset.values(), acceleratedKeyframe.values(), context, bounds);
};

KeyframeInterpolation::AccumulationCallback accumulateProperty = [&](const KeyframeInterpolation::Keyframe&) {
Expand All @@ -374,7 +397,8 @@ void AcceleratedEffect::apply(Seconds currentTime, AcceleratedEffectValues& valu

KeyframeInterpolation::InterpolationCallback interpolateProperty = [&](double intervalProgress, double, IterationCompositeOperation) {
// FIXME: handle currentIteration and iterationCompositeOperation.
blend(animatedProperty, values, startKeyframeValues, endKeyframeValues, { intervalProgress }, bounds);
BlendingContext context { intervalProgress };
blend(animatedProperty, values, startKeyframeValues, endKeyframeValues, context, bounds);
};

KeyframeInterpolation::RequiresBlendingForAccumulativeIterationCallback requiresBlendingForAccumulativeIterationCallback = [&]() {
Expand Down Expand Up @@ -470,17 +494,7 @@ void AcceleratedEffect::validateFilters(const AcceleratedEffectValues& baseValue

bool AcceleratedEffect::animatesTransformRelatedProperty() const
{
return m_animatedProperties.containsAny({
AcceleratedEffectProperty::Transform,
AcceleratedEffectProperty::Translate,
AcceleratedEffectProperty::Rotate,
AcceleratedEffectProperty::Scale,
AcceleratedEffectProperty::OffsetPath,
AcceleratedEffectProperty::OffsetDistance,
AcceleratedEffectProperty::OffsetPosition,
AcceleratedEffectProperty::OffsetAnchor,
AcceleratedEffectProperty::OffsetRotate
});
return m_animatedProperties.containsAny(transformRelatedAcceleratedProperties);
}

const KeyframeInterpolation::Keyframe& AcceleratedEffect::keyframeAtIndex(size_t index) const
Expand Down
24 changes: 22 additions & 2 deletions Source/WebCore/platform/animation/AcceleratedEffectValues.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@

#include "IntSize.h"
#include "LengthFunctions.h"
#include "MotionPath.h"
#include "Path.h"
#include "RenderElementInlines.h"
#include "RenderLayerModelObject.h"
#include "RenderStyleInlines.h"
#include "TransformOperationData.h"

Expand All @@ -40,6 +43,9 @@ AcceleratedEffectValues::AcceleratedEffectValues(const AcceleratedEffectValues&
{
opacity = src.opacity;

transformOperationData = src.transformOperationData;
transformBox = src.transformBox;

auto& transformOperations = transform.operations();
auto& srcTransformOperations = src.transform.operations();
transformOperations.appendVector(srcTransformOperations);
Expand All @@ -66,6 +72,10 @@ AcceleratedEffectValues::AcceleratedEffectValues(const AcceleratedEffectValues&

AcceleratedEffectValues AcceleratedEffectValues::clone() const
{
std::optional<TransformOperationData> clonedTransformOperationData;
if (transformOperationData)
clonedTransformOperationData = transformOperationData;

auto clonedTransformOrigin = transformOrigin;

TransformOperations clonedTransform { transform.operations().map([](const auto& operation) {
Expand Down Expand Up @@ -103,7 +113,9 @@ AcceleratedEffectValues AcceleratedEffectValues::clone() const

return {
opacity,
WTFMove(clonedTransformOperationData),
WTFMove(clonedTransformOrigin),
transformBox,
WTFMove(clonedTransform),
WTFMove(clonedTranslate),
WTFMove(clonedScale),
Expand All @@ -128,18 +140,23 @@ static LengthPoint nonCalculatedLengthPoint(LengthPoint lengthPoint, const IntSi
};
}

AcceleratedEffectValues::AcceleratedEffectValues(const RenderStyle& style, const IntRect& borderBoxRect)
AcceleratedEffectValues::AcceleratedEffectValues(const RenderStyle& style, const IntRect& borderBoxRect, const RenderLayerModelObject* renderer)
{
opacity = style.opacity();

auto borderBoxSize = borderBoxRect.size();

if (renderer)
transformOperationData = TransformOperationData(renderer->transformReferenceBoxRect(style), renderer);

auto& transformOperations = transform.operations();
auto& srcTransformOperations = style.transform().operations();
transformOperations.appendContainerWithMapping(srcTransformOperations, [&](auto& srcTransformOperation) {
return srcTransformOperation->selfOrCopyWithResolvedCalculatedValues(borderBoxSize);
});

transformBox = style.transformBox();

if (auto* srcTranslate = style.translate())
translate = srcTranslate->selfOrCopyWithResolvedCalculatedValues(borderBoxSize);
if (auto* srcScale = style.scale())
Expand Down Expand Up @@ -194,7 +211,10 @@ TransformationMatrix AcceleratedEffectValues::computedTransformationMatrix(const
scale->apply(matrix, boundingBox.size());

// 6. Translate and rotate by the transform specified by offset.
// FIXME: implement this step to support CSS Motion Path properties.
if (transformOperationData && offsetPath) {
auto computedTransformOrigin = boundingBox.location() + floatPointForLengthPoint(transformOrigin, boundingBox.size());
MotionPath::applyMotionPathTransform(matrix, *transformOperationData, computedTransformOrigin, *offsetPath, offsetAnchor, offsetDistance, offsetRotate, transformBox);
}

// 7. Multiply by each of the transform functions in transform from left to right.
for (auto& transformOperation : transform.operations())
Expand Down
12 changes: 8 additions & 4 deletions Source/WebCore/platform/animation/AcceleratedEffectValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "LengthPoint.h"
#include "OffsetRotation.h"
#include "PathOperation.h"
#include "RenderStyleConstants.h"
#include "RotateTransformOperation.h"
#include "ScaleTransformOperation.h"
#include "TransformOperations.h"
Expand All @@ -42,11 +43,14 @@ namespace WebCore {

class IntRect;
class Path;
class RenderLayerModelObject;
class RenderStyle;

struct AcceleratedEffectValues {
float opacity { 1 };
std::optional<TransformOperationData> transformOperationData;
LengthPoint transformOrigin { };
TransformBox transformBox { TransformBox::ContentBox };
TransformOperations transform { };
RefPtr<TransformOperation> translate;
RefPtr<TransformOperation> scale;
Expand All @@ -63,11 +67,11 @@ struct AcceleratedEffectValues {
{
}

AcceleratedEffectValues(float opacity, LengthPoint&& transformOrigin, TransformOperations&& transform, RefPtr<TransformOperation>&& translate, RefPtr<TransformOperation>&& scale, RefPtr<TransformOperation>&& rotate, RefPtr<PathOperation>&& offsetPath, Length&& offsetDistance, LengthPoint&& offsetPosition, LengthPoint&& offsetAnchor, OffsetRotation&& offsetRotate, FilterOperations&& filter
, FilterOperations&& backdropFilter
)
AcceleratedEffectValues(float opacity, std::optional<TransformOperationData>&& transformOperationData, LengthPoint&& transformOrigin, TransformBox transformBox, TransformOperations&& transform, RefPtr<TransformOperation>&& translate, RefPtr<TransformOperation>&& scale, RefPtr<TransformOperation>&& rotate, RefPtr<PathOperation>&& offsetPath, Length&& offsetDistance, LengthPoint&& offsetPosition, LengthPoint&& offsetAnchor, OffsetRotation&& offsetRotate, FilterOperations&& filter, FilterOperations&& backdropFilter)
: opacity(opacity)
, transformOperationData(WTFMove(transformOperationData))
, transformOrigin(WTFMove(transformOrigin))
, transformBox(transformBox)
, transform(WTFMove(transform))
, translate(WTFMove(translate))
, scale(WTFMove(scale))
Expand All @@ -85,7 +89,7 @@ struct AcceleratedEffectValues {
WEBCORE_EXPORT AcceleratedEffectValues clone() const;

WEBCORE_EXPORT AcceleratedEffectValues(const AcceleratedEffectValues&);
AcceleratedEffectValues(const RenderStyle&, const IntRect&);
AcceleratedEffectValues(const RenderStyle&, const IntRect&, const RenderLayerModelObject* = nullptr);
AcceleratedEffectValues& operator=(const AcceleratedEffectValues&) = default;

WEBCORE_EXPORT TransformationMatrix computedTransformationMatrix(const FloatRect&) const;
Expand Down
11 changes: 11 additions & 0 deletions Source/WebCore/platform/animation/AnimationUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ struct BlendingContext {
{
return compositeOperation == CompositeOperation::Replace && iterationCompositeOperation == IterationCompositeOperation::Replace;
}

void normalizeProgress()
{
// https://drafts.csswg.org/web-animations-1/#discrete
// The property's values cannot be meaningfully combined, thus it is not additive and
// interpolation swaps from Va to Vb at 50% (p=0.5).
if (isDiscrete) {
progress = progress < 0.5 ? 0 : 1;
compositeOperation = CompositeOperation::Replace;
}
}
};

inline int blend(int from, int to, const BlendingContext& context)
Expand Down
40 changes: 23 additions & 17 deletions Source/WebCore/rendering/MotionPath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,43 +115,49 @@ static PathTraversalState traversalStateAtDistance(const Path& path, const Lengt
return path.traversalStateAtLength(resolvedLength);
}

void MotionPath::applyMotionPathTransform(const RenderStyle& style, const TransformOperationData& transformData, TransformationMatrix& transform)
void MotionPath::applyMotionPathTransform(TransformationMatrix& matrix, const TransformOperationData& transformData, const FloatPoint& transformOrigin, const PathOperation& offsetPath, const LengthPoint& offsetAnchor, const Length& offsetDistance, const OffsetRotation& offsetRotate, TransformBox transformBox)
{
if (!style.offsetPath())
return;

auto& boundingBox = transformData.boundingBox;
auto transformOrigin = style.computeTransformOrigin(boundingBox).xy();
auto anchor = transformOrigin;
if (!style.offsetAnchor().x().isAuto())
anchor = floatPointForLengthPoint(style.offsetAnchor(), boundingBox.size()) + boundingBox.location();
if (!offsetAnchor.x().isAuto())
anchor = floatPointForLengthPoint(offsetAnchor, boundingBox.size()) + boundingBox.location();

// Shift element to the point on path specified by offset-path and offset-distance.
auto path = style.offsetPath()->getPath(transformData);
auto path = offsetPath.getPath(transformData);
if (!path)
return;
auto traversalState = traversalStateAtDistance(*path, style.offsetDistance());
transform.translate(traversalState.current().x(), traversalState.current().y());
auto traversalState = traversalStateAtDistance(*path, offsetDistance);
matrix.translate(traversalState.current().x(), traversalState.current().y());

auto shiftToOrigin = anchor - transformOrigin;

// Adjust anchor for SVG.
if (transformData.isSVGRenderer && style.transformBox() != TransformBox::ViewBox)
if (transformData.isSVGRenderer && transformBox != TransformBox::ViewBox)
anchor += boundingBox.location();

// Shift element to the anchor specified by offset-anchor.
transform.translate(-anchor.x(), -anchor.y());
matrix.translate(-anchor.x(), -anchor.y());

transform.translate(shiftToOrigin.width(), shiftToOrigin.height());
matrix.translate(shiftToOrigin.width(), shiftToOrigin.height());

// Apply rotation.
auto rotation = style.offsetRotate();
auto& rotation = offsetRotate;
if (rotation.hasAuto())
transform.rotate(traversalState.normalAngle() + rotation.angle());
matrix.rotate(traversalState.normalAngle() + rotation.angle());
else
transform.rotate(rotation.angle());
matrix.rotate(rotation.angle());

matrix.translate(-shiftToOrigin.width(), -shiftToOrigin.height());
}

void MotionPath::applyMotionPathTransform(const RenderStyle& style, const TransformOperationData& transformData, TransformationMatrix& matrix)
{
auto* offsetPath = style.offsetPath();
if (!offsetPath)
return;

transform.translate(-shiftToOrigin.width(), -shiftToOrigin.height());
auto transformOrigin = style.computeTransformOrigin(transformData.boundingBox).xy();
applyMotionPathTransform(matrix, transformData, transformOrigin, *offsetPath, style.offsetAnchor(), style.offsetDistance(), style.offsetRotate(), style.transformBox());
}

bool MotionPath::needsUpdateAfterContainingBlockLayout(const PathOperation& pathOperation)
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/rendering/MotionPath.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class MotionPath {
public:
static std::optional<MotionPathData> motionPathDataForRenderer(const RenderElement&);
static bool needsUpdateAfterContainingBlockLayout(const PathOperation&);
static void applyMotionPathTransform(TransformationMatrix&, const TransformOperationData&, const FloatPoint& transformOrigin, const PathOperation&, const LengthPoint& offsetAnchor, const Length& offsetDistance, const OffsetRotation&, TransformBox);
static void applyMotionPathTransform(const RenderStyle&, const TransformOperationData&, TransformationMatrix&);
WEBCORE_EXPORT static std::optional<Path> computePathForBox(const BoxPathOperation&, const TransformOperationData&);
static std::optional<Path> computePathForRay(const RayPathOperation&, const TransformOperationData&);
Expand Down
7 changes: 4 additions & 3 deletions Source/WebCore/rendering/RenderLayerBacking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4050,18 +4050,19 @@ bool RenderLayerBacking::startAnimation(double timeOffset, const Animation& anim
#if ENABLE(THREADED_ANIMATION_RESOLUTION)
bool RenderLayerBacking::updateAcceleratedEffectsAndBaseValues()
{
if (!renderer().settings().acceleratedCompositedAnimationsEnabled())
auto& renderer = this->renderer();
if (!renderer.settings().acceleratedCompositedAnimationsEnabled())
return false;

auto target = Styleable::fromRenderer(renderer());
auto target = Styleable::fromRenderer(renderer);
ASSERT(target);

bool hasInterpolatingEffect = false;
auto borderBoxRect = snappedIntRect(m_owningLayer.rendererBorderBoxRect());

auto baseValues = [&]() -> AcceleratedEffectValues {
if (auto* style = target->lastStyleChangeEventStyle())
return { *style, borderBoxRect };
return { *style, borderBoxRect, &renderer };
return { };
}();

Expand Down
Loading

0 comments on commit 6e1a463

Please sign in to comment.