blob: 96c523a837802b21585094401c7bfac6e392f195 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/animation/worklet_animation.h"
#include <utility>
#include "cc/animation/animation_delegate.h"
#include "cc/animation/animation_id_provider.h"
#include "cc/animation/animation_timeline.h"
#include "cc/animation/keyframe_effect.h"
#include "cc/animation/scroll_timeline.h"
#include "cc/trees/animation_effect_timings.h"
#include "cc/trees/animation_options.h"
namespace cc {
WorkletAnimation::WorkletAnimation(
int cc_animation_id,
WorkletAnimationId worklet_animation_id,
const std::string& name,
double playback_rate_value,
std::unique_ptr<AnimationOptions> options,
std::unique_ptr<AnimationEffectTimings> effect_timings,
bool is_controlling_instance)
: Animation(cc_animation_id),
worklet_animation_id_(worklet_animation_id),
name_(name),
playback_rate_(playback_rate_value),
options_(std::move(options)),
effect_timings_(std::move(effect_timings)),
is_impl_instance_(is_controlling_instance) {}
WorkletAnimation::~WorkletAnimation() = default;
scoped_refptr<WorkletAnimation> WorkletAnimation::Create(
WorkletAnimationId worklet_animation_id,
const std::string& name,
double playback_rate_value,
std::unique_ptr<AnimationOptions> options,
std::unique_ptr<AnimationEffectTimings> effect_timings) {
return WrapRefCounted(new WorkletAnimation(
AnimationIdProvider::NextAnimationId(), worklet_animation_id, name,
playback_rate_value, std::move(options), std::move(effect_timings),
false));
}
scoped_refptr<Animation> WorkletAnimation::CreateImplInstance() const {
return WrapRefCounted(
new WorkletAnimation(id(), worklet_animation_id_, name(), playback_rate(),
CloneOptions(), CloneEffectTimings(), true));
}
void WorkletAnimation::PushPropertiesTo(Animation* animation_impl) {
Animation::PushPropertiesTo(animation_impl);
WorkletAnimation* worklet_animation_impl = ToWorkletAnimation(animation_impl);
worklet_animation_impl->SetPlaybackRate(playback_rate());
}
void WorkletAnimation::Tick(base::TimeTicks monotonic_time) {
// Do not tick worklet animations on main thread as we will tick them on the
// compositor and the tick is more expensive than regular animations.
if (!is_impl_instance_)
return;
if (!local_time_.Read(*this).has_value())
return;
// As the output of a WorkletAnimation is driven by a script-provided local
// time, we don't want the underlying effect to participate in the normal
// animations lifecycle. To avoid this we pause the underlying keyframe effect
// at the local time obtained from the user script - essentially turning each
// call to |WorkletAnimation::Tick| into a seek in the effect.
keyframe_effect()->Pause(local_time_.Read(*this).value());
keyframe_effect()->Tick(base::TimeTicks());
}
void WorkletAnimation::UpdateState(bool start_ready_animations,
AnimationEvents* events) {
Animation::UpdateState(start_ready_animations, events);
if (last_synced_local_time_.Read(*this) != local_time_.Read(*this))
events->set_needs_time_updated_events(true);
}
void WorkletAnimation::TakeTimeUpdatedEvent(AnimationEvents* events) {
DCHECK(events->needs_time_updated_events());
if (last_synced_local_time_.Read(*this) != local_time_.Read(*this)) {
AnimationEvent event(animation_timeline()->id(), id_,
local_time_.Read(*this));
events->events_.push_back(event);
last_synced_local_time_.Write(*this) = local_time_.Read(*this);
}
}
void WorkletAnimation::UpdateInputState(MutatorInputState* input_state,
base::TimeTicks monotonic_time,
const ScrollTree& scroll_tree,
bool is_active_tree) {
bool is_timeline_active = IsTimelineActive(scroll_tree, is_active_tree);
// Record the monotonic time to be the start time first time state is
// generated. This time is used as the origin for computing the current time.
// The start time of scroll-linked animations is always initialized to zero.
// See: https://github.com/w3c/csswg-drafts/issues/2075
// To stay consistent with blink::WorkletAnimation, record start time only
// when the timeline becomes active.
if (!start_time_.Read(*this).has_value() && is_timeline_active)
start_time_.Write(*this) = animation_timeline()->IsScrollTimeline()
? base::TimeTicks()
: monotonic_time;
if (is_active_tree && has_pending_tree_lock_.Read(*this))
return;
// Skip running worklet animations with unchanged input time and reuse
// their value from the previous animation call.
if (!NeedsUpdate(monotonic_time, scroll_tree, is_active_tree))
return;
DCHECK(is_timeline_active || state_.Read(*this) == State::REMOVED);
// TODO(https://crbug.com/1011138): Initialize current_time to null if the
// timeline is inactive. It might be inactive here when state is
// State::REMOVED.
absl::optional<base::TimeDelta> current_time =
CurrentTime(monotonic_time, scroll_tree, is_active_tree);
// When the timeline is inactive (only the case with scroll timelines), the
// animation holds its last current time and last current output. This
// means we don't need to produce any new input state. See also:
// https://drafts.csswg.org/web-animations/#responding-to-a-newly-inactive-timeline
if (!is_timeline_active)
current_time = last_current_time_.Read(*this);
// TODO(https://crbug.com/1011138): Do not early exit if state is
// State::REMOVED. The animation must be removed in this case.
if (!current_time)
return;
last_current_time_.Write(*this) = current_time;
// Prevent active tree mutations from queuing up until pending tree is
// activated to preserve flow of time for scroll timelines.
has_pending_tree_lock_.Write(*this) =
!is_active_tree && animation_timeline()->IsScrollTimeline();
switch (state_.Read(*this)) {
case State::PENDING:
input_state->Add({worklet_animation_id(), name(),
current_time->InMillisecondsF(), CloneOptions(),
CloneEffectTimings()});
state_.Write(*this) = State::RUNNING;
break;
case State::RUNNING:
// TODO(jortaylo): EffectTimings need to be sent to the worklet during
// updates, otherwise the timing info will become outdated.
// https://crbug.com/915344.
input_state->Update(
{worklet_animation_id(), current_time->InMillisecondsF()});
break;
case State::REMOVED:
input_state->Remove(worklet_animation_id());
break;
}
}
void WorkletAnimation::SetOutputState(
const MutatorOutputState::AnimationState& state) {
DCHECK_EQ(state.local_times.size(), 1u);
local_time_.Write(*this) = state.local_times[0];
}
void WorkletAnimation::SetPlaybackRate(double rate) {
if (rate == playback_rate())
return;
// Setting playback rate is rejected in the blink side if playback_rate_ is
// zero.
DCHECK(playback_rate());
if (start_time_.Read(*this) && last_current_time_.Read(*this)) {
// Update startTime in order to maintain previous currentTime and,
// as a result, prevent the animation from jumping.
base::TimeDelta current_time = last_current_time_.Read(*this).value();
start_time_.Write(*this) = start_time_.Read(*this).value() +
current_time / playback_rate() -
current_time / rate;
}
playback_rate_.Write(*this) = rate;
}
void WorkletAnimation::UpdatePlaybackRate(double rate) {
if (rate == playback_rate())
return;
playback_rate_.Write(*this) = rate;
SetNeedsPushProperties();
}
void WorkletAnimation::ReleasePendingTreeLock() {
has_pending_tree_lock_.Write(*this) = false;
}
absl::optional<base::TimeDelta> WorkletAnimation::CurrentTime(
base::TimeTicks monotonic_time,
const ScrollTree& scroll_tree,
bool is_active_tree) {
DCHECK(IsTimelineActive(scroll_tree, is_active_tree));
base::TimeTicks timeline_time;
if (animation_timeline()->IsScrollTimeline()) {
absl::optional<base::TimeTicks> scroll_monotonic_time =
ToScrollTimeline(animation_timeline())
->CurrentTime(scroll_tree, is_active_tree);
if (!scroll_monotonic_time)
return absl::nullopt;
timeline_time = scroll_monotonic_time.value();
} else {
timeline_time = monotonic_time;
}
return (timeline_time - start_time_.Read(*this).value()) * playback_rate();
}
bool WorkletAnimation::NeedsUpdate(base::TimeTicks monotonic_time,
const ScrollTree& scroll_tree,
bool is_active_tree) {
if (state_.Read(*this) == State::REMOVED)
return true;
// When the timeline is inactive we apply the last current time to the
// animation.
if (!IsTimelineActive(scroll_tree, is_active_tree))
return false;
absl::optional<base::TimeDelta> current_time =
CurrentTime(monotonic_time, scroll_tree, is_active_tree);
bool needs_update = last_current_time_.Read(*this) != current_time;
return needs_update;
}
bool WorkletAnimation::IsTimelineActive(const ScrollTree& scroll_tree,
bool is_active_tree) const {
if (!animation_timeline()->IsScrollTimeline())
return true;
return ToScrollTimeline(animation_timeline())
->IsActive(scroll_tree, is_active_tree);
}
void WorkletAnimation::RemoveKeyframeModel(int keyframe_model_id) {
state_.Write(*this) = State::REMOVED;
Animation::RemoveKeyframeModel(keyframe_model_id);
}
bool WorkletAnimation::IsWorkletAnimation() const {
return true;
}
} // namespace cc