blob: 452fd21c8da6eb6a1d5e6ab8df3a4530c81a18b2 [file] [log] [blame]
// Copyright 2021 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 "components/viz/service/transitions/surface_animation_manager.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/time/time.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/resources/returned_resource.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "components/viz/common/transition_utils.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/service/surfaces/surface_saved_frame_storage.h"
#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h"
#include "ui/gfx/animation/keyframe/timing_function.h"
namespace viz {
namespace {
CompositorRenderPassId NextRenderPassId(const CompositorRenderPassId& id) {
return CompositorRenderPassId(id.GetUnsafeValue() + 1);
}
constexpr base::TimeDelta kDefaultAnimationDuration =
base::TimeDelta::FromMilliseconds(250);
constexpr base::TimeDelta kSharedOpacityAnimationDuration =
base::TimeDelta::FromMilliseconds(60);
constexpr base::TimeDelta kSharedOpacityAnimationDelay =
base::TimeDelta::FromMilliseconds(60);
// Scale the overall duration to produce the opacity duration. Opacity
// transitions which reveal an element (i.e., transition opacity from 0 -> 1)
// should finish ahead of a translation. This way, you'll see the next page fade
// into view and settle while fully opaque. Similarly, transitions which hide an
// element (i.e., transition opacity from 1 -> 0) should, for a brief period,
// animate with full opacity so the user can get a sense of the motion before
// the element disappears.
constexpr float kOpacityTransitionDurationScaleFactor = 0.8f;
// When performing slides, the amount moved is proportional to the minimum
// viewport dimension -- this controls that proportion.
constexpr float kTranslationProportion = 0.05f;
// When performing implosions or explosions layers grow or shrink. This value
// determines the scaling done to achieve the larger of the two sizes.
constexpr float kScaleProportion = 1.1f;
void CreateAndAppendSrcTextureQuad(CompositorRenderPass* render_pass,
const gfx::Rect& output_rect,
const gfx::Transform& src_transform,
float src_opacity,
bool y_flipped,
ResourceId id) {
auto* src_quad_state = render_pass->CreateAndAppendSharedQuadState();
src_quad_state->SetAll(
/*quad_to_target_transform=*/src_transform,
/*quad_layer_rect=*/output_rect,
/*visible_layer_rect=*/output_rect,
/*mask_filter_info=*/gfx::MaskFilterInfo(),
/*clip_rect=*/base::nullopt, /*are_contents_opaque=*/false,
/*opacity=*/src_opacity,
/*blend_mode=*/SkBlendMode::kSrcOver, /*sorting_context_id=*/0);
auto* src_quad = render_pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
float vertex_opacity[] = {1.f, 1.f, 1.f, 1.f};
src_quad->SetNew(
/*shared_quad_state=*/src_quad_state,
/*rect=*/output_rect,
/*visible_rect=*/output_rect,
/*needs_blending=*/true,
/*resource_id=*/id,
/*premultiplied_alpha=*/true,
/*uv_top_left=*/gfx::PointF(0, 0),
/*uv_bottom_right=*/gfx::PointF(1, 1),
/*background_color=*/SK_ColorTRANSPARENT,
/*vertex_opacity=*/vertex_opacity, y_flipped,
/*nearest_neighbor=*/true,
/*secure_output_only=*/false,
/*protected_video_type=*/gfx::ProtectedVideoType::kClear);
}
void CreateAndAppendSharedRenderPassDrawQuad(
CompositorRenderPass* render_pass,
const gfx::Rect& rect,
gfx::Transform transform,
float opacity,
CompositorRenderPassId render_pass_id,
const CompositorRenderPassDrawQuad& sample_quad) {
gfx::Transform rect_scale;
// Convert the needed destination rect to be a scale on the sample quad rect.
// This ensures that the rects match but are scaled, instead of being clipped.
rect_scale.Scale(
rect.width() / static_cast<float>(sample_quad.rect.width()),
rect.height() / static_cast<float>(sample_quad.rect.height()));
transform.PreconcatTransform(rect_scale);
auto* quad_state = render_pass->CreateAndAppendSharedQuadState();
quad_state->SetAll(
/*quad_to_target_transform=*/transform,
/*quad_layer_rect=*/rect,
/*visible_layer_rect=*/rect,
/*mask_filter_info=*/gfx::MaskFilterInfo(),
/*clip_rect=*/base::nullopt,
/*are_contents_opaque=*/false,
/*opacity=*/opacity,
/*blend_mode=*/SkBlendMode::kSrcOver, /*sorting_context_id=*/0);
auto* quad =
render_pass->CreateAndAppendDrawQuad<CompositorRenderPassDrawQuad>();
quad->SetNew(
/*shared_quad_state=*/quad_state,
/*rect=*/sample_quad.rect,
/*visible_rect=*/sample_quad.visible_rect,
/*render_pass_id=*/render_pass_id,
/*mask_resource_id=*/sample_quad.mask_resource_id(),
/*mask_uv_rect=*/sample_quad.mask_uv_rect,
/*mask_texture_size=*/sample_quad.mask_texture_size,
/*filters_scale=*/sample_quad.filters_scale,
/*filters_origin=*/sample_quad.filters_origin,
/*tex_coord_rect=*/sample_quad.tex_coord_rect,
/*force_anti_aliasing_off=*/sample_quad.force_anti_aliasing_off,
/*backdrop_filter_quality*/ sample_quad.backdrop_filter_quality);
}
} // namespace
SurfaceAnimationManager::SurfaceAnimationManager(
SharedBitmapManager* shared_bitmap_manager)
: transferable_resource_tracker_(shared_bitmap_manager) {}
SurfaceAnimationManager::~SurfaceAnimationManager() = default;
void SurfaceAnimationManager::SetDirectiveFinishedCallback(
TransitionDirectiveCompleteCallback sequence_id_finished_callback) {
sequence_id_finished_callback_ = std::move(sequence_id_finished_callback);
}
bool SurfaceAnimationManager::ProcessTransitionDirectives(
const std::vector<CompositorFrameTransitionDirective>& directives,
SurfaceSavedFrameStorage* storage) {
bool started_animation = false;
for (auto& directive : directives) {
// Don't process directives with sequence ids smaller than or equal to the
// last seen one. It is possible that we call this with the same frame
// multiple times.
if (directive.sequence_id() <= last_processed_sequence_id_)
continue;
last_processed_sequence_id_ = directive.sequence_id();
bool handled = false;
// Dispatch to a specialized function based on type.
switch (directive.type()) {
case CompositorFrameTransitionDirective::Type::kSave:
handled = ProcessSaveDirective(directive, storage);
break;
case CompositorFrameTransitionDirective::Type::kAnimate:
handled = ProcessAnimateDirective(directive, storage);
started_animation |= handled;
break;
}
// If we didn't handle the directive, it means that we're in a state that
// does not permit the directive to be processed, and it was ignored. We
// should notify that we've fully processed the directive in this case to
// allow code that is waiting for this to continue.
if (!handled)
sequence_id_finished_callback_.Run(directive.sequence_id());
}
return started_animation;
}
bool SurfaceAnimationManager::ProcessSaveDirective(
const CompositorFrameTransitionDirective& directive,
SurfaceSavedFrameStorage* storage) {
// We need to be in the idle state in order to save.
if (state_ != State::kIdle)
return false;
storage->ProcessSaveDirective(directive, sequence_id_finished_callback_);
return true;
}
bool SurfaceAnimationManager::ProcessAnimateDirective(
const CompositorFrameTransitionDirective& directive,
SurfaceSavedFrameStorage* storage) {
// We can only begin an animate if we are currently idle.
if (state_ != State::kIdle)
return false;
// Make sure we don't actually have anything saved as a texture.
DCHECK(!saved_textures_.has_value());
auto saved_frame = storage->TakeSavedFrame();
// We can't animate if we don't have a saved frame.
if (!saved_frame || !saved_frame->IsValid())
return false;
// Take the save directive.
save_directive_.emplace(saved_frame->directive());
animate_directive_.emplace(directive);
// Import the saved frame, which converts it to a ResourceFrame -- a structure
// which has transferable resources.
saved_textures_.emplace(
transferable_resource_tracker_.ImportResources(std::move(saved_frame)));
CreateRootAnimationCurves(saved_textures_->root.draw_data.rect.size());
CreateSharedElementCurves();
TickAnimations(latest_time_);
state_ = State::kAnimating;
return true;
}
bool SurfaceAnimationManager::NeedsBeginFrame() const {
// If we're animating we need to keep pumping frames to advance the animation.
// If we're done, we require one more frame to switch back to idle state.
return root_animation_.driver().IsAnimating() || state_ == State::kLastFrame;
}
void SurfaceAnimationManager::TickAnimations(base::TimeTicks new_time) {
root_animation_.driver().Tick(new_time);
for (auto& shared : shared_animations_)
shared.driver().Tick(new_time);
}
void SurfaceAnimationManager::NotifyFrameAdvanced() {
TickAnimations(latest_time_);
switch (state_) {
case State::kIdle:
NOTREACHED() << "We should not advance frames when idle";
break;
case State::kAnimating:
FinishAnimationIfNeeded();
break;
case State::kLastFrame:
FinalizeAndDisposeOfState();
break;
}
}
void SurfaceAnimationManager::FinishAnimationIfNeeded() {
DCHECK_EQ(state_, State::kAnimating);
DCHECK(saved_textures_.has_value());
DCHECK(save_directive_.has_value());
DCHECK(animate_directive_.has_value());
if (!root_animation_.driver().IsAnimating()) {
state_ = State::kLastFrame;
sequence_id_finished_callback_.Run(animate_directive_->sequence_id());
}
}
void SurfaceAnimationManager::FinalizeAndDisposeOfState() {
DCHECK_EQ(state_, State::kLastFrame);
DCHECK(saved_textures_.has_value());
// Set state to idle.
state_ = State::kIdle;
// Ensure to return the texture / unref it.
transferable_resource_tracker_.ReturnFrame(*saved_textures_);
saved_textures_.reset();
save_directive_.reset();
animate_directive_.reset();
}
void SurfaceAnimationManager::InterpolateFrame(Surface* surface) {
DCHECK(saved_textures_);
if (state_ == State::kLastFrame) {
surface->ResetInterpolatedFrame();
return;
}
const auto& active_frame = surface->GetActiveFrame();
CompositorFrame interpolated_frame;
interpolated_frame.metadata = active_frame.metadata.Clone();
interpolated_frame.resource_list = active_frame.resource_list;
interpolated_frame.resource_list.push_back(saved_textures_->root.resource);
gfx::Rect output_rect = active_frame.render_pass_list.back()->output_rect;
auto animation_pass = CreateAnimationCompositorRenderPass(output_rect);
CopyAndInterpolateSharedElements(active_frame.render_pass_list,
animation_pass.get(), &interpolated_frame);
bool src_on_top = false;
switch (save_directive_->effect()) {
case CompositorFrameTransitionDirective::Effect::kRevealRight:
case CompositorFrameTransitionDirective::Effect::kRevealLeft:
case CompositorFrameTransitionDirective::Effect::kRevealUp:
case CompositorFrameTransitionDirective::Effect::kRevealDown:
case CompositorFrameTransitionDirective::Effect::kExplode:
case CompositorFrameTransitionDirective::Effect::kFade:
src_on_top = true;
break;
default:
break;
}
gfx::Transform src_transform = root_animation_.src_transform().Apply();
gfx::Transform dst_transform = root_animation_.dst_transform().Apply();
// GPU textures are flipped but software bitmaps are not.
bool y_flipped = !saved_textures_->root.resource.is_software;
if (src_on_top) {
CreateAndAppendSrcTextureQuad(animation_pass.get(), output_rect,
src_transform, root_animation_.src_opacity(),
y_flipped, saved_textures_->root.resource.id);
}
auto* dst_quad_state = animation_pass->CreateAndAppendSharedQuadState();
dst_quad_state->SetAll(
/*quad_to_target_transform=*/dst_transform,
/*quad_layer_rect=*/output_rect,
/*visible_layer_rect=*/output_rect,
/*mask_filter_info=*/gfx::MaskFilterInfo(),
/*clip_rect=*/base::nullopt,
/*are_contents_opaque=*/false,
/*opacity=*/root_animation_.dst_opacity(),
/*blend_mode=*/SkBlendMode::kSrcOver, /*sorting_context_id=*/0);
auto* dst_quad =
animation_pass->CreateAndAppendDrawQuad<CompositorRenderPassDrawQuad>();
dst_quad->SetNew(
/*shared_quad_state=*/dst_quad_state,
/*rect=*/output_rect,
/*visible_rect=*/output_rect,
/*render_pass_id=*/active_frame.render_pass_list.back()->id,
/*mask_resource_id=*/kInvalidResourceId,
/*mask_uv_rect=*/gfx::RectF(),
/*mask_texture_size=*/gfx::Size(),
/*filters_scale=*/gfx::Vector2dF(),
/*filters_origin=*/gfx::PointF(),
/*tex_coord_rect=*/gfx::RectF(output_rect),
/*force_anti_aliasing_off=*/false,
/*backdrop_filter_quality*/ 1.0f);
if (!src_on_top) {
CreateAndAppendSrcTextureQuad(animation_pass.get(), output_rect,
src_transform, root_animation_.src_opacity(),
y_flipped, saved_textures_->root.resource.id);
}
interpolated_frame.render_pass_list.push_back(std::move(animation_pass));
surface->SetInterpolatedFrame(std::move(interpolated_frame));
}
std::unique_ptr<CompositorRenderPass>
SurfaceAnimationManager::CreateAnimationCompositorRenderPass(
const gfx::Rect& output_rect) const {
// One quad for the root render pass, and expect that each shared texture is
// non-nullopt. Then we double it: 1 for the source frame, one for the
// destination frame.
size_t quad_size_limit = 2 * (1 + saved_textures_->shared.size());
// Reserve the same number of shared quad states as there are quads, since we
// expect each quad to have a separate shared quad state.
auto animation_pass =
CompositorRenderPass::Create(quad_size_limit, quad_size_limit);
// We create an animation pass before copying the existing frame, since we'll
// do a smart copy -- interpolating shared elements as we encounter them
// during the copy. As a result, we use id 1 here, since we don't know what is
// the maximum id yet, we'll update it after we do the copy.
animation_pass->SetNew(CompositorRenderPassId(1), output_rect, output_rect,
gfx::Transform());
return animation_pass;
}
void SurfaceAnimationManager::CopyAndInterpolateSharedElements(
const std::vector<std::unique_ptr<CompositorRenderPass>>& source_passes,
CompositorRenderPass* animation_pass,
CompositorFrame* interpolated_frame) {
// First create a placeholder for the shared render passes. We need this to
// quickly check whether a compositor render pass is shared (i.e. do we need
// to do something special for this).
base::flat_map<CompositorRenderPassId, RenderPassDrawData> shared_draw_data;
for (const CompositorRenderPassId& shared_render_pass_id :
animate_directive_->shared_render_pass_ids()) {
shared_draw_data.emplace(shared_render_pass_id, RenderPassDrawData());
}
// Now run through all the source passes, making a 'smart' copy and filtering
// based on whether the pass is a shared element or not. After this loop, we
// should have populated `shared_render_passes` with the shared passes, and
// `animation_draw_quads` with draw quads for those render passes. All other
// passes would have been copied and added into the interpolated frame.
CompositorRenderPassId max_id = CompositorRenderPassId(0);
for (auto& render_pass : source_passes) {
// First, clear the copy requests.
// TODO(vmpstr): Can we preserve these in some situations?
render_pass->copy_requests.clear();
// Get the max_id for any render pass, since we'll need to create new passes
// with ids that don't conflict with existing passes.
if (render_pass->id > max_id)
max_id = render_pass->id;
// Now do the pass copy, filtering shared element quads into
// `shared_draw_data` instead of the render pass.
auto pass_copy =
CopyPassWithoutSharedElementQuads(*render_pass, shared_draw_data);
// If this is a shared pass, store it in `shared_draw_data`. Otherwise,
// put it directly into the interpolated frame since we don't need to do
// anything special with it.
auto shared_it = shared_draw_data.find(pass_copy->id);
if (shared_it != shared_draw_data.end()) {
RenderPassDrawData& data = shared_it->second;
data.render_pass = std::move(pass_copy);
data.opacity = TransitionUtils::ComputeAccumulatedOpacity(
source_passes, data.render_pass->id);
} else {
interpolated_frame->render_pass_list.emplace_back(std::move(pass_copy));
}
}
// Update the animation pass id to avoid conflicts.
max_id = animation_pass->id = NextRenderPassId(max_id);
const std::vector<CompositorRenderPassId>& shared_render_pass_ids =
animate_directive_->shared_render_pass_ids();
for (size_t i = 0; i < shared_render_pass_ids.size(); ++i) {
const CompositorRenderPassId& shared_pass_id = shared_render_pass_ids[i];
AnimationState& animation = shared_animations_[i];
auto& draw_data = shared_draw_data[shared_pass_id];
bool has_destination_pass = draw_data.render_pass && draw_data.draw_quad;
// We have to retarget the animations, whether or not we have a destination
// pass.
// If we have a rect model, it means we have a source texture because we
// would have only created the rect model if we had a source texture (see
// `CreateSharedElementCurves()`). If the destination also exists, we have
// to target it since that's our target. If it isn't there, but was there at
// some point, then we have to retarget the animmation to its current value
// so that the animation stops moving.
//
// If we don't have a
// rect_model, meaning that we don't have a source texture, we update the
// state to whatever the destination rect is directly on the animation.
gfx::KeyframeModel* rect_model =
animation.driver().GetKeyframeModel(AnimationState::kRect);
const gfx::Rect target_rect =
has_destination_pass ? draw_data.draw_quad->rect : animation.rect();
if (rect_model) {
rect_model->Retarget(latest_time_, AnimationState::kRect, target_rect);
} else {
animation.OnRectAnimated(target_rect, AnimationState::kRect, nullptr);
}
// Now do the same for transform animation.
gfx::KeyframeModel* transform_model =
animation.driver().GetKeyframeModel(AnimationState::kSrcTransform);
gfx::TransformOperations target_transform_ops;
target_transform_ops.AppendMatrix(
has_destination_pass ? draw_data.render_pass->transform_to_root_target
: animation.src_transform().Apply());
if (transform_model) {
transform_model->Retarget(latest_time_, AnimationState::kSrcTransform,
target_transform_ops);
} else {
animation.OnTransformAnimated(target_transform_ops,
AnimationState::kSrcTransform, nullptr);
}
// Now that we have updated the animations, we can append the interpolations
// to the animation pass.
const base::Optional<TransferableResourceTracker::PositionedResource>&
src_texture = saved_textures_->shared[i];
const gfx::Rect& rect = animation.rect();
const gfx::Transform& transform = animation.src_transform().Apply();
float opacity = animation.src_opacity();
if (src_texture.has_value()) {
bool y_flipped = !src_texture->resource.is_software;
CreateAndAppendSrcTextureQuad(animation_pass, rect, transform,
opacity * src_texture->draw_data.opacity,
y_flipped, src_texture->resource.id);
interpolated_frame->resource_list.push_back(src_texture->resource);
}
if (!has_destination_pass)
continue;
// Now that we know we have a render pass destination, create a copy of the
// shared render pass, and update it with all the right values.
auto pass_copy = draw_data.render_pass->DeepCopy();
max_id = pass_copy->id = NextRenderPassId(max_id);
pass_copy->transform_to_root_target = transform;
// Create an quad for the pass into our animation pass.
// TODO(vmpstr): This needs to be a more sophisticated blending. See
// crbug.com/1201251 for details.
CreateAndAppendSharedRenderPassDrawQuad(
animation_pass, rect, transform, draw_data.opacity * (1.f - opacity),
pass_copy->id, *draw_data.draw_quad);
// Finally, add the pass into the interpolated frame. Make sure this comes
// after CreateAndAppend* call, because we use a pass id, so we need to
// access the pass before moving it here.
interpolated_frame->render_pass_list.emplace_back(std::move(pass_copy));
}
}
std::unique_ptr<CompositorRenderPass>
SurfaceAnimationManager::CopyPassWithoutSharedElementQuads(
const CompositorRenderPass& source_pass,
base::flat_map<CompositorRenderPassId, RenderPassDrawData>&
shared_draw_data) {
// This code is similar to CompositorRenderPass::DeepCopy, but does special
// logic when copying compositor render pass draw quads which draw shared
// elements.
auto copy_pass = CompositorRenderPass::Create(
source_pass.shared_quad_state_list.size(), source_pass.quad_list.size());
copy_pass->SetAll(
source_pass.id, source_pass.output_rect, source_pass.damage_rect,
source_pass.transform_to_root_target, source_pass.filters,
source_pass.backdrop_filters, source_pass.backdrop_filter_bounds,
source_pass.subtree_capture_id, source_pass.has_transparent_background,
source_pass.cache_render_pass,
source_pass.has_damage_from_contributing_content,
source_pass.generate_mipmap);
if (source_pass.shared_quad_state_list.empty())
return copy_pass;
SharedQuadStateList::ConstIterator sqs_iter =
source_pass.shared_quad_state_list.begin();
SharedQuadState* copy_shared_quad_state =
copy_pass->CreateAndAppendSharedQuadState();
*copy_shared_quad_state = **sqs_iter;
for (auto* quad : source_pass.quad_list) {
while (quad->shared_quad_state != *sqs_iter) {
++sqs_iter;
DCHECK(sqs_iter != source_pass.shared_quad_state_list.end());
copy_shared_quad_state = copy_pass->CreateAndAppendSharedQuadState();
*copy_shared_quad_state = **sqs_iter;
}
DCHECK(quad->shared_quad_state == *sqs_iter);
if (quad->material == DrawQuad::Material::kCompositorRenderPass) {
const auto* pass_quad = CompositorRenderPassDrawQuad::MaterialCast(quad);
auto shared_it = shared_draw_data.find(pass_quad->render_pass_id);
// If the quad is shared, then add it to the `shared_draw_data`.
// Otherwise, add it to the copy pass directly.
if (shared_it != shared_draw_data.end()) {
shared_it->second.draw_quad = *pass_quad;
} else {
copy_pass->CopyFromAndAppendRenderPassDrawQuad(
pass_quad, pass_quad->render_pass_id);
}
} else {
copy_pass->CopyFromAndAppendDrawQuad(quad);
}
}
return copy_pass;
}
void SurfaceAnimationManager::RefResources(
const std::vector<TransferableResource>& resources) {
if (transferable_resource_tracker_.is_empty())
return;
for (const auto& resource : resources) {
if (resource.id >= kVizReservedRangeStartId)
transferable_resource_tracker_.RefResource(resource.id);
}
}
void SurfaceAnimationManager::UnrefResources(
const std::vector<ReturnedResource>& resources) {
if (transferable_resource_tracker_.is_empty())
return;
for (const auto& resource : resources) {
if (resource.id >= kVizReservedRangeStartId)
transferable_resource_tracker_.UnrefResource(resource.id, resource.count);
}
}
void SurfaceAnimationManager::UpdateFrameTime(base::TimeTicks now) {
latest_time_ = now;
}
void SurfaceAnimationManager::CreateRootAnimationCurves(
const gfx::Size& output_size) {
// A small translation. We want to roughly scale this with screen size, but
// we choose the minimum screen dimension to keep horizontal and vertical
// transitions consistent and to avoid the impact of very oblong screen.
const float delta = std::min(output_size.width(), output_size.height()) *
kTranslationProportion;
gfx::TransformOperations start_transform;
gfx::TransformOperations end_transform;
int transform_property_id = AnimationState::kDstTransform;
float start_opacity = 0.0f;
float end_opacity = 1.0f;
int opacity_property_id = AnimationState::kDstOpacity;
DCHECK(save_directive_.has_value());
switch (save_directive_->effect()) {
case CompositorFrameTransitionDirective::Effect::kCoverLeft: {
start_transform.AppendTranslate(delta, 0.0f, 0.0f);
break;
}
case CompositorFrameTransitionDirective::Effect::kCoverRight: {
start_transform.AppendTranslate(-delta, 0.0f, 0.0f);
break;
}
case CompositorFrameTransitionDirective::Effect::kCoverUp: {
start_transform.AppendTranslate(0.0f, delta, 0.0f);
break;
}
case CompositorFrameTransitionDirective::Effect::kCoverDown: {
start_transform.AppendTranslate(0.0f, -delta, 0.0f);
break;
}
case CompositorFrameTransitionDirective::Effect::kRevealLeft: {
end_transform.AppendTranslate(-delta, 0.0f, 0.0f);
transform_property_id = AnimationState::kSrcTransform;
std::swap(start_opacity, end_opacity);
opacity_property_id = AnimationState::kSrcOpacity;
break;
}
case CompositorFrameTransitionDirective::Effect::kRevealRight: {
end_transform.AppendTranslate(delta, 0.0f, 0.0f);
transform_property_id = AnimationState::kSrcTransform;
std::swap(start_opacity, end_opacity);
opacity_property_id = AnimationState::kSrcOpacity;
break;
}
case CompositorFrameTransitionDirective::Effect::kRevealUp: {
end_transform.AppendTranslate(0.0f, -delta, 0.0f);
transform_property_id = AnimationState::kSrcTransform;
std::swap(start_opacity, end_opacity);
opacity_property_id = AnimationState::kSrcOpacity;
break;
}
case CompositorFrameTransitionDirective::Effect::kRevealDown: {
end_transform.AppendTranslate(0.0f, delta, 0.0f);
transform_property_id = AnimationState::kSrcTransform;
std::swap(start_opacity, end_opacity);
opacity_property_id = AnimationState::kSrcOpacity;
break;
}
case CompositorFrameTransitionDirective::Effect::kExplode: {
start_transform.AppendTranslate(output_size.width() * 0.5f,
output_size.height() * 0.5f, 0.0f);
start_transform.AppendScale(1.0f, 1.0f, 1.0f);
start_transform.AppendTranslate(-output_size.width() * 0.5f,
-output_size.height() * 0.5f, 0.0f);
end_transform.AppendTranslate(output_size.width() * 0.5f,
output_size.height() * 0.5f, 0.0f);
end_transform.AppendScale(kScaleProportion, kScaleProportion, 1.0f);
end_transform.AppendTranslate(-output_size.width() * 0.5f,
-output_size.height() * 0.5f, 0.0f);
transform_property_id = AnimationState::kSrcTransform;
std::swap(start_opacity, end_opacity);
opacity_property_id = AnimationState::kSrcOpacity;
break;
}
case CompositorFrameTransitionDirective::Effect::kFade: {
// Fade is effectively an explode with no scaling.
transform_property_id = AnimationState::kSrcTransform;
std::swap(start_opacity, end_opacity);
opacity_property_id = AnimationState::kSrcOpacity;
break;
}
case CompositorFrameTransitionDirective::Effect::kNone: {
transform_property_id = AnimationState::kSrcTransform;
start_opacity = end_opacity = 0.0f;
opacity_property_id = AnimationState::kSrcOpacity;
break;
}
case CompositorFrameTransitionDirective::Effect::kImplode: {
start_transform.AppendTranslate(output_size.width() * 0.5f,
output_size.height() * 0.5f, 0.0f);
start_transform.AppendScale(kScaleProportion, kScaleProportion, 1.0f);
start_transform.AppendTranslate(-output_size.width() * 0.5f,
-output_size.height() * 0.5f, 0.0f);
end_transform.AppendTranslate(output_size.width() * 0.5f,
output_size.height() * 0.5f, 0.0f);
end_transform.AppendScale(1.0f, 1.0f, 1.0f);
end_transform.AppendTranslate(-output_size.width() * 0.5f,
-output_size.height() * 0.5f, 0.0f);
break;
}
}
// Ensure we have no conflicting animation.
root_animation_.driver().RemoveAllKeyframeModels();
// We will use the ease in or ease out timing function (used by CSS
// transitions) depending on whether the the new content is being covered or
// revealed. If it's being covered, then we want to immediately start moving,
// but ease into position, eg.
std::unique_ptr<gfx::CubicBezierTimingFunction> timing_function =
gfx::CubicBezierTimingFunction::CreatePreset(
opacity_property_id == AnimationState::kSrcOpacity
? gfx::CubicBezierTimingFunction::EaseType::EASE_IN
: gfx::CubicBezierTimingFunction::EaseType::EASE_OUT);
// Create the transform curve.
base::TimeDelta transform_duration = kDefaultAnimationDuration;
std::unique_ptr<gfx::KeyframedTransformAnimationCurve> transform_curve(
gfx::KeyframedTransformAnimationCurve::Create());
transform_curve->AddKeyframe(gfx::TransformKeyframe::Create(
base::TimeDelta(), start_transform, timing_function->Clone()));
transform_curve->AddKeyframe(gfx::TransformKeyframe::Create(
transform_duration, end_transform, timing_function->Clone()));
transform_curve->set_target(&root_animation_);
root_animation_.driver().AddKeyframeModel(gfx::KeyframeModel::Create(
std::move(transform_curve), gfx::KeyframeEffect::GetNextKeyframeModelId(),
transform_property_id));
// Create the opacity curve. Somewhat more complicated because it may be
// delayed wrt to the transform curve. See description of
// |kOpacityTransitionDurationScaleFactor| above.
base::TimeDelta opacity_duration =
transform_duration * kOpacityTransitionDurationScaleFactor;
base::TimeDelta opacity_delay = start_opacity == 0.0f
? base::TimeDelta()
: transform_duration - opacity_duration;
// Opacity transitions do not need to ease in or out. By passing nullptr for
// the timing function here, we are choosing the "linear" timing function.
std::unique_ptr<gfx::KeyframedFloatAnimationCurve> float_curve(
gfx::KeyframedFloatAnimationCurve::Create());
if (!opacity_delay.is_zero()) {
float_curve->AddKeyframe(
gfx::FloatKeyframe::Create(base::TimeDelta(), start_opacity, nullptr));
}
float_curve->AddKeyframe(
gfx::FloatKeyframe::Create(opacity_delay, start_opacity, nullptr));
float_curve->AddKeyframe(
gfx::FloatKeyframe::Create(opacity_duration, end_opacity, nullptr));
float_curve->set_target(&root_animation_);
root_animation_.driver().AddKeyframeModel(gfx::KeyframeModel::Create(
std::move(float_curve), gfx::KeyframeEffect::GetNextKeyframeModelId(),
opacity_property_id));
// We should now have animations queued up.
DCHECK(root_animation_.driver().IsAnimating());
// To ensure we don't flicker at the beginning of the animation, ensure that
// our initial state is correct before we start ticking.
root_animation_.Reset();
root_animation_.OnTransformAnimated(start_transform, transform_property_id,
nullptr);
root_animation_.OnFloatAnimated(start_opacity, opacity_property_id, nullptr);
}
void SurfaceAnimationManager::CreateSharedElementCurves() {
DCHECK(animate_directive_.has_value());
// Clear and resize, to reset the shared animations state if any.
shared_animations_.clear();
shared_animations_.resize(
animate_directive_->shared_render_pass_ids().size());
// Since we don't have a target state yet, create animations as if all of the
// shared elements are targeted to stay in place with opacity going to 0.
for (size_t i = 0; i < saved_textures_->shared.size(); ++i) {
auto& shared = saved_textures_->shared[i];
auto& state = shared_animations_[i];
// Opacity goes from 1 to 0 linearly on the source.
float start_opacity = 1.f;
float end_opacity = 0.f;
auto float_curve = gfx::KeyframedFloatAnimationCurve::Create();
float_curve->set_target(&state);
// The curve starts at opacity delay and runs for opacity animation, so it
// potentially has 4 points:
// time 0 == start opacity
// time 'delay' == start opacity
// time 'delay' + 'duration' == end opacity
// time end of animation == end opacity
float_curve->AddKeyframe(
gfx::FloatKeyframe::Create(base::TimeDelta(), start_opacity, nullptr));
if (!kSharedOpacityAnimationDelay.is_zero()) {
float_curve->AddKeyframe(gfx::FloatKeyframe::Create(
kSharedOpacityAnimationDelay, start_opacity, nullptr));
}
float_curve->AddKeyframe(gfx::FloatKeyframe::Create(
kSharedOpacityAnimationDuration + kSharedOpacityAnimationDelay,
end_opacity, nullptr));
float_curve->AddKeyframe(gfx::FloatKeyframe::Create(
kDefaultAnimationDuration, end_opacity, nullptr));
state.driver().AddKeyframeModel(gfx::KeyframeModel::Create(
std::move(float_curve), gfx::KeyframeEffect::GetNextKeyframeModelId(),
AnimationState::kSrcOpacity));
// If we don't have a source, we will always use the destination
// rect/transform, so don't create the animation curves for those.
if (!shared.has_value())
continue;
// Set transform value to be the same at the start and end; we will
// re-target the end transform when we update the curves for a given
// compositor frame if needed.
// The specific timing function is fine tuned for the effect.
auto ease_timing =
gfx::CubicBezierTimingFunction::Create(0.4, 0.0, 0.2, 1.0);
gfx::TransformOperations transform_ops;
transform_ops.AppendMatrix(shared->draw_data.target_transform);
auto transform_curve = gfx::KeyframedTransformAnimationCurve::Create();
transform_curve->set_target(&state);
transform_curve->AddKeyframe(gfx::TransformKeyframe::Create(
base::TimeDelta(), transform_ops, ease_timing->Clone()));
transform_curve->AddKeyframe(gfx::TransformKeyframe::Create(
kDefaultAnimationDuration, transform_ops, ease_timing->Clone()));
// Note that src and dst share the transform, but we use src value here.
state.driver().AddKeyframeModel(gfx::KeyframeModel::Create(
std::move(transform_curve),
gfx::KeyframeEffect::GetNextKeyframeModelId(),
AnimationState::kSrcTransform));
const gfx::Rect& rect = shared->draw_data.rect;
auto rect_curve = gfx::KeyframedRectAnimationCurve::Create();
rect_curve->set_target(&state);
rect_curve->AddKeyframe(gfx::RectKeyframe::Create(base::TimeDelta(), rect,
ease_timing->Clone()));
rect_curve->AddKeyframe(gfx::RectKeyframe::Create(
kDefaultAnimationDuration, rect, ease_timing->Clone()));
// Note that src and dst share the rect, but we use src value here.
state.driver().AddKeyframeModel(gfx::KeyframeModel::Create(
std::move(rect_curve), gfx::KeyframeEffect::GetNextKeyframeModelId(),
AnimationState::kRect));
}
}
// AnimationState
SurfaceAnimationManager::AnimationState::AnimationState() = default;
SurfaceAnimationManager::AnimationState::AnimationState(AnimationState&&) =
default;
SurfaceAnimationManager::AnimationState::~AnimationState() = default;
void SurfaceAnimationManager::AnimationState::OnFloatAnimated(
const float& value,
int target_property_id,
gfx::KeyframeModel* keyframe_model) {
if (target_property_id == kDstOpacity) {
dst_opacity_ = value;
} else {
src_opacity_ = value;
}
}
void SurfaceAnimationManager::AnimationState::OnTransformAnimated(
const gfx::TransformOperations& operations,
int target_property_id,
gfx::KeyframeModel* keyframe_model) {
if (target_property_id == kDstTransform) {
dst_transform_ = operations;
} else {
src_transform_ = operations;
}
}
void SurfaceAnimationManager::AnimationState::OnRectAnimated(
const gfx::Rect& value,
int target_property_id,
gfx::KeyframeModel* keyframe_model) {
DCHECK_EQ(target_property_id, kRect);
rect_ = value;
}
void SurfaceAnimationManager::AnimationState::Reset() {
src_opacity_ = 1.0f;
dst_opacity_ = 1.0f;
src_transform_ = gfx::TransformOperations();
dst_transform_ = gfx::TransformOperations();
rect_ = gfx::Rect();
}
SurfaceAnimationManager::RenderPassDrawData::RenderPassDrawData() = default;
SurfaceAnimationManager::RenderPassDrawData::RenderPassDrawData(
RenderPassDrawData&&) = default;
SurfaceAnimationManager::RenderPassDrawData::~RenderPassDrawData() = default;
} // namespace viz