| // 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 "base/logging.h" |
| #include "cc/base/math_util.h" |
| #include "cc/trees/property_tree.h" |
| |
| namespace cc { |
| |
| template <typename T> |
| PropertyTree<T>::PropertyTree() { |
| nodes_.push_back(T()); |
| back()->id = 0; |
| back()->parent_id = -1; |
| } |
| |
| template <typename T> |
| PropertyTree<T>::~PropertyTree() { |
| } |
| |
| 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 class PropertyTree<TransformNode>; |
| template class PropertyTree<ClipNode>; |
| template class PropertyTree<OpacityNode>; |
| |
| TransformNodeData::TransformNodeData() |
| : target_id(-1), |
| content_target_id(-1), |
| needs_local_transform_update(true), |
| is_invertible(true), |
| ancestors_are_invertible(true), |
| is_animated(false), |
| to_screen_is_animated(false), |
| flattens(false), |
| scrolls(false), |
| needs_sublayer_scale(false), |
| layer_scale_factor(1.0f) { |
| } |
| |
| TransformNodeData::~TransformNodeData() { |
| } |
| |
| ClipNodeData::ClipNodeData() : transform_id(-1), target_id(-1) { |
| } |
| |
| 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 && IsDescendant(source_id, dest_id)) |
| return CombineTransformsBetween(source_id, dest_id, transform); |
| |
| if (dest_id > source_id && IsDescendant(dest_id, source_id)) |
| return CombineInversesBetween(source_id, dest_id, transform); |
| |
| int lca = LowestCommonAncestor(source_id, dest_id); |
| |
| bool no_singular_matrices_to_lca = |
| CombineTransformsBetween(source_id, lca, transform); |
| |
| bool no_singular_matrices_from_lca = |
| CombineInversesBetween(lca, dest_id, transform); |
| |
| return no_singular_matrices_to_lca && no_singular_matrices_from_lca; |
| } |
| |
| bool TransformTree::Are2DAxisAligned(int source_id, int dest_id) const { |
| gfx::Transform transform; |
| return ComputeTransform(source_id, dest_id, &transform) && |
| transform.Preserves2dAxisAlignment(); |
| } |
| |
| 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) |
| UpdateLocalTransform(node); |
| UpdateScreenSpaceTransform(node, parent_node, target_node); |
| UpdateSublayerScale(node); |
| UpdateTargetSpaceTransform(node, target_node); |
| UpdateIsAnimated(node, parent_node); |
| UpdateSnapping(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; |
| } |
| |
| int TransformTree::LowestCommonAncestor(int a, int b) const { |
| std::set<int> chain_a; |
| std::set<int> chain_b; |
| while (a || b) { |
| if (a) { |
| a = Node(a)->parent_id; |
| if (a > -1 && chain_b.find(a) != chain_b.end()) |
| return a; |
| chain_a.insert(a); |
| } |
| if (b) { |
| b = Node(b)->parent_id; |
| if (b > -1 && chain_a.find(b) != chain_a.end()) |
| return b; |
| chain_b.insert(b); |
| } |
| } |
| NOTREACHED(); |
| return 0; |
| } |
| |
| bool TransformTree::CombineTransformsBetween(int source_id, |
| int dest_id, |
| gfx::Transform* transform) const { |
| const TransformNode* current = Node(source_id); |
| const TransformNode* dest = Node(dest_id); |
| if (!dest || dest->data.ancestors_are_invertible) { |
| transform->ConcatTransform(current->data.to_screen); |
| if (dest) |
| transform->ConcatTransform(dest->data.from_screen); |
| return true; |
| } |
| |
| bool all_are_invertible = true; |
| for (; current && current->id > dest_id; current = parent(current)) { |
| transform->ConcatTransform(current->data.to_parent); |
| if (!current->data.is_invertible) |
| all_are_invertible = false; |
| } |
| |
| return all_are_invertible; |
| } |
| |
| bool TransformTree::CombineInversesBetween(int source_id, |
| int dest_id, |
| gfx::Transform* transform) const { |
| const TransformNode* current = Node(dest_id); |
| const TransformNode* dest = Node(source_id); |
| if (current->data.ancestors_are_invertible) { |
| transform->PreconcatTransform(current->data.from_screen); |
| if (dest) |
| transform->PreconcatTransform(dest->data.to_screen); |
| return true; |
| } |
| |
| bool all_are_invertible = true; |
| for (; current && current->id > source_id; current = parent(current)) { |
| transform->PreconcatTransform(current->data.from_parent); |
| if (!current->data.is_invertible) |
| all_are_invertible = false; |
| } |
| |
| return all_are_invertible; |
| } |
| |
| void TransformTree::UpdateLocalTransform(TransformNode* node) { |
| gfx::Transform transform = node->data.post_local; |
| transform.Translate(-node->data.scroll_offset.x(), |
| -node->data.scroll_offset.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; |
| } else if (parent_node->data.flattens) { |
| // Flattening is tricky. Once a layer is drawn into its render target, it |
| // cannot escape, so we only need to consider transforms between the layer |
| // and its target when flattening (i.e., its draw transform). To compute the |
| // screen space transform when flattening is involved we combine three |
| // transforms, A * B * C, where A is the screen space transform of the |
| // target, B is the flattened draw transform of the layer's parent, and C is |
| // the local transform. |
| node->data.to_screen = target_node->data.to_screen; |
| gfx::Transform flattened; |
| ComputeTransform(parent_node->id, target_node->id, &flattened); |
| flattened.FlattenTo2d(); |
| node->data.to_screen.PreconcatTransform(flattened); |
| node->data.to_screen.PreconcatTransform(node->data.to_parent); |
| node->data.ancestors_are_invertible = |
| parent_node->data.ancestors_are_invertible; |
| } else { |
| node->data.to_screen = parent_node->data.to_screen; |
| node->data.to_screen.PreconcatTransform(node->data.to_parent); |
| node->data.ancestors_are_invertible = |
| parent_node->data.ancestors_are_invertible; |
| } |
| |
| 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. |
| node->data.sublayer_scale = |
| node->data.needs_sublayer_scale |
| ? MathUtil::ComputeTransform2dScaleComponents( |
| node->data.to_screen, node->data.layer_scale_factor) |
| : gfx::Vector2dF(1.0f, 1.0f); |
| } |
| |
| void TransformTree::UpdateTargetSpaceTransform(TransformNode* node, |
| TransformNode* target_node) { |
| node->data.to_target.MakeIdentity(); |
| if (node->data.needs_sublayer_scale) { |
| node->data.to_target.Scale(node->data.sublayer_scale.x(), |
| node->data.sublayer_scale.y()); |
| } else { |
| const bool target_is_root_surface = target_node->id == 1; |
| // 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_is_root_surface ? 0 : target_node->id; |
| if (target_node) { |
| node->data.to_target.Scale(target_node->data.sublayer_scale.x(), |
| target_node->data.sublayer_scale.y()); |
| } |
| |
| gfx::Transform unscaled_target_transform; |
| ComputeTransform(node->id, target_id, &unscaled_target_transform); |
| node->data.to_target.PreconcatTransform(unscaled_target_transform); |
| } |
| |
| if (!node->data.to_target.GetInverse(&node->data.from_target)) |
| node->data.ancestors_are_invertible = false; |
| } |
| |
| void TransformTree::UpdateIsAnimated(TransformNode* node, |
| TransformNode* parent_node) { |
| if (parent_node) { |
| node->data.to_screen_is_animated = |
| node->data.is_animated || parent_node->data.to_screen_is_animated; |
| } |
| } |
| |
| void TransformTree::UpdateSnapping(TransformNode* node) { |
| if (!node->data.scrolls || node->data.to_screen_is_animated || |
| !node->data.to_target.IsScaleOrTranslation()) { |
| 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; |
| gfx::Transform inverse_delta(gfx::Transform::kSkipInitialization); |
| bool invertible_delta = delta.GetInverse(&inverse_delta); |
| |
| // The delta should be a translation, modulo floating point error, and should |
| // therefore be invertible. |
| DCHECK(invertible_delta); |
| |
| // Now that we have our scroll delta, we must apply it to each of our |
| // combined, to/from matrices. |
| node->data.to_parent.PreconcatTransform(delta); |
| node->data.from_parent.ConcatTransform(inverse_delta); |
| node->data.to_target.PreconcatTransform(delta); |
| node->data.from_target.ConcatTransform(inverse_delta); |
| node->data.to_screen.PreconcatTransform(delta); |
| node->data.from_screen.ConcatTransform(inverse_delta); |
| } |
| |
| } // namespace cc |