Skip to content

Commit

Permalink
[threaded-animation-resolution] discrete filter interpolation shoul…
Browse files Browse the repository at this point in the history
…d not be accelerated

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

Reviewed by Dean Jackson.

The CSS `filter` property animates discretely if two `filter` values do not have a shared initial list
of filter operations, as specified in https://drafts.fxtf.org/filter-effects/#interpolation-of-filters.
It does not make much sense to accelerate discrete animation of any value but, more importantly, the
system we use to animate `filter` in the UIProcess relies on `CAPresentationModifier` on macOS and
requires a known list of filter operations for initial setup, which falls apart if we are dealing
with mis-matching filter lists. As such we must make sure we don't create `AcceleratedEffect` objects
that contain `filter` values that will animate discretely.

We change `AcceleratedEffect::create()` to return a `RefPtr` rather than a `Ref` and only return a
value after running the new validation function `validateFilters()` which will find all keyframe
intervals, including those relying on implicit 0% and 100% keyframes, and check there is no
set of filters that will yield a discrete animation. If we find such a pair of filters, we remove
the property from the list of animated properties for those keyframes and the effect itself and,
should we not have a single animated property left, return a `nullptr` value.

* Source/WebCore/platform/animation/AcceleratedEffect.cpp:
(WebCore::AcceleratedEffect::Keyframe::clearProperty):
(WebCore::AcceleratedEffect::create):
(WebCore::AcceleratedEffect::validateFilters):
* Source/WebCore/platform/animation/AcceleratedEffect.h:
* Source/WebCore/rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::updateAcceleratedEffectsAndBaseValues):

Canonical link: https://commits.webkit.org/274755@main
  • Loading branch information
graouts committed Feb 15, 2024
1 parent 7d701b1 commit c78d9ea
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 10 deletions.
96 changes: 94 additions & 2 deletions Source/WebCore/platform/animation/AcceleratedEffect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "CSSPropertyAnimation.h"
#include "CSSPropertyNames.h"
#include "Document.h"
#include "FilterOperations.h"
#include "FloatRect.h"
#include "KeyframeEffect.h"
#include "LayoutSize.h"
Expand All @@ -58,6 +59,11 @@ AcceleratedEffect::Keyframe::Keyframe(double offset, AcceleratedEffectValues&& v
{
}

void AcceleratedEffect::Keyframe::clearProperty(AcceleratedEffectProperty property)
{
m_animatedProperties.remove({ property });
}

bool AcceleratedEffect::Keyframe::animatesProperty(KeyframeInterpolation::Property property) const
{
return WTF::switchOn(property, [&](const AcceleratedEffectProperty acceleratedProperty) {
Expand Down Expand Up @@ -161,9 +167,13 @@ static CSSPropertyID cssPropertyFromAcceleratedProperty(AcceleratedEffectPropert
}
}

Ref<AcceleratedEffect> AcceleratedEffect::create(const KeyframeEffect& effect, const IntRect& borderBoxRect)
RefPtr<AcceleratedEffect> AcceleratedEffect::create(const KeyframeEffect& effect, const IntRect& borderBoxRect, const AcceleratedEffectValues& baseValues)
{
return adoptRef(*new AcceleratedEffect(effect, borderBoxRect));
auto* acceleratedEffect = new AcceleratedEffect(effect, borderBoxRect);
acceleratedEffect->validateFilters(baseValues);
if (acceleratedEffect->animatedProperties().isEmpty())
return nullptr;
return adoptRef(*acceleratedEffect);
}

Ref<AcceleratedEffect> AcceleratedEffect::create(AnimationEffectTiming timing, Vector<Keyframe>&& keyframes, WebAnimationType type, CompositeOperation composite, RefPtr<TimingFunction>&& defaultKeyframeTimingFunction, OptionSet<WebCore::AcceleratedEffectProperty>&& animatedProperties, bool paused, double playbackRate, std::optional<Seconds> startTime, std::optional<Seconds> holdTime)
Expand Down Expand Up @@ -376,6 +386,88 @@ void AcceleratedEffect::apply(Seconds currentTime, AcceleratedEffectValues& valu
}
}

using OptionalKeyframeIndex = std::optional<size_t>;

void AcceleratedEffect::validateFilters(const AcceleratedEffectValues& baseValues)
{
auto numberOfKeyframes = m_keyframes.size();

auto findKeyframePair = [&](size_t startingIndex, AcceleratedEffectProperty property) -> std::pair<OptionalKeyframeIndex, OptionalKeyframeIndex> {
OptionalKeyframeIndex firstKeyframeIndex;
for (size_t i = startingIndex; i < numberOfKeyframes; ++i) {
auto& keyframe = m_keyframes[i];
// Nothing to do for a keyframe that doesn't contain this property.
if (!keyframe.animatesProperty(property))
continue;

// If we're dealing with a keyframe with offset = 1, this can only be our last keyframe,
// so there will not be another keyframe pair with the provided starting index.
if (keyframe.offset() == 1 && !firstKeyframeIndex)
return { };

if (firstKeyframeIndex)
return { *firstKeyframeIndex, i };
firstKeyframeIndex = i;
}

if (!firstKeyframeIndex)
return { };

// If we get here this means we have a first keyframe but no last keyframe. Thus we
// create a pair with an implicit keyframe. If the starting index is 0, this means
// we were looking for our very first pair and the implicit keyframe is the first
// keyframe.
if (!startingIndex)
return { std::nullopt, *firstKeyframeIndex };
return { *firstKeyframeIndex, std::nullopt };
};

auto filterOperations = [&](OptionalKeyframeIndex index, AcceleratedEffectProperty property) -> const FilterOperations& {
ASSERT(!index || *index < numberOfKeyframes);
auto& values = index ? m_keyframes[*index].values() : baseValues;
switch (property) {
case AcceleratedEffectProperty::Filter:
return values.filter;
case AcceleratedEffectProperty::BackdropFilter:
return values.backdropFilter;
default:
ASSERT_NOT_REACHED();
return values.filter;
}
};

auto clearProperty = [&](AcceleratedEffectProperty property) {
m_animatedProperties.remove({ property });
for (auto& keyframe : m_keyframes)
keyframe.clearProperty(property);
};

auto validateProperty = [&](AcceleratedEffectProperty property) {
for (size_t i = 0; i < numberOfKeyframes;) {
auto indexes = findKeyframePair(i, property);
if (!indexes.first && !indexes.second)
return;

auto& fromFilters = filterOperations(indexes.first, property);
auto& toFilters = filterOperations(indexes.second, property);
// FIXME: we should provide the actual composite operation here.
if (!fromFilters.canInterpolate(toFilters, CompositeOperation::Replace)) {
clearProperty(property);
return;
}

if (!indexes.second)
return;
i = *indexes.second;
}
};

if (m_animatedProperties.contains(AcceleratedEffectProperty::Filter))
validateProperty(AcceleratedEffectProperty::Filter);
if (m_animatedProperties.contains(AcceleratedEffectProperty::BackdropFilter))
validateProperty(AcceleratedEffectProperty::BackdropFilter);
}

bool AcceleratedEffect::animatesTransformRelatedProperty() const
{
return m_animatedProperties.containsAny({
Expand Down
5 changes: 4 additions & 1 deletion Source/WebCore/platform/animation/AcceleratedEffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class AcceleratedEffect : public RefCounted<AcceleratedEffect>, public KeyframeI
bool animatesProperty(KeyframeInterpolation::Property) const final;
bool isAcceleratedEffectKeyframe() const final { return true; }

void clearProperty(AcceleratedEffectProperty);
const OptionSet<AcceleratedEffectProperty>& animatedProperties() const { return m_animatedProperties; }
const RefPtr<TimingFunction>& timingFunction() const { return m_timingFunction; }
const AcceleratedEffectValues& values() const { return m_values; }
Expand All @@ -72,7 +73,7 @@ class AcceleratedEffect : public RefCounted<AcceleratedEffect>, public KeyframeI
OptionSet<AcceleratedEffectProperty> m_animatedProperties;
};

static Ref<AcceleratedEffect> create(const KeyframeEffect&, const IntRect&);
static RefPtr<AcceleratedEffect> create(const KeyframeEffect&, const IntRect&, const AcceleratedEffectValues&);
WEBCORE_EXPORT static Ref<AcceleratedEffect> create(AnimationEffectTiming, Vector<Keyframe>&&, WebAnimationType, CompositeOperation, RefPtr<TimingFunction>&& defaultKeyframeTimingFunction, OptionSet<AcceleratedEffectProperty>&&, bool paused, double playbackRate, std::optional<Seconds> startTime, std::optional<Seconds> holdTime);

virtual ~AcceleratedEffect() = default;
Expand Down Expand Up @@ -101,6 +102,8 @@ class AcceleratedEffect : public RefCounted<AcceleratedEffect>, public KeyframeI
explicit AcceleratedEffect(AnimationEffectTiming, Vector<Keyframe>&&, WebAnimationType, CompositeOperation, RefPtr<TimingFunction>&& defaultKeyframeTimingFunction, OptionSet<AcceleratedEffectProperty>&&, bool paused, double playbackRate, std::optional<WTF::Seconds> startTime, std::optional<WTF::Seconds> holdTime);
explicit AcceleratedEffect(const AcceleratedEffect&, OptionSet<AcceleratedEffectProperty>&);

void validateFilters(const AcceleratedEffectValues& baseValues);

// KeyframeInterpolation
bool isPropertyAdditiveOrCumulative(KeyframeInterpolation::Property) const final;
const KeyframeInterpolation::Keyframe& keyframeAtIndex(size_t) const final;
Expand Down
17 changes: 10 additions & 7 deletions Source/WebCore/rendering/RenderLayerBacking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4059,14 +4059,23 @@ bool RenderLayerBacking::updateAcceleratedEffectsAndBaseValues()
bool hasInterpolatingEffect = false;
auto borderBoxRect = snappedIntRect(m_owningLayer.rendererBorderBoxRect());

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

AcceleratedEffects acceleratedEffects;
if (auto* effectStack = target->keyframeEffectStack()) {
for (const auto& effect : effectStack->sortedEffects()) {
if (!effect || !effect->canBeAccelerated())
continue;
auto acceleratedEffect = AcceleratedEffect::create(*effect, borderBoxRect, baseValues);
if (!acceleratedEffect)
continue;
if (!hasInterpolatingEffect && effect->isRunningAccelerated())
hasInterpolatingEffect = true;
acceleratedEffects.append(AcceleratedEffect::create(*effect, borderBoxRect));
acceleratedEffects.append(acceleratedEffect.releaseNonNull());
}
}

Expand All @@ -4076,12 +4085,6 @@ bool RenderLayerBacking::updateAcceleratedEffectsAndBaseValues()
if (!hasInterpolatingEffect)
acceleratedEffects.clear();

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

m_graphicsLayer->setAcceleratedEffectsAndBaseValues(WTFMove(acceleratedEffects), WTFMove(baseValues));

m_owningLayer.setNeedsPostLayoutCompositingUpdate();
Expand Down

0 comments on commit c78d9ea

Please sign in to comment.