blob: d6f6c3cecdbd4f057e5848268690a1fbf459f5cb [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 "base/cancelable_callback.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "cc/cc_export.h"
#include "cc/paint/discardable_image_map.h"
#include "cc/paint/image_id.h"
#include "cc/paint/paint_image.h"
#include "cc/paint/paint_image_generator.h"
#include "cc/tiles/tile_priority.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
namespace cc {
class PaintImage;
// ImageAnimationController is responsible for tracking state for ticking image
// animations in the compositor.
// 1) It receives the updated metadata for these images from new recordings
// received from the client using UpdateAnimatedImage. The controller tracks
// the frame index of an image used on a tree, and advances the animation to
// the desired frame each time a new sync tree is created.
// 2) An AnimationDriver can register itself for deciding whether the
// controller animates an image. The animation is paused if there are no
// registered drivers interested in animating it.
// 3) An animation is only advanced on the sync tree, which is requested to be
// created using the |client| callbacks. This effectively means that
// the frame of the image used remains consistent throughout the lifetime of
// a tree, guaranteeing that the image update is atomic.
class CC_EXPORT ImageAnimationController {
// AnimationDrivers are clients interested in driving image animations. An
// animation is ticked if there is at least one driver registered for which
// ShouldAnimate returns true. Once
// no drivers are registered for an image, or none of the registered drivers
// want us to animate, the animation is no longer ticked.
class CC_EXPORT AnimationDriver {
virtual ~AnimationDriver() {}
// Returns true if the image should be animated.
virtual bool ShouldAnimate(PaintImage::Id paint_image_id) const = 0;
class CC_EXPORT Client {
virtual ~Client() {}
// Notifies the client that an impl frame is needed to animate an image.
virtual void RequestBeginFrameForAnimatedImages() = 0;
// Notifies the client that a sync tree is needed to invalidate the animated
// images in this impl frame. This should only be called from within an impl
// frame.
virtual void RequestInvalidationForAnimatedImages() = 0;
// |task_runner| is the thread on which the controller is used. The |client|
// can only be called on this thread.
// |enable_image_animation_resync| specifies whether the animation can be
// reset to the beginning to avoid skipping many frames.
ImageAnimationController(base::SingleThreadTaskRunner* task_runner,
Client* client,
bool enable_image_animation_resync);
// Called to update the state for a PaintImage received in a new recording.
void UpdateAnimatedImage(
const DiscardableImageMap::AnimatedImageMetadata& data);
// Registers/Unregisters an animation driver interested in animating this
// image.
// Note that the state for this image must have been populated to the
// controller using UpdatePaintImage prior to registering any drivers.
void RegisterAnimationDriver(PaintImage::Id paint_image_id,
AnimationDriver* driver);
void UnregisterAnimationDriver(PaintImage::Id paint_image_id,
AnimationDriver* driver);
// Called to advance the animations to the frame to be used on the sync tree.
// This should be called only once for a sync tree and must be followed with
// a call to DidActivate when this tree is activated.
// Returns the set of images that were animated and should be invalidated on
// this sync tree.
const PaintImageIdFlatSet& AnimateForSyncTree(
const viz::BeginFrameArgs& args);
// Called whenever the ShouldAnimate response for a driver could have changed.
// For instance on a change in the visibility of the image, we would pause
// off-screen animations.
// This is called after every DrawProperties update and commit.
void UpdateStateFromDrivers();
// Called when the sync tree was activated and the animations' associated
// state should be pushed to the active tree.
void DidActivate();
// Returns the frame index to use for the given PaintImage and tree.
size_t GetFrameIndexForImage(PaintImage::Id paint_image_id,
WhichTree tree) const;
// Notifies the beginning of an impl frame with the given |args|.
void WillBeginImplFrame(const viz::BeginFrameArgs& args);
void set_did_navigate() { did_navigate_ = true; }
const base::flat_set<AnimationDriver*>& GetDriversForTesting(
PaintImage::Id paint_image_id) const;
size_t GetLastNumOfFramesSkippedForTesting(
PaintImage::Id paint_image_id) const;
size_t animation_state_map_size_for_testing() {
return animation_state_map_.size();
using NowCallback = base::RepeatingCallback<base::TimeTicks()>;
void set_now_callback_for_testing(NowCallback cb) {
class AnimationState {
AnimationState(AnimationState&& other);
AnimationState& operator=(AnimationState&& other);
bool ShouldAnimate() const;
bool AdvanceFrame(const viz::BeginFrameArgs& args,
bool enable_image_animation_resync);
void UpdateMetadata(const DiscardableImageMap::AnimatedImageMetadata& data);
void PushPendingToActive();
void AddDriver(AnimationDriver* driver);
void RemoveDriver(AnimationDriver* driver);
void UpdateStateFromDrivers();
bool has_drivers() const { return !drivers_.empty(); }
size_t pending_index() const { return pending_index_; }
size_t active_index() const { return active_index_; }
base::TimeTicks next_desired_tick_time() const {
return next_desired_tick_time_;
const base::flat_set<AnimationDriver*>& drivers_for_testing() const {
return drivers_;
size_t last_num_frames_skipped_for_testing() const {
return last_num_frames_skipped_;
std::string ToString() const;
void ResetAnimation();
size_t NextFrameIndex() const;
bool is_complete() const {
return completion_state_ == PaintImage::CompletionState::DONE;
bool needs_invalidation() const { return pending_index_ != active_index_; }
PaintImage::Id paint_image_id_ = PaintImage::kInvalidId;
// The frame metadata received from the most updated recording with this
// PaintImage.
std::vector<FrameMetadata> frames_;
// The number of animation loops requested for this image. For a value > 0,
// this number represents the exact number of iterations requested. A few
// special cases are represented using constants defined in
// cc/paint/image_animation_count.h
int requested_repetitions_ = kAnimationNone;
// The number of loops the animation has finished so far.
int repetitions_completed_ = 0;
// A set of drivers interested in animating this image.
base::flat_set<AnimationDriver*> drivers_;
// The index being used on the active tree, if a recording with this image
// is still present.
size_t active_index_ = PaintImage::kDefaultFrameIndex;
// The index being displayed on the pending tree.
size_t pending_index_ = PaintImage::kDefaultFrameIndex;
// The time at which we would like to display the next frame. This can be in
// the past, for instance, if we pause the animation from the image becoming
// invisible. This time is updated strictly based on the animation timeline
// provided by the image.
base::TimeTicks next_desired_frame_time_;
// The time of the next tick at which we want to invalidate and update the
// current frame.
base::TimeTicks next_desired_tick_time_;
// Set if there is at least one driver interested in animating this image,
// cached from the last update.
bool should_animate_from_drivers_ = false;
// Set if the animation has been started.
bool animation_started_ = false;
// Used for tracing, the time at which the animation was started.
base::TimeTicks animation_started_time_;
// The last synchronized sequence id for resetting this animation.
PaintImage::AnimationSequenceId reset_animation_sequence_id_ = 0;
// Whether the image is known to be completely loaded in the most recent
// recording received.
PaintImage::CompletionState completion_state_ =
// The number of frames skipped during catch-up the last time this animation
// was advanced.
size_t last_num_frames_skipped_ = 0u;
class InvalidationScheduler {
InvalidationScheduler(base::SingleThreadTaskRunner* task_runner,
Client* client);
void Schedule(base::TimeTicks animation_time);
void Cancel();
void WillAnimate();
void WillBeginImplFrame(const viz::BeginFrameArgs& args);
void set_now_callback_for_testing(NowCallback cb) {
now_callback_for_testing_ = cb;
enum InvalidationState {
// No notification pending.
// Task pending to request impl frame.
// Impl frame request pending after request dispatched to client.
// Sync tree for animation pending after request dispatched to client.
void RequestBeginFrame();
void RequestInvalidation();
base::SingleThreadTaskRunner* task_runner_;
Client* const client_;
NowCallback now_callback_for_testing_;
InvalidationState state_ = InvalidationState::kIdle;
// The time at which the next animation is expected to run.
base::TimeTicks next_animation_time_;
base::WeakPtrFactory<InvalidationScheduler> weak_factory_;
// The AnimationState for images is persisted until they are cleared on
// navigation. This is because while an image might not be painted anymore, if
// it moves out of the interest rect for instance, the state retained is
// necessary to resume the animation.
// TODO(khushalsagar): Implement clearing of state on navigations.
using AnimationStateMap = base::flat_map<PaintImage::Id, AnimationState>;
AnimationStateMap animation_state_map_;
// The set of animations with registered drivers.
PaintImageIdFlatSet registered_animations_;
// The set of images that were animated and invalidated on the last sync tree.
PaintImageIdFlatSet images_animated_on_sync_tree_;
InvalidationScheduler scheduler_;
const bool enable_image_animation_resync_;
bool did_navigate_ = false;
} // namespace cc