| // Copyright 2014 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 <set> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "cc/base/math_util.h" |
| #include "cc/proto/gfx_conversions.h" |
| #include "cc/proto/property_tree.pb.h" |
| #include "cc/proto/scroll_offset.pb.h" |
| #include "cc/proto/transform.pb.h" |
| #include "cc/proto/vector2df.pb.h" |
| #include "cc/trees/property_tree.h" |
| |
| namespace cc { |
| |
| template <typename T> |
| bool TreeNode<T>::operator==(const TreeNode<T>& other) const { |
| return id == other.id && parent_id == other.parent_id && |
| owner_id == other.owner_id && data == other.data; |
| } |
| |
| template <typename T> |
| void TreeNode<T>::ToProtobuf(proto::TreeNode* proto) const { |
| proto->set_id(id); |
| proto->set_parent_id(parent_id); |
| proto->set_owner_id(owner_id); |
| data.ToProtobuf(proto); |
| } |
| |
| template <typename T> |
| void TreeNode<T>::FromProtobuf(const proto::TreeNode& proto) { |
| id = proto.id(); |
| parent_id = proto.parent_id(); |
| owner_id = proto.owner_id(); |
| data.FromProtobuf(proto); |
| } |
| |
| template struct TreeNode<TransformNodeData>; |
| template struct TreeNode<ClipNodeData>; |
| template struct TreeNode<EffectNodeData>; |
| |
| template <typename T> |
| PropertyTree<T>::PropertyTree() |
| : needs_update_(false) { |
| nodes_.push_back(T()); |
| back()->id = 0; |
| back()->parent_id = -1; |
| } |
| |
| template <typename T> |
| PropertyTree<T>::~PropertyTree() { |
| } |
| |
| TransformTree::TransformTree() |
| : source_to_parent_updates_allowed_(true), |
| page_scale_factor_(1.f), |
| device_scale_factor_(1.f), |
| device_transform_scale_factor_(1.f) {} |
| |
| TransformTree::~TransformTree() { |
| } |
| |
| template <typename T> |
| int PropertyTree<T>::Insert(const T& tree_node, int parent_id) { |
| DCHECK_GT(nodes_.size(), 0u); |
| nodes_.push_back(tree_node); |
| T& node = nodes_.back(); |
| node.parent_id = parent_id; |
| node.id = static_cast<int>(nodes_.size()) - 1; |
| return node.id; |
| } |
| |
| template <typename T> |
| void PropertyTree<T>::clear() { |
| nodes_.clear(); |
| nodes_.push_back(T()); |
| back()->id = 0; |
| back()->parent_id = -1; |
| } |
| |
| template <typename T> |
| bool PropertyTree<T>::operator==(const PropertyTree<T>& other) const { |
| return nodes_ == other.nodes() && needs_update_ == other.needs_update(); |
| } |
| |
| template <typename T> |
| void PropertyTree<T>::ToProtobuf(proto::PropertyTree* proto) const { |
| DCHECK_EQ(0, proto->nodes_size()); |
| for (const auto& node : nodes_) |
| node.ToProtobuf(proto->add_nodes()); |
| proto->set_needs_update(needs_update_); |
| } |
| |
| template <typename T> |
| void PropertyTree<T>::FromProtobuf(const proto::PropertyTree& proto) { |
| // Verify that the property tree is empty. |
| DCHECK_EQ(static_cast<int>(nodes_.size()), 1); |
| DCHECK_EQ(back()->id, 0); |
| DCHECK_EQ(back()->parent_id, -1); |
| |
| // Add the first node. |
| DCHECK_GT(proto.nodes_size(), 0); |
| nodes_.back().FromProtobuf(proto.nodes(0)); |
| |
| for (int i = 1; i < proto.nodes_size(); ++i) { |
| nodes_.push_back(T()); |
| nodes_.back().FromProtobuf(proto.nodes(i)); |
| } |
| |
| needs_update_ = proto.needs_update(); |
| } |
| |
| template class PropertyTree<TransformNode>; |
| template class PropertyTree<ClipNode>; |
| template class PropertyTree<EffectNode>; |
| |
| TransformNodeData::TransformNodeData() |
| : target_id(-1), |
| content_target_id(-1), |
| source_node_id(-1), |
| needs_local_transform_update(true), |
| is_invertible(true), |
| ancestors_are_invertible(true), |
| is_animated(false), |
| to_screen_is_animated(false), |
| has_only_translation_animations(true), |
| to_screen_has_scale_animation(false), |
| flattens_inherited_transform(false), |
| node_and_ancestors_are_flat(true), |
| node_and_ancestors_have_only_integer_translation(true), |
| scrolls(false), |
| needs_sublayer_scale(false), |
| affected_by_inner_viewport_bounds_delta_x(false), |
| affected_by_inner_viewport_bounds_delta_y(false), |
| affected_by_outer_viewport_bounds_delta_x(false), |
| affected_by_outer_viewport_bounds_delta_y(false), |
| in_subtree_of_page_scale_layer(false), |
| post_local_scale_factor(1.0f), |
| local_maximum_animation_target_scale(0.f), |
| local_starting_animation_scale(0.f), |
| combined_maximum_animation_target_scale(0.f), |
| combined_starting_animation_scale(0.f) {} |
| |
| TransformNodeData::~TransformNodeData() { |
| } |
| |
| bool TransformNodeData::operator==(const TransformNodeData& other) const { |
| return pre_local == other.pre_local && local == other.local && |
| post_local == other.post_local && to_parent == other.to_parent && |
| to_target == other.to_target && from_target == other.from_target && |
| to_screen == other.to_screen && from_screen == other.from_screen && |
| target_id == other.target_id && |
| content_target_id == other.content_target_id && |
| source_node_id == other.source_node_id && |
| needs_local_transform_update == other.needs_local_transform_update && |
| is_invertible == other.is_invertible && |
| ancestors_are_invertible == other.ancestors_are_invertible && |
| is_animated == other.is_animated && |
| to_screen_is_animated == other.to_screen_is_animated && |
| has_only_translation_animations == |
| other.has_only_translation_animations && |
| to_screen_has_scale_animation == other.to_screen_has_scale_animation && |
| flattens_inherited_transform == other.flattens_inherited_transform && |
| node_and_ancestors_are_flat == other.node_and_ancestors_are_flat && |
| node_and_ancestors_have_only_integer_translation == |
| other.node_and_ancestors_have_only_integer_translation && |
| scrolls == other.scrolls && |
| needs_sublayer_scale == other.needs_sublayer_scale && |
| affected_by_inner_viewport_bounds_delta_x == |
| other.affected_by_inner_viewport_bounds_delta_x && |
| affected_by_inner_viewport_bounds_delta_y == |
| other.affected_by_inner_viewport_bounds_delta_y && |
| affected_by_outer_viewport_bounds_delta_x == |
| other.affected_by_outer_viewport_bounds_delta_x && |
| affected_by_outer_viewport_bounds_delta_y == |
| other.affected_by_outer_viewport_bounds_delta_y && |
| in_subtree_of_page_scale_layer == |
| other.in_subtree_of_page_scale_layer && |
| post_local_scale_factor == other.post_local_scale_factor && |
| local_maximum_animation_target_scale == |
| other.local_maximum_animation_target_scale && |
| local_starting_animation_scale == |
| other.local_starting_animation_scale && |
| combined_maximum_animation_target_scale == |
| other.combined_maximum_animation_target_scale && |
| combined_starting_animation_scale == |
| other.combined_starting_animation_scale && |
| sublayer_scale == other.sublayer_scale && |
| scroll_offset == other.scroll_offset && |
| scroll_snap == other.scroll_snap && |
| source_offset == other.source_offset && |
| source_to_parent == other.source_to_parent; |
| } |
| |
| void TransformNodeData::update_pre_local_transform( |
| const gfx::Point3F& transform_origin) { |
| pre_local.MakeIdentity(); |
| pre_local.Translate3d(-transform_origin.x(), -transform_origin.y(), |
| -transform_origin.z()); |
| } |
| |
| void TransformNodeData::update_post_local_transform( |
| const gfx::PointF& position, |
| const gfx::Point3F& transform_origin) { |
| post_local.MakeIdentity(); |
| post_local.Scale(post_local_scale_factor, post_local_scale_factor); |
| post_local.Translate3d( |
| position.x() + source_offset.x() + transform_origin.x(), |
| position.y() + source_offset.y() + transform_origin.y(), |
| transform_origin.z()); |
| } |
| |
| void TransformNodeData::ToProtobuf(proto::TreeNode* proto) const { |
| DCHECK(!proto->has_transform_node_data()); |
| proto::TranformNodeData* data = proto->mutable_transform_node_data(); |
| |
| TransformToProto(pre_local, data->mutable_pre_local()); |
| TransformToProto(local, data->mutable_local()); |
| TransformToProto(post_local, data->mutable_post_local()); |
| |
| TransformToProto(to_parent, data->mutable_to_parent()); |
| |
| TransformToProto(to_target, data->mutable_to_target()); |
| TransformToProto(from_target, data->mutable_from_target()); |
| |
| TransformToProto(to_screen, data->mutable_to_screen()); |
| TransformToProto(from_screen, data->mutable_from_screen()); |
| |
| data->set_target_id(target_id); |
| data->set_content_target_id(content_target_id); |
| data->set_source_node_id(source_node_id); |
| |
| data->set_needs_local_transform_update(needs_local_transform_update); |
| |
| data->set_is_invertible(is_invertible); |
| data->set_ancestors_are_invertible(ancestors_are_invertible); |
| |
| data->set_is_animated(is_animated); |
| data->set_to_screen_is_animated(to_screen_is_animated); |
| data->set_has_only_translation_animations(has_only_translation_animations); |
| data->set_to_screen_has_scale_animation(to_screen_has_scale_animation); |
| |
| data->set_flattens_inherited_transform(flattens_inherited_transform); |
| data->set_node_and_ancestors_are_flat(node_and_ancestors_are_flat); |
| |
| data->set_node_and_ancestors_have_only_integer_translation( |
| node_and_ancestors_have_only_integer_translation); |
| data->set_scrolls(scrolls); |
| data->set_needs_sublayer_scale(needs_sublayer_scale); |
| |
| data->set_affected_by_inner_viewport_bounds_delta_x( |
| affected_by_inner_viewport_bounds_delta_x); |
| data->set_affected_by_inner_viewport_bounds_delta_y( |
| affected_by_inner_viewport_bounds_delta_y); |
| data->set_affected_by_outer_viewport_bounds_delta_x( |
| affected_by_outer_viewport_bounds_delta_x); |
| data->set_affected_by_outer_viewport_bounds_delta_y( |
| affected_by_outer_viewport_bounds_delta_y); |
| |
| data->set_in_subtree_of_page_scale_layer(in_subtree_of_page_scale_layer); |
| data->set_post_local_scale_factor(post_local_scale_factor); |
| data->set_local_maximum_animation_target_scale( |
| local_maximum_animation_target_scale); |
| data->set_local_starting_animation_scale(local_starting_animation_scale); |
| data->set_combined_maximum_animation_target_scale( |
| combined_maximum_animation_target_scale); |
| data->set_combined_starting_animation_scale( |
| combined_starting_animation_scale); |
| |
| Vector2dFToProto(sublayer_scale, data->mutable_sublayer_scale()); |
| ScrollOffsetToProto(scroll_offset, data->mutable_scroll_offset()); |
| Vector2dFToProto(scroll_snap, data->mutable_scroll_snap()); |
| Vector2dFToProto(source_offset, data->mutable_source_offset()); |
| Vector2dFToProto(source_to_parent, data->mutable_source_to_parent()); |
| } |
| |
| void TransformNodeData::FromProtobuf(const proto::TreeNode& proto) { |
| DCHECK(proto.has_transform_node_data()); |
| const proto::TranformNodeData& data = proto.transform_node_data(); |
| |
| pre_local = ProtoToTransform(data.pre_local()); |
| local = ProtoToTransform(data.local()); |
| post_local = ProtoToTransform(data.post_local()); |
| |
| to_parent = ProtoToTransform(data.to_parent()); |
| |
| to_target = ProtoToTransform(data.to_target()); |
| from_target = ProtoToTransform(data.from_target()); |
| |
| to_screen = ProtoToTransform(data.to_screen()); |
| from_screen = ProtoToTransform(data.from_screen()); |
| |
| target_id = data.target_id(); |
| content_target_id = data.content_target_id(); |
| source_node_id = data.source_node_id(); |
| |
| needs_local_transform_update = data.needs_local_transform_update(); |
| |
| is_invertible = data.is_invertible(); |
| ancestors_are_invertible = data.ancestors_are_invertible(); |
| |
| is_animated = data.is_animated(); |
| to_screen_is_animated = data.to_screen_is_animated(); |
| has_only_translation_animations = data.has_only_translation_animations(); |
| to_screen_has_scale_animation = data.to_screen_has_scale_animation(); |
| |
| flattens_inherited_transform = data.flattens_inherited_transform(); |
| node_and_ancestors_are_flat = data.node_and_ancestors_are_flat(); |
| |
| node_and_ancestors_have_only_integer_translation = |
| data.node_and_ancestors_have_only_integer_translation(); |
| scrolls = data.scrolls(); |
| needs_sublayer_scale = data.needs_sublayer_scale(); |
| |
| affected_by_inner_viewport_bounds_delta_x = |
| data.affected_by_inner_viewport_bounds_delta_x(); |
| affected_by_inner_viewport_bounds_delta_y = |
| data.affected_by_inner_viewport_bounds_delta_y(); |
| affected_by_outer_viewport_bounds_delta_x = |
| data.affected_by_outer_viewport_bounds_delta_x(); |
| affected_by_outer_viewport_bounds_delta_y = |
| data.affected_by_outer_viewport_bounds_delta_y(); |
| |
| in_subtree_of_page_scale_layer = data.in_subtree_of_page_scale_layer(); |
| post_local_scale_factor = data.post_local_scale_factor(); |
| local_maximum_animation_target_scale = |
| data.local_maximum_animation_target_scale(); |
| local_starting_animation_scale = data.local_starting_animation_scale(); |
| combined_maximum_animation_target_scale = |
| data.combined_maximum_animation_target_scale(); |
| combined_starting_animation_scale = data.combined_starting_animation_scale(); |
| |
| sublayer_scale = ProtoToVector2dF(data.sublayer_scale()); |
| scroll_offset = ProtoToScrollOffset(data.scroll_offset()); |
| scroll_snap = ProtoToVector2dF(data.scroll_snap()); |
| source_offset = ProtoToVector2dF(data.source_offset()); |
| source_to_parent = ProtoToVector2dF(data.source_to_parent()); |
| } |
| |
| ClipNodeData::ClipNodeData() |
| : transform_id(-1), |
| target_id(-1), |
| applies_local_clip(true), |
| layer_clipping_uses_only_local_clip(false), |
| target_is_clipped(false), |
| layers_are_clipped(false), |
| layers_are_clipped_when_surfaces_disabled(false), |
| resets_clip(false) {} |
| |
| bool ClipNodeData::operator==(const ClipNodeData& other) const { |
| return clip == other.clip && |
| combined_clip_in_target_space == other.combined_clip_in_target_space && |
| clip_in_target_space == other.clip_in_target_space && |
| transform_id == other.transform_id && target_id == other.target_id && |
| applies_local_clip == other.applies_local_clip && |
| layer_clipping_uses_only_local_clip == |
| other.layer_clipping_uses_only_local_clip && |
| target_is_clipped == other.target_is_clipped && |
| layers_are_clipped == other.layers_are_clipped && |
| layers_are_clipped_when_surfaces_disabled == |
| other.layers_are_clipped_when_surfaces_disabled && |
| resets_clip == other.resets_clip; |
| } |
| |
| void ClipNodeData::ToProtobuf(proto::TreeNode* proto) const { |
| DCHECK(!proto->has_clip_node_data()); |
| proto::ClipNodeData* data = proto->mutable_clip_node_data(); |
| |
| RectFToProto(clip, data->mutable_clip()); |
| RectFToProto(combined_clip_in_target_space, |
| data->mutable_combined_clip_in_target_space()); |
| RectFToProto(clip_in_target_space, data->mutable_clip_in_target_space()); |
| |
| data->set_transform_id(transform_id); |
| data->set_target_id(target_id); |
| data->set_applies_local_clip(applies_local_clip); |
| data->set_layer_clipping_uses_only_local_clip( |
| layer_clipping_uses_only_local_clip); |
| data->set_target_is_clipped(target_is_clipped); |
| data->set_layers_are_clipped(layers_are_clipped); |
| data->set_layers_are_clipped_when_surfaces_disabled( |
| layers_are_clipped_when_surfaces_disabled); |
| data->set_resets_clip(resets_clip); |
| } |
| |
| void ClipNodeData::FromProtobuf(const proto::TreeNode& proto) { |
| DCHECK(proto.has_clip_node_data()); |
| const proto::ClipNodeData& data = proto.clip_node_data(); |
| |
| clip = ProtoToRectF(data.clip()); |
| combined_clip_in_target_space = |
| ProtoToRectF(data.combined_clip_in_target_space()); |
| clip_in_target_space = ProtoToRectF(data.clip_in_target_space()); |
| |
| transform_id = data.transform_id(); |
| target_id = data.target_id(); |
| applies_local_clip = data.applies_local_clip(); |
| layer_clipping_uses_only_local_clip = |
| data.layer_clipping_uses_only_local_clip(); |
| target_is_clipped = data.target_is_clipped(); |
| layers_are_clipped = data.layers_are_clipped(); |
| layers_are_clipped_when_surfaces_disabled = |
| data.layers_are_clipped_when_surfaces_disabled(); |
| resets_clip = data.resets_clip(); |
| } |
| |
| EffectNodeData::EffectNodeData() |
| : opacity(1.f), |
| screen_space_opacity(1.f), |
| has_render_surface(false), |
| transform_id(0), |
| clip_id(0) {} |
| |
| bool EffectNodeData::operator==(const EffectNodeData& other) const { |
| return opacity == other.opacity && |
| screen_space_opacity == other.screen_space_opacity && |
| has_render_surface == other.has_render_surface && |
| transform_id == other.transform_id && clip_id == other.clip_id; |
| } |
| |
| void EffectNodeData::ToProtobuf(proto::TreeNode* proto) const { |
| DCHECK(!proto->has_effect_node_data()); |
| proto::EffectNodeData* data = proto->mutable_effect_node_data(); |
| data->set_opacity(opacity); |
| data->set_screen_space_opacity(screen_space_opacity); |
| data->set_has_render_surface(has_render_surface); |
| data->set_transform_id(transform_id); |
| data->set_clip_id(clip_id); |
| } |
| |
| void EffectNodeData::FromProtobuf(const proto::TreeNode& proto) { |
| DCHECK(proto.has_effect_node_data()); |
| const proto::EffectNodeData& data = proto.effect_node_data(); |
| |
| opacity = data.opacity(); |
| screen_space_opacity = data.screen_space_opacity(); |
| has_render_surface = data.has_render_surface(); |
| transform_id = data.transform_id(); |
| clip_id = data.clip_id(); |
| } |
| |
| void TransformTree::clear() { |
| PropertyTree<TransformNode>::clear(); |
| |
| nodes_affected_by_inner_viewport_bounds_delta_.clear(); |
| nodes_affected_by_outer_viewport_bounds_delta_.clear(); |
| } |
| |
| bool TransformTree::ComputeTransform(int source_id, |
| int dest_id, |
| gfx::Transform* transform) const { |
| transform->MakeIdentity(); |
| |
| if (source_id == dest_id) |
| return true; |
| |
| if (source_id > dest_id) { |
| return CombineTransformsBetween(source_id, dest_id, transform); |
| } |
| |
| return CombineInversesBetween(source_id, dest_id, transform); |
| } |
| |
| bool TransformTree::ComputeTransformWithDestinationSublayerScale( |
| int source_id, |
| int dest_id, |
| gfx::Transform* transform) const { |
| bool success = ComputeTransform(source_id, dest_id, transform); |
| |
| const TransformNode* dest_node = Node(dest_id); |
| if (!dest_node->data.needs_sublayer_scale) |
| return success; |
| |
| transform->matrix().postScale(dest_node->data.sublayer_scale.x(), |
| dest_node->data.sublayer_scale.y(), 1.f); |
| return success; |
| } |
| |
| bool TransformTree::ComputeTransformWithSourceSublayerScale( |
| int source_id, |
| int dest_id, |
| gfx::Transform* transform) const { |
| bool success = ComputeTransform(source_id, dest_id, transform); |
| |
| const TransformNode* source_node = Node(source_id); |
| if (!source_node->data.needs_sublayer_scale) |
| return success; |
| |
| if (source_node->data.sublayer_scale.x() == 0 || |
| source_node->data.sublayer_scale.y() == 0) |
| return false; |
| |
| transform->Scale(1.f / source_node->data.sublayer_scale.x(), |
| 1.f / source_node->data.sublayer_scale.y()); |
| return success; |
| } |
| |
| bool TransformTree::Are2DAxisAligned(int source_id, int dest_id) const { |
| gfx::Transform transform; |
| return ComputeTransform(source_id, dest_id, &transform) && |
| transform.Preserves2dAxisAlignment(); |
| } |
| |
| bool TransformTree::NeedsSourceToParentUpdate(TransformNode* node) { |
| return (source_to_parent_updates_allowed() && |
| node->parent_id != node->data.source_node_id); |
| } |
| |
| void TransformTree::UpdateTransforms(int id) { |
| TransformNode* node = Node(id); |
| TransformNode* parent_node = parent(node); |
| TransformNode* target_node = Node(node->data.target_id); |
| if (node->data.needs_local_transform_update || |
| NeedsSourceToParentUpdate(node)) |
| UpdateLocalTransform(node); |
| else |
| UndoSnapping(node); |
| UpdateScreenSpaceTransform(node, parent_node, target_node); |
| UpdateSublayerScale(node); |
| UpdateTargetSpaceTransform(node, target_node); |
| UpdateAnimationProperties(node, parent_node); |
| UpdateSnapping(node); |
| UpdateNodeAndAncestorsHaveIntegerTranslations(node, parent_node); |
| } |
| |
| bool TransformTree::IsDescendant(int desc_id, int source_id) const { |
| while (desc_id != source_id) { |
| if (desc_id < 0) |
| return false; |
| desc_id = Node(desc_id)->parent_id; |
| } |
| return true; |
| } |
| |
| bool TransformTree::CombineTransformsBetween(int source_id, |
| int dest_id, |
| gfx::Transform* transform) const { |
| DCHECK(source_id > dest_id); |
| const TransformNode* current = Node(source_id); |
| const TransformNode* dest = Node(dest_id); |
| // Combine transforms to and from the screen when possible. Since flattening |
| // is a non-linear operation, we cannot use this approach when there is |
| // non-trivial flattening between the source and destination nodes. For |
| // example, consider the tree R->A->B->C, where B flattens its inherited |
| // transform, and A has a non-flat transform. Suppose C is the source and A is |
| // the destination. The expected result is C * B. But C's to_screen |
| // transform is C * B * flattened(A * R), and A's from_screen transform is |
| // R^{-1} * A^{-1}. If at least one of A and R isn't flat, the inverse of |
| // flattened(A * R) won't be R^{-1} * A{-1}, so multiplying C's to_screen and |
| // A's from_screen will not produce the correct result. |
| if (!dest || (dest->data.ancestors_are_invertible && |
| dest->data.node_and_ancestors_are_flat)) { |
| transform->ConcatTransform(current->data.to_screen); |
| if (dest) |
| transform->ConcatTransform(dest->data.from_screen); |
| return true; |
| } |
| |
| // Flattening is defined in a way that requires it to be applied while |
| // traversing downward in the tree. We first identify nodes that are on the |
| // path from the source to the destination (this is traversing upward), and |
| // then we visit these nodes in reverse order, flattening as needed. We |
| // early-out if we get to a node whose target node is the destination, since |
| // we can then re-use the target space transform stored at that node. However, |
| // we cannot re-use a stored target space transform if the destination has a |
| // zero sublayer scale, since stored target space transforms have sublayer |
| // scale baked in, but we need to compute an unscaled transform. |
| std::vector<int> source_to_destination; |
| source_to_destination.push_back(current->id); |
| current = parent(current); |
| bool destination_has_non_zero_sublayer_scale = |
| dest->data.sublayer_scale.x() != 0.f && |
| dest->data.sublayer_scale.y() != 0.f; |
| DCHECK(destination_has_non_zero_sublayer_scale || |
| !dest->data.ancestors_are_invertible); |
| for (; current && current->id > dest_id; current = parent(current)) { |
| if (destination_has_non_zero_sublayer_scale && |
| current->data.target_id == dest_id && |
| current->data.content_target_id == dest_id) |
| break; |
| source_to_destination.push_back(current->id); |
| } |
| |
| gfx::Transform combined_transform; |
| if (current->id > dest_id) { |
| combined_transform = current->data.to_target; |
| // The stored target space transform has sublayer scale baked in, but we |
| // need the unscaled transform. |
| combined_transform.Scale(1.0f / dest->data.sublayer_scale.x(), |
| 1.0f / dest->data.sublayer_scale.y()); |
| } else if (current->id < dest_id) { |
| // We have reached the lowest common ancestor of the source and destination |
| // nodes. This case can occur when we are transforming between a node |
| // corresponding to a fixed-position layer (or its descendant) and the node |
| // corresponding to the layer's render target. For example, consider the |
| // layer tree R->T->S->F where F is fixed-position, S owns a render surface, |
| // and T has a significant transform. This will yield the following |
| // transform tree: |
| // R |
| // | |
| // T |
| // /| |
| // S F |
| // In this example, T will have id 2, S will have id 3, and F will have id |
| // 4. When walking up the ancestor chain from F, the first node with a |
| // smaller id than S will be T, the lowest common ancestor of these nodes. |
| // We compute the transform from T to S here, and then from F to T in the |
| // loop below. |
| DCHECK(IsDescendant(dest_id, current->id)); |
| CombineInversesBetween(current->id, dest_id, &combined_transform); |
| DCHECK(combined_transform.IsApproximatelyIdentityOrTranslation( |
| SkDoubleToMScalar(1e-4))); |
| } |
| |
| size_t source_to_destination_size = source_to_destination.size(); |
| for (size_t i = 0; i < source_to_destination_size; ++i) { |
| size_t index = source_to_destination_size - 1 - i; |
| const TransformNode* node = Node(source_to_destination[index]); |
| if (node->data.flattens_inherited_transform) |
| combined_transform.FlattenTo2d(); |
| combined_transform.PreconcatTransform(node->data.to_parent); |
| } |
| |
| transform->ConcatTransform(combined_transform); |
| return true; |
| } |
| |
| bool TransformTree::CombineInversesBetween(int source_id, |
| int dest_id, |
| gfx::Transform* transform) const { |
| DCHECK(source_id < dest_id); |
| const TransformNode* current = Node(dest_id); |
| const TransformNode* dest = Node(source_id); |
| // Just as in CombineTransformsBetween, we can use screen space transforms in |
| // this computation only when there isn't any non-trivial flattening |
| // involved. |
| if (current->data.ancestors_are_invertible && |
| current->data.node_and_ancestors_are_flat) { |
| transform->PreconcatTransform(current->data.from_screen); |
| if (dest) |
| transform->PreconcatTransform(dest->data.to_screen); |
| return true; |
| } |
| |
| // Inverting a flattening is not equivalent to flattening an inverse. This |
| // means we cannot, for example, use the inverse of each node's to_parent |
| // transform, flattening where needed. Instead, we must compute the transform |
| // from the destination to the source, with flattening, and then invert the |
| // result. |
| gfx::Transform dest_to_source; |
| CombineTransformsBetween(dest_id, source_id, &dest_to_source); |
| gfx::Transform source_to_dest; |
| bool all_are_invertible = dest_to_source.GetInverse(&source_to_dest); |
| transform->PreconcatTransform(source_to_dest); |
| return all_are_invertible; |
| } |
| |
| void TransformTree::UpdateLocalTransform(TransformNode* node) { |
| gfx::Transform transform = node->data.post_local; |
| if (NeedsSourceToParentUpdate(node)) { |
| gfx::Transform to_parent; |
| ComputeTransform(node->data.source_node_id, node->parent_id, &to_parent); |
| node->data.source_to_parent = to_parent.To2dTranslation(); |
| } |
| |
| gfx::Vector2dF fixed_position_adjustment; |
| if (node->data.affected_by_inner_viewport_bounds_delta_x) |
| fixed_position_adjustment.set_x(inner_viewport_bounds_delta_.x()); |
| else if (node->data.affected_by_outer_viewport_bounds_delta_x) |
| fixed_position_adjustment.set_x(outer_viewport_bounds_delta_.x()); |
| |
| if (node->data.affected_by_inner_viewport_bounds_delta_y) |
| fixed_position_adjustment.set_y(inner_viewport_bounds_delta_.y()); |
| else if (node->data.affected_by_outer_viewport_bounds_delta_y) |
| fixed_position_adjustment.set_y(outer_viewport_bounds_delta_.y()); |
| |
| transform.Translate( |
| node->data.source_to_parent.x() - node->data.scroll_offset.x() + |
| fixed_position_adjustment.x(), |
| node->data.source_to_parent.y() - node->data.scroll_offset.y() + |
| fixed_position_adjustment.y()); |
| transform.PreconcatTransform(node->data.local); |
| transform.PreconcatTransform(node->data.pre_local); |
| node->data.set_to_parent(transform); |
| node->data.needs_local_transform_update = false; |
| } |
| |
| void TransformTree::UpdateScreenSpaceTransform(TransformNode* node, |
| TransformNode* parent_node, |
| TransformNode* target_node) { |
| if (!parent_node) { |
| node->data.to_screen = node->data.to_parent; |
| node->data.ancestors_are_invertible = true; |
| node->data.to_screen_is_animated = false; |
| node->data.node_and_ancestors_are_flat = node->data.to_parent.IsFlat(); |
| } else { |
| node->data.to_screen = parent_node->data.to_screen; |
| if (node->data.flattens_inherited_transform) |
| node->data.to_screen.FlattenTo2d(); |
| node->data.to_screen.PreconcatTransform(node->data.to_parent); |
| node->data.ancestors_are_invertible = |
| parent_node->data.ancestors_are_invertible; |
| node->data.node_and_ancestors_are_flat = |
| parent_node->data.node_and_ancestors_are_flat && |
| node->data.to_parent.IsFlat(); |
| } |
| |
| if (!node->data.to_screen.GetInverse(&node->data.from_screen)) |
| node->data.ancestors_are_invertible = false; |
| } |
| |
| void TransformTree::UpdateSublayerScale(TransformNode* node) { |
| // The sublayer scale depends on the screen space transform, so update it too. |
| if (!node->data.needs_sublayer_scale) { |
| node->data.sublayer_scale = gfx::Vector2dF(1.0f, 1.0f); |
| return; |
| } |
| |
| float layer_scale_factor = |
| device_scale_factor_ * device_transform_scale_factor_; |
| if (node->data.in_subtree_of_page_scale_layer) |
| layer_scale_factor *= page_scale_factor_; |
| node->data.sublayer_scale = MathUtil::ComputeTransform2dScaleComponents( |
| node->data.to_screen, layer_scale_factor); |
| } |
| |
| void TransformTree::UpdateTargetSpaceTransform(TransformNode* node, |
| TransformNode* target_node) { |
| if (node->data.needs_sublayer_scale) { |
| node->data.to_target.MakeIdentity(); |
| node->data.to_target.Scale(node->data.sublayer_scale.x(), |
| node->data.sublayer_scale.y()); |
| } else { |
| // In order to include the root transform for the root surface, we walk up |
| // to the root of the transform tree in ComputeTransform. |
| int target_id = target_node->id; |
| ComputeTransformWithDestinationSublayerScale(node->id, target_id, |
| &node->data.to_target); |
| } |
| |
| if (!node->data.to_target.GetInverse(&node->data.from_target)) |
| node->data.ancestors_are_invertible = false; |
| } |
| |
| void TransformTree::UpdateAnimationProperties(TransformNode* node, |
| TransformNode* parent_node) { |
| bool ancestor_is_animating = false; |
| bool ancestor_is_animating_scale = false; |
| float ancestor_maximum_target_scale = 0.f; |
| float ancestor_starting_animation_scale = 0.f; |
| if (parent_node) { |
| ancestor_is_animating = parent_node->data.to_screen_is_animated; |
| ancestor_is_animating_scale = |
| parent_node->data.to_screen_has_scale_animation; |
| ancestor_maximum_target_scale = |
| parent_node->data.combined_maximum_animation_target_scale; |
| ancestor_starting_animation_scale = |
| parent_node->data.combined_starting_animation_scale; |
| } |
| node->data.to_screen_is_animated = |
| node->data.is_animated || ancestor_is_animating; |
| node->data.to_screen_has_scale_animation = |
| !node->data.has_only_translation_animations || |
| ancestor_is_animating_scale; |
| |
| // Once we've failed to compute a maximum animated scale at an ancestor, we |
| // continue to fail. |
| bool failed_at_ancestor = |
| ancestor_is_animating_scale && ancestor_maximum_target_scale == 0.f; |
| |
| // Computing maximum animated scale in the presence of non-scale/translation |
| // transforms isn't supported. |
| bool failed_for_non_scale_or_translation = |
| !node->data.to_target.IsScaleOrTranslation(); |
| |
| // We don't attempt to accumulate animation scale from multiple nodes with |
| // scale animations, because of the risk of significant overestimation. For |
| // example, one node might be increasing scale from 1 to 10 at the same time |
| // as another node is decreasing scale from 10 to 1. Naively combining these |
| // scales would produce a scale of 100. |
| bool failed_for_multiple_scale_animations = |
| ancestor_is_animating_scale && |
| !node->data.has_only_translation_animations; |
| |
| if (failed_at_ancestor || failed_for_non_scale_or_translation || |
| failed_for_multiple_scale_animations) { |
| node->data.combined_maximum_animation_target_scale = 0.f; |
| node->data.combined_starting_animation_scale = 0.f; |
| |
| // This ensures that descendants know we've failed to compute a maximum |
| // animated scale. |
| node->data.to_screen_has_scale_animation = true; |
| return; |
| } |
| |
| if (!node->data.to_screen_has_scale_animation) { |
| node->data.combined_maximum_animation_target_scale = 0.f; |
| node->data.combined_starting_animation_scale = 0.f; |
| return; |
| } |
| |
| // At this point, we know exactly one of this node or an ancestor is animating |
| // scale. |
| if (node->data.has_only_translation_animations) { |
| // An ancestor is animating scale. |
| gfx::Vector2dF local_scales = |
| MathUtil::ComputeTransform2dScaleComponents(node->data.local, 0.f); |
| float max_local_scale = std::max(local_scales.x(), local_scales.y()); |
| node->data.combined_maximum_animation_target_scale = |
| max_local_scale * ancestor_maximum_target_scale; |
| node->data.combined_starting_animation_scale = |
| max_local_scale * ancestor_starting_animation_scale; |
| return; |
| } |
| |
| if (node->data.local_starting_animation_scale == 0.f || |
| node->data.local_maximum_animation_target_scale == 0.f) { |
| node->data.combined_maximum_animation_target_scale = 0.f; |
| node->data.combined_starting_animation_scale = 0.f; |
| return; |
| } |
| |
| gfx::Vector2dF ancestor_scales = |
| parent_node ? MathUtil::ComputeTransform2dScaleComponents( |
| parent_node->data.to_target, 0.f) |
| : gfx::Vector2dF(1.f, 1.f); |
| float max_ancestor_scale = std::max(ancestor_scales.x(), ancestor_scales.y()); |
| node->data.combined_maximum_animation_target_scale = |
| max_ancestor_scale * node->data.local_maximum_animation_target_scale; |
| node->data.combined_starting_animation_scale = |
| max_ancestor_scale * node->data.local_starting_animation_scale; |
| } |
| |
| void TransformTree::UndoSnapping(TransformNode* node) { |
| // to_parent transform has the scroll snap from previous frame baked in. |
| // We need to undo it and use the un-snapped transform to compute current |
| // target and screen space transforms. |
| node->data.to_parent.Translate(-node->data.scroll_snap.x(), |
| -node->data.scroll_snap.y()); |
| } |
| |
| void TransformTree::UpdateSnapping(TransformNode* node) { |
| if (!node->data.scrolls || node->data.to_screen_is_animated || |
| !node->data.to_target.IsScaleOrTranslation() || |
| !node->data.ancestors_are_invertible) { |
| return; |
| } |
| |
| // Scroll snapping must be done in target space (the pixels we care about). |
| // This means we effectively snap the target space transform. If TT is the |
| // target space transform and TT' is TT with its translation components |
| // rounded, then what we're after is the scroll delta X, where TT * X = TT'. |
| // I.e., we want a transform that will realize our scroll snap. It follows |
| // that X = TT^-1 * TT'. We cache TT and TT^-1 to make this more efficient. |
| gfx::Transform rounded = node->data.to_target; |
| rounded.RoundTranslationComponents(); |
| gfx::Transform delta = node->data.from_target; |
| delta *= rounded; |
| |
| DCHECK(delta.IsApproximatelyIdentityOrTranslation(SkDoubleToMScalar(1e-4))) |
| << delta.ToString(); |
| |
| gfx::Vector2dF translation = delta.To2dTranslation(); |
| |
| // Now that we have our scroll delta, we must apply it to each of our |
| // combined, to/from matrices. |
| node->data.to_target = rounded; |
| node->data.to_parent.Translate(translation.x(), translation.y()); |
| node->data.from_target.matrix().postTranslate(-translation.x(), |
| -translation.y(), 0); |
| node->data.to_screen.Translate(translation.x(), translation.y()); |
| node->data.from_screen.matrix().postTranslate(-translation.x(), |
| -translation.y(), 0); |
| |
| node->data.scroll_snap = translation; |
| } |
| |
| void TransformTree::SetDeviceTransform(const gfx::Transform& transform, |
| gfx::PointF root_position) { |
| gfx::Transform root_post_local = transform; |
| TransformNode* node = Node(1); |
| root_post_local.Scale(node->data.post_local_scale_factor, |
| node->data.post_local_scale_factor); |
| root_post_local.Translate(root_position.x(), root_position.y()); |
| if (node->data.post_local == root_post_local) |
| return; |
| |
| node->data.post_local = root_post_local; |
| node->data.needs_local_transform_update = true; |
| set_needs_update(true); |
| } |
| |
| void TransformTree::SetDeviceTransformScaleFactor( |
| const gfx::Transform& transform) { |
| gfx::Vector2dF device_transform_scale_components = |
| MathUtil::ComputeTransform2dScaleComponents(transform, 1.f); |
| |
| // Not handling the rare case of different x and y device scale. |
| device_transform_scale_factor_ = |
| std::max(device_transform_scale_components.x(), |
| device_transform_scale_components.y()); |
| } |
| |
| void TransformTree::SetInnerViewportBoundsDelta(gfx::Vector2dF bounds_delta) { |
| if (inner_viewport_bounds_delta_ == bounds_delta) |
| return; |
| |
| inner_viewport_bounds_delta_ = bounds_delta; |
| |
| if (nodes_affected_by_inner_viewport_bounds_delta_.empty()) |
| return; |
| |
| set_needs_update(true); |
| for (int i : nodes_affected_by_inner_viewport_bounds_delta_) |
| Node(i)->data.needs_local_transform_update = true; |
| } |
| |
| void TransformTree::SetOuterViewportBoundsDelta(gfx::Vector2dF bounds_delta) { |
| if (outer_viewport_bounds_delta_ == bounds_delta) |
| return; |
| |
| outer_viewport_bounds_delta_ = bounds_delta; |
| |
| if (nodes_affected_by_outer_viewport_bounds_delta_.empty()) |
| return; |
| |
| set_needs_update(true); |
| for (int i : nodes_affected_by_outer_viewport_bounds_delta_) |
| Node(i)->data.needs_local_transform_update = true; |
| } |
| |
| void TransformTree::AddNodeAffectedByInnerViewportBoundsDelta(int node_id) { |
| nodes_affected_by_inner_viewport_bounds_delta_.push_back(node_id); |
| } |
| |
| void TransformTree::AddNodeAffectedByOuterViewportBoundsDelta(int node_id) { |
| nodes_affected_by_outer_viewport_bounds_delta_.push_back(node_id); |
| } |
| |
| bool TransformTree::HasNodesAffectedByInnerViewportBoundsDelta() const { |
| return !nodes_affected_by_inner_viewport_bounds_delta_.empty(); |
| } |
| |
| bool TransformTree::HasNodesAffectedByOuterViewportBoundsDelta() const { |
| return !nodes_affected_by_outer_viewport_bounds_delta_.empty(); |
| } |
| |
| bool TransformTree::operator==(const TransformTree& other) const { |
| return PropertyTree::operator==(other) && |
| source_to_parent_updates_allowed_ == |
| other.source_to_parent_updates_allowed() && |
| page_scale_factor_ == other.page_scale_factor() && |
| device_scale_factor_ == other.device_scale_factor() && |
| device_transform_scale_factor_ == |
| other.device_transform_scale_factor() && |
| inner_viewport_bounds_delta_ == other.inner_viewport_bounds_delta() && |
| outer_viewport_bounds_delta_ == other.outer_viewport_bounds_delta() && |
| nodes_affected_by_inner_viewport_bounds_delta_ == |
| other.nodes_affected_by_inner_viewport_bounds_delta() && |
| nodes_affected_by_outer_viewport_bounds_delta_ == |
| other.nodes_affected_by_outer_viewport_bounds_delta(); |
| } |
| |
| void TransformTree::ToProtobuf(proto::PropertyTree* proto) const { |
| DCHECK(!proto->has_property_type()); |
| proto->set_property_type(proto::PropertyTree::Transform); |
| |
| PropertyTree::ToProtobuf(proto); |
| proto::TransformTreeData* data = proto->mutable_transform_tree_data(); |
| |
| data->set_source_to_parent_updates_allowed(source_to_parent_updates_allowed_); |
| data->set_page_scale_factor(page_scale_factor_); |
| data->set_device_scale_factor(device_scale_factor_); |
| data->set_device_transform_scale_factor(device_transform_scale_factor_); |
| |
| Vector2dFToProto(inner_viewport_bounds_delta_, |
| data->mutable_inner_viewport_bounds_delta()); |
| Vector2dFToProto(outer_viewport_bounds_delta_, |
| data->mutable_outer_viewport_bounds_delta()); |
| |
| for (auto i : nodes_affected_by_inner_viewport_bounds_delta_) |
| data->add_nodes_affected_by_inner_viewport_bounds_delta(i); |
| |
| for (auto i : nodes_affected_by_outer_viewport_bounds_delta_) |
| data->add_nodes_affected_by_outer_viewport_bounds_delta(i); |
| } |
| |
| void TransformTree::FromProtobuf(const proto::PropertyTree& proto) { |
| DCHECK(proto.has_property_type()); |
| DCHECK_EQ(proto.property_type(), proto::PropertyTree::Transform); |
| |
| PropertyTree::FromProtobuf(proto); |
| const proto::TransformTreeData& data = proto.transform_tree_data(); |
| |
| source_to_parent_updates_allowed_ = data.source_to_parent_updates_allowed(); |
| page_scale_factor_ = data.page_scale_factor(); |
| device_scale_factor_ = data.device_scale_factor(); |
| device_transform_scale_factor_ = data.device_transform_scale_factor(); |
| |
| inner_viewport_bounds_delta_ = |
| ProtoToVector2dF(data.inner_viewport_bounds_delta()); |
| outer_viewport_bounds_delta_ = |
| ProtoToVector2dF(data.outer_viewport_bounds_delta()); |
| |
| DCHECK(nodes_affected_by_inner_viewport_bounds_delta_.empty()); |
| for (int i = 0; i < data.nodes_affected_by_inner_viewport_bounds_delta_size(); |
| ++i) { |
| nodes_affected_by_inner_viewport_bounds_delta_.push_back( |
| data.nodes_affected_by_inner_viewport_bounds_delta(i)); |
| } |
| |
| DCHECK(nodes_affected_by_outer_viewport_bounds_delta_.empty()); |
| for (int i = 0; i < data.nodes_affected_by_outer_viewport_bounds_delta_size(); |
| ++i) { |
| nodes_affected_by_outer_viewport_bounds_delta_.push_back( |
| data.nodes_affected_by_outer_viewport_bounds_delta(i)); |
| } |
| } |
| |
| void EffectTree::UpdateOpacities(EffectNode* node, EffectNode* parent_node) { |
| node->data.screen_space_opacity = node->data.opacity; |
| |
| if (parent_node) |
| node->data.screen_space_opacity *= parent_node->data.screen_space_opacity; |
| } |
| |
| void EffectTree::UpdateEffects(int id) { |
| EffectNode* node = Node(id); |
| EffectNode* parent_node = parent(node); |
| |
| UpdateOpacities(node, parent_node); |
| } |
| |
| void TransformTree::UpdateNodeAndAncestorsHaveIntegerTranslations( |
| TransformNode* node, |
| TransformNode* parent_node) { |
| node->data.node_and_ancestors_have_only_integer_translation = |
| node->data.to_parent.IsIdentityOrIntegerTranslation(); |
| if (parent_node) |
| node->data.node_and_ancestors_have_only_integer_translation = |
| node->data.node_and_ancestors_have_only_integer_translation && |
| parent_node->data.node_and_ancestors_have_only_integer_translation; |
| } |
| |
| void ClipTree::SetViewportClip(gfx::RectF viewport_rect) { |
| if (size() < 2) |
| return; |
| ClipNode* node = Node(1); |
| if (viewport_rect == node->data.clip) |
| return; |
| node->data.clip = viewport_rect; |
| set_needs_update(true); |
| } |
| |
| gfx::RectF ClipTree::ViewportClip() { |
| const unsigned long min_size = 1; |
| DCHECK_GT(size(), min_size); |
| return Node(1)->data.clip; |
| } |
| |
| bool ClipTree::operator==(const ClipTree& other) const { |
| return PropertyTree::operator==(other); |
| } |
| |
| void ClipTree::ToProtobuf(proto::PropertyTree* proto) const { |
| DCHECK(!proto->has_property_type()); |
| proto->set_property_type(proto::PropertyTree::Clip); |
| |
| PropertyTree::ToProtobuf(proto); |
| } |
| |
| void ClipTree::FromProtobuf(const proto::PropertyTree& proto) { |
| DCHECK(proto.has_property_type()); |
| DCHECK_EQ(proto.property_type(), proto::PropertyTree::Clip); |
| |
| PropertyTree::FromProtobuf(proto); |
| } |
| |
| bool EffectTree::operator==(const EffectTree& other) const { |
| return PropertyTree::operator==(other); |
| } |
| |
| void EffectTree::ToProtobuf(proto::PropertyTree* proto) const { |
| DCHECK(!proto->has_property_type()); |
| proto->set_property_type(proto::PropertyTree::Effect); |
| |
| PropertyTree::ToProtobuf(proto); |
| } |
| |
| void EffectTree::FromProtobuf(const proto::PropertyTree& proto) { |
| DCHECK(proto.has_property_type()); |
| DCHECK_EQ(proto.property_type(), proto::PropertyTree::Effect); |
| |
| PropertyTree::FromProtobuf(proto); |
| } |
| |
| PropertyTrees::PropertyTrees() |
| : needs_rebuild(true), |
| non_root_surfaces_enabled(true), |
| sequence_number(0) {} |
| |
| bool PropertyTrees::operator==(const PropertyTrees& other) const { |
| return transform_tree == other.transform_tree && |
| effect_tree == other.effect_tree && clip_tree == other.clip_tree && |
| needs_rebuild == other.needs_rebuild && |
| non_root_surfaces_enabled == other.non_root_surfaces_enabled && |
| sequence_number == other.sequence_number; |
| } |
| |
| void PropertyTrees::ToProtobuf(proto::PropertyTrees* proto) const { |
| // TODO(khushalsagar): Add support for sending diffs when serializaing |
| // property trees. See crbug/555370. |
| transform_tree.ToProtobuf(proto->mutable_transform_tree()); |
| effect_tree.ToProtobuf(proto->mutable_effect_tree()); |
| clip_tree.ToProtobuf(proto->mutable_clip_tree()); |
| proto->set_needs_rebuild(needs_rebuild); |
| proto->set_non_root_surfaces_enabled(non_root_surfaces_enabled); |
| |
| // TODO(khushalsagar): Consider using the sequence number to decide if |
| // property trees need to be serialized again for a commit. See crbug/555370. |
| proto->set_sequence_number(sequence_number); |
| } |
| |
| // static |
| void PropertyTrees::FromProtobuf(const proto::PropertyTrees& proto) { |
| transform_tree.FromProtobuf(proto.transform_tree()); |
| effect_tree.FromProtobuf(proto.effect_tree()); |
| clip_tree.FromProtobuf(proto.clip_tree()); |
| |
| needs_rebuild = proto.needs_rebuild(); |
| non_root_surfaces_enabled = proto.non_root_surfaces_enabled(); |
| sequence_number = proto.sequence_number(); |
| } |
| |
| } // namespace cc |