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)
}
}
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
function runAnimationTest(expected, callbacks, trigger, disablePauseAnimationAPI, doPixelTest, startTestImmediately)
{
......@@ -261,22 +282,7 @@ function runAnimationTest(expected, callbacks, trigger, disablePauseAnimationAPI
if (disablePauseAnimationAPI)
hasPauseAnimationAPI = false;
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));
}
var checks = convertExpectationsToChecks(expected, callbacks);
var doPixelTest = Boolean(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;
class EffectTiming;
class ComputedEffectTiming;
class OptionalEffectTiming;
class WorkletAnimation;
enum TimingUpdateReason {
kTimingUpdateOnDemand,
......@@ -65,6 +66,7 @@ class CORE_EXPORT AnimationEffect : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
// Calls Attach/Detach, GetAnimation, UpdateInheritedTime.
friend class Animation;
friend class WorkletAnimation;
// Calls GetAnimation().
// TODO(majidvp): Remove this. EffectStack should not need to access animation
......
......@@ -47,6 +47,7 @@ namespace {
void UpdateAnimationTiming(Document& document, TimingUpdateReason reason) {
document.Timeline().ServiceAnimations(reason);
document.GetWorkletAnimationController().UpdateAnimationTimings(reason);
}
} // namespace
......@@ -95,7 +96,7 @@ void DocumentAnimations::UpdateAnimations(
}
}
document.GetWorkletAnimationController().Update();
document.GetWorkletAnimationController().UpdateAnimationCompositingStates();
document.Timeline().ScheduleNextService();
}
......
......@@ -319,12 +319,12 @@ EffectModel::CompositeOperation KeyframeEffect::CompositeInternal() const {
void KeyframeEffect::ApplyEffects() {
DCHECK(IsInEffect());
DCHECK(GetAnimation());
if (!target_ || !model_->HasFrames())
return;
if (HasIncompatibleStyle())
if (GetAnimation() && HasIncompatibleStyle()) {
GetAnimation()->CancelAnimationOnCompositor();
}
double iteration = CurrentIteration();
DCHECK_GE(iteration, 0);
......@@ -358,12 +358,12 @@ void KeyframeEffect::ApplyEffects() {
}
void KeyframeEffect::ClearEffects() {
DCHECK(GetAnimation());
DCHECK(sampled_effect_);
sampled_effect_->Clear();
sampled_effect_ = nullptr;
GetAnimation()->RestartAnimationOnCompositor();
if (GetAnimation())
GetAnimation()->RestartAnimationOnCompositor();
target_->SetNeedsAnimationStyleRecalc();
if (RuntimeEnabledFeatures::WebAnimationsSVGEnabled() &&
target_->IsSVGElement())
......@@ -374,7 +374,7 @@ void KeyframeEffect::ClearEffects() {
void KeyframeEffect::UpdateChildrenAndEffects() const {
if (!model_->HasFrames())
return;
DCHECK(GetAnimation());
DCHECK(owner_);
if (IsInEffect() && !owner_->EffectSuppressed())
const_cast<KeyframeEffect*>(this)->ApplyEffects();
else if (sampled_effect_)
......
......@@ -18,6 +18,9 @@ class CORE_EXPORT WorkletAnimationBase : public ScriptWrappable {
public:
~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
// it succeeds or false otherwise. If false is returned and failure_message
// was non-null, failure_message may be filled with an error description.
......
......@@ -42,7 +42,7 @@ void WorkletAnimationController::DetachAnimation(
compositor_animations_.erase(&animation);
}
void WorkletAnimationController::Update() {
void WorkletAnimationController::UpdateAnimationCompositingStates() {
DCHECK(IsMainThread());
HeapHashSet<Member<WorkletAnimationBase>> animations;
animations.swap(pending_animations_);
......@@ -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) {
visitor->Trace(pending_animations_);
visitor->Trace(compositor_animations_);
......
......@@ -5,6 +5,7 @@
#ifndef 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/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
......@@ -34,7 +35,8 @@ class CORE_EXPORT WorkletAnimationController
void AttachAnimation(WorkletAnimationBase&);
void DetachAnimation(WorkletAnimationBase&);
void Update();
void UpdateAnimationCompositingStates();
void UpdateAnimationTimings(TimingUpdateReason);
void Trace(blink::Visitor*);
......
......@@ -152,6 +152,13 @@ std::unique_ptr<CompositorScrollTimeline> ToCompositorScrollTimeline(
return std::make_unique<CompositorScrollTimeline>(element_id, orientation,
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
WorkletAnimation* WorkletAnimation::Create(
......@@ -194,7 +201,8 @@ WorkletAnimation::WorkletAnimation(
const HeapVector<Member<KeyframeEffect>>& effects,
DocumentTimelineOrScrollTimeline timeline,
scoped_refptr<SerializedScriptValue> options)
: animator_name_(animator_name),
: sequence_number_(NextSequenceNumber()),
animator_name_(animator_name),
play_state_(Animation::kIdle),
document_(document),
effects_(effects),
......@@ -203,6 +211,9 @@ WorkletAnimation::WorkletAnimation(
DCHECK(IsMainThread());
DCHECK(Platform::Current()->IsThreadedAnimationEnabled());
DCHECK(Platform::Current()->CompositorSupport());
AnimationEffect* target_effect = effects_.at(0);
target_effect->Attach(this);
}
String WorkletAnimation::playState() {
......@@ -250,11 +261,52 @@ void WorkletAnimation::cancel() {
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) {
DCHECK(IsMainThread());
KeyframeEffect* target_effect = effects_.at(0);
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
// keyframe groups have been created. To ensure this we manually snapshot the
// frames in the target effect.
......@@ -311,6 +363,13 @@ bool WorkletAnimation::StartOnCompositor(String* failure_message) {
playback_rate,
compositor_animation_.get());
play_state_ = Animation::kRunning;
AnimationTimeline& timeline = GetAnimationTimeline();
bool is_null;
double time = timeline.currentTime(is_null);
if (!is_null)
start_time_ = time;
return true;
}
......
......@@ -8,6 +8,7 @@
#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/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/worklet_animation_base.h"
#include "third_party/blink/renderer/modules/modules_export.h"
......@@ -32,8 +33,10 @@ class AnimationEffectOrAnimationEffectSequence;
// Spec: https://wicg.github.io/animation-worklet/#worklet-animation-desc
class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
public CompositorAnimationClient,
public CompositorAnimationDelegate {
public CompositorAnimationDelegate,
public AnimationEffectOwner {
DEFINE_WRAPPERTYPEINFO();
USING_GARBAGE_COLLECTED_MIXIN(WorkletAnimation);
USING_PRE_FINALIZER(WorkletAnimation, Dispose);
public:
......@@ -50,7 +53,26 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
void play();
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.
void Update(TimingUpdateReason) override;
bool StartOnCompositor(String* failure_message) override;
// CompositorAnimationClient implementation.
......@@ -83,8 +105,14 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
scoped_refptr<SerializedScriptValue>);
void DestroyCompositorAnimation();
AnimationTimeline& GetAnimationTimeline();
unsigned sequence_number_;
const String animator_name_;
Animation::AnimationPlayState play_state_;
// Start time in ms.
WTF::Optional<double> start_time_;
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