1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 #include "base/logging.h"
9 #include "cc/base/math_util.h"
10 #include "cc/trees/property_tree.h"
15 PropertyTree
<T
>::PropertyTree()
16 : needs_update_(false) {
17 nodes_
.push_back(T());
19 back()->parent_id
= -1;
23 PropertyTree
<T
>::~PropertyTree() {
26 TransformTree::TransformTree() : source_to_parent_updates_allowed_(true) {}
28 TransformTree::~TransformTree() {
32 int PropertyTree
<T
>::Insert(const T
& tree_node
, int parent_id
) {
33 DCHECK_GT(nodes_
.size(), 0u);
34 nodes_
.push_back(tree_node
);
35 T
& node
= nodes_
.back();
36 node
.parent_id
= parent_id
;
37 node
.id
= static_cast<int>(nodes_
.size()) - 1;
42 void PropertyTree
<T
>::clear() {
44 nodes_
.push_back(T());
46 back()->parent_id
= -1;
49 template class PropertyTree
<TransformNode
>;
50 template class PropertyTree
<ClipNode
>;
51 template class PropertyTree
<EffectNode
>;
53 TransformNodeData::TransformNodeData()
55 content_target_id(-1),
57 needs_local_transform_update(true),
59 ancestors_are_invertible(true),
61 to_screen_is_animated(false),
62 has_only_translation_animations(true),
63 to_screen_has_scale_animation(false),
64 flattens_inherited_transform(false),
65 node_and_ancestors_are_flat(true),
66 node_and_ancestors_have_only_integer_translation(true),
68 needs_sublayer_scale(false),
69 affected_by_inner_viewport_bounds_delta_x(false),
70 affected_by_inner_viewport_bounds_delta_y(false),
71 affected_by_outer_viewport_bounds_delta_x(false),
72 affected_by_outer_viewport_bounds_delta_y(false),
73 layer_scale_factor(1.0f
),
74 post_local_scale_factor(1.0f
),
75 local_maximum_animation_target_scale(0.f
),
76 local_starting_animation_scale(0.f
),
77 combined_maximum_animation_target_scale(0.f
),
78 combined_starting_animation_scale(0.f
) {}
80 TransformNodeData::~TransformNodeData() {
83 void TransformNodeData::update_pre_local_transform(
84 const gfx::Point3F
& transform_origin
) {
85 pre_local
.MakeIdentity();
86 pre_local
.Translate3d(-transform_origin
.x(), -transform_origin
.y(),
87 -transform_origin
.z());
90 void TransformNodeData::update_post_local_transform(
91 const gfx::PointF
& position
,
92 const gfx::Point3F
& transform_origin
) {
93 post_local
.MakeIdentity();
94 post_local
.Scale(post_local_scale_factor
, post_local_scale_factor
);
95 post_local
.Translate3d(
96 position
.x() + source_offset
.x() + transform_origin
.x(),
97 position
.y() + source_offset
.y() + transform_origin
.y(),
98 transform_origin
.z());
101 ClipNodeData::ClipNodeData()
104 inherit_parent_target_space_clip(false),
105 requires_tight_clip_rect(true),
106 render_surface_is_clipped(false) {}
108 EffectNodeData::EffectNodeData() : opacity(1.f
), screen_space_opacity(1.f
) {}
110 void TransformTree::clear() {
111 PropertyTree
<TransformNode
>::clear();
113 nodes_affected_by_inner_viewport_bounds_delta_
.clear();
114 nodes_affected_by_outer_viewport_bounds_delta_
.clear();
117 bool TransformTree::ComputeTransform(int source_id
,
119 gfx::Transform
* transform
) const {
120 transform
->MakeIdentity();
122 if (source_id
== dest_id
)
125 if (source_id
> dest_id
) {
126 return CombineTransformsBetween(source_id
, dest_id
, transform
);
129 return CombineInversesBetween(source_id
, dest_id
, transform
);
132 bool TransformTree::ComputeTransformWithDestinationSublayerScale(
135 gfx::Transform
* transform
) const {
136 bool success
= ComputeTransform(source_id
, dest_id
, transform
);
138 const TransformNode
* dest_node
= Node(dest_id
);
139 if (!dest_node
->data
.needs_sublayer_scale
)
142 transform
->matrix().postScale(dest_node
->data
.sublayer_scale
.x(),
143 dest_node
->data
.sublayer_scale
.y(), 1.f
);
147 bool TransformTree::ComputeTransformWithSourceSublayerScale(
150 gfx::Transform
* transform
) const {
151 bool success
= ComputeTransform(source_id
, dest_id
, transform
);
153 const TransformNode
* source_node
= Node(source_id
);
154 if (!source_node
->data
.needs_sublayer_scale
)
157 if (source_node
->data
.sublayer_scale
.x() == 0 ||
158 source_node
->data
.sublayer_scale
.y() == 0)
161 transform
->Scale(1.f
/ source_node
->data
.sublayer_scale
.x(),
162 1.f
/ source_node
->data
.sublayer_scale
.y());
166 bool TransformTree::Are2DAxisAligned(int source_id
, int dest_id
) const {
167 gfx::Transform transform
;
168 return ComputeTransform(source_id
, dest_id
, &transform
) &&
169 transform
.Preserves2dAxisAlignment();
172 bool TransformTree::NeedsSourceToParentUpdate(TransformNode
* node
) {
173 return (source_to_parent_updates_allowed() &&
174 node
->parent_id
!= node
->data
.source_node_id
);
177 void TransformTree::UpdateTransforms(int id
) {
178 TransformNode
* node
= Node(id
);
179 TransformNode
* parent_node
= parent(node
);
180 TransformNode
* target_node
= Node(node
->data
.target_id
);
181 if (node
->data
.needs_local_transform_update
||
182 NeedsSourceToParentUpdate(node
))
183 UpdateLocalTransform(node
);
184 UpdateScreenSpaceTransform(node
, parent_node
, target_node
);
185 UpdateSublayerScale(node
);
186 UpdateTargetSpaceTransform(node
, target_node
);
187 UpdateAnimationProperties(node
, parent_node
);
188 UpdateSnapping(node
);
189 UpdateNodeAndAncestorsHaveIntegerTranslations(node
, parent_node
);
192 bool TransformTree::IsDescendant(int desc_id
, int source_id
) const {
193 while (desc_id
!= source_id
) {
196 desc_id
= Node(desc_id
)->parent_id
;
201 bool TransformTree::CombineTransformsBetween(int source_id
,
203 gfx::Transform
* transform
) const {
204 DCHECK(source_id
> dest_id
);
205 const TransformNode
* current
= Node(source_id
);
206 const TransformNode
* dest
= Node(dest_id
);
207 // Combine transforms to and from the screen when possible. Since flattening
208 // is a non-linear operation, we cannot use this approach when there is
209 // non-trivial flattening between the source and destination nodes. For
210 // example, consider the tree R->A->B->C, where B flattens its inherited
211 // transform, and A has a non-flat transform. Suppose C is the source and A is
212 // the destination. The expected result is C * B. But C's to_screen
213 // transform is C * B * flattened(A * R), and A's from_screen transform is
214 // R^{-1} * A^{-1}. If at least one of A and R isn't flat, the inverse of
215 // flattened(A * R) won't be R^{-1} * A{-1}, so multiplying C's to_screen and
216 // A's from_screen will not produce the correct result.
217 if (!dest
|| (dest
->data
.ancestors_are_invertible
&&
218 dest
->data
.node_and_ancestors_are_flat
)) {
219 transform
->ConcatTransform(current
->data
.to_screen
);
221 transform
->ConcatTransform(dest
->data
.from_screen
);
225 // Flattening is defined in a way that requires it to be applied while
226 // traversing downward in the tree. We first identify nodes that are on the
227 // path from the source to the destination (this is traversing upward), and
228 // then we visit these nodes in reverse order, flattening as needed. We
229 // early-out if we get to a node whose target node is the destination, since
230 // we can then re-use the target space transform stored at that node. However,
231 // we cannot re-use a stored target space transform if the destination has a
232 // zero sublayer scale, since stored target space transforms have sublayer
233 // scale baked in, but we need to compute an unscaled transform.
234 std::vector
<int> source_to_destination
;
235 source_to_destination
.push_back(current
->id
);
236 current
= parent(current
);
237 bool destination_has_non_zero_sublayer_scale
=
238 dest
->data
.sublayer_scale
.x() != 0.f
&&
239 dest
->data
.sublayer_scale
.y() != 0.f
;
240 DCHECK(destination_has_non_zero_sublayer_scale
||
241 !dest
->data
.ancestors_are_invertible
);
242 for (; current
&& current
->id
> dest_id
; current
= parent(current
)) {
243 if (destination_has_non_zero_sublayer_scale
&&
244 current
->data
.target_id
== dest_id
&&
245 current
->data
.content_target_id
== dest_id
)
247 source_to_destination
.push_back(current
->id
);
250 gfx::Transform combined_transform
;
251 if (current
->id
> dest_id
) {
252 combined_transform
= current
->data
.to_target
;
253 // The stored target space transform has sublayer scale baked in, but we
254 // need the unscaled transform.
255 combined_transform
.Scale(1.0f
/ dest
->data
.sublayer_scale
.x(),
256 1.0f
/ dest
->data
.sublayer_scale
.y());
257 } else if (current
->id
< dest_id
) {
258 // We have reached the lowest common ancestor of the source and destination
259 // nodes. This case can occur when we are transforming between a node
260 // corresponding to a fixed-position layer (or its descendant) and the node
261 // corresponding to the layer's render target. For example, consider the
262 // layer tree R->T->S->F where F is fixed-position, S owns a render surface,
263 // and T has a significant transform. This will yield the following
270 // In this example, T will have id 2, S will have id 3, and F will have id
271 // 4. When walking up the ancestor chain from F, the first node with a
272 // smaller id than S will be T, the lowest common ancestor of these nodes.
273 // We compute the transform from T to S here, and then from F to T in the
275 DCHECK(IsDescendant(dest_id
, current
->id
));
276 CombineInversesBetween(current
->id
, dest_id
, &combined_transform
);
277 DCHECK(combined_transform
.IsApproximatelyIdentityOrTranslation(
278 SkDoubleToMScalar(1e-4)));
281 size_t source_to_destination_size
= source_to_destination
.size();
282 for (size_t i
= 0; i
< source_to_destination_size
; ++i
) {
283 size_t index
= source_to_destination_size
- 1 - i
;
284 const TransformNode
* node
= Node(source_to_destination
[index
]);
285 if (node
->data
.flattens_inherited_transform
)
286 combined_transform
.FlattenTo2d();
287 combined_transform
.PreconcatTransform(node
->data
.to_parent
);
290 transform
->ConcatTransform(combined_transform
);
294 bool TransformTree::CombineInversesBetween(int source_id
,
296 gfx::Transform
* transform
) const {
297 DCHECK(source_id
< dest_id
);
298 const TransformNode
* current
= Node(dest_id
);
299 const TransformNode
* dest
= Node(source_id
);
300 // Just as in CombineTransformsBetween, we can use screen space transforms in
301 // this computation only when there isn't any non-trivial flattening
303 if (current
->data
.ancestors_are_invertible
&&
304 current
->data
.node_and_ancestors_are_flat
) {
305 transform
->PreconcatTransform(current
->data
.from_screen
);
307 transform
->PreconcatTransform(dest
->data
.to_screen
);
311 // Inverting a flattening is not equivalent to flattening an inverse. This
312 // means we cannot, for example, use the inverse of each node's to_parent
313 // transform, flattening where needed. Instead, we must compute the transform
314 // from the destination to the source, with flattening, and then invert the
316 gfx::Transform dest_to_source
;
317 CombineTransformsBetween(dest_id
, source_id
, &dest_to_source
);
318 gfx::Transform source_to_dest
;
319 bool all_are_invertible
= dest_to_source
.GetInverse(&source_to_dest
);
320 transform
->PreconcatTransform(source_to_dest
);
321 return all_are_invertible
;
324 void TransformTree::UpdateLocalTransform(TransformNode
* node
) {
325 gfx::Transform transform
= node
->data
.post_local
;
326 if (NeedsSourceToParentUpdate(node
)) {
327 gfx::Transform to_parent
;
328 ComputeTransform(node
->data
.source_node_id
, node
->parent_id
, &to_parent
);
329 node
->data
.source_to_parent
= to_parent
.To2dTranslation();
332 gfx::Vector2dF fixed_position_adjustment
;
333 if (node
->data
.affected_by_inner_viewport_bounds_delta_x
)
334 fixed_position_adjustment
.set_x(inner_viewport_bounds_delta_
.x());
335 else if (node
->data
.affected_by_outer_viewport_bounds_delta_x
)
336 fixed_position_adjustment
.set_x(outer_viewport_bounds_delta_
.x());
338 if (node
->data
.affected_by_inner_viewport_bounds_delta_y
)
339 fixed_position_adjustment
.set_y(inner_viewport_bounds_delta_
.y());
340 else if (node
->data
.affected_by_outer_viewport_bounds_delta_y
)
341 fixed_position_adjustment
.set_y(outer_viewport_bounds_delta_
.y());
344 node
->data
.source_to_parent
.x() - node
->data
.scroll_offset
.x() +
345 fixed_position_adjustment
.x(),
346 node
->data
.source_to_parent
.y() - node
->data
.scroll_offset
.y() +
347 fixed_position_adjustment
.y());
348 transform
.PreconcatTransform(node
->data
.local
);
349 transform
.PreconcatTransform(node
->data
.pre_local
);
350 node
->data
.set_to_parent(transform
);
351 node
->data
.needs_local_transform_update
= false;
354 void TransformTree::UpdateScreenSpaceTransform(TransformNode
* node
,
355 TransformNode
* parent_node
,
356 TransformNode
* target_node
) {
358 node
->data
.to_screen
= node
->data
.to_parent
;
359 node
->data
.ancestors_are_invertible
= true;
360 node
->data
.to_screen_is_animated
= false;
361 node
->data
.node_and_ancestors_are_flat
= node
->data
.to_parent
.IsFlat();
363 node
->data
.to_screen
= parent_node
->data
.to_screen
;
364 if (node
->data
.flattens_inherited_transform
)
365 node
->data
.to_screen
.FlattenTo2d();
366 node
->data
.to_screen
.PreconcatTransform(node
->data
.to_parent
);
367 node
->data
.ancestors_are_invertible
=
368 parent_node
->data
.ancestors_are_invertible
;
369 node
->data
.node_and_ancestors_are_flat
=
370 parent_node
->data
.node_and_ancestors_are_flat
&&
371 node
->data
.to_parent
.IsFlat();
374 if (!node
->data
.to_screen
.GetInverse(&node
->data
.from_screen
))
375 node
->data
.ancestors_are_invertible
= false;
378 void TransformTree::UpdateSublayerScale(TransformNode
* node
) {
379 // The sublayer scale depends on the screen space transform, so update it too.
380 node
->data
.sublayer_scale
=
381 node
->data
.needs_sublayer_scale
382 ? MathUtil::ComputeTransform2dScaleComponents(
383 node
->data
.to_screen
, node
->data
.layer_scale_factor
)
384 : gfx::Vector2dF(1.0f
, 1.0f
);
387 void TransformTree::UpdateTargetSpaceTransform(TransformNode
* node
,
388 TransformNode
* target_node
) {
389 if (node
->data
.needs_sublayer_scale
) {
390 node
->data
.to_target
.MakeIdentity();
391 node
->data
.to_target
.Scale(node
->data
.sublayer_scale
.x(),
392 node
->data
.sublayer_scale
.y());
394 const bool target_is_root_surface
= target_node
->id
== 1;
395 // In order to include the root transform for the root surface, we walk up
396 // to the root of the transform tree in ComputeTransform.
397 int target_id
= target_is_root_surface
? 0 : target_node
->id
;
398 ComputeTransformWithDestinationSublayerScale(node
->id
, target_id
,
399 &node
->data
.to_target
);
402 if (!node
->data
.to_target
.GetInverse(&node
->data
.from_target
))
403 node
->data
.ancestors_are_invertible
= false;
406 void TransformTree::UpdateAnimationProperties(TransformNode
* node
,
407 TransformNode
* parent_node
) {
408 bool ancestor_is_animating
= false;
409 bool ancestor_is_animating_scale
= false;
410 float ancestor_maximum_target_scale
= 0.f
;
411 float ancestor_starting_animation_scale
= 0.f
;
413 ancestor_is_animating
= parent_node
->data
.to_screen_is_animated
;
414 ancestor_is_animating_scale
=
415 parent_node
->data
.to_screen_has_scale_animation
;
416 ancestor_maximum_target_scale
=
417 parent_node
->data
.combined_maximum_animation_target_scale
;
418 ancestor_starting_animation_scale
=
419 parent_node
->data
.combined_starting_animation_scale
;
421 node
->data
.to_screen_is_animated
=
422 node
->data
.is_animated
|| ancestor_is_animating
;
423 node
->data
.to_screen_has_scale_animation
=
424 !node
->data
.has_only_translation_animations
||
425 ancestor_is_animating_scale
;
427 // Once we've failed to compute a maximum animated scale at an ancestor, we
429 bool failed_at_ancestor
=
430 ancestor_is_animating_scale
&& ancestor_maximum_target_scale
== 0.f
;
432 // Computing maximum animated scale in the presence of non-scale/translation
433 // transforms isn't supported.
434 bool failed_for_non_scale_or_translation
=
435 !node
->data
.to_target
.IsScaleOrTranslation();
437 // We don't attempt to accumulate animation scale from multiple nodes with
438 // scale animations, because of the risk of significant overestimation. For
439 // example, one node might be increasing scale from 1 to 10 at the same time
440 // as another node is decreasing scale from 10 to 1. Naively combining these
441 // scales would produce a scale of 100.
442 bool failed_for_multiple_scale_animations
=
443 ancestor_is_animating_scale
&&
444 !node
->data
.has_only_translation_animations
;
446 if (failed_at_ancestor
|| failed_for_non_scale_or_translation
||
447 failed_for_multiple_scale_animations
) {
448 node
->data
.combined_maximum_animation_target_scale
= 0.f
;
449 node
->data
.combined_starting_animation_scale
= 0.f
;
451 // This ensures that descendants know we've failed to compute a maximum
453 node
->data
.to_screen_has_scale_animation
= true;
457 if (!node
->data
.to_screen_has_scale_animation
) {
458 node
->data
.combined_maximum_animation_target_scale
= 0.f
;
459 node
->data
.combined_starting_animation_scale
= 0.f
;
463 // At this point, we know exactly one of this node or an ancestor is animating
465 if (node
->data
.has_only_translation_animations
) {
466 // An ancestor is animating scale.
467 gfx::Vector2dF local_scales
=
468 MathUtil::ComputeTransform2dScaleComponents(node
->data
.local
, 0.f
);
469 float max_local_scale
= std::max(local_scales
.x(), local_scales
.y());
470 node
->data
.combined_maximum_animation_target_scale
=
471 max_local_scale
* ancestor_maximum_target_scale
;
472 node
->data
.combined_starting_animation_scale
=
473 max_local_scale
* ancestor_starting_animation_scale
;
477 if (node
->data
.local_starting_animation_scale
== 0.f
||
478 node
->data
.local_maximum_animation_target_scale
== 0.f
) {
479 node
->data
.combined_maximum_animation_target_scale
= 0.f
;
480 node
->data
.combined_starting_animation_scale
= 0.f
;
484 gfx::Vector2dF ancestor_scales
=
485 parent_node
? MathUtil::ComputeTransform2dScaleComponents(
486 parent_node
->data
.to_target
, 0.f
)
487 : gfx::Vector2dF(1.f
, 1.f
);
488 float max_ancestor_scale
= std::max(ancestor_scales
.x(), ancestor_scales
.y());
489 node
->data
.combined_maximum_animation_target_scale
=
490 max_ancestor_scale
* node
->data
.local_maximum_animation_target_scale
;
491 node
->data
.combined_starting_animation_scale
=
492 max_ancestor_scale
* node
->data
.local_starting_animation_scale
;
495 void TransformTree::UpdateSnapping(TransformNode
* node
) {
496 if (!node
->data
.scrolls
|| node
->data
.to_screen_is_animated
||
497 !node
->data
.to_target
.IsScaleOrTranslation()) {
501 // Scroll snapping must be done in target space (the pixels we care about).
502 // This means we effectively snap the target space transform. If TT is the
503 // target space transform and TT' is TT with its translation components
504 // rounded, then what we're after is the scroll delta X, where TT * X = TT'.
505 // I.e., we want a transform that will realize our scroll snap. It follows
506 // that X = TT^-1 * TT'. We cache TT and TT^-1 to make this more efficient.
507 gfx::Transform rounded
= node
->data
.to_target
;
508 rounded
.RoundTranslationComponents();
509 gfx::Transform delta
= node
->data
.from_target
;
512 DCHECK(delta
.IsApproximatelyIdentityOrTranslation(SkDoubleToMScalar(1e-4)))
515 gfx::Vector2dF translation
= delta
.To2dTranslation();
517 // Now that we have our scroll delta, we must apply it to each of our
518 // combined, to/from matrices.
519 node
->data
.to_parent
.Translate(translation
.x(), translation
.y());
520 node
->data
.to_target
.Translate(translation
.x(), translation
.y());
521 node
->data
.from_target
.matrix().postTranslate(-translation
.x(),
522 -translation
.y(), 0);
523 node
->data
.to_screen
.Translate(translation
.x(), translation
.y());
524 node
->data
.from_screen
.matrix().postTranslate(-translation
.x(),
525 -translation
.y(), 0);
527 node
->data
.scroll_snap
= translation
;
530 void TransformTree::SetInnerViewportBoundsDelta(gfx::Vector2dF bounds_delta
) {
531 if (inner_viewport_bounds_delta_
== bounds_delta
)
534 inner_viewport_bounds_delta_
= bounds_delta
;
536 if (nodes_affected_by_inner_viewport_bounds_delta_
.empty())
539 set_needs_update(true);
540 for (int i
: nodes_affected_by_inner_viewport_bounds_delta_
)
541 Node(i
)->data
.needs_local_transform_update
= true;
544 void TransformTree::SetOuterViewportBoundsDelta(gfx::Vector2dF bounds_delta
) {
545 if (outer_viewport_bounds_delta_
== bounds_delta
)
548 outer_viewport_bounds_delta_
= bounds_delta
;
550 if (nodes_affected_by_outer_viewport_bounds_delta_
.empty())
553 set_needs_update(true);
554 for (int i
: nodes_affected_by_outer_viewport_bounds_delta_
)
555 Node(i
)->data
.needs_local_transform_update
= true;
558 void TransformTree::AddNodeAffectedByInnerViewportBoundsDelta(int node_id
) {
559 nodes_affected_by_inner_viewport_bounds_delta_
.push_back(node_id
);
562 void TransformTree::AddNodeAffectedByOuterViewportBoundsDelta(int node_id
) {
563 nodes_affected_by_outer_viewport_bounds_delta_
.push_back(node_id
);
566 bool TransformTree::HasNodesAffectedByInnerViewportBoundsDelta() const {
567 return !nodes_affected_by_inner_viewport_bounds_delta_
.empty();
570 bool TransformTree::HasNodesAffectedByOuterViewportBoundsDelta() const {
571 return !nodes_affected_by_outer_viewport_bounds_delta_
.empty();
574 void EffectTree::UpdateOpacities(int id
) {
575 EffectNode
* node
= Node(id
);
576 node
->data
.screen_space_opacity
= node
->data
.opacity
;
578 EffectNode
* parent_node
= parent(node
);
580 node
->data
.screen_space_opacity
*= parent_node
->data
.screen_space_opacity
;
583 void TransformTree::UpdateNodeAndAncestorsHaveIntegerTranslations(
585 TransformNode
* parent_node
) {
586 node
->data
.node_and_ancestors_have_only_integer_translation
=
587 node
->data
.to_parent
.IsIdentityOrIntegerTranslation();
589 node
->data
.node_and_ancestors_have_only_integer_translation
=
590 node
->data
.node_and_ancestors_have_only_integer_translation
&&
591 parent_node
->data
.node_and_ancestors_have_only_integer_translation
;
594 void ClipTree::SetViewportClip(gfx::RectF viewport_rect
) {
597 ClipNode
* node
= Node(1);
598 if (viewport_rect
== node
->data
.clip
)
600 node
->data
.clip
= viewport_rect
;
601 set_needs_update(true);
604 gfx::RectF
ClipTree::ViewportClip() {
605 const unsigned long min_size
= 1;
606 DCHECK_GT(size(), min_size
);
607 return Node(1)->data
.clip
;
610 PropertyTrees::PropertyTrees() : needs_rebuild(true), sequence_number(0) {