Commit 80dca7a2 authored by Majid Valipour's avatar Majid Valipour Committed by Commit Bot

[animation-worklet] Worklet animation updates the effect time and style

Make WorkletAnimation implement AnimationEffectOwner interface which
enables it to attach to its effect and update its inherited time.

For now, we use "0" as inherited time of the effect but follow up
patches will instead use local time values that will be plumbed from
animation worklet instead.

TODO:
 - Plumb the time values from worklet and use that
 - WorkletAnimation should allow its effect to cancel and restart it.

Bug: 814851
Change-Id: I8f0b9ff914130cdf73d0e9df41dec13ccc03bcf9
Reviewed-on: https://chromium-review.googlesource.com/902725Reviewed-by: default avatarMajid Valipour <majidvp@chromium.org>
Reviewed-by: default avatarStephen McGruer <smcgruer@chromium.org>
Commit-Queue: Majid Valipour <majidvp@chromium.org>
Cr-Commit-Position: refs/heads/master@{#553752}
parent 53a4a650
...@@ -250,6 +250,27 @@ function waitForAnimationsToStart(callback) ...@@ -250,6 +250,27 @@ function waitForAnimationsToStart(callback)
} }
} }
function convertExpectationsToChecks(expected, callbacks) {
var checks = {};
if (typeof callbacks == 'function') {
checks[0] = [callbacks];
} else for (var time in callbacks) {
timeMs = Math.round(time * 1000);
checks[timeMs] = [callbacks[time]];
}
for (var i = 0; i < expected.length; i++) {
var expectation = expected[i];
var timeMs = Math.round(expectation[0] * 1000);
if (!checks[timeMs])
checks[timeMs] = [];
checks[timeMs].push(checkExpectedValue.bind(null, expected, i));
}
return checks;
}
// FIXME: disablePauseAnimationAPI and doPixelTest // FIXME: disablePauseAnimationAPI and doPixelTest
function runAnimationTest(expected, callbacks, trigger, disablePauseAnimationAPI, doPixelTest, startTestImmediately) function runAnimationTest(expected, callbacks, trigger, disablePauseAnimationAPI, doPixelTest, startTestImmediately)
{ {
...@@ -261,22 +282,7 @@ function runAnimationTest(expected, callbacks, trigger, disablePauseAnimationAPI ...@@ -261,22 +282,7 @@ function runAnimationTest(expected, callbacks, trigger, disablePauseAnimationAPI
if (disablePauseAnimationAPI) if (disablePauseAnimationAPI)
hasPauseAnimationAPI = false; hasPauseAnimationAPI = false;
var checks = {}; var checks = convertExpectationsToChecks(expected, callbacks);
if (typeof callbacks == 'function') {
checks[0] = [callbacks];
} else for (var time in callbacks) {
timeMs = Math.round(time * 1000);
checks[timeMs] = [callbacks[time]];
}
for (var i = 0; i < expected.length; i++) {
var expectation = expected[i];
var timeMs = Math.round(expectation[0] * 1000);
if (!checks[timeMs])
checks[timeMs] = [];
checks[timeMs].push(checkExpectedValue.bind(null, expected, i));
}
var doPixelTest = Boolean(doPixelTest); var doPixelTest = Boolean(doPixelTest);
useResultElement = doPixelTest; useResultElement = doPixelTest;
......
FAIL - "opacity" property for "box" element at 0.2s expected: 0.5 but saw: 1
FAIL - "transform.5" property for "box" element at 0.2s expected: 100 but saw: matrix(1, 0, 0, 1, 0, 0)
FAIL - "opacity" property for "box" element at 0.5s expected: 0.5 but saw: 1
FAIL - "transform.5" property for "box" element at 0.5s expected: 100 but saw: matrix(1, 0, 0, 1, 0, 0)
FAIL - "opacity" property for "box" element at 1s expected: 0.5 but saw: 1
FAIL - "transform.5" property for "box" element at 1s expected: 100 but saw: matrix(1, 0, 0, 1, 0, 0)
<!DOCTYPE html>
<style>
#box {
width: 100px;
height: 100px;
background-color: #00ff00;
/*
* Force compositing.
* TODO(majidvp): Should not be needed when http://crbug.com/776533 is fixed.
*/
will-change: transform, opacity;
}
</style>
<div id="box"></div>
<script id="visual_update" type="text/worklet">
registerAnimator("test_animator", class {
animate(currentTime, effect) {
effect.localTime = 500;
}
});
</script>
<script src="resources/animation-worklet-tests.js"></script>
<script src="../../../../animations/resources/animation-test-helpers.js"></script>
<script>
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}
runInAnimationWorklet(
document.getElementById('visual_update').textContent
).then(() => {
const box = document.getElementById('box');
const effect = new KeyframeEffect(box,
[
{ transform: 'translateY(0px)', opacity: 1},
{ transform: 'translateY(200px)', opacity: 0}
], {
duration: 1000,
fill: "forwards"
}
);
const animation = new WorkletAnimation('test_animator', [effect], document.timeline, {});
animation.play();
window.animStartTime = performance.now();
// TODO(crbug.com/756539): The expected values are based on assumption that
// worklet produced local time values are plumbed to main thread. This is
// currently not the case and 0 is used instead. As a result the expectation
// for the following checks to fail.
const expectedValues = [
// [time, element-id, property, expected-value, tolerance]
[0.2, "box", "opacity", 0.5, 0.05],
[0.5, "box", "opacity", 0.5, 0.05],
[1, "box", "opacity", 0.5, 0.05],
[0.2, "box", "transform.5", 100, 5],
[0.5, "box", "transform.5", 100, 5],
[1, "box", "transform.5", 100, 5],
];
const checks = convertExpectationsToChecks(expectedValues);
runChecksWithRAF(checks);
});
</script>
...@@ -45,6 +45,7 @@ class AnimationEffectOwner; ...@@ -45,6 +45,7 @@ class AnimationEffectOwner;
class EffectTiming; class EffectTiming;
class ComputedEffectTiming; class ComputedEffectTiming;
class OptionalEffectTiming; class OptionalEffectTiming;
class WorkletAnimation;
enum TimingUpdateReason { enum TimingUpdateReason {
kTimingUpdateOnDemand, kTimingUpdateOnDemand,
...@@ -65,6 +66,7 @@ class CORE_EXPORT AnimationEffect : public ScriptWrappable { ...@@ -65,6 +66,7 @@ class CORE_EXPORT AnimationEffect : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
// Calls Attach/Detach, GetAnimation, UpdateInheritedTime. // Calls Attach/Detach, GetAnimation, UpdateInheritedTime.
friend class Animation; friend class Animation;
friend class WorkletAnimation;
// Calls GetAnimation(). // Calls GetAnimation().
// TODO(majidvp): Remove this. EffectStack should not need to access animation // TODO(majidvp): Remove this. EffectStack should not need to access animation
......
...@@ -47,6 +47,7 @@ namespace { ...@@ -47,6 +47,7 @@ namespace {
void UpdateAnimationTiming(Document& document, TimingUpdateReason reason) { void UpdateAnimationTiming(Document& document, TimingUpdateReason reason) {
document.Timeline().ServiceAnimations(reason); document.Timeline().ServiceAnimations(reason);
document.GetWorkletAnimationController().UpdateAnimationTimings(reason);
} }
} // namespace } // namespace
...@@ -95,7 +96,7 @@ void DocumentAnimations::UpdateAnimations( ...@@ -95,7 +96,7 @@ void DocumentAnimations::UpdateAnimations(
} }
} }
document.GetWorkletAnimationController().Update(); document.GetWorkletAnimationController().UpdateAnimationCompositingStates();
document.Timeline().ScheduleNextService(); document.Timeline().ScheduleNextService();
} }
......
...@@ -319,12 +319,12 @@ EffectModel::CompositeOperation KeyframeEffect::CompositeInternal() const { ...@@ -319,12 +319,12 @@ EffectModel::CompositeOperation KeyframeEffect::CompositeInternal() const {
void KeyframeEffect::ApplyEffects() { void KeyframeEffect::ApplyEffects() {
DCHECK(IsInEffect()); DCHECK(IsInEffect());
DCHECK(GetAnimation());
if (!target_ || !model_->HasFrames()) if (!target_ || !model_->HasFrames())
return; return;
if (HasIncompatibleStyle()) if (GetAnimation() && HasIncompatibleStyle()) {
GetAnimation()->CancelAnimationOnCompositor(); GetAnimation()->CancelAnimationOnCompositor();
}
double iteration = CurrentIteration(); double iteration = CurrentIteration();
DCHECK_GE(iteration, 0); DCHECK_GE(iteration, 0);
...@@ -358,12 +358,12 @@ void KeyframeEffect::ApplyEffects() { ...@@ -358,12 +358,12 @@ void KeyframeEffect::ApplyEffects() {
} }
void KeyframeEffect::ClearEffects() { void KeyframeEffect::ClearEffects() {
DCHECK(GetAnimation());
DCHECK(sampled_effect_); DCHECK(sampled_effect_);
sampled_effect_->Clear(); sampled_effect_->Clear();
sampled_effect_ = nullptr; sampled_effect_ = nullptr;
GetAnimation()->RestartAnimationOnCompositor(); if (GetAnimation())
GetAnimation()->RestartAnimationOnCompositor();
target_->SetNeedsAnimationStyleRecalc(); target_->SetNeedsAnimationStyleRecalc();
if (RuntimeEnabledFeatures::WebAnimationsSVGEnabled() && if (RuntimeEnabledFeatures::WebAnimationsSVGEnabled() &&
target_->IsSVGElement()) target_->IsSVGElement())
...@@ -374,7 +374,7 @@ void KeyframeEffect::ClearEffects() { ...@@ -374,7 +374,7 @@ void KeyframeEffect::ClearEffects() {
void KeyframeEffect::UpdateChildrenAndEffects() const { void KeyframeEffect::UpdateChildrenAndEffects() const {
if (!model_->HasFrames()) if (!model_->HasFrames())
return; return;
DCHECK(GetAnimation()); DCHECK(owner_);
if (IsInEffect() && !owner_->EffectSuppressed()) if (IsInEffect() && !owner_->EffectSuppressed())
const_cast<KeyframeEffect*>(this)->ApplyEffects(); const_cast<KeyframeEffect*>(this)->ApplyEffects();
else if (sampled_effect_) else if (sampled_effect_)
......
...@@ -18,6 +18,9 @@ class CORE_EXPORT WorkletAnimationBase : public ScriptWrappable { ...@@ -18,6 +18,9 @@ class CORE_EXPORT WorkletAnimationBase : public ScriptWrappable {
public: public:
~WorkletAnimationBase() override = default; ~WorkletAnimationBase() override = default;
// Asks the animation to update its effect inherited time.
virtual void Update(TimingUpdateReason) = 0;
// Attempts to start the animation on the compositor side, returning true if // Attempts to start the animation on the compositor side, returning true if
// it succeeds or false otherwise. If false is returned and failure_message // it succeeds or false otherwise. If false is returned and failure_message
// was non-null, failure_message may be filled with an error description. // was non-null, failure_message may be filled with an error description.
......
...@@ -42,7 +42,7 @@ void WorkletAnimationController::DetachAnimation( ...@@ -42,7 +42,7 @@ void WorkletAnimationController::DetachAnimation(
compositor_animations_.erase(&animation); compositor_animations_.erase(&animation);
} }
void WorkletAnimationController::Update() { void WorkletAnimationController::UpdateAnimationCompositingStates() {
DCHECK(IsMainThread()); DCHECK(IsMainThread());
HeapHashSet<Member<WorkletAnimationBase>> animations; HeapHashSet<Member<WorkletAnimationBase>> animations;
animations.swap(pending_animations_); animations.swap(pending_animations_);
...@@ -57,6 +57,20 @@ void WorkletAnimationController::Update() { ...@@ -57,6 +57,20 @@ void WorkletAnimationController::Update() {
} }
} }
void WorkletAnimationController::UpdateAnimationTimings(
TimingUpdateReason reason) {
DCHECK(IsMainThread());
// Worklet animations inherited time values are only ever updated once per
// animation frame. This means the inherited time does not change outside of
// the frame so return early in the on-demand case.
if (reason == kTimingUpdateOnDemand)
return;
for (const auto& animation : compositor_animations_) {
animation->Update(reason);
}
}
void WorkletAnimationController::Trace(blink::Visitor* visitor) { void WorkletAnimationController::Trace(blink::Visitor* visitor) {
visitor->Trace(pending_animations_); visitor->Trace(pending_animations_);
visitor->Trace(compositor_animations_); visitor->Trace(compositor_animations_);
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_WORKLET_ANIMATION_CONTROLLER_H_ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_WORKLET_ANIMATION_CONTROLLER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_WORKLET_ANIMATION_CONTROLLER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_WORKLET_ANIMATION_CONTROLLER_H_
#include "third_party/blink/renderer/core/animation/animation_effect.h"
#include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h" #include "third_party/blink/renderer/platform/heap/heap_allocator.h"
...@@ -34,7 +35,8 @@ class CORE_EXPORT WorkletAnimationController ...@@ -34,7 +35,8 @@ class CORE_EXPORT WorkletAnimationController
void AttachAnimation(WorkletAnimationBase&); void AttachAnimation(WorkletAnimationBase&);
void DetachAnimation(WorkletAnimationBase&); void DetachAnimation(WorkletAnimationBase&);
void Update(); void UpdateAnimationCompositingStates();
void UpdateAnimationTimings(TimingUpdateReason);
void Trace(blink::Visitor*); void Trace(blink::Visitor*);
......
...@@ -152,6 +152,13 @@ std::unique_ptr<CompositorScrollTimeline> ToCompositorScrollTimeline( ...@@ -152,6 +152,13 @@ std::unique_ptr<CompositorScrollTimeline> ToCompositorScrollTimeline(
return std::make_unique<CompositorScrollTimeline>(element_id, orientation, return std::make_unique<CompositorScrollTimeline>(element_id, orientation,
time_range.GetAsDouble()); time_range.GetAsDouble());
} }
unsigned NextSequenceNumber() {
// TODO(majidvp): This should actually come from the same source as other
// animation so that they have the correct ordering.
static unsigned next = 0;
return ++next;
}
} // namespace } // namespace
WorkletAnimation* WorkletAnimation::Create( WorkletAnimation* WorkletAnimation::Create(
...@@ -194,7 +201,8 @@ WorkletAnimation::WorkletAnimation( ...@@ -194,7 +201,8 @@ WorkletAnimation::WorkletAnimation(
const HeapVector<Member<KeyframeEffect>>& effects, const HeapVector<Member<KeyframeEffect>>& effects,
DocumentTimelineOrScrollTimeline timeline, DocumentTimelineOrScrollTimeline timeline,
scoped_refptr<SerializedScriptValue> options) scoped_refptr<SerializedScriptValue> options)
: animator_name_(animator_name), : sequence_number_(NextSequenceNumber()),
animator_name_(animator_name),
play_state_(Animation::kIdle), play_state_(Animation::kIdle),
document_(document), document_(document),
effects_(effects), effects_(effects),
...@@ -203,6 +211,9 @@ WorkletAnimation::WorkletAnimation( ...@@ -203,6 +211,9 @@ WorkletAnimation::WorkletAnimation(
DCHECK(IsMainThread()); DCHECK(IsMainThread());
DCHECK(Platform::Current()->IsThreadedAnimationEnabled()); DCHECK(Platform::Current()->IsThreadedAnimationEnabled());
DCHECK(Platform::Current()->CompositorSupport()); DCHECK(Platform::Current()->CompositorSupport());
AnimationEffect* target_effect = effects_.at(0);
target_effect->Attach(this);
} }
String WorkletAnimation::playState() { String WorkletAnimation::playState() {
...@@ -250,11 +261,52 @@ void WorkletAnimation::cancel() { ...@@ -250,11 +261,52 @@ void WorkletAnimation::cancel() {
target->SetNeedsAnimationStyleRecalc(); target->SetNeedsAnimationStyleRecalc();
} }
bool WorkletAnimation::Playing() const {
return play_state_ == Animation::kRunning;
}
void WorkletAnimation::UpdateIfNecessary() {
// TODO(crbug.com/833846): This is updating more often than necessary. This
// gets fixed once WorkletAnimation becomes a subclass of Animation.
Update(kTimingUpdateOnDemand);
}
void WorkletAnimation::Update(TimingUpdateReason reason) {
if (play_state_ != Animation::kRunning)
return;
if (!start_time_)
return;
// TODO(crbug.com/756359): For now we use 0 as inherited time in but we will
// need to get the inherited time from worklet context.
double inherited_time_seconds = 0;
KeyframeEffect* target_effect = effects_.at(0);
target_effect->UpdateInheritedTime(inherited_time_seconds, reason);
}
AnimationTimeline& WorkletAnimation::GetAnimationTimeline() {
DCHECK(!timeline_.IsNull());
if (timeline_.IsScrollTimeline())
return *timeline_.GetAsScrollTimeline();
return *timeline_.GetAsDocumentTimeline();
}
bool WorkletAnimation::StartOnCompositor(String* failure_message) { bool WorkletAnimation::StartOnCompositor(String* failure_message) {
DCHECK(IsMainThread()); DCHECK(IsMainThread());
KeyframeEffect* target_effect = effects_.at(0); KeyframeEffect* target_effect = effects_.at(0);
Element& target = *target_effect->target(); Element& target = *target_effect->target();
// TODO(crbug.com/836393): This should not be possible but it is currently
// happening and needs to be investigated/fixed.
if (!target.GetComputedStyle()) {
if (failure_message)
*failure_message = "The target element does not have style.";
return false;
}
// CheckCanStartAnimationOnCompositor requires that the property-specific // CheckCanStartAnimationOnCompositor requires that the property-specific
// keyframe groups have been created. To ensure this we manually snapshot the // keyframe groups have been created. To ensure this we manually snapshot the
// frames in the target effect. // frames in the target effect.
...@@ -311,6 +363,13 @@ bool WorkletAnimation::StartOnCompositor(String* failure_message) { ...@@ -311,6 +363,13 @@ bool WorkletAnimation::StartOnCompositor(String* failure_message) {
playback_rate, playback_rate,
compositor_animation_.get()); compositor_animation_.get());
play_state_ = Animation::kRunning; play_state_ = Animation::kRunning;
AnimationTimeline& timeline = GetAnimationTimeline();
bool is_null;
double time = timeline.currentTime(is_null);
if (!is_null)
start_time_ = time;
return true; return true;
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
#include "third_party/blink/renderer/bindings/modules/v8/document_timeline_or_scroll_timeline.h" #include "third_party/blink/renderer/bindings/modules/v8/document_timeline_or_scroll_timeline.h"
#include "third_party/blink/renderer/core/animation/animation.h" #include "third_party/blink/renderer/core/animation/animation.h"
#include "third_party/blink/renderer/core/animation/animation_effect_owner.h"
#include "third_party/blink/renderer/core/animation/keyframe_effect.h" #include "third_party/blink/renderer/core/animation/keyframe_effect.h"
#include "third_party/blink/renderer/core/animation/worklet_animation_base.h" #include "third_party/blink/renderer/core/animation/worklet_animation_base.h"
#include "third_party/blink/renderer/modules/modules_export.h" #include "third_party/blink/renderer/modules/modules_export.h"
...@@ -32,8 +33,10 @@ class AnimationEffectOrAnimationEffectSequence; ...@@ -32,8 +33,10 @@ class AnimationEffectOrAnimationEffectSequence;
// Spec: https://wicg.github.io/animation-worklet/#worklet-animation-desc // Spec: https://wicg.github.io/animation-worklet/#worklet-animation-desc
class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase, class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
public CompositorAnimationClient, public CompositorAnimationClient,
public CompositorAnimationDelegate { public CompositorAnimationDelegate,
public AnimationEffectOwner {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
USING_GARBAGE_COLLECTED_MIXIN(WorkletAnimation);
USING_PRE_FINALIZER(WorkletAnimation, Dispose); USING_PRE_FINALIZER(WorkletAnimation, Dispose);
public: public:
...@@ -50,7 +53,26 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase, ...@@ -50,7 +53,26 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
void play(); void play();
void cancel(); void cancel();
// AnimationEffectOwner implementation:
unsigned SequenceNumber() const override { return sequence_number_; }
bool Playing() const override;
// Always allow dispatching events for worklet animations. This is only ever
// relevant to CSS animations which means it does not have any material effect
// on worklet animations either way.
bool IsEventDispatchAllowed() const override { return true; }
// Effect supression is used by devtool's animation inspection machinery which
// is not currently supported by worklet animations.
bool EffectSuppressed() const override { return false; }
// TODO(crbug.com/833846): We should update compositor animation when this
// happens.
void SpecifiedTimingChanged() override {}
void UpdateIfNecessary() override;
Animation* GetAnimation() override { return nullptr; }
// WorkletAnimationBase implementation. // WorkletAnimationBase implementation.
void Update(TimingUpdateReason) override;
bool StartOnCompositor(String* failure_message) override; bool StartOnCompositor(String* failure_message) override;
// CompositorAnimationClient implementation. // CompositorAnimationClient implementation.
...@@ -83,8 +105,14 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase, ...@@ -83,8 +105,14 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
scoped_refptr<SerializedScriptValue>); scoped_refptr<SerializedScriptValue>);
void DestroyCompositorAnimation(); void DestroyCompositorAnimation();
AnimationTimeline& GetAnimationTimeline();
unsigned sequence_number_;
const String animator_name_; const String animator_name_;
Animation::AnimationPlayState play_state_; Animation::AnimationPlayState play_state_;
// Start time in ms.
WTF::Optional<double> start_time_;
Member<Document> document_; Member<Document> document_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment