⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 294391 in webkit


Ignore:
Timestamp:
May 18, 2022, 5:54:37 AM (4 years ago)
Author:
graouts@webkit.org
Message:

[web-animations] dynamically toggle acceleration of composition animations depending on ability of animations in the effect stack to be accelerated
​https://bugs.webkit.org/show_bug.cgi?id=236708
<rdar://89029577>

Reviewed by Dean Jackson.

We currently don't have support for composite operations at the GraphicsLayerCA level. There are many situations
under which an effect depends on another effect lower in the effect stack, and if that effect fails to run accelerated,
such as due to a non-replace composite operation, we should not run any other effect for that property using
acceleration.

While we could add logic to correctly determine the cases where we actually can run effects using acceleration,
it is a relatively involved task and a simpler, less error-prone approach is to correctly run a whole effect
stack containing some non-replace composite operations without any acceleration.

We already did something similar for cases where we'd fail to run an effect targeting a transform property because
GraphicsLayerCA::addAnimation() would return false. Such situations include using a steps() timing function, a
playback rate other than 1 or providing fewer than 2 animation values.

We now use a single mechanism to deal not only with the case of animations that fail to run using GraphicsLayerCA,
but also with animations that we know GraphicsLayerCA cannot handle, such as non-replace composite operations.

To do this, we change the RunningAccelerated enum to have both "Prevented" and "Failed" values instead of the
single No value to determine the reason why an effect does not accelerated. The "Prevented" value indicates that
the effect stack or the effect itself prevented the effect from running accelerated, while the "Failed" value
indicates that while we attempted to run the animation accelerated through GraphicsLayerCA, we failed to.

We now account for the "Failed" value in preventsAcceleration() to determine that an effect should prevent other
effects from running accelerated. This method is used by KeyframeEffectStack to check that all of its effects
are able to run accelerated and to toggle acceleration based on this value.

As an effect's composite operation or keyframes is set, or when pending accelerated actions are applied, we now
instantiate a CanBeAcceleratedMutationScope which will automatically detect if the preventsAcceleration() value
changes before and after such mutations, and informs the effect stack if that is the case to make sure the effect
stack knows whether it is newly able to run or not run using acceleration.

Finally, to be able to correctly interact with GraphicsLayerCA, we must make sure to reuse the same key when creating
keyframes passed to its animation APIs, so we add a m_keyframesName member.

  • Source/WebCore/animation/DocumentTimeline.cpp:

(WebCore::DocumentTimeline::applyPendingAcceleratedAnimations):

  • Source/WebCore/animation/KeyframeEffect.cpp:

(WebCore::KeyframeEffect::KeyframeEffect):
(WebCore::KeyframeEffect::copyPropertiesFromSource):
(WebCore::KeyframeEffect::updateBlendingKeyframes):
(WebCore::KeyframeEffect::setBlendingKeyframes):
(WebCore::KeyframeEffect::computeCSSTransitionBlendingKeyframes):
(WebCore::KeyframeEffect::preventsAcceleration const):
(WebCore::KeyframeEffect::updateAcceleratedActions):
(WebCore::KeyframeEffect::addPendingAcceleratedAction):
(WebCore::KeyframeEffect::animationDidChangeTimingProperties):
(WebCore::KeyframeEffect::applyPendingAcceleratedActions):
(WebCore::KeyframeEffect::setComposite):
(WebCore::KeyframeEffect::effectStackNoLongerPreventsAcceleration):
(WebCore::KeyframeEffect::effectStackNoLongerAllowsAcceleration):
(WebCore::KeyframeEffect::abilityToBeAcceleratedDidChange):
(WebCore::KeyframeEffect::CanBeAcceleratedMutationScope::CanBeAcceleratedMutationScope):
(WebCore::KeyframeEffect::CanBeAcceleratedMutationScope::~CanBeAcceleratedMutationScope):
(WebCore::KeyframeEffect::stopAcceleratingTransformRelatedProperties): Deleted.

  • Source/WebCore/animation/KeyframeEffect.h:
  • Source/WebCore/animation/KeyframeEffectStack.cpp:

(WebCore::KeyframeEffectStack::addEffect):
(WebCore::KeyframeEffectStack::removeEffect):
(WebCore::KeyframeEffectStack::effectAbilityToBeAcceleratedDidChange):
(WebCore::KeyframeEffectStack::allowsAcceleration const):
(WebCore::KeyframeEffectStack::startAcceleratedAnimationsIfPossible):
(WebCore::KeyframeEffectStack::stopAcceleratedAnimations):
(WebCore::KeyframeEffectStack::stopAcceleratingTransformRelatedProperties): Deleted.
(WebCore::KeyframeEffectStack::containsEffectThatPreventsAccelerationOfEffect): Deleted.

  • Source/WebCore/animation/KeyframeEffectStack.h:
  • Source/WebCore/animation/WebAnimationTypes.h:
  • LayoutTests/webanimations/accelerated-animations-and-composite-expected.txt:
  • LayoutTests/webanimations/accelerated-animations-and-composite.html:

Canonical link: ​https://commits.webkit.org/250686@main

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/webanimations/accelerated-animations-and-composite-expected.txt

    r289598 r294391  
    66PASS Setting 'composite' to 'add' on an animation lower down the stack should prevent replace animations with an implicity keyframe further up the stack to be accelerated
    77PASS Setting 'composite' to 'add' on an animation lower down the stack targeting a property that isn't accelerated should allow replace animations with implicit keyframes further up the stack to be accelerated
     8PASS Dynamically setting 'composite' on an effect should toggle acceleration
     9PASS Dynamically setting 'composite' on a keyframe should toggle acceleration
     10PASS Dynamically setting 'composite' to 'add' on an animation further up the stack should toggle acceleration on lower animations in the stack
     11PASS Dynamically setting 'composite' to 'add' on an animation lower down the stack should toggle acceleration but always allow replace animations further up the stack to be accelerated
     12PASS Dynamically setting 'composite' to 'add' on an animation lower down the stack should toggle acceleration on replace animations with an implicity keyframe further up the stack
     13PASS Dynamically setting 'composite' to 'add' on an animation lower down the stack targeting a property that isn't accelerated shouldn't prevent acceleration of animations with implicit keyframes further up the stack to be accelerated
     14PASS Adding a composing effect on top of an existing replace effect should prevent both effects from running accelerated
    815
  • trunk/LayoutTests/webanimations/accelerated-animations-and-composite.html

    r289605 r294391  
    8484    ));
    8585    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
    86     assert_equals(internals.acceleratedAnimationsForElement(target).length, 1);
     86    assert_equals(internals.acceleratedAnimationsForElement(target).length, 0);
    8787}, "Setting 'composite' to 'add' on an animation lower down the stack should allow replace animations further up the stack to be accelerated");
    8888
    … …  
    124124}, "Setting 'composite' to 'add' on an animation lower down the stack targeting a property that isn't accelerated should allow replace animations with implicit keyframes further up the stack to be accelerated");
    125125
     126// The following tests check the dynamic interruption and/or start of accelerated animations
     127// after changes made to other effects in the stack.
     128
     129promise_test(async test => {
     130    const target = createDiv(test);
     131    const animation = target.animate({ transform: "translateX(100px)" }, duration);
     132
     133    await animationReadyToAnimateAccelerated(animation);
     134    assert_equals(internals.acceleratedAnimationsForElement(target).length, 1);
     135
     136    // Setting the effect composition to "add" means that the animation should
     137    // no longer be accelerated.
     138    animation.effect.composite = "add";
     139    await animationReadyToAnimateAccelerated(animation);
     140    assert_equals(internals.acceleratedAnimationsForElement(target).length, 0);
     141
     142    // Resetting the effect composition to "replace" means that the animation
     143    // should be accelerated again.
     144    animation.effect.composite = "replace";
     145    await animationReadyToAnimateAccelerated(animation);
     146    assert_equals(internals.acceleratedAnimationsForElement(target).length, 1);
     147}, "Dynamically setting 'composite' on an effect should toggle acceleration");
     148
     149promise_test(async test => {
     150    const keyframes = composite => [{ offset: 1, composite, transform: "translateX(100px)" }];
     151
     152    const target = createDiv(test);
     153    const animation = target.animate(keyframes("replace"), duration);
     154    await animationReadyToAnimateAccelerated(animation);
     155    assert_equals(internals.acceleratedAnimationsForElement(target).length, 1);
     156
     157    // Setting the keyframe composition to "add" means that the animation should
     158    // no longer be accelerated.
     159    animation.effect.setKeyframes(keyframes("add"));
     160    await animationReadyToAnimateAccelerated(animation);
     161    assert_equals(internals.acceleratedAnimationsForElement(target).length, 0);
     162
     163    // Resetting the keyframe composition to "replace" means that the animation
     164    // should be accelerated again.
     165    animation.effect.setKeyframes(keyframes("replace"));
     166    await animationReadyToAnimateAccelerated(animation);
     167    assert_equals(internals.acceleratedAnimationsForElement(target).length, 1);
     168}, "Dynamically setting 'composite' on a keyframe should toggle acceleration");
     169
     170promise_test(async test => {
     171    const target = createDiv(test);
     172    const animations = [];
     173
     174    animations.push(target.animate({ transform: "translateX(100px)" }, duration));
     175    animations.push(target.animate({ transform: "translateY(100px)" }, duration));
     176
     177    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     178    assert_equals(internals.acceleratedAnimationsForElement(target).length, 2, "Step 1");
     179
     180    // Make the second animation a composing animation. This will prevent the first animation
     181    // from running accelerated as well.
     182    animations[1].effect.composite = "add";
     183    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     184    assert_equals(internals.acceleratedAnimationsForElement(target).length, 0, "Step 2");
     185
     186    // Reset to the original state.
     187    animations[1].effect.composite = "replace";
     188    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     189    assert_equals(internals.acceleratedAnimationsForElement(target).length, 2, "Step 3");
     190}, "Dynamically setting 'composite' to 'add' on an animation further up the stack should toggle acceleration on lower animations in the stack");
     191
     192promise_test(async test => {
     193    const target = createDiv(test);
     194
     195    const animations = [];
     196    animations.push(target.animate({ transform: "translateX(100px)" }, duration));
     197    animations.push(target.animate({ transform: ["none", "translateY(100px)"] }, duration));
     198    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     199    assert_equals(internals.acceleratedAnimationsForElement(target).length, 2);
     200
     201    // Make the first animation a composing animation. This will prevent the other animation from running accelerated.
     202    animations[0].effect.composite = "add";
     203    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     204    assert_equals(internals.acceleratedAnimationsForElement(target).length, 0);
     205
     206    // Reset to the original state.
     207    animations[0].effect.composite = "replace";
     208    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     209    assert_equals(internals.acceleratedAnimationsForElement(target).length, 2);
     210}, "Dynamically setting 'composite' to 'add' on an animation lower down the stack should toggle acceleration but always allow replace animations further up the stack to be accelerated");
     211
     212promise_test(async test => {
     213    const target = createDiv(test);
     214    const animations = [];
     215
     216    animations.push(target.animate({ transform: "translateX(100px)" }, duration));
     217    animations.push(target.animate({ transform: "translateX(100px)" }, duration));
     218    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     219    assert_equals(internals.acceleratedAnimationsForElement(target).length, 2);
     220
     221    // Make the first animation a composing animation. This will prevent both animations
     222    // from running accelerated since the second animation uses the first animation as
     223    // input due to using an implicit 0% keyframe.
     224    animations[0].effect.composite = "add";
     225    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     226    assert_equals(internals.acceleratedAnimationsForElement(target).length, 0);
     227
     228    // Reset to the original state.
     229    animations[0].effect.composite = "replace";
     230    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     231    assert_equals(internals.acceleratedAnimationsForElement(target).length, 2);
     232}, "Dynamically setting 'composite' to 'add' on an animation lower down the stack should toggle acceleration on replace animations with an implicity keyframe further up the stack");
     233
     234promise_test(async test => {
     235    const target = createDiv(test);
     236    const animations = [];
     237
     238    animations.push(target.animate({ marginLeft: "100px" }, duration));
     239    animations.push(target.animate({ transform: "translateX(100px)" }, duration));
     240    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     241    assert_equals(internals.acceleratedAnimationsForElement(target).length, 1);
     242
     243    // Make the first animation a composing animation. This should not prevent the other animation from running accelerated.
     244    animations[0].effect.composite = "add";
     245    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     246    assert_equals(internals.acceleratedAnimationsForElement(target).length, 1);
     247
     248    // Reset to the original state.
     249    animations[0].effect.composite = "replace";
     250    await Promise.all(animations.map(animation => animationReadyToAnimateAccelerated(animation)));
     251    assert_equals(internals.acceleratedAnimationsForElement(target).length, 1);
     252}, "Dynamically setting 'composite' to 'add' on an animation lower down the stack targeting a property that isn't accelerated shouldn't prevent acceleration of animations with implicit keyframes further up the stack to be accelerated");
     253
     254promise_test(async test => {
     255    const target = createDiv(test);
     256    const animation = target.animate({ transform: "translateX(100px)" }, duration);
     257
     258    await animationReadyToAnimateAccelerated(animation);
     259    assert_equals(internals.acceleratedAnimationsForElement(target).length, 1);
     260
     261    const composingAnimation = target.animate(
     262        { transform: "translateY(100px)" },
     263        { composite: "add", duration }
     264    );
     265
     266    await animationReadyToAnimateAccelerated(composingAnimation);
     267    assert_equals(internals.acceleratedAnimationsForElement(target).length, 0);
     268}, "Adding a composing effect on top of an existing replace effect should prevent both effects from running accelerated");
     269
    126270</script>
    127271</body>
  • trunk/Source/WebCore/animation/DocumentTimeline.cpp

    r291863 r294391  
    391391    m_acceleratedAnimationsPendingRunningStateChange.clear();
    392392
    393     // Animations may fail to run accelerated for reasons private to GraphicsLayerCA. If that happens, and the animation
    394     // in question targets a transform-related property, we must prevent all other transform-related animations for this
    395     // element to run accelerated since we can't run some transform-related animations accelerated, and some not. To do
    396     // this, we keep a list of all KeyframeEffectStack objects containing an effect that failed to start a transform-related
    397     // animation so that we can return any transform-related accelerated animation to run non-accelerated.
    398     HashSet<KeyframeEffectStack*> effectStacksContainingEffectThatFailedToRunAcceleratedTransformRelatedAnimation;
    399 
    400393    bool hasForcedLayout = false;
    401394    for (auto& animation : acceleratedAnimationsPendingRunningStateChange) {
    … …  
    407400        if (!hasForcedLayout)
    408401            hasForcedLayout |= keyframeEffect.forceLayoutIfNeeded();
    409         auto pendingAccelerationActionResult = keyframeEffect.applyPendingAcceleratedActions();
    410         if (pendingAccelerationActionResult.contains(AcceleratedActionApplicationResult::TransformRelatedAnimationCannotBeAccelerated)) {
    411             ASSERT(keyframeEffect.targetStyleable());
    412             ASSERT(keyframeEffect.targetStyleable()->keyframeEffectStack());
    413             effectStacksContainingEffectThatFailedToRunAcceleratedTransformRelatedAnimation.add(keyframeEffect.targetStyleable()->keyframeEffectStack());
    414         }
    415     }
    416 
    417     for (auto& effectStack : effectStacksContainingEffectThatFailedToRunAcceleratedTransformRelatedAnimation)
    418         effectStack->stopAcceleratingTransformRelatedProperties(UseAcceleratedAction::No);
     402        keyframeEffect.applyPendingAcceleratedActions();
     403    }
    419404}
    420405
  • trunk/Source/WebCore/animation/KeyframeEffect.cpp

    r293879 r294391  
    550550
    551551KeyframeEffect::KeyframeEffect(Element* target, PseudoId pseudoId)
    552     : m_target(target)
     552    : m_keyframesName(makeAtomString("keyframe-effect-"_s, UUID::createVersion4Weak()))
     553    , m_target(target)
    553554    , m_pseudoId(pseudoId)
    554555{
    … …  
    586587    updateStaticTimingProperties();
    587588
    588     KeyframeList keyframeList(makeAtomString("keyframe-effect-"_s, UUID::createVersion4Weak()));
     589    KeyframeList keyframeList(m_keyframesName);
    589590    keyframeList.copyKeyframes(source->m_blendingKeyframes);
    590591    setBlendingKeyframes(keyframeList);
    … …  
    812813        return;
    813814
    814     KeyframeList keyframeList(makeAtomString("keyframe-effect-"_s, UUID::createVersion4Weak()));
     815    KeyframeList keyframeList(m_keyframesName);
    815816    auto& styleResolver = m_target->styleResolver();
    816817
    … …  
    901902void KeyframeEffect::setBlendingKeyframes(KeyframeList& blendingKeyframes)
    902903{
     904    CanBeAcceleratedMutationScope mutationScope(this);
     905
    903906    m_blendingKeyframes = WTFMove(blendingKeyframes);
    904907    m_animatedProperties.clear();
    … …  
    10381041        Style::loadPendingResources(*toStyle, *document(), m_target.get());
    10391042
    1040     KeyframeList keyframeList(makeAtomString("keyframe-effect-"_s, UUID::createVersion4Weak()));
     1043    KeyframeList keyframeList(m_keyframesName);
    10411044    keyframeList.addProperty(property);
    10421045
    … …  
    16011604bool KeyframeEffect::preventsAcceleration() const
    16021605{
    1603     return m_acceleratedPropertiesState != AcceleratedProperties::None && !canBeAccelerated();
     1606    if (m_acceleratedPropertiesState == AcceleratedProperties::None)
     1607        return false;
     1608
     1609    return !canBeAccelerated() || m_runningAccelerated == RunningAccelerated::Failed;
    16041610}
    16051611
    … …  
    16101616        return;
    16111617
    1612     if (!canBeAccelerated()) {
    1613         // In the case where this animation is actively targeting a transform-related property and yet
    1614         // cannot be accelerated, we must notify the effect stack such that any running accelerated
    1615         // transform-related animation targeting this element reverts to running non-accelerated.
    1616         if (isTargetingTransformRelatedProperty()
    1617             && animation()->playState() == WebAnimation::PlayState::Running
    1618             && getComputedTiming().phase == AnimationEffectPhase::Active) {
    1619             ASSERT(targetStyleable());
    1620             ASSERT(targetStyleable()->keyframeEffectStack());
    1621             targetStyleable()->keyframeEffectStack()->stopAcceleratingTransformRelatedProperties(UseAcceleratedAction::Yes);
    1622         }
    1623         return;
    1624     }
     1618    if (!canBeAccelerated())
     1619        return;
    16251620
    16261621    auto computedTiming = getComputedTiming();
    … …  
    16571652void KeyframeEffect::addPendingAcceleratedAction(AcceleratedAction action)
    16581653{
     1654    if (m_runningAccelerated == RunningAccelerated::Prevented || m_runningAccelerated == RunningAccelerated::Failed)
     1655        return;
     1656
    16591657    if (action == m_lastRecordedAcceleratedAction)
    16601658        return;
    … …  
    16781676    computeSomeKeyframesUseStepsTimingFunction();
    16791677
    1680     if (isRunningAccelerated() || isAboutToRunAccelerated())
    1681         addPendingAcceleratedAction(canBeAccelerated() ? AcceleratedAction::UpdateTiming : AcceleratedAction::Stop);
    1682     else if (canBeAccelerated())
     1678    if (isRunningAccelerated() || isAboutToRunAccelerated()) {
     1679        if (canBeAccelerated())
     1680            addPendingAcceleratedAction(AcceleratedAction::UpdateTiming);
     1681        else {
     1682            abilityToBeAcceleratedDidChange();
     1683            addPendingAcceleratedAction(AcceleratedAction::Stop);
     1684        }
     1685    } else if (canBeAccelerated())
    16831686        m_runningAccelerated = RunningAccelerated::NotStarted;
    16841687}
    … …  
    17221725}
    17231726
    1724 OptionSet<AcceleratedActionApplicationResult> KeyframeEffect::applyPendingAcceleratedActions()
    1725 {
    1726     OptionSet<AcceleratedActionApplicationResult> result;
     1727void KeyframeEffect::applyPendingAcceleratedActions()
     1728{
     1729    CanBeAcceleratedMutationScope mutationScope(this);
    17271730
    17281731    // Once an accelerated animation has been committed, we no longer want to force a layout.
    … …  
    17321735
    17331736    if (m_pendingAcceleratedActions.isEmpty())
    1734         return result;
     1737        return;
    17351738
    17361739    auto* renderer = this->renderer();
    … …  
    17421745            m_runningAccelerated = RunningAccelerated::NotStarted;
    17431746        }
    1744         return result;
     1747        return;
    17451748    }
    17461749
    … …  
    17521755
    17531756    auto startAnimation = [&]() -> RunningAccelerated {
    1754         if (m_runningAccelerated == RunningAccelerated::Yes)
     1757        if (isRunningAccelerated())
    17551758            renderer->animationFinished(m_blendingKeyframes.animationName());
    1756 
    1757         if (!m_hasImplicitKeyframeForAcceleratedProperty)
    1758             return renderer->startAnimation(timeOffset, backingAnimationForCompositedRenderer(), m_blendingKeyframes) ? RunningAccelerated::Yes : RunningAccelerated::No;
    17591759
    17601760        ASSERT(m_target);
    … …  
    17621762        ASSERT(effectStack);
    17631763
    1764         if (effectStack->containsEffectThatPreventsAccelerationOfEffect(*this))
    1765             return RunningAccelerated::No;
     1764        if (!effectStack->allowsAcceleration())
     1765            return RunningAccelerated::Prevented;
     1766
     1767        if (!m_hasImplicitKeyframeForAcceleratedProperty)
     1768            return renderer->startAnimation(timeOffset, backingAnimationForCompositedRenderer(), m_blendingKeyframes) ? RunningAccelerated::Yes : RunningAccelerated::Failed;
    17661769
    17671770        // We need to resolve all animations up to this point to ensure any forward-filling
    … …  
    17831786        explicitKeyframes.copyKeyframes(m_blendingKeyframes);
    17841787        explicitKeyframes.fillImplicitKeyframes(*this, *underlyingStyle);
    1785         return renderer->startAnimation(timeOffset, backingAnimationForCompositedRenderer(), explicitKeyframes) ? RunningAccelerated::Yes : RunningAccelerated::No;
     1788        return renderer->startAnimation(timeOffset, backingAnimationForCompositedRenderer(), explicitKeyframes) ? RunningAccelerated::Yes : RunningAccelerated::Failed;
    17861789    };
    17871790
    … …  
    17901793        case AcceleratedAction::Play:
    17911794            m_runningAccelerated = startAnimation();
    1792             LOG_WITH_STREAM(Animations, stream << "KeyframeEffect " << this << " applyPendingAcceleratedActions " << m_blendingKeyframes.animationName() << " Play, started accelerated: " << (m_runningAccelerated == RunningAccelerated::Yes));
    1793             if (m_runningAccelerated == RunningAccelerated::No) {
     1795            LOG_WITH_STREAM(Animations, stream << "KeyframeEffect " << this << " applyPendingAcceleratedActions " << m_blendingKeyframes.animationName() << " Play, started accelerated: " << isRunningAccelerated());
     1796            if (!isRunningAccelerated()) {
    17941797                m_lastRecordedAcceleratedAction = AcceleratedAction::Stop;
    1795                 if (isTargetingTransformRelatedProperty())
    1796                     result.add(AcceleratedActionApplicationResult::TransformRelatedAnimationCannotBeAccelerated);
    1797                 return result;
     1798                return;
    17981799            }
    17991800            break;
    … …  
    18031804        case AcceleratedAction::UpdateTiming:
    18041805            m_runningAccelerated = startAnimation();
    1805             LOG_WITH_STREAM(Animations, stream << "KeyframeEffect " << this << " applyPendingAcceleratedActions " << m_blendingKeyframes.animationName() << " UpdateTiming, started accelerated: " << (m_runningAccelerated == RunningAccelerated::Yes));
     1806            LOG_WITH_STREAM(Animations, stream << "KeyframeEffect " << this << " applyPendingAcceleratedActions " << m_blendingKeyframes.animationName() << " UpdateTiming, started accelerated: " << isRunningAccelerated());
    18061807            if (animation()->playState() == WebAnimation::PlayState::Paused)
    18071808                renderer->animationPaused(timeOffset, m_blendingKeyframes.animationName());
    … …  
    18121813            if (!document()->renderTreeBeingDestroyed())
    18131814                m_target->invalidateStyleAndLayerComposition();
    1814             m_runningAccelerated = canBeAccelerated() ? RunningAccelerated::NotStarted : RunningAccelerated::No;
     1815            m_runningAccelerated = canBeAccelerated() ? RunningAccelerated::NotStarted : RunningAccelerated::Prevented;
    18151816            break;
    18161817        case AcceleratedAction::TransformChange:
    … …  
    18201821    }
    18211822
    1822     if (m_runningAccelerated == RunningAccelerated::No && isTargetingTransformRelatedProperty())
    1823         result.add(AcceleratedActionApplicationResult::TransformRelatedAnimationCannotBeAccelerated);
    1824 
    1825     return result;
    1826 }
    1827 
    1828 void KeyframeEffect::stopAcceleratingTransformRelatedProperties(UseAcceleratedAction useAcceleratedAction)
    1829 {
    1830     if (!isRunningAcceleratedTransformRelatedAnimation())
    1831         return;
    1832 
    1833     if (useAcceleratedAction == UseAcceleratedAction::Yes) {
    1834         addPendingAcceleratedAction(AcceleratedAction::Stop);
    1835         return;
    1836     }
    1837 
    1838     auto* renderer = this->renderer();
    1839     if (!renderer || !renderer->isComposited())
    1840         return;
    1841 
    1842     ASSERT(document());
    1843     renderer->animationFinished(m_blendingKeyframes.animationName());
    1844     if (!document()->renderTreeBeingDestroyed())
    1845         m_target->invalidateStyleAndLayerComposition();
    1846 
    1847     m_runningAccelerated = RunningAccelerated::No;
     1823    return;
    18481824}
    18491825
    … …  
    21382114        return;
    21392115
     2116    CanBeAcceleratedMutationScope mutationScope(this);
    21402117    m_compositeOperation = compositeOperation;
    21412118    invalidate();
    … …  
    22672244}
    22682245
     2246void KeyframeEffect::effectStackNoLongerPreventsAcceleration()
     2247{
     2248    if (m_runningAccelerated == RunningAccelerated::Failed)
     2249        return;
     2250
     2251    if (m_runningAccelerated == RunningAccelerated::Prevented)
     2252        m_runningAccelerated = RunningAccelerated::NotStarted;
     2253
     2254    updateAcceleratedActions();
     2255}
     2256
     2257void KeyframeEffect::effectStackNoLongerAllowsAcceleration()
     2258{
     2259    addPendingAcceleratedAction(AcceleratedAction::Stop);
     2260}
     2261
     2262void KeyframeEffect::abilityToBeAcceleratedDidChange()
     2263{
     2264    if (!m_inTargetEffectStack)
     2265        return;
     2266
     2267    ASSERT(m_target);
     2268    if (auto* effectStack = m_target->keyframeEffectStack(m_pseudoId))
     2269        effectStack->effectAbilityToBeAcceleratedDidChange(*this);
     2270}
     2271
     2272KeyframeEffect::CanBeAcceleratedMutationScope::CanBeAcceleratedMutationScope(KeyframeEffect* effect)
     2273    : m_effect(effect)
     2274{
     2275    ASSERT(effect);
     2276    m_couldOriginallyPreventAcceleration = effect->preventsAcceleration();
     2277}
     2278
     2279KeyframeEffect::CanBeAcceleratedMutationScope::~CanBeAcceleratedMutationScope()
     2280{
     2281    if (m_effect && m_couldOriginallyPreventAcceleration != m_effect->preventsAcceleration())
     2282        m_effect->abilityToBeAcceleratedDidChange();
     2283}
     2284
    22692285} // namespace WebCore
  • trunk/Source/WebCore/animation/KeyframeEffect.h

    r293292 r294391  
    128128    void transformRelatedPropertyDidChange();
    129129    void propertyAffectingKeyframeResolutionDidChange(RenderStyle&, const Style::ResolutionContext&);
    130     OptionSet<AcceleratedActionApplicationResult> applyPendingAcceleratedActions();
     130    void applyPendingAcceleratedActions();
    131131
    132132    void willChangeRenderer();
    … …  
    173173    bool hasImplicitKeyframes() const;
    174174
    175     void stopAcceleratingTransformRelatedProperties(UseAcceleratedAction);
    176 
    177175    void keyframesRuleDidChange();
    178176
     177    bool canBeAccelerated() const;
    179178    bool preventsAcceleration() const;
     179    void effectStackNoLongerPreventsAcceleration();
     180    void effectStackNoLongerAllowsAcceleration();
    180181
    181182    static String CSSPropertyIDToIDLAttributeName(CSSPropertyID);
    … …  
    187188    enum class BlendingKeyframesSource : uint8_t { CSSAnimation, CSSTransition, WebAnimation };
    188189    enum class AcceleratedProperties : uint8_t { None, Some, All };
    189     enum class RunningAccelerated : uint8_t { NotStarted, Yes, No };
     190    enum class RunningAccelerated : uint8_t { NotStarted, Yes, Prevented, Failed };
     191
     192    class CanBeAcceleratedMutationScope {
     193        WTF_MAKE_NONCOPYABLE(CanBeAcceleratedMutationScope);
     194    public:
     195        CanBeAcceleratedMutationScope(KeyframeEffect*);
     196        ~CanBeAcceleratedMutationScope();
     197
     198    private:
     199        KeyframeEffect* m_effect;
     200        bool m_couldOriginallyPreventAcceleration;
     201    };
    190202
    191203    Document* document() const;
    … …  
    195207    ExceptionOr<void> processKeyframes(JSC::JSGlobalObject&, Document&, JSC::Strong<JSC::JSObject>&&);
    196208    void addPendingAcceleratedAction(AcceleratedAction);
    197     bool canBeAccelerated() const;
    198209    bool isCompletelyAccelerated() const { return m_acceleratedPropertiesState == AcceleratedProperties::All; }
    199210    void updateAcceleratedActions();
    … …  
    221232    void computeHasImplicitKeyframeForAcceleratedProperty();
    222233    void computeHasKeyframeComposingAcceleratedProperty();
     234    void abilityToBeAcceleratedDidChange();
    223235
    224236    // AnimationEffect
    … …  
    234246    std::optional<double> progressUntilNextStep(double) const final;
    235247
     248    AtomString m_keyframesName;
    236249    KeyframeList m_blendingKeyframes { emptyAtom() };
    237250    HashSet<CSSPropertyID> m_animatedProperties;
    … …  
    248261    AcceleratedProperties m_acceleratedPropertiesState { AcceleratedProperties::None };
    249262    AnimationEffectPhase m_phaseAtLastApplication { AnimationEffectPhase::Idle };
    250     RunningAccelerated m_runningAccelerated { RunningAccelerated::No };
     263    RunningAccelerated m_runningAccelerated { RunningAccelerated::NotStarted };
    251264    bool m_needsForcedLayout { false };
    252265    bool m_triggersStackingContext { false };
  • trunk/Source/WebCore/animation/KeyframeEffectStack.cpp

    r290831 r294391  
    5555    m_effects.append(effect);
    5656    m_isSorted = false;
     57
     58    if (m_effects.size() > 1 && effect.preventsAcceleration())
     59        stopAcceleratedAnimations();
     60
    5761    return true;
    5862}
    … …  
    6064void KeyframeEffectStack::removeEffect(KeyframeEffect& effect)
    6165{
    62     m_effects.removeFirst(&effect);
     66    auto removedEffect = m_effects.removeFirst(&effect);
     67    if (removedEffect && !m_effects.isEmpty() && !effect.canBeAccelerated())
     68        startAcceleratedAnimationsIfPossible();
    6369}
    6470
    … …  
    173179}
    174180
    175 void KeyframeEffectStack::stopAcceleratingTransformRelatedProperties(UseAcceleratedAction useAcceleratedAction)
    176 {
     181void KeyframeEffectStack::clearInvalidCSSAnimationNames()
     182{
     183    m_invalidCSSAnimationNames.clear();
     184}
     185
     186bool KeyframeEffectStack::hasInvalidCSSAnimationNames() const
     187{
     188    return !m_invalidCSSAnimationNames.isEmpty();
     189}
     190
     191bool KeyframeEffectStack::containsInvalidCSSAnimationName(const String& name) const
     192{
     193    return m_invalidCSSAnimationNames.contains(name);
     194}
     195
     196void KeyframeEffectStack::addInvalidCSSAnimationName(const String& name)
     197{
     198    m_invalidCSSAnimationNames.add(name);
     199}
     200
     201void KeyframeEffectStack::effectAbilityToBeAcceleratedDidChange(const KeyframeEffect& effect)
     202{
     203    ASSERT(m_effects.contains(&effect));
     204    if (effect.preventsAcceleration())
     205        stopAcceleratedAnimations();
     206    else
     207        startAcceleratedAnimationsIfPossible();
     208}
     209
     210bool KeyframeEffectStack::allowsAcceleration() const
     211{
     212    // We could try and be a lot smarter here and do this on a per-property basis and
     213    // account for fully replacing effects which could co-exist with effects that
     214    // don't support acceleration lower in the stack, etc. But, if we are not able to run
     215    // all effects that could support acceleration using acceleration, then we might
     216    // as well not run any at all since we'll be updating effects for this stack
     217    // for each animation frame. So for now, we simply return false if any effect in the
     218    // stack is unable to be accelerated.
     219    for (auto& effect : m_effects) {
     220        if (effect->preventsAcceleration())
     221            return false;
     222    }
     223    return true;
     224}
     225
     226void KeyframeEffectStack::startAcceleratedAnimationsIfPossible()
     227{
     228    if (!allowsAcceleration())
     229        return;
     230
    177231    for (auto& effect : m_effects)
    178         effect->stopAcceleratingTransformRelatedProperties(useAcceleratedAction);
    179 }
    180 
    181 void KeyframeEffectStack::clearInvalidCSSAnimationNames()
    182 {
    183     m_invalidCSSAnimationNames.clear();
    184 }
    185 
    186 bool KeyframeEffectStack::hasInvalidCSSAnimationNames() const
    187 {
    188     return !m_invalidCSSAnimationNames.isEmpty();
    189 }
    190 
    191 bool KeyframeEffectStack::containsInvalidCSSAnimationName(const String& name) const
    192 {
    193     return m_invalidCSSAnimationNames.contains(name);
    194 }
    195 
    196 void KeyframeEffectStack::addInvalidCSSAnimationName(const String& name)
    197 {
    198     m_invalidCSSAnimationNames.add(name);
    199 }
    200 
    201 bool KeyframeEffectStack::containsEffectThatPreventsAccelerationOfEffect(const KeyframeEffect& potentiallyAcceleratedEffect)
    202 {
    203     ensureEffectsAreSorted();
    204 
    205     for (auto& effect : m_effects) {
    206         if (effect.get() == &potentiallyAcceleratedEffect)
    207             continue;
    208         if (effect->preventsAcceleration())
    209             return true;
    210     }
    211 
    212     return false;
     232        effect->effectStackNoLongerPreventsAcceleration();
     233}
     234
     235void KeyframeEffectStack::stopAcceleratedAnimations()
     236{
     237    for (auto& effect : m_effects)
     238        effect->effectStackNoLongerAllowsAcceleration();
    213239}
    214240
  • trunk/Source/WebCore/animation/KeyframeEffectStack.h

    r290730 r294391  
    5858    bool hasEffectWithImplicitKeyframes() const;
    5959
    60     bool containsEffectThatPreventsAccelerationOfEffect(const KeyframeEffect&);
    61 
    62     void stopAcceleratingTransformRelatedProperties(UseAcceleratedAction);
     60    void effectAbilityToBeAcceleratedDidChange(const KeyframeEffect&);
     61    bool allowsAcceleration() const;
    6362
    6463    void clearInvalidCSSAnimationNames();
    … …  
    7069    void ensureEffectsAreSorted();
    7170
     71    void startAcceleratedAnimationsIfPossible();
     72    void stopAcceleratedAnimations();
     73
    7274    Vector<WeakPtr<KeyframeEffect>> m_effects;
    7375    HashSet<String> m_invalidCSSAnimationNames;
  • trunk/Source/WebCore/animation/WebAnimationTypes.h

    r271524 r294391  
    5656};
    5757
    58 enum class AcceleratedActionApplicationResult {
    59     TransformRelatedAnimationCannotBeAccelerated  = 1 << 0
    60 };
    61 
    6258enum class UseAcceleratedAction : uint8_t { Yes, No };
    6359
Note: See TracChangeset for help on using the changeset viewer.