Commit c49d046d authored by Olga Gerchikov's avatar Olga Gerchikov Committed by Commit Bot

Initial support for WorkletAnimation.playbackRate

Changes for scroll-linked worklet animations will come in a separate pull request.


Bug: 852475
Change-Id: Ie6dd14242797cbc14a7e1e377f23c3dda174fe15
Reviewed-on: https://chromium-review.googlesource.com/c/1423537Reviewed-by: default avatarStephen McGruer <smcgruer@chromium.org>
Reviewed-by: default avatarMajid Valipour <majidvp@chromium.org>
Commit-Queue: Olga Gerchikov <gerchiko@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#628800}
parent 28106947
...@@ -37,7 +37,7 @@ class AnimationHostTest : public AnimationTimelinesTest { ...@@ -37,7 +37,7 @@ class AnimationHostTest : public AnimationTimelinesTest {
client_impl_.RegisterElement(element_id_, ElementListType::ACTIVE); client_impl_.RegisterElement(element_id_, ElementListType::ACTIVE);
worklet_animation_ = WorkletAnimation::Create( worklet_animation_ = WorkletAnimation::Create(
worklet_animation_id_, "test_name", nullptr, nullptr); worklet_animation_id_, "test_name", 1, nullptr, nullptr);
int cc_id = worklet_animation_->id(); int cc_id = worklet_animation_->id();
worklet_animation_->AttachElement(element_id_); worklet_animation_->AttachElement(element_id_);
host_->AddAnimationTimeline(timeline_); host_->AddAnimationTimeline(timeline_);
...@@ -334,7 +334,7 @@ TEST_F(AnimationHostTest, LayerTreeMutatorUpdateReflectsScrollAnimations) { ...@@ -334,7 +334,7 @@ TEST_F(AnimationHostTest, LayerTreeMutatorUpdateReflectsScrollAnimations) {
// Create a worklet animation that is bound to the scroll timeline. // Create a worklet animation that is bound to the scroll timeline.
scoped_refptr<WorkletAnimation> worklet_animation( scoped_refptr<WorkletAnimation> worklet_animation(
new WorkletAnimation(animation_id2, worklet_animation_id, "test_name", new WorkletAnimation(animation_id2, worklet_animation_id, "test_name", 1,
std::move(scroll_timeline), nullptr, true)); std::move(scroll_timeline), nullptr, true));
worklet_animation->AttachElement(element_id); worklet_animation->AttachElement(element_id);
timeline_->AttachAnimation(worklet_animation); timeline_->AttachAnimation(worklet_animation);
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "cc/animation/worklet_animation.h" #include "cc/animation/worklet_animation.h"
#include <utility>
#include "cc/animation/animation_id_provider.h" #include "cc/animation/animation_id_provider.h"
#include "cc/animation/keyframe_effect.h" #include "cc/animation/keyframe_effect.h"
#include "cc/animation/scroll_timeline.h" #include "cc/animation/scroll_timeline.h"
...@@ -15,12 +16,14 @@ WorkletAnimation::WorkletAnimation( ...@@ -15,12 +16,14 @@ WorkletAnimation::WorkletAnimation(
int cc_animation_id, int cc_animation_id,
WorkletAnimationId worklet_animation_id, WorkletAnimationId worklet_animation_id,
const std::string& name, const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline, std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options, std::unique_ptr<AnimationOptions> options,
bool is_controlling_instance) bool is_controlling_instance)
: WorkletAnimation(cc_animation_id, : WorkletAnimation(cc_animation_id,
worklet_animation_id, worklet_animation_id,
name, name,
playback_rate,
std::move(scroll_timeline), std::move(scroll_timeline),
std::move(options), std::move(options),
is_controlling_instance, is_controlling_instance,
...@@ -30,6 +33,7 @@ WorkletAnimation::WorkletAnimation( ...@@ -30,6 +33,7 @@ WorkletAnimation::WorkletAnimation(
int cc_animation_id, int cc_animation_id,
WorkletAnimationId worklet_animation_id, WorkletAnimationId worklet_animation_id,
const std::string& name, const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline, std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options, std::unique_ptr<AnimationOptions> options,
bool is_controlling_instance, bool is_controlling_instance,
...@@ -38,6 +42,7 @@ WorkletAnimation::WorkletAnimation( ...@@ -38,6 +42,7 @@ WorkletAnimation::WorkletAnimation(
worklet_animation_id_(worklet_animation_id), worklet_animation_id_(worklet_animation_id),
name_(name), name_(name),
scroll_timeline_(std::move(scroll_timeline)), scroll_timeline_(std::move(scroll_timeline)),
playback_rate_(playback_rate),
options_(std::move(options)), options_(std::move(options)),
local_time_(base::nullopt), local_time_(base::nullopt),
start_time_(base::nullopt), start_time_(base::nullopt),
...@@ -50,11 +55,12 @@ WorkletAnimation::~WorkletAnimation() = default; ...@@ -50,11 +55,12 @@ WorkletAnimation::~WorkletAnimation() = default;
scoped_refptr<WorkletAnimation> WorkletAnimation::Create( scoped_refptr<WorkletAnimation> WorkletAnimation::Create(
WorkletAnimationId worklet_animation_id, WorkletAnimationId worklet_animation_id,
const std::string& name, const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline, std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options) { std::unique_ptr<AnimationOptions> options) {
return WrapRefCounted(new WorkletAnimation( return WrapRefCounted(new WorkletAnimation(
AnimationIdProvider::NextAnimationId(), worklet_animation_id, name, AnimationIdProvider::NextAnimationId(), worklet_animation_id, name,
std::move(scroll_timeline), std::move(options), false)); playback_rate, std::move(scroll_timeline), std::move(options), false));
} }
scoped_refptr<Animation> WorkletAnimation::CreateImplInstance() const { scoped_refptr<Animation> WorkletAnimation::CreateImplInstance() const {
...@@ -62,9 +68,9 @@ scoped_refptr<Animation> WorkletAnimation::CreateImplInstance() const { ...@@ -62,9 +68,9 @@ scoped_refptr<Animation> WorkletAnimation::CreateImplInstance() const {
if (scroll_timeline_) if (scroll_timeline_)
impl_timeline = scroll_timeline_->CreateImplInstance(); impl_timeline = scroll_timeline_->CreateImplInstance();
return WrapRefCounted(new WorkletAnimation(id(), worklet_animation_id_, return WrapRefCounted(
name(), std::move(impl_timeline), new WorkletAnimation(id(), worklet_animation_id_, name(), playback_rate_,
CloneOptions(), true)); std::move(impl_timeline), CloneOptions(), true));
} }
void WorkletAnimation::PushPropertiesTo(Animation* animation_impl) { void WorkletAnimation::PushPropertiesTo(Animation* animation_impl) {
...@@ -74,6 +80,7 @@ void WorkletAnimation::PushPropertiesTo(Animation* animation_impl) { ...@@ -74,6 +80,7 @@ void WorkletAnimation::PushPropertiesTo(Animation* animation_impl) {
scroll_timeline_->PushPropertiesTo( scroll_timeline_->PushPropertiesTo(
worklet_animation_impl->scroll_timeline_.get()); worklet_animation_impl->scroll_timeline_.get());
} }
worklet_animation_impl->SetPlaybackRate(playback_rate_);
} }
void WorkletAnimation::Tick(base::TimeTicks monotonic_time) { void WorkletAnimation::Tick(base::TimeTicks monotonic_time) {
...@@ -140,7 +147,34 @@ void WorkletAnimation::SetOutputState( ...@@ -140,7 +147,34 @@ void WorkletAnimation::SetOutputState(
local_time_ = state.local_times[0]; local_time_ = state.local_times[0];
} }
// TODO(crbug.com/780151): Multiply the result by the play back rate. void WorkletAnimation::SetPlaybackRate(double playback_rate) {
if (playback_rate == playback_rate_)
return;
// Setting playback rate is rejected in the blink side if any of the
// conditions below is false.
DCHECK(playback_rate_ && !scroll_timeline_);
if (start_time_ && last_current_time_) {
// Update startTime in order to maintain previous currentTime and,
// as a result, prevent the animation from jumping.
base::TimeDelta current_time =
base::TimeDelta::FromMillisecondsD(last_current_time_.value());
start_time_ = start_time_.value() + current_time / playback_rate_ -
current_time / playback_rate;
}
playback_rate_ = playback_rate;
}
void WorkletAnimation::UpdatePlaybackRate(double playback_rate) {
if (playback_rate == playback_rate_)
return;
playback_rate_ = playback_rate;
SetNeedsPushProperties();
}
// TODO(gerchiko): Implement support playback_rate for scroll-linked
// animations. http://crbug.com/852475.
double WorkletAnimation::CurrentTime(base::TimeTicks monotonic_time, double WorkletAnimation::CurrentTime(base::TimeTicks monotonic_time,
const ScrollTree& scroll_tree, const ScrollTree& scroll_tree,
bool is_active_tree) { bool is_active_tree) {
...@@ -148,7 +182,8 @@ double WorkletAnimation::CurrentTime(base::TimeTicks monotonic_time, ...@@ -148,7 +182,8 @@ double WorkletAnimation::CurrentTime(base::TimeTicks monotonic_time,
// by the start time. See: https://github.com/w3c/csswg-drafts/issues/2075 // by the start time. See: https://github.com/w3c/csswg-drafts/issues/2075
if (scroll_timeline_) if (scroll_timeline_)
return scroll_timeline_->CurrentTime(scroll_tree, is_active_tree); return scroll_timeline_->CurrentTime(scroll_tree, is_active_tree);
return (monotonic_time - start_time_.value()).InMillisecondsF(); return (monotonic_time - start_time_.value()).InMillisecondsF() *
playback_rate_;
} }
bool WorkletAnimation::NeedsUpdate(base::TimeTicks monotonic_time, bool WorkletAnimation::NeedsUpdate(base::TimeTicks monotonic_time,
......
...@@ -25,6 +25,13 @@ class ScrollTimeline; ...@@ -25,6 +25,13 @@ class ScrollTimeline;
// A WorkletAnimation is an animation that allows its animation // A WorkletAnimation is an animation that allows its animation
// timing to be controlled by an animator instance that is running in a // timing to be controlled by an animator instance that is running in a
// AnimationWorkletGlobalScope. // AnimationWorkletGlobalScope.
// Two instances of this class are created for Blink WorkletAnimation:
// 1. UI thread instance that keeps all the meta data.
// 2. Impl thread instance that ticks the animations on the Impl thread.
// When Blink WorkletAnimation is updated, it calls the UI thread instance to
// modify its properties. The updated properties are pushed by the UI thread
// instance to the Impl thread instance during commit.
class CC_ANIMATION_EXPORT WorkletAnimation final class CC_ANIMATION_EXPORT WorkletAnimation final
: public SingleKeyframeEffectAnimation { : public SingleKeyframeEffectAnimation {
public: public:
...@@ -32,12 +39,14 @@ class CC_ANIMATION_EXPORT WorkletAnimation final ...@@ -32,12 +39,14 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
WorkletAnimation(int cc_animation_id, WorkletAnimation(int cc_animation_id,
WorkletAnimationId worklet_animation_id, WorkletAnimationId worklet_animation_id,
const std::string& name, const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline, std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options, std::unique_ptr<AnimationOptions> options,
bool is_controlling_instance); bool is_controlling_instance);
static scoped_refptr<WorkletAnimation> Create( static scoped_refptr<WorkletAnimation> Create(
WorkletAnimationId worklet_animation_id, WorkletAnimationId worklet_animation_id,
const std::string& name, const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline, std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options); std::unique_ptr<AnimationOptions> options);
scoped_refptr<Animation> CreateImplInstance() const override; scoped_refptr<Animation> CreateImplInstance() const override;
...@@ -70,6 +79,12 @@ class CC_ANIMATION_EXPORT WorkletAnimation final ...@@ -70,6 +79,12 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
// require updating the ElementId for the ScrollTimeline scroll source. // require updating the ElementId for the ScrollTimeline scroll source.
void PromoteScrollTimelinePendingToActive() override; void PromoteScrollTimelinePendingToActive() override;
// Called by Blink WorkletAnimation when its playback rate is updated.
void UpdatePlaybackRate(double playback_rate);
void SetPlaybackRateForTesting(double playback_rate) {
SetPlaybackRate(playback_rate);
}
void RemoveKeyframeModel(int keyframe_model_id) override; void RemoveKeyframeModel(int keyframe_model_id) override;
private: private:
...@@ -78,6 +93,7 @@ class CC_ANIMATION_EXPORT WorkletAnimation final ...@@ -78,6 +93,7 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
WorkletAnimation(int cc_animation_id, WorkletAnimation(int cc_animation_id,
WorkletAnimationId worklet_animation_id, WorkletAnimationId worklet_animation_id,
const std::string& name, const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline, std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options, std::unique_ptr<AnimationOptions> options,
bool is_controlling_instance, bool is_controlling_instance,
...@@ -101,6 +117,10 @@ class CC_ANIMATION_EXPORT WorkletAnimation final ...@@ -101,6 +117,10 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
return options_ ? options_->Clone() : nullptr; return options_ ? options_->Clone() : nullptr;
} }
// Updates the playback rate of the Impl thread instance.
// Called by the UI thread WorletAnimation instance during commit.
void SetPlaybackRate(double playback_rate);
WorkletAnimationId worklet_animation_id_; WorkletAnimationId worklet_animation_id_;
std::string name_; std::string name_;
...@@ -112,6 +132,15 @@ class CC_ANIMATION_EXPORT WorkletAnimation final ...@@ -112,6 +132,15 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
// some other future implementation. // some other future implementation.
std::unique_ptr<ScrollTimeline> scroll_timeline_; std::unique_ptr<ScrollTimeline> scroll_timeline_;
// Controls speed of the animation.
// https://drafts.csswg.org/web-animations-2/#animation-effect-playback-rate
// For UI thread instance contains the meta value to be pushed to the Impl
// thread instance.
// For the Impl thread instance contains the actual playback rate of the
// animation.
double playback_rate_;
std::unique_ptr<AnimationOptions> options_; std::unique_ptr<AnimationOptions> options_;
// Local time is used as an input to the keyframe effect of this animation. // Local time is used as an input to the keyframe effect of this animation.
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "cc/animation/worklet_animation.h" #include "cc/animation/worklet_animation.h"
#include <utility>
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "cc/animation/scroll_timeline.h" #include "cc/animation/scroll_timeline.h"
#include "cc/test/animation_test_common.h" #include "cc/test/animation_test_common.h"
...@@ -35,7 +36,7 @@ class WorkletAnimationTest : public AnimationTimelinesTest { ...@@ -35,7 +36,7 @@ class WorkletAnimationTest : public AnimationTimelinesTest {
client_.RegisterElement(element_id_, ElementListType::ACTIVE); client_.RegisterElement(element_id_, ElementListType::ACTIVE);
worklet_animation_ = WrapRefCounted( worklet_animation_ = WrapRefCounted(
new WorkletAnimation(1, worklet_animation_id_, "test_name", nullptr, new WorkletAnimation(1, worklet_animation_id_, "test_name", 1, nullptr,
nullptr, true /* controlling instance*/)); nullptr, true /* controlling instance*/));
worklet_animation_->AttachElement(element_id_); worklet_animation_->AttachElement(element_id_);
host_->AddAnimationTimeline(timeline_); host_->AddAnimationTimeline(timeline_);
...@@ -64,7 +65,7 @@ TEST_F(WorkletAnimationTest, NonImplInstanceDoesNotTickKeyframe) { ...@@ -64,7 +65,7 @@ TEST_F(WorkletAnimationTest, NonImplInstanceDoesNotTickKeyframe) {
scoped_refptr<WorkletAnimation> worklet_animation = scoped_refptr<WorkletAnimation> worklet_animation =
WrapRefCounted(new WorkletAnimation( WrapRefCounted(new WorkletAnimation(
1, worklet_animation_id_, "test_name", nullptr, nullptr, 1, worklet_animation_id_, "test_name", 1, nullptr, nullptr,
false /* not impl instance*/, std::move(effect))); false /* not impl instance*/, std::move(effect)));
EXPECT_CALL(*mock_effect, Tick(_)).Times(0); EXPECT_CALL(*mock_effect, Tick(_)).Times(0);
...@@ -109,8 +110,9 @@ TEST_F(WorkletAnimationTest, LocalTimeIsUsedWhenTicking) { ...@@ -109,8 +110,9 @@ TEST_F(WorkletAnimationTest, LocalTimeIsUsedWhenTicking) {
TEST_F(WorkletAnimationTest, CurrentTimeCorrectlyUsesScrollTimeline) { TEST_F(WorkletAnimationTest, CurrentTimeCorrectlyUsesScrollTimeline) {
auto scroll_timeline = std::make_unique<MockScrollTimeline>(); auto scroll_timeline = std::make_unique<MockScrollTimeline>();
EXPECT_CALL(*scroll_timeline, CurrentTime(_, _)).WillRepeatedly(Return(1234)); EXPECT_CALL(*scroll_timeline, CurrentTime(_, _)).WillRepeatedly(Return(1234));
scoped_refptr<WorkletAnimation> worklet_animation = WorkletAnimation::Create( scoped_refptr<WorkletAnimation> worklet_animation =
worklet_animation_id_, "test_name", std::move(scroll_timeline), nullptr); WorkletAnimation::Create(worklet_animation_id_, "test_name", 1,
std::move(scroll_timeline), nullptr);
ScrollTree scroll_tree; ScrollTree scroll_tree;
std::unique_ptr<MutatorInputState> state = std::unique_ptr<MutatorInputState> state =
...@@ -125,7 +127,7 @@ TEST_F(WorkletAnimationTest, CurrentTimeCorrectlyUsesScrollTimeline) { ...@@ -125,7 +127,7 @@ TEST_F(WorkletAnimationTest, CurrentTimeCorrectlyUsesScrollTimeline) {
TEST_F(WorkletAnimationTest, TEST_F(WorkletAnimationTest,
CurrentTimeFromRegularTimelineIsOffsetByStartTime) { CurrentTimeFromRegularTimelineIsOffsetByStartTime) {
scoped_refptr<WorkletAnimation> worklet_animation = WorkletAnimation::Create( scoped_refptr<WorkletAnimation> worklet_animation = WorkletAnimation::Create(
worklet_animation_id_, "test_name", nullptr, nullptr); worklet_animation_id_, "test_name", 1, nullptr, nullptr);
base::TimeTicks first_ticks = base::TimeTicks first_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111); base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111);
...@@ -156,6 +158,56 @@ TEST_F(WorkletAnimationTest, ...@@ -156,6 +158,56 @@ TEST_F(WorkletAnimationTest,
EXPECT_EQ(246.8, input->updated_animations[0].current_time); EXPECT_EQ(246.8, input->updated_animations[0].current_time);
} }
// Verifies correctness of current time when playback rate is set on
// initializing the animation and while the animation is playing.
TEST_F(WorkletAnimationTest, DocumentTimelineSetPlaybackRate) {
const double playback_rate_double = 2;
const double playback_rate_half = 0.5;
scoped_refptr<WorkletAnimation> worklet_animation =
WorkletAnimation::Create(worklet_animation_id_, "test_name",
playback_rate_double, nullptr, nullptr);
base::TimeTicks first_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111);
base::TimeTicks second_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111 + 123.4);
base::TimeTicks third_ticks =
base::TimeTicks() +
base::TimeDelta::FromMillisecondsD(111 + 123.4 + 200.0);
ScrollTree scroll_tree;
std::unique_ptr<MutatorInputState> state =
std::make_unique<MutatorInputState>();
// Start the animation.
worklet_animation->UpdateInputState(state.get(), first_ticks, scroll_tree,
true);
state.reset(new MutatorInputState);
// Play until second_ticks.
worklet_animation->UpdateInputState(state.get(), second_ticks, scroll_tree,
true);
std::unique_ptr<AnimationWorkletInput> input =
state->TakeWorkletState(worklet_animation_id_.worklet_id);
// Verify that the current time is updated twice faster than the timeline
// time.
EXPECT_EQ(123.4 * playback_rate_double,
input->updated_animations[0].current_time);
// Update the playback rate.
worklet_animation->SetPlaybackRateForTesting(playback_rate_half);
state.reset(new MutatorInputState());
// Play until third_ticks.
worklet_animation->UpdateInputState(state.get(), third_ticks, scroll_tree,
true);
input = state->TakeWorkletState(worklet_animation_id_.worklet_id);
// Verify that the current time is updated half as fast as the timeline time.
EXPECT_EQ(123.4 * playback_rate_double + 200.0 * playback_rate_half,
input->updated_animations[0].current_time);
}
// This test verifies that worklet animation state is properly updated. // This test verifies that worklet animation state is properly updated.
TEST_F(WorkletAnimationTest, UpdateInputStateProducesCorrectState) { TEST_F(WorkletAnimationTest, UpdateInputStateProducesCorrectState) {
AttachWorkletAnimation(); AttachWorkletAnimation();
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "third_party/blink/renderer/core/animation/worklet_animation_controller.h" #include "third_party/blink/renderer/core/animation/worklet_animation_controller.h"
#include "third_party/blink/renderer/core/dom/node.h" #include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h" #include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/frame/frame_console.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/inspector/console_message.h" #include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/layout_box.h"
...@@ -132,6 +133,17 @@ void StartEffectOnCompositor(CompositorAnimation* animation, ...@@ -132,6 +133,17 @@ void StartEffectOnCompositor(CompositorAnimation* animation,
int group = 0; int group = 0;
base::Optional<double> start_time = base::nullopt; base::Optional<double> start_time = base::nullopt;
double time_offset = 0; double time_offset = 0;
// Normally the playback rate of a blink animation gets translated into
// equivalent playback rate of cc::KeyframeModels.
// This has worked for regular animations since their current time was not
// exposed in cc. However, for worklet animations this does not work because
// the current time is exposed and it is an animation level concept as
// opposed to a keyframe model level concept.
// So it makes sense here that we use "1" as playback rate for KeyframeModels
// and separately plumb the playback rate to cc worklet animation.
// TODO(majidvp): Remove playbackRate from KeyframeModel in favor of having
// it on animation. https://crbug.com/925373.
double playback_rate = 1; double playback_rate = 1;
effect->StartAnimationOnCompositor(group, start_time, time_offset, effect->StartAnimationOnCompositor(group, start_time, time_offset,
...@@ -229,6 +241,7 @@ WorkletAnimation::WorkletAnimation( ...@@ -229,6 +241,7 @@ WorkletAnimation::WorkletAnimation(
animator_name_(animator_name), animator_name_(animator_name),
play_state_(Animation::kIdle), play_state_(Animation::kIdle),
last_play_state_(play_state_), last_play_state_(play_state_),
playback_rate_(1),
document_(document), document_(document),
effects_(effects), effects_(effects),
timeline_(timeline), timeline_(timeline),
...@@ -332,6 +345,69 @@ void WorkletAnimation::UpdateIfNecessary() { ...@@ -332,6 +345,69 @@ void WorkletAnimation::UpdateIfNecessary() {
Update(kTimingUpdateOnDemand); Update(kTimingUpdateOnDemand);
} }
double WorkletAnimation::playbackRate(ScriptState* script_state) const {
return playback_rate_;
}
void WorkletAnimation::setPlaybackRate(ScriptState* script_state,
double playback_rate) {
if (playback_rate == playback_rate_)
return;
// TODO(https://crbug.com/821910): Implement 0 playback rate after pause()
// support is in.
if (!playback_rate) {
if (document_->GetFrame() && ExecutionContext::From(script_state)) {
document_->GetFrame()->Console().AddMessage(
ConsoleMessage::Create(kJSMessageSource, kWarningMessageLevel,
"WorkletAnimation currently does not support "
"playback rate of Zero."));
}
return;
}
// TODO(gerchiko): Implement support playback_rate for scroll-linked
// animations. http://crbug.com/852475.
if (timeline_->IsScrollTimeline()) {
if (document_->GetFrame() && ExecutionContext::From(script_state)) {
document_->GetFrame()->Console().AddMessage(
ConsoleMessage::Create(kJSMessageSource, kWarningMessageLevel,
"Scroll-linked WorkletAnimation currently "
"does not support setting playback rate."));
}
return;
}
SetPlaybackRateInternal(playback_rate);
}
void WorkletAnimation::SetPlaybackRateInternal(double playback_rate) {
DCHECK(std::isfinite(playback_rate));
DCHECK_NE(playback_rate, playback_rate_);
DCHECK(playback_rate);
DCHECK(!timeline_->IsScrollTimeline());
if (start_time_) {
base::Optional<base::TimeDelta> current_time = CurrentTime();
// TODO(gerchiko): support unresolved current time.
// Blocked by ability to change timeline.
if (current_time) {
// Update startTime in order to maintain previous currentTime and, as a
// result, prevent the animation from jumping.
// See currentTime calculation in CurrentTime().
bool is_null;
double timeline_time_ms = timeline_->currentTime(is_null);
DCHECK(!is_null);
start_time_ = base::TimeDelta::FromMillisecondsD(timeline_time_ms) -
current_time.value() / playback_rate;
}
}
playback_rate_ = playback_rate;
if (Playing())
document_->GetWorkletAnimationController().InvalidateAnimation(*this);
}
void WorkletAnimation::EffectInvalidated() { void WorkletAnimation::EffectInvalidated() {
InvalidateCompositingState(); InvalidateCompositingState();
} }
...@@ -445,7 +521,7 @@ bool WorkletAnimation::StartOnCompositor() { ...@@ -445,7 +521,7 @@ bool WorkletAnimation::StartOnCompositor() {
// update the compositor to have the correct orientation and start/end // update the compositor to have the correct orientation and start/end
// offset information. // offset information.
compositor_animation_ = CompositorAnimation::CreateWorkletAnimation( compositor_animation_ = CompositorAnimation::CreateWorkletAnimation(
id_, animator_name_, id_, animator_name_, playback_rate_,
scroll_timeline_util::ToCompositorScrollTimeline(timeline_), scroll_timeline_util::ToCompositorScrollTimeline(timeline_),
std::move(options_)); std::move(options_));
compositor_animation_->SetAnimationDelegate(this); compositor_animation_->SetAnimationDelegate(this);
...@@ -506,6 +582,7 @@ void WorkletAnimation::UpdateOnCompositor() { ...@@ -506,6 +582,7 @@ void WorkletAnimation::UpdateOnCompositor() {
scroll_timeline_util::GetCompositorScrollElementId(scroll_source), scroll_timeline_util::GetCompositorScrollElementId(scroll_source),
start_scroll_offset, end_scroll_offset); start_scroll_offset, end_scroll_offset);
} }
compositor_animation_->UpdatePlaybackRate(playback_rate_);
} }
void WorkletAnimation::DestroyCompositorAnimation() { void WorkletAnimation::DestroyCompositorAnimation() {
...@@ -551,7 +628,7 @@ base::Optional<base::TimeDelta> WorkletAnimation::CurrentTime() const { ...@@ -551,7 +628,7 @@ base::Optional<base::TimeDelta> WorkletAnimation::CurrentTime() const {
if (timeline_->IsScrollTimeline()) if (timeline_->IsScrollTimeline())
return timeline_time; return timeline_time;
DCHECK(start_time_); DCHECK(start_time_);
return timeline_time - start_time_.value(); return (timeline_time - start_time_.value()) * playback_rate_;
} }
bool WorkletAnimation::NeedsPeek(base::TimeDelta current_time) { bool WorkletAnimation::NeedsPeek(base::TimeDelta current_time) {
......
...@@ -73,6 +73,8 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase, ...@@ -73,6 +73,8 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
AnimationTimeline* timeline() { return timeline_; } AnimationTimeline* timeline() { return timeline_; }
String playState(); String playState();
double currentTime(bool& is_null); double currentTime(bool& is_null);
double playbackRate(ScriptState* script_state) const;
void setPlaybackRate(ScriptState* script_state, double playback_rate);
void play(ExceptionState& exception_state); void play(ExceptionState& exception_state);
void cancel(); void cancel();
...@@ -142,6 +144,13 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase, ...@@ -142,6 +144,13 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
bool CheckCanStart(String* failure_message); bool CheckCanStart(String* failure_message);
void SetStartTimeToNow(); void SetStartTimeToNow();
// For DocumentTimeline animations, adjusts start_time_ according to playback
// rate change to preserve current time and avoid the animation output from
// jumping.
// Setting playback rate is currently not supported for scroll-linked
// animations.
void SetPlaybackRateInternal(double);
// Updates a running animation on the compositor side. // Updates a running animation on the compositor side.
void UpdateOnCompositor(); void UpdateOnCompositor();
...@@ -161,6 +170,9 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase, ...@@ -161,6 +170,9 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
const String animator_name_; const String animator_name_;
Animation::AnimationPlayState play_state_; Animation::AnimationPlayState play_state_;
Animation::AnimationPlayState last_play_state_; Animation::AnimationPlayState last_play_state_;
// Controls speed of the animation.
// https://drafts.csswg.org/web-animations-2/#animation-effect-playback-rate
double playback_rate_;
base::Optional<base::TimeDelta> start_time_; base::Optional<base::TimeDelta> start_time_;
Vector<base::Optional<base::TimeDelta>> local_times_; Vector<base::Optional<base::TimeDelta>> local_times_;
// We use this to skip updating if current time has not changed since last // We use this to skip updating if current time has not changed since last
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
readonly attribute AnimationTimeline? timeline; readonly attribute AnimationTimeline? timeline;
readonly attribute AnimationPlayState playState; readonly attribute AnimationPlayState playState;
readonly attribute double? currentTime; readonly attribute double? currentTime;
[CallWith=ScriptState] attribute double playbackRate;
[RaisesException] void play(); [RaisesException] void play();
void cancel(); void cancel();
}; };
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "third_party/blink/renderer/modules/animationworklet/worklet_animation.h" #include "third_party/blink/renderer/modules/animationworklet/worklet_animation.h"
#include <memory>
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#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/animation_effect_or_animation_effect_sequence.h" #include "third_party/blink/renderer/bindings/modules/v8/animation_effect_or_animation_effect_sequence.h"
...@@ -256,4 +257,87 @@ TEST_F(WorkletAnimationTest, MainThreadSendsPeekRequestTest) { ...@@ -256,4 +257,87 @@ TEST_F(WorkletAnimationTest, MainThreadSendsPeekRequestTest) {
state.reset(new AnimationWorkletDispatcherInput); state.reset(new AnimationWorkletDispatcherInput);
} }
// Verifies correctness of current time when playback rate is set while the
// animation is in idle state.
TEST_F(WorkletAnimationTest, DocumentTimelineSetPlaybackRate) {
GetDocument().GetAnimationClock().ResetTimeForTesting();
GetDocument().Timeline().ResetForTesting();
double error = base::TimeDelta::FromMicrosecondsD(1).InMillisecondsF();
WorkletAnimationId id = worklet_animation_->GetWorkletAnimationId();
base::TimeTicks first_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111.0);
base::TimeTicks second_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111.0 + 123.4);
double playback_rate = 2.0;
GetDocument().GetAnimationClock().ResetTimeForTesting(first_ticks);
DummyExceptionStateForTesting exception_state;
worklet_animation_->setPlaybackRate(nullptr, playback_rate);
worklet_animation_->play(exception_state);
worklet_animation_->UpdateCompositingState();
std::unique_ptr<AnimationWorkletDispatcherInput> state =
std::make_unique<AnimationWorkletDispatcherInput>();
worklet_animation_->UpdateInputState(state.get());
std::unique_ptr<AnimationWorkletInput> input =
state->TakeWorkletState(id.worklet_id);
// Zero current time is not impacted by playback rate.
EXPECT_NEAR(0, input->added_and_updated_animations[0].current_time, error);
state.reset(new AnimationWorkletDispatcherInput);
// Play the animation until second_ticks.
GetDocument().GetAnimationClock().ResetTimeForTesting(second_ticks);
worklet_animation_->UpdateInputState(state.get());
input = state->TakeWorkletState(id.worklet_id);
// Verify that the current time is updated playback_rate faster than the
// timeline time.
EXPECT_NEAR(123.4 * playback_rate, input->updated_animations[0].current_time,
error);
}
// Verifies correctness of current time when playback rate is set while the
// animation is playing.
TEST_F(WorkletAnimationTest, DocumentTimelineSetPlaybackRateWhilePlaying) {
GetDocument().GetAnimationClock().ResetTimeForTesting();
GetDocument().Timeline().ResetForTesting();
double error = base::TimeDelta::FromMicrosecondsD(1).InMillisecondsF();
WorkletAnimationId id = worklet_animation_->GetWorkletAnimationId();
base::TimeTicks first_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111.0);
base::TimeTicks second_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111.0 + 123.4);
base::TimeTicks third_ticks =
base::TimeTicks() +
base::TimeDelta::FromMillisecondsD(111.0 + 123.4 + 200.0);
double playback_rate = 0.5;
// Start animation.
GetDocument().GetAnimationClock().ResetTimeForTesting(first_ticks);
DummyExceptionStateForTesting exception_state;
worklet_animation_->play(exception_state);
worklet_animation_->UpdateCompositingState();
std::unique_ptr<AnimationWorkletDispatcherInput> state =
std::make_unique<AnimationWorkletDispatcherInput>();
worklet_animation_->UpdateInputState(state.get());
state.reset(new AnimationWorkletDispatcherInput);
// Update playback rate after second tick.
GetDocument().GetAnimationClock().ResetTimeForTesting(second_ticks);
worklet_animation_->UpdateInputState(state.get());
worklet_animation_->setPlaybackRate(nullptr, playback_rate);
state.reset(new AnimationWorkletDispatcherInput);
// Verify current time after third tick.
GetDocument().GetAnimationClock().ResetTimeForTesting(third_ticks);
worklet_animation_->UpdateInputState(state.get());
std::unique_ptr<AnimationWorkletInput> input =
state->TakeWorkletState(id.worklet_id);
EXPECT_NEAR(123.4 + 200.0 * playback_rate,
input->updated_animations[0].current_time, error);
}
} // namespace blink } // namespace blink
...@@ -22,11 +22,12 @@ std::unique_ptr<CompositorAnimation> ...@@ -22,11 +22,12 @@ std::unique_ptr<CompositorAnimation>
CompositorAnimation::CreateWorkletAnimation( CompositorAnimation::CreateWorkletAnimation(
cc::WorkletAnimationId worklet_animation_id, cc::WorkletAnimationId worklet_animation_id,
const String& name, const String& name,
double playback_rate,
std::unique_ptr<CompositorScrollTimeline> scroll_timeline, std::unique_ptr<CompositorScrollTimeline> scroll_timeline,
std::unique_ptr<cc::AnimationOptions> options) { std::unique_ptr<cc::AnimationOptions> options) {
return std::make_unique<CompositorAnimation>(cc::WorkletAnimation::Create( return std::make_unique<CompositorAnimation>(cc::WorkletAnimation::Create(
worklet_animation_id, std::string(name.Ascii().data(), name.length()), worklet_animation_id, std::string(name.Ascii().data(), name.length()),
std::move(scroll_timeline), std::move(options))); playback_rate, std::move(scroll_timeline), std::move(options)));
} }
CompositorAnimation::CompositorAnimation( CompositorAnimation::CompositorAnimation(
...@@ -90,6 +91,10 @@ void CompositorAnimation::UpdateScrollTimeline( ...@@ -90,6 +91,10 @@ void CompositorAnimation::UpdateScrollTimeline(
end_scroll_offset); end_scroll_offset);
} }
void CompositorAnimation::UpdatePlaybackRate(double playback_rate) {
cc::ToWorkletAnimation(animation_.get())->UpdatePlaybackRate(playback_rate);
}
void CompositorAnimation::NotifyAnimationStarted(base::TimeTicks monotonic_time, void CompositorAnimation::NotifyAnimationStarted(base::TimeTicks monotonic_time,
int target_property, int target_property,
int group) { int group) {
......
...@@ -37,6 +37,7 @@ class PLATFORM_EXPORT CompositorAnimation : public cc::AnimationDelegate { ...@@ -37,6 +37,7 @@ class PLATFORM_EXPORT CompositorAnimation : public cc::AnimationDelegate {
static std::unique_ptr<CompositorAnimation> CreateWorkletAnimation( static std::unique_ptr<CompositorAnimation> CreateWorkletAnimation(
cc::WorkletAnimationId, cc::WorkletAnimationId,
const String& name, const String& name,
double playback_rate,
std::unique_ptr<CompositorScrollTimeline>, std::unique_ptr<CompositorScrollTimeline>,
std::unique_ptr<cc::AnimationOptions>); std::unique_ptr<cc::AnimationOptions>);
...@@ -64,6 +65,7 @@ class PLATFORM_EXPORT CompositorAnimation : public cc::AnimationDelegate { ...@@ -64,6 +65,7 @@ class PLATFORM_EXPORT CompositorAnimation : public cc::AnimationDelegate {
void UpdateScrollTimeline(base::Optional<cc::ElementId>, void UpdateScrollTimeline(base::Optional<cc::ElementId>,
base::Optional<double> start_scroll_offset, base::Optional<double> start_scroll_offset,
base::Optional<double> end_scroll_offset); base::Optional<double> end_scroll_offset);
void UpdatePlaybackRate(double playback_rate);
private: private:
// cc::AnimationDelegate implementation. // cc::AnimationDelegate implementation.
......
<!DOCTYPE html>
<meta charset=utf-8>
<title>The playback rate of a worklet animation</title>
<link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';
// Presence of playback rate adds FP operations to calculating start_time
// and current_time of animations. That's why it's needed to increase FP error
// for comparing times in these tests.
window.assert_times_equal = (actual, expected, description) => {
assert_approx_equals(actual, expected, 0.002, description);
};
</script>
<script src="/web-animations/testcommon.js"></script>
<script src="common.js"></script>
<body>
<div id="log"></div>
<script>
'use strict';
function InstantiateWorkletAnimation(test) {
const DURATION = 10000; // ms
const KEYFRAMES = { height : ['100px', '50px'] };
return new WorkletAnimation('passthrough', new KeyframeEffect(createDiv(test),
KEYFRAMES, DURATION), document.timeline);
}
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);
animation.playbackRate = 0.5;
animation.play();
assert_equals(animation.currentTime, 0,
'Zero current time is not affected by playbackRate.');
}, 'Zero current time is not affected by playbackRate set while the animation is in idle state.');
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);
animation.play();
animation.playbackRate = 0.5;
assert_equals(animation.currentTime, 0,
'Zero current time is not affected by playbackRate.');
}, 'Zero current time is not affected by playbackRate set while the animation is in play-pending state.');
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);
const playbackRate = 2;
animation.play();
await waitForNextFrame();
// Set playback rate while the animation is playing.
const prevCurrentTime = animation.currentTime;
animation.playbackRate = playbackRate;
assert_times_equal(animation.currentTime, prevCurrentTime,
'The current time should stay unaffected by setting playback rate.');
}, 'Non zero current time is not affected by playbackRate set while the animation is in play state.');
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);
const playbackRate = 2;
animation.play();
await waitForNextFrame();
// Set playback rate while the animation is playing
const prevCurrentTime = animation.currentTime;
const prevTimelineTime = document.timeline.currentTime;
animation.playbackRate = playbackRate;
// Play the animation some more.
await waitForNextFrame();
const currentTime = animation.currentTime;
const currentTimelineTime = document.timeline.currentTime;
assert_times_equal(currentTime - prevCurrentTime, (currentTimelineTime - prevTimelineTime) * playbackRate,
'The current time should increase two times faster than timeline.');
}, 'The playback rate affects the rate of progress of the current time.');
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);;
const playbackRate = 2;
// Set playback rate while the animation is in 'idle' state.
animation.playbackRate = playbackRate;
animation.play();
const prevTimelineTime = document.timeline.currentTime;
await waitForNextFrame();
const currentTime = animation.currentTime;
const timelineTime = document.timeline.currentTime;
assert_times_equal(currentTime, (timelineTime - prevTimelineTime) * playbackRate,
'The current time should increase two times faster than timeline.');
}, 'The playback rate set before the animation started playing affects the ' +
'rate of progress of the current time');
promise_test(async t => {
await registerPassthroughAnimator();
const timing = { duration: 100,
easing: 'linear',
fill: 'none',
iterations: 1
};
const target = createDiv(t);
const keyframeEffect = new KeyframeEffect(target, { opacity: [0, 1] }, timing);
const animation = new WorkletAnimation('passthrough', keyframeEffect, document.timeline);
const playbackRate = 2;
animation.play();
animation.playbackRate = playbackRate;
await waitForNextFrame();
assert_times_equal(keyframeEffect.getComputedTiming().localTime, animation.currentTime,
'When playback rate is set on WorkletAnimation, the underlying effect\'s timing should be properly updated.');
assert_approx_equals(Number(getComputedStyle(target).opacity),
animation.currentTime / 100, 0.001,
'When playback rate is set on WorkletAnimation, the underlying effect should produce correct visual result.');
}, 'When playback rate is updated, the underlying effect is properly updated ' +
'with the current time of its WorkletAnimation and produces correct ' +
'visual result.');
</script>
</body>
\ No newline at end of file
...@@ -191,13 +191,13 @@ function waitForAnimationFramesWithDelay(minDelay) { ...@@ -191,13 +191,13 @@ function waitForAnimationFramesWithDelay(minDelay) {
function waitForNextFrame() { function waitForNextFrame() {
const timeAtStart = document.timeline.currentTime; const timeAtStart = document.timeline.currentTime;
return new Promise(resolve => { return new Promise(resolve => {
window.requestAnimationFrame(() => { (function handleFrame() {
if (timeAtStart === document.timeline.currentTime) { if (timeAtStart === document.timeline.currentTime) {
window.requestAnimationFrame(resolve); window.requestAnimationFrame(handleFrame);
} else { } else {
resolve(); resolve();
} }
}); }());
}); });
} }
......
...@@ -10274,10 +10274,12 @@ interface WorkletAnimation ...@@ -10274,10 +10274,12 @@ interface WorkletAnimation
attribute @@toStringTag attribute @@toStringTag
getter currentTime getter currentTime
getter playState getter playState
getter playbackRate
getter timeline getter timeline
method cancel method cancel
method constructor method constructor
method play method play
setter playbackRate
interface WritableStream interface WritableStream
attribute @@toStringTag attribute @@toStringTag
getter locked getter locked
......
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