Changeset 261926 in webkit


Ignore:
Timestamp:
May 20, 2020 9:26:16 AM (4 years ago)
Author:
graouts@webkit.org
Message:

[Web Animations] Animation engine should not wake up every tick for steps timing functions
https://bugs.webkit.org/show_bug.cgi?id=212103
<rdar://problem/62737868>

Reviewed by Simon Fraser.

Source/WebCore:

Tests: webanimations/scheduling-of-animation-with-steps-timing-function-on-effect.html

webanimations/scheduling-of-animation-with-steps-timing-function-on-keyframe.html
webanimations/scheduling-of-css-animation-with-explicit-steps-timing-function-on-some-keyframes.html
webanimations/scheduling-of-css-animation-with-implicit-steps-timing-function.html

When an animation uses a steps() timing function, it will appear to animate discretely between values such
that there is only n visual changes, where n is the number of steps provided. This gives us an opportunity
to be more efficient when scheduling animations using steps() timing functions.

In WebAnimation::timeToNextTick() we now ask the associated effect for the amount of progress until the next
step. For an effect-wide steps() timing function, we can use the provided iteration progress. For animations
with a linear effect-wide timing function (the default), we have to map the provided iteration progress to
a keyframe interval, provided that interval uses a steps() timing function.

The new {Animation|Keyframe}Effect::progressUntilNextStep() method returns WTF::nullopt for any other case.

In order to test this, we add a new internals.timeToNextAnimationTick(animation) method which we use in the
two new tests.

  • animation/AnimationEffect.cpp:

(WebCore::AnimationEffect::progressUntilNextStep const):

  • animation/AnimationEffect.h:
  • animation/KeyframeEffect.cpp:

(WebCore::KeyframeEffect::setBlendingKeyframes):
(WebCore::KeyframeEffect::computeSomeKeyframesUseStepsTimingFunction):
(WebCore::KeyframeEffect::timingFunctionForKeyframeAtIndex const): Avoid any out-of-bounds use of the underlying data
structures by returning nullptr for cases where we don't have an explicit keyframe. We also make the function const
such that it may be called from progressUntilNextStep(), it always was const but wasn't marked as such.
(WebCore::KeyframeEffect::progressUntilNextStep const):

  • animation/KeyframeEffect.h:
  • animation/WebAnimation.cpp:

(WebCore::WebAnimation::timeToNextTick const):

  • animation/WebAnimation.h:
  • animation/WebAnimation.idl:
  • testing/Internals.cpp:

(WebCore::Internals::timeToNextAnimationTick const):

  • testing/Internals.h:
  • testing/Internals.idl:

Source/WTF:

Allow Seconds to be divided or multiplied by a double with operands in any order.

  • wtf/Seconds.h:

(WTF::operator*):
(WTF::operator/):

LayoutTests:

Add tests that check that an animation using a steps() timing function correctly computes the time to
the next tick accouning for the fact that it won't compute a different iteration progress until the
next step.

  • webanimations/scheduling-of-animation-with-steps-timing-function-on-effect-expected.txt: Added.
  • webanimations/scheduling-of-animation-with-steps-timing-function-on-effect.html: Added.
  • webanimations/scheduling-of-animation-with-steps-timing-function-on-keyframe-expected.txt: Added.
  • webanimations/scheduling-of-animation-with-steps-timing-function-on-keyframe.html: Added.
  • webanimations/scheduling-of-css-animation-with-explicit-steps-timing-function-on-some-keyframes-expected.txt: Added.
  • webanimations/scheduling-of-css-animation-with-explicit-steps-timing-function-on-some-keyframes.html: Added.
  • webanimations/scheduling-of-css-animation-with-implicit-steps-timing-function-expected.txt: Added.
  • webanimations/scheduling-of-css-animation-with-implicit-steps-timing-function.html: Added.
Location:
trunk
Files:
8 added
16 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r261924 r261926  
     12020-05-20  Antoine Quint  <graouts@apple.com>
     2
     3        [Web Animations] Animation engine should not wake up every tick for steps timing functions
     4        https://bugs.webkit.org/show_bug.cgi?id=212103
     5        <rdar://problem/62737868>
     6
     7        Reviewed by Simon Fraser.
     8
     9        Add tests that check that an animation using a steps() timing function correctly computes the time to
     10        the next tick accouning for the fact that it won't compute a different iteration progress until the
     11        next step.
     12
     13        * webanimations/scheduling-of-animation-with-steps-timing-function-on-effect-expected.txt: Added.
     14        * webanimations/scheduling-of-animation-with-steps-timing-function-on-effect.html: Added.
     15        * webanimations/scheduling-of-animation-with-steps-timing-function-on-keyframe-expected.txt: Added.
     16        * webanimations/scheduling-of-animation-with-steps-timing-function-on-keyframe.html: Added.
     17        * webanimations/scheduling-of-css-animation-with-explicit-steps-timing-function-on-some-keyframes-expected.txt: Added.
     18        * webanimations/scheduling-of-css-animation-with-explicit-steps-timing-function-on-some-keyframes.html: Added.
     19        * webanimations/scheduling-of-css-animation-with-implicit-steps-timing-function-expected.txt: Added.
     20        * webanimations/scheduling-of-css-animation-with-implicit-steps-timing-function.html: Added.
     21
    1222020-05-20  Noam Rosenthal  <noam@webkit.org>
    223
  • trunk/Source/WTF/ChangeLog

    r261915 r261926  
     12020-05-20  Antoine Quint  <graouts@apple.com>
     2
     3        [Web Animations] Animation engine should not wake up every tick for steps timing functions
     4        https://bugs.webkit.org/show_bug.cgi?id=212103
     5        <rdar://problem/62737868>
     6
     7        Reviewed by Simon Fraser.
     8
     9        Allow Seconds to be divided or multiplied by a double with operands in any order.
     10
     11        * wtf/Seconds.h:
     12        (WTF::operator*):
     13        (WTF::operator/):
     14
    1152020-05-20  Youenn Fablet  <youenn@apple.com>
    216
  • trunk/Source/WTF/wtf/Seconds.h

    r259917 r261926  
    333333} // inline seconds_literals
    334334
     335inline Seconds operator*(double scalar, Seconds seconds)
     336{
     337    return Seconds(scalar * seconds.value());
     338}
     339
     340inline Seconds operator/(double scalar, Seconds seconds)
     341{
     342    return Seconds(scalar / seconds.value());
     343}
     344
    335345WTF_EXPORT_PRIVATE TextStream& operator<<(TextStream&, Seconds);
    336346
  • trunk/Source/WebCore/ChangeLog

    r261924 r261926  
     12020-05-20  Antoine Quint  <graouts@apple.com>
     2
     3        [Web Animations] Animation engine should not wake up every tick for steps timing functions
     4        https://bugs.webkit.org/show_bug.cgi?id=212103
     5        <rdar://problem/62737868>
     6
     7        Reviewed by Simon Fraser.
     8
     9        Tests: webanimations/scheduling-of-animation-with-steps-timing-function-on-effect.html
     10               webanimations/scheduling-of-animation-with-steps-timing-function-on-keyframe.html
     11               webanimations/scheduling-of-css-animation-with-explicit-steps-timing-function-on-some-keyframes.html
     12               webanimations/scheduling-of-css-animation-with-implicit-steps-timing-function.html
     13
     14        When an animation uses a steps() timing function, it will appear to animate discretely between values such
     15        that there is only n visual changes, where n is the number of steps provided. This gives us an opportunity
     16        to be more efficient when scheduling animations using steps() timing functions.
     17
     18        In WebAnimation::timeToNextTick() we now ask the associated effect for the amount of progress until the next
     19        step. For an effect-wide steps() timing function, we can use the provided iteration progress. For animations
     20        with a linear effect-wide timing function (the default), we have to map the provided iteration progress to
     21        a keyframe interval, provided that interval uses a steps() timing function.
     22
     23        The new {Animation|Keyframe}Effect::progressUntilNextStep() method returns WTF::nullopt for any other case.
     24
     25        In order to test this, we add a new internals.timeToNextAnimationTick(animation) method which we use in the
     26        two new tests.
     27
     28        * animation/AnimationEffect.cpp:
     29        (WebCore::AnimationEffect::progressUntilNextStep const):
     30        * animation/AnimationEffect.h:
     31        * animation/KeyframeEffect.cpp:
     32        (WebCore::KeyframeEffect::setBlendingKeyframes):
     33        (WebCore::KeyframeEffect::computeSomeKeyframesUseStepsTimingFunction):
     34        (WebCore::KeyframeEffect::timingFunctionForKeyframeAtIndex const): Avoid any out-of-bounds use of the underlying data
     35        structures by returning nullptr for cases where we don't have an explicit keyframe. We also make the function const
     36        such that it may be called from progressUntilNextStep(), it always was const but wasn't marked as such.
     37        (WebCore::KeyframeEffect::progressUntilNextStep const):
     38        * animation/KeyframeEffect.h:
     39        * animation/WebAnimation.cpp:
     40        (WebCore::WebAnimation::timeToNextTick const):
     41        * animation/WebAnimation.h:
     42        * animation/WebAnimation.idl:
     43        * testing/Internals.cpp:
     44        (WebCore::Internals::timeToNextAnimationTick const):
     45        * testing/Internals.h:
     46        * testing/Internals.idl:
     47
    1482020-05-20  Noam Rosenthal  <noam@webkit.org>
    249
  • trunk/Source/WebCore/animation/AnimationEffect.cpp

    r261637 r261926  
    542542}
    543543
     544Optional<double> AnimationEffect::progressUntilNextStep(double iterationProgress) const
     545{
     546    if (!is<StepsTimingFunction>(m_timingFunction))
     547        return WTF::nullopt;
     548
     549    auto numberOfSteps = downcast<StepsTimingFunction>(*m_timingFunction).numberOfSteps();
     550    auto nextStepProgress = ceil(iterationProgress * numberOfSteps) / numberOfSteps;
     551    return nextStepProgress - iterationProgress;
     552}
     553
    544554} // namespace WebCore
  • trunk/Source/WebCore/animation/AnimationEffect.h

    r261637 r261926  
    103103    void updateStaticTimingProperties();
    104104
     105    virtual Optional<double> progressUntilNextStep(double) const;
     106
    105107protected:
    106108    explicit AnimationEffect();
  • trunk/Source/WebCore/animation/AnimationTimeline.cpp

    r261029 r261926  
    400400            // start value of the transition shoud be to make sure that we don't account for animated values that would have been blended onto
    401401            // the style applied during the last style resolution.
     402
     403            // FIXME: NO. We should be applying animations with the current time.
     404
    402405            if (auto* unanimatedStyle = keyframeEffect->unanimatedStyle())
    403406                return RenderStyle::clone(*unanimatedStyle);
  • trunk/Source/WebCore/animation/KeyframeEffect.cpp

    r261756 r261926  
    847847    computeStackingContextImpact();
    848848    computeAcceleratedPropertiesState();
     849    computeSomeKeyframesUseStepsTimingFunction();
    849850
    850851    checkForMatchingTransformFunctionLists();
     
    12641265    else
    12651266        m_acceleratedPropertiesState = AcceleratedProperties::All;
     1267}
     1268
     1269void KeyframeEffect::computeSomeKeyframesUseStepsTimingFunction()
     1270{
     1271    m_someKeyframesUseStepsTimingFunction = false;
     1272
     1273    size_t numberOfKeyframes = m_blendingKeyframes.size();
     1274
     1275    // If we're dealing with a CSS Animation and it specifies a default steps() timing function,
     1276    // we need to check that any of the specified keyframes either does not have an explicit timing
     1277    // function or specifies an explicit steps() timing function.
     1278    if (is<CSSAnimation>(animation()) && is<StepsTimingFunction>(downcast<DeclarativeAnimation>(*animation()).backingAnimation().timingFunction())) {
     1279        for (size_t i = 0; i < numberOfKeyframes; i++) {
     1280            auto* timingFunction = m_blendingKeyframes[i].timingFunction();
     1281            if (!timingFunction || is<StepsTimingFunction>(timingFunction)) {
     1282                m_someKeyframesUseStepsTimingFunction = true;
     1283                return;
     1284            }
     1285        }
     1286        return;
     1287    }
     1288
     1289    // For any other type of animation, we just need to check whether any of the keyframes specify
     1290    // an explicit steps() timing function.
     1291    for (size_t i = 0; i < numberOfKeyframes; i++) {
     1292        if (is<StepsTimingFunction>(m_blendingKeyframes[i].timingFunction())) {
     1293            m_someKeyframesUseStepsTimingFunction = true;
     1294            return;
     1295        }
     1296    }
    12661297}
    12671298
     
    14401471}
    14411472
    1442 TimingFunction* KeyframeEffect::timingFunctionForKeyframeAtIndex(size_t index)
    1443 {
    1444     if (!m_parsedKeyframes.isEmpty())
     1473TimingFunction* KeyframeEffect::timingFunctionForKeyframeAtIndex(size_t index) const
     1474{
     1475    if (!m_parsedKeyframes.isEmpty()) {
     1476        if (index >= m_parsedKeyframes.size())
     1477            return nullptr;
    14451478        return m_parsedKeyframes[index].timingFunction.get();
     1479    }
    14461480
    14471481    auto effectAnimation = animation();
     
    14491483        // If we're dealing with a CSS Animation, the timing function is specified either on the keyframe itself.
    14501484        if (is<CSSAnimation>(effectAnimation)) {
     1485            if (index >= m_blendingKeyframes.size())
     1486                return nullptr;
    14511487            if (auto* timingFunction = m_blendingKeyframes[index].timingFunction())
    14521488                return timingFunction;
     
    17961832}
    17971833
     1834Optional<double> KeyframeEffect::progressUntilNextStep(double iterationProgress) const
     1835{
     1836    ASSERT(iterationProgress >= 0 && iterationProgress <= 1);
     1837
     1838    if (auto progress = AnimationEffect::progressUntilNextStep(iterationProgress))
     1839        return progress;
     1840
     1841    if (!is<LinearTimingFunction>(timingFunction()) || !m_someKeyframesUseStepsTimingFunction)
     1842        return WTF::nullopt;
     1843
     1844    if (m_blendingKeyframes.isEmpty())
     1845        return WTF::nullopt;
     1846
     1847    auto progressUntilNextStepInInterval = [iterationProgress](double intervalStartProgress, double intervalEndProgress, TimingFunction* timingFunction) -> Optional<double> {
     1848        if (!is<StepsTimingFunction>(timingFunction))
     1849            return WTF::nullopt;
     1850
     1851        auto numberOfSteps = downcast<StepsTimingFunction>(*timingFunction).numberOfSteps();
     1852        auto intervalProgress = intervalEndProgress - intervalStartProgress;
     1853        auto iterationProgressMappedToCurrentInterval = (iterationProgress - intervalStartProgress) / intervalProgress;
     1854        auto nextStepProgress = ceil(iterationProgressMappedToCurrentInterval * numberOfSteps) / numberOfSteps;
     1855        return (nextStepProgress - iterationProgressMappedToCurrentInterval) * intervalProgress;
     1856    };
     1857
     1858    for (size_t i = 0; i < m_blendingKeyframes.size(); ++i) {
     1859        auto intervalEndProgress = m_blendingKeyframes[i].key();
     1860        // We can stop once we find a keyframe for which the progress is more than the provided iteration progress.
     1861        if (intervalEndProgress <= iterationProgress)
     1862            continue;
     1863
     1864        // In case we're on the first keyframe, then this means we are dealing with an implicit 0% keyframe.
     1865        // This will be a linear timing function unless we're dealing with a CSS Animation which might have
     1866        // the default timing function for its keyframes defined on its backing Animation object.
     1867        if (!i) {
     1868            if (is<CSSAnimation>(animation()))
     1869                return progressUntilNextStepInInterval(0, intervalEndProgress, downcast<DeclarativeAnimation>(*animation()).backingAnimation().timingFunction());
     1870            return WTF::nullopt;
     1871        }
     1872
     1873        return progressUntilNextStepInInterval(m_blendingKeyframes[i - 1].key(), intervalEndProgress, timingFunctionForKeyframeAtIndex(i - 1));
     1874    }
     1875
     1876    // If we end up here, then this means we are dealing with an implicit 100% keyframe.
     1877    // This will be a linear timing function unless we're dealing with a CSS Animation which might have
     1878    // the default timing function for its keyframes defined on its backing Animation object.
     1879    auto& lastExplicitKeyframe = m_blendingKeyframes[m_blendingKeyframes.size() - 1];
     1880    if (is<CSSAnimation>(animation()))
     1881        return progressUntilNextStepInInterval(lastExplicitKeyframe.key(), 1, downcast<DeclarativeAnimation>(*animation()).backingAnimation().timingFunction());
     1882
     1883    // In any other case, we are not dealing with an interval with a steps() timing function.
     1884    return WTF::nullopt;
     1885}
     1886
    17981887} // namespace WebCore
  • trunk/Source/WebCore/animation/KeyframeEffect.h

    r261637 r261926  
    182182    void updateAcceleratedActions();
    183183    void setAnimatedPropertiesInStyle(RenderStyle&, double);
    184     TimingFunction* timingFunctionForKeyframeAtIndex(size_t);
     184    TimingFunction* timingFunctionForKeyframeAtIndex(size_t) const;
    185185    Ref<const Animation> backingAnimationForCompositedRenderer() const;
    186186    void computedNeedsForcedLayout();
    187187    void computeStackingContextImpact();
     188    void computeSomeKeyframesUseStepsTimingFunction();
    188189    void clearBlendingKeyframes();
    189190    void updateBlendingKeyframes(RenderStyle&);
     
    192193    void computeAcceleratedPropertiesState();
    193194    void setBlendingKeyframes(KeyframeList&);
     195    Optional<double> progressUntilNextStep(double) const final;
    194196    void checkForMatchingTransformFunctionLists();
    195197    void checkForMatchingFilterFunctionLists();
     
    223225    bool m_colorFilterFunctionListsMatch { false };
    224226    bool m_inTargetEffectStack { false };
     227    bool m_someKeyframesUseStepsTimingFunction { false };
    225228};
    226229
  • trunk/Source/WebCore/animation/WebAnimation.cpp

    r261637 r261926  
    14831483            return (effect.endTime() - timing.localTime.value()) / playbackRate;
    14841484        }
     1485        if (auto iterationProgress = effect.getComputedTiming().simpleIterationProgress) {
     1486            // In case we're in a range that uses a steps() timing function, we can compute the time until the next step starts.
     1487            if (auto progressUntilNextStep = effect.progressUntilNextStep(*iterationProgress))
     1488                return effect.iterationDuration() * *progressUntilNextStep / playbackRate;
     1489        }
    14851490        // Other animations in the "active" phase will need to update their animated value at the immediate next opportunity.
    14861491        return 0_s;
  • trunk/Source/WebCore/animation/WebAnimation.h

    r261637 r261926  
    121121    bool needsTick() const;
    122122    virtual void tick();
    123     Seconds timeToNextTick() const;
     123    WEBCORE_EXPORT Seconds timeToNextTick() const;
    124124    virtual void resolve(RenderStyle&);
    125125    void effectTargetDidChange(Element* previousTarget, Element* newTarget);
  • trunk/Source/WebCore/animation/WebAnimation.idl

    r260671 r261926  
    4242    InterfaceName=Animation,
    4343    CustomConstructor(),
    44     CustomToJSObject
     44    CustomToJSObject,
     45    ExportMacro=WEBCORE_EXPORT
    4546] interface WebAnimation : EventTarget {
    4647    attribute DOMString id;
  • trunk/Source/WebCore/style/StyleTreeResolver.cpp

    r260774 r261926  
    318318                m_document.timeline().updateCSSTransitionsForElement(element, *oldStyle, *newStyle);
    319319
     320            // FIXME: Maybe need to do this first such that CSS Transitions are aware of newly created or canceled animations
    320321            if ((oldStyle && oldStyle->hasAnimations()) || newStyle->hasAnimations())
    321322                m_document.timeline().updateCSSAnimationsForElement(element, oldStyle, *newStyle);
  • trunk/Source/WebCore/testing/Internals.cpp

    r261897 r261926  
    197197#include "VoidCallback.h"
    198198#include "WebAnimation.h"
     199#include "WebAnimationUtilities.h"
    199200#include "WebCoreJSClientData.h"
    200201#include "WindowProxy.h"
     
    11991200        return frame()->document()->timeline().numberOfAnimationTimelineInvalidationsForTesting();
    12001201    return 0;
     1202}
     1203
     1204double Internals::timeToNextAnimationTick(WebAnimation& animation) const
     1205{
     1206    return secondsToWebAnimationsAPITime(animation.timeToNextTick());
    12011207}
    12021208
  • trunk/Source/WebCore/testing/Internals.h

    r261897 r261926  
    107107class UnsuspendableActiveDOMObject;
    108108class VoidCallback;
     109class WebAnimation;
    109110class WebGLRenderingContext;
    110111class WindowProxy;
     
    240241    Vector<AcceleratedAnimation> acceleratedAnimationsForElement(Element&);
    241242    unsigned numberOfAnimationTimelineInvalidations() const;
     243    double timeToNextAnimationTick(WebAnimation&) const;
    242244
    243245    // For animations testing, we need a way to get at pseudo elements.
  • trunk/Source/WebCore/testing/Internals.idl

    r261897 r261926  
    298298    sequence<AcceleratedAnimation> acceleratedAnimationsForElement(Element element);
    299299    unsigned long numberOfAnimationTimelineInvalidations();
     300    double timeToNextAnimationTick(WebAnimation animation);
    300301
    301302    // For animations testing, we need a way to get at pseudo elements.
Note: See TracChangeset for help on using the changeset viewer.