blob: b00404cf9e777b9bbf0b5421584e133ce87a19c8 [file] [log] [blame]
// 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 "cc/layers/layer_utils.h"
#include "cc/animation/animation_host.h"
#include "cc/animation/animation_id_provider.h"
#include "cc/animation/transform_operations.h"
#include "cc/layers/layer_impl.h"
#include "cc/test/animation_test_common.h"
#include "cc/test/fake_impl_task_runner_provider.h"
#include "cc/test/fake_layer_tree_host_impl.h"
#include "cc/test/test_shared_bitmap_manager.h"
#include "cc/test/test_task_graph_runner.h"
#include "cc/trees/layer_tree_host_common.h"
#include "cc/trees/layer_tree_impl.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/box_f.h"
#include "ui/gfx/test/gfx_util.h"
namespace cc {
namespace {
float diagonal(float width, float height) {
return std::sqrt(width * width + height * height);
}
class LayerUtilsGetAnimationBoundsTest : public testing::Test {
public:
LayerUtilsGetAnimationBoundsTest()
: host_impl_(&task_runner_provider_,
&shared_bitmap_manager_,
&task_graph_runner_),
root_(CreateTwoForkTree(&host_impl_)),
parent1_(root_->test_properties()->children[0]),
parent2_(root_->test_properties()->children[1]),
child1_(parent1_->test_properties()->children[0]),
child2_(parent2_->test_properties()->children[0]),
grand_child_(child2_->test_properties()->children[0]),
great_grand_child_(grand_child_->test_properties()->children[0]) {
timeline_ =
AnimationTimeline::Create(AnimationIdProvider::NextTimelineId());
host_impl_.animation_host()->AddAnimationTimeline(timeline_);
host_impl_.active_tree()->SetElementIdsForTesting();
}
LayerImpl* root() { return root_; }
LayerImpl* parent1() { return parent1_; }
LayerImpl* child1() { return child1_; }
LayerImpl* parent2() { return parent2_; }
LayerImpl* child2() { return child2_; }
LayerImpl* grand_child() { return grand_child_; }
LayerImpl* great_grand_child() { return great_grand_child_; }
scoped_refptr<AnimationTimeline> timeline() { return timeline_; }
FakeLayerTreeHostImpl& host_impl() { return host_impl_; }
private:
static LayerImpl* CreateTwoForkTree(LayerTreeHostImpl* host_impl) {
std::unique_ptr<LayerImpl> root =
LayerImpl::Create(host_impl->active_tree(), 1);
LayerImpl* root_ptr = root.get();
root->test_properties()->AddChild(
LayerImpl::Create(host_impl->active_tree(), 2));
root->test_properties()->children[0]->test_properties()->AddChild(
LayerImpl::Create(host_impl->active_tree(), 3));
root->test_properties()->AddChild(
LayerImpl::Create(host_impl->active_tree(), 4));
root->test_properties()->children[1]->test_properties()->AddChild(
LayerImpl::Create(host_impl->active_tree(), 5));
root->test_properties()
->children[1]
->test_properties()
->children[0]
->test_properties()
->AddChild(LayerImpl::Create(host_impl->active_tree(), 6));
root->test_properties()
->children[1]
->test_properties()
->children[0]
->test_properties()
->children[0]
->test_properties()
->AddChild(LayerImpl::Create(host_impl->active_tree(), 7));
host_impl->active_tree()->SetRootLayerForTesting(std::move(root));
return root_ptr;
}
FakeImplTaskRunnerProvider task_runner_provider_;
TestSharedBitmapManager shared_bitmap_manager_;
TestTaskGraphRunner task_graph_runner_;
FakeLayerTreeHostImpl host_impl_;
LayerImpl* root_;
LayerImpl* parent1_;
LayerImpl* parent2_;
LayerImpl* child1_;
LayerImpl* child2_;
LayerImpl* grand_child_;
LayerImpl* great_grand_child_;
scoped_refptr<AnimationTimeline> timeline_;
};
TEST_F(LayerUtilsGetAnimationBoundsTest, ScaleRoot) {
double duration = 1.0;
TransformOperations start;
start.AppendScale(1.f, 1.f, 1.f);
TransformOperations end;
end.AppendScale(2.f, 2.f, 1.f);
AddAnimatedTransformToElementWithPlayer(root()->element_id(), timeline(),
duration, start, end);
root()->SetPosition(gfx::PointF());
parent1()->SetPosition(gfx::PointF());
parent1()->SetBounds(gfx::Size(350, 200));
child1()->SetDrawsContent(true);
child1()->SetPosition(gfx::PointF(150.f, 50.f));
child1()->SetBounds(gfx::Size(100, 200));
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*child1(), &box);
EXPECT_TRUE(success);
gfx::BoxF expected(150.f, 50.f, 0.f, 350.f, 450.f, 0.f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest, TranslateParentLayer) {
double duration = 1.0;
TransformOperations start;
start.AppendTranslate(0.f, 0.f, 0.f);
TransformOperations end;
end.AppendTranslate(50.f, 50.f, 0.f);
AddAnimatedTransformToElementWithPlayer(parent1()->element_id(), timeline(),
duration, start, end);
parent1()->SetBounds(gfx::Size(350, 200));
child1()->SetDrawsContent(true);
child1()->SetPosition(gfx::PointF(150.f, 50.f));
child1()->SetBounds(gfx::Size(100, 200));
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*child1(), &box);
EXPECT_TRUE(success);
gfx::BoxF expected(150.f, 50.f, 0.f, 150.f, 250.f, 0.f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest, TranslateChildLayer) {
double duration = 1.0;
TransformOperations start;
start.AppendTranslate(0.f, 0.f, 0.f);
TransformOperations end;
end.AppendTranslate(50.f, 50.f, 0.f);
AddAnimatedTransformToElementWithPlayer(child1()->element_id(), timeline(),
duration, start, end);
parent1()->SetBounds(gfx::Size(350, 200));
child1()->SetDrawsContent(true);
child1()->SetPosition(gfx::PointF(150.f, 50.f));
child1()->SetBounds(gfx::Size(100, 200));
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*child1(), &box);
EXPECT_TRUE(success);
gfx::BoxF expected(150.f, 50.f, 0.f, 150.f, 250.f, 0.f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest, TranslateBothLayers) {
double duration = 1.0;
TransformOperations start;
start.AppendTranslate(0.f, 0.f, 0.f);
TransformOperations child_end;
child_end.AppendTranslate(50.f, 0.f, 0.f);
AddAnimatedTransformToElementWithPlayer(parent1()->element_id(), timeline(),
duration, start, child_end);
TransformOperations grand_child_end;
grand_child_end.AppendTranslate(0.f, 50.f, 0.f);
AddAnimatedTransformToElementWithPlayer(child1()->element_id(), timeline(),
duration, start, grand_child_end);
parent1()->SetBounds(gfx::Size(350, 200));
child1()->SetDrawsContent(true);
child1()->SetPosition(gfx::PointF(150.f, 50.f));
child1()->SetBounds(gfx::Size(100, 200));
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*child1(), &box);
EXPECT_TRUE(success);
gfx::BoxF expected(150.f, 50.f, 0.f, 150.f, 250.f, 0.f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest, RotateXNoPerspective) {
double duration = 1.0;
TransformOperations start;
start.AppendRotate(1.f, 0.f, 0.f, 0.f);
TransformOperations end;
end.AppendRotate(1.f, 0.f, 0.f, 90.f);
AddAnimatedTransformToElementWithPlayer(child1()->element_id(), timeline(),
duration, start, end);
parent1()->SetBounds(gfx::Size(350, 200));
gfx::Size bounds(100, 100);
child1()->SetDrawsContent(true);
child1()->SetPosition(gfx::PointF(150.f, 50.f));
child1()->SetBounds(bounds);
child1()->test_properties()->transform_origin =
gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0);
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*child1(), &box);
EXPECT_TRUE(success);
gfx::BoxF expected(150.f, 50.f, -50.f, 100.f, 100.f, 100.f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest, RotateXWithPerspective) {
double duration = 1.0;
TransformOperations start;
start.AppendRotate(1.f, 0.f, 0.f, 0.f);
TransformOperations end;
end.AppendRotate(1.f, 0.f, 0.f, 90.f);
AddAnimatedTransformToElementWithPlayer(child1()->element_id(), timeline(),
duration, start, end);
// Make the anchor point not the default 0.5 value and line up with the
// child center to make the math easier.
parent1()->test_properties()->transform_origin =
gfx::Point3F(0.375f * 400.f, 0.375f * 400.f, 0.f);
parent1()->SetBounds(gfx::Size(400, 400));
gfx::Transform perspective;
perspective.ApplyPerspectiveDepth(100.f);
parent1()->test_properties()->transform = perspective;
gfx::Size bounds(100, 100);
child1()->SetDrawsContent(true);
child1()->SetPosition(gfx::PointF(100.f, 100.f));
child1()->SetBounds(bounds);
child1()->test_properties()->transform_origin =
gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0);
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*child1(), &box);
EXPECT_TRUE(success);
gfx::BoxF expected(50.f, 50.f, -33.333336f, 200.f, 200.f, 133.333344f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest, RotateXWithPerspectiveOnSameLayer) {
// This test is used to check whether GetAnimationBounds correctly ignores the
// local transform when there is an animation applied to the layer / node.
// The intended behavior is that the animation should overwrite the transform.
double duration = 1.0;
TransformOperations start;
start.AppendRotate(1.f, 0.f, 0.f, 0.f);
TransformOperations end;
end.AppendRotate(1.f, 0.f, 0.f, 90.f);
AddAnimatedTransformToElementWithPlayer(parent1()->element_id(), timeline(),
duration, start, end);
// Make the anchor point not the default 0.5 value and line up
// with the child center to make the math easier.
parent1()->test_properties()->transform_origin =
gfx::Point3F(0.375f * 400.f, 0.375f * 400.f, 0.f);
parent1()->SetBounds(gfx::Size(400, 400));
gfx::Transform perspective;
perspective.ApplyPerspectiveDepth(100.f);
parent1()->test_properties()->transform = perspective;
gfx::Size bounds(100, 100);
child1()->SetDrawsContent(true);
child1()->SetPosition(gfx::PointF(100.f, 100.f));
child1()->SetBounds(bounds);
child1()->test_properties()->transform_origin =
gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0);
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*child1(), &box);
EXPECT_TRUE(success);
gfx::BoxF expected(100.f, 100.f, -50.f, 100.f, 100.f, 100.f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest, RotateZ) {
double duration = 1.0;
TransformOperations start;
start.AppendRotate(0.f, 0.f, 1.f, 0.f);
TransformOperations end;
end.AppendRotate(0.f, 0.f, 1.f, 90.f);
AddAnimatedTransformToElementWithPlayer(child1()->element_id(), timeline(),
duration, start, end);
parent1()->SetBounds(gfx::Size(350, 200));
gfx::Size bounds(100, 100);
child1()->SetDrawsContent(true);
child1()->SetPosition(gfx::PointF(150.f, 50.f));
child1()->SetBounds(bounds);
child1()->test_properties()->transform_origin =
gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0);
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*child1(), &box);
EXPECT_TRUE(success);
float diag = diagonal(bounds.width(), bounds.height());
gfx::BoxF expected(150.f + 0.5f * (bounds.width() - diag),
50.f + 0.5f * (bounds.height() - diag),
0.f,
diag,
diag,
0.f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest, MismatchedTransforms) {
double duration = 1.0;
TransformOperations start;
start.AppendTranslate(5, 6, 7);
TransformOperations end;
end.AppendRotate(0.f, 0.f, 1.f, 90.f);
AddAnimatedTransformToElementWithPlayer(child1()->element_id(), timeline(),
duration, start, end);
parent1()->SetBounds(gfx::Size(350, 200));
gfx::Size bounds(100, 100);
child1()->SetDrawsContent(true);
child1()->SetPosition(gfx::PointF(150.f, 50.f));
child1()->SetBounds(bounds);
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*child1(), &box);
EXPECT_FALSE(success);
}
TEST_F(LayerUtilsGetAnimationBoundsTest,
TransformUnderAncestorsWithPositionOr2DTransform) {
// Tree topology:
// +root (Bounds)
// +--parent2 (Position)
// +----child2 (2d transform)
// +------grand_child (Transform animation)
// +--------great_grand_child (DrawsContent)
// This test is used to check if GetAnimationBounds correctly skips layers and
// take layers which do not own a transform_node into consideration.
// Under this topology, only root and grand_child own transform_nodes.
double duration = 1.0;
TransformOperations start;
start.AppendTranslate(0.f, 0.f, 0.f);
TransformOperations great_grand_child_end;
great_grand_child_end.AppendTranslate(50.f, 0.f, 0.f);
AddAnimatedTransformToElementWithPlayer(grand_child()->element_id(),
timeline(), duration, start,
great_grand_child_end);
gfx::Transform translate_2d_transform;
translate_2d_transform.Translate(80.f, 60.f);
root()->SetBounds(gfx::Size(350, 200));
parent2()->SetPosition(gfx::PointF(40.f, 45.f));
child2()->test_properties()->transform = translate_2d_transform;
great_grand_child()->SetDrawsContent(true);
great_grand_child()->SetPosition(gfx::PointF(150.f, 50.f));
great_grand_child()->SetBounds(gfx::Size(100, 200));
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*great_grand_child(), &box);
EXPECT_TRUE(success);
gfx::BoxF expected(270.f, 155.f, 0.f, 150.f, 200.f, 0.f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest,
RotateZUnderAncestorsWithPositionOr2DTransform) {
double duration = 1.0;
TransformOperations start;
start.AppendRotate(0.f, 0.f, 1.f, 0.f);
TransformOperations great_grand_child_end;
great_grand_child_end.AppendRotate(0.f, 0.f, 1.f, 90.f);
AddAnimatedTransformToElementWithPlayer(grand_child()->element_id(),
timeline(), duration, start,
great_grand_child_end);
gfx::Transform translate_2d_transform;
translate_2d_transform.Translate(80.f, 60.f);
root()->SetBounds(gfx::Size(350, 200));
parent2()->SetPosition(gfx::PointF(40.f, 45.f));
child2()->test_properties()->transform = translate_2d_transform;
gfx::Size bounds(100, 100);
grand_child()->SetPosition(gfx::PointF(150.f, 50.f));
grand_child()->SetBounds(bounds);
grand_child()->test_properties()->transform_origin =
gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0);
great_grand_child()->SetPosition(gfx::PointF(25.f, 25.f));
great_grand_child()->SetBounds(gfx::Size(50.f, 50.f));
great_grand_child()->SetDrawsContent(true);
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*great_grand_child(), &box);
EXPECT_TRUE(success);
float diag = diagonal(50.f, 50.f);
gfx::BoxF expected(320.f - 0.5f * diag, 205.f - 0.5f * diag, 0.f, diag, diag,
0.f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest,
RotateXWithPerspectiveUnderAncestorsWithPositionOr2DTransform) {
// Tree topology:
// +root (Bounds)
// +--parent2 (Position)
// +----child2 (2d transform)
// +------grand_child (Perspective)
// +--------great_grand_child (RotateX, DrawsContent)
// Due to the RotateX animation, great_grand_child also owns a transform_node
double duration = 1.0;
TransformOperations start;
start.AppendRotate(1.f, 0.f, 0.f, 0.f);
TransformOperations great_grand_child_end;
great_grand_child_end.AppendRotate(1.f, 0.f, 0.f, 90.f);
AddAnimatedTransformToElementWithPlayer(great_grand_child()->element_id(),
timeline(), duration, start,
great_grand_child_end);
gfx::Transform translate_2d_transform;
translate_2d_transform.Translate(80.f, 60.f);
root()->SetBounds(gfx::Size(350, 200));
parent2()->SetPosition(gfx::PointF(40.f, 45.f));
child2()->test_properties()->transform = translate_2d_transform;
gfx::Transform perspective;
perspective.ApplyPerspectiveDepth(100.f);
gfx::Size bounds(100.f, 100.f);
grand_child()->SetPosition(gfx::PointF(150.f, 50.f));
grand_child()->SetBounds(bounds);
grand_child()->test_properties()->transform = perspective;
grand_child()->test_properties()->transform_origin =
gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0);
great_grand_child()->test_properties()->transform_origin =
gfx::Point3F(bounds.width() * 0.25f, bounds.height() * 0.25f, 0);
great_grand_child()->SetPosition(gfx::PointF(25.f, 25.f));
great_grand_child()->SetBounds(gfx::Size(50.f, 50.f));
great_grand_child()->SetDrawsContent(true);
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*great_grand_child(), &box);
EXPECT_TRUE(success);
// Translate and position offset to the root:
// 40 + 150 + 80 + 25 = 295
// 45 + 60 +50 + 25 = 180
// 0
// Animation Bounds Before Perspective:
// 0, 0, -25, 50, 50, 50
// Apply Perspective:
// When RotateX theta:
// y
// ^
// d1 |
// | / |
// | / |
// |/ |
// /| |
// / | |
// / | |
// theta |
// z<----------------------X
// d2
//
// the diag is 25 * 2
//
// box w: 2 * 25 * 100 / (100 - 25 * sin(theta)), max = 200 / 3 = 66.67
// h: same as w, 66.67
// d1: 25 * 100 * sin(theta) / (100 + 25 * sin(theta)), max = 100 / 5
// = 20
// d2: 25 * 100 * sin(theta) / (100 - 25 * sin(theta)), max = 100 / 3
// = 33.33
// d = d1 + d2 = 53.33
//
// Position Fix:
// 295 - (66.67 - 50) / 2 = 286.67
// 180 - (66.67 - 50) / 2 = 171.67
// 0 - 20 = -20
gfx::BoxF expected(286.666687f, 171.666672f, -20.f, 66.666656f, 66.666672f,
53.333336f);
EXPECT_BOXF_EQ(expected, box);
}
TEST_F(LayerUtilsGetAnimationBoundsTest,
RotateXNoPerspectiveUnderAncestorsWithPositionOr2DTransform) {
double duration = 1.0;
TransformOperations start;
start.AppendRotate(1.f, 0.f, 0.f, 0.f);
TransformOperations rotate_x_end;
rotate_x_end.AppendRotate(1.f, 0.f, 0.f, 90.f);
AddAnimatedTransformToElementWithPlayer(great_grand_child()->element_id(),
timeline(), duration, start,
rotate_x_end);
gfx::Transform translate_2d_transform;
translate_2d_transform.Translate(80.f, 60.f);
root()->SetBounds(gfx::Size(350, 200));
parent2()->SetPosition(gfx::PointF(40.f, 45.f));
child2()->test_properties()->transform = translate_2d_transform;
gfx::Size bounds(100.f, 100.f);
grand_child()->SetPosition(gfx::PointF(150.f, 50.f));
grand_child()->SetBounds(bounds);
great_grand_child()->test_properties()->transform_origin =
gfx::Point3F(bounds.width() * 0.25f, bounds.height() * 0.25f, 0);
great_grand_child()->SetPosition(gfx::PointF(25.f, 25.f));
great_grand_child()->SetBounds(
gfx::Size(bounds.width() * 0.5f, bounds.height() * 0.5f));
great_grand_child()->SetDrawsContent(true);
host_impl().active_tree()->BuildLayerListAndPropertyTreesForTesting();
gfx::BoxF box;
bool success = LayerUtils::GetAnimationBounds(*great_grand_child(), &box);
EXPECT_TRUE(success);
// Same as RotateXWithPerspectiveUnderAncestorsWithPositionOr2DTransform test,
// except for the perspective calculations.
gfx::BoxF expected(295.f, 180.f, -25.f, 50.f, 50.f, 50.f);
EXPECT_BOXF_EQ(expected, box);
}
} // namespace
} // namespace cc