| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cc/trees/layer_tree_host.h" |
| |
| #include "base/time/time.h" |
| #include "cc/test/fake_picture_layer.h" |
| #include "cc/test/fake_recording_source.h" |
| #include "cc/test/layer_tree_test.h" |
| #include "cc/test/property_tree_test_utils.h" |
| #include "cc/trees/layer_tree_impl.h" |
| #include "components/viz/common/quads/compositor_render_pass_draw_quad.h" |
| |
| namespace cc { |
| namespace { |
| |
| class LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin |
| : public LayerTreeTest { |
| protected: |
| void SetupTree() override { |
| // The masked layer has bounds 50x50, but it has a child that causes |
| // the surface bounds to be larger. It also has a parent that clips the |
| // masked layer and its surface. |
| |
| SetInitialRootBounds(gfx::Size(100, 100)); |
| LayerTreeTest::SetupTree(); |
| Layer* root = layer_tree_host()->root_layer(); |
| |
| scoped_refptr<FakePictureLayer> content_layer = |
| FakePictureLayer::Create(&client_); |
| |
| mask_client_.set_fill_with_nonsolid_color(true); |
| auto mask_layer = FakePictureLayer::Create(&mask_client_); |
| content_layer->SetMaskLayer(mask_layer); |
| |
| gfx::Size layer_size(100, 100); |
| content_layer->SetBounds(layer_size); |
| |
| gfx::Size mask_size(100, 100); |
| mask_layer->SetBounds(mask_size); |
| mask_client_.set_bounds(mask_size); |
| mask_layer_id_ = mask_layer->id(); |
| |
| scoped_refptr<Layer> clip_layer = Layer::Create(); |
| clip_layer->SetBounds(gfx::Size(50, 50)); |
| clip_layer->SetMasksToBounds(true); |
| |
| scoped_refptr<Layer> scroll_layer = Layer::Create(); |
| scroll_layer->SetBounds(layer_size); |
| scroll_layer->SetScrollable(gfx::Size(50, 50)); |
| scroll_layer->SetMasksToBounds(true); |
| scroll_layer->SetElementId( |
| LayerIdToElementIdForTesting(scroll_layer->id())); |
| |
| root->AddChild(clip_layer); |
| clip_layer->AddChild(scroll_layer); |
| scroll_layer->AddChild(content_layer); |
| |
| client_.set_bounds(root->bounds()); |
| scroll_layer->SetScrollOffset(gfx::PointF(50, 50)); |
| } |
| |
| void BeginTest() override { PostSetNeedsCommitToMainThread(); } |
| |
| DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, |
| LayerTreeHostImpl::FrameData* frame_data, |
| DrawResult draw_result) override { |
| EXPECT_EQ(3u, frame_data->render_passes.size()); |
| viz::CompositorRenderPass* root_pass = |
| frame_data->render_passes.back().get(); |
| EXPECT_EQ(2u, root_pass->quad_list.size()); |
| |
| // There's a solid color quad under everything. |
| EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, |
| root_pass->quad_list.back()->material); |
| |
| EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass, |
| root_pass->quad_list.front()->material); |
| const viz::CompositorRenderPassDrawQuad* render_pass_quad = |
| viz::CompositorRenderPassDrawQuad::MaterialCast( |
| root_pass->quad_list.front()); |
| gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect( |
| render_pass_quad->shared_quad_state->quad_to_target_transform, |
| render_pass_quad->rect); |
| EXPECT_EQ(gfx::Rect(0, 0, 50, 50), rect_in_target_space); |
| |
| // We use kDstIn blend mode instead of the mask feature of RenderPass. |
| EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect); |
| viz::CompositorRenderPass* mask_pass = frame_data->render_passes[1].get(); |
| EXPECT_EQ(SkBlendMode::kDstIn, |
| mask_pass->quad_list.front()->shared_quad_state->blend_mode); |
| EndTest(); |
| return draw_result; |
| } |
| |
| int mask_layer_id_; |
| FakeContentLayerClient client_; |
| FakeContentLayerClient mask_client_; |
| }; |
| |
| SINGLE_AND_MULTI_THREAD_TEST_F( |
| LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin); |
| |
| class LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOriginWithLayerList |
| : public LayerTreeTest { |
| protected: |
| LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOriginWithLayerList() { |
| SetUseLayerLists(); |
| } |
| |
| void SetupTree() override { |
| // The masked layer has bounds 50x50, but it has a child that causes |
| // the surface bounds to be larger. It also has a parent that clips the |
| // masked layer and its surface. |
| |
| SetInitialRootBounds(gfx::Size(100, 100)); |
| LayerTreeTest::SetupTree(); |
| |
| Layer* root = layer_tree_host()->root_layer(); |
| |
| gfx::Size layer_size(100, 100); |
| SetupViewport(root, gfx::Size(50, 50), layer_size); |
| |
| auto* scroll = layer_tree_host()->OuterViewportScrollLayerForTesting(); |
| SetScrollOffset(scroll, gfx::PointF(50, 50)); |
| |
| client_.set_bounds(root->bounds()); |
| client_.set_fill_with_nonsolid_color(true); |
| auto content_layer = FakePictureLayer::Create(&client_); |
| content_layer->SetBounds(layer_size); |
| CopyProperties(scroll, content_layer.get()); |
| root->AddChild(content_layer); |
| |
| auto mask_layer = FakePictureLayer::Create(&client_); |
| SetupMaskProperties(content_layer.get(), mask_layer.get()); |
| root->AddChild(mask_layer); |
| |
| mask_layer_id_ = mask_layer->id(); |
| } |
| |
| void BeginTest() override { PostSetNeedsCommitToMainThread(); } |
| |
| DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, |
| LayerTreeHostImpl::FrameData* frame_data, |
| DrawResult draw_result) override { |
| EXPECT_EQ(1u, frame_data->render_passes.size()); |
| viz::CompositorRenderPass* pass = frame_data->render_passes.back().get(); |
| EXPECT_EQ(3u, pass->quad_list.size()); |
| |
| // There's a solid color quad under everything. |
| EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, |
| pass->quad_list.back()->material); |
| |
| EXPECT_EQ(viz::DrawQuad::Material::kTiledContent, |
| pass->quad_list.ElementAt(1)->material); |
| |
| auto* mask_quad = pass->quad_list.front(); |
| EXPECT_EQ(viz::DrawQuad::Material::kTiledContent, mask_quad->material); |
| gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect( |
| mask_quad->shared_quad_state->quad_to_target_transform, |
| mask_quad->rect); |
| EXPECT_EQ(gfx::Rect(0, 0, 50, 50), rect_in_target_space); |
| // We use kDstIn blend mode for mask. |
| EXPECT_EQ(SkBlendMode::kDstIn, mask_quad->shared_quad_state->blend_mode); |
| EndTest(); |
| return draw_result; |
| } |
| |
| int mask_layer_id_; |
| FakeContentLayerClient client_; |
| }; |
| |
| SINGLE_AND_MULTI_THREAD_TEST_F( |
| LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOriginWithLayerList); |
| |
| class LayerTreeTestMaskLayerForSurfaceWithClippedLayer : public LayerTreeTest { |
| protected: |
| void SetupTree() override { |
| // The masked layer has bounds 50x50, but it has a child that causes |
| // the surface bounds to be larger. It also has a parent that clips the |
| // masked layer and its surface. |
| |
| scoped_refptr<Layer> root = Layer::Create(); |
| |
| scoped_refptr<Layer> clipping_layer = Layer::Create(); |
| root->AddChild(clipping_layer); |
| |
| scoped_refptr<FakePictureLayer> content_layer = |
| FakePictureLayer::Create(&client_); |
| clipping_layer->AddChild(content_layer); |
| |
| scoped_refptr<FakePictureLayer> content_child_layer = |
| FakePictureLayer::Create(&client_); |
| content_layer->AddChild(content_child_layer); |
| |
| mask_client_.set_fill_with_nonsolid_color(true); |
| auto mask_layer = FakePictureLayer::Create(&mask_client_); |
| content_layer->SetMaskLayer(mask_layer); |
| |
| gfx::Size root_size(100, 100); |
| root->SetBounds(root_size); |
| |
| gfx::PointF clipping_origin(20.f, 10.f); |
| gfx::Size clipping_size(10, 20); |
| clipping_layer->SetBounds(clipping_size); |
| clipping_layer->SetPosition(clipping_origin); |
| clipping_layer->SetMasksToBounds(true); |
| |
| gfx::Size layer_size(50, 50); |
| content_layer->SetBounds(layer_size); |
| content_layer->SetPosition(gfx::PointF() - |
| clipping_origin.OffsetFromOrigin()); |
| |
| gfx::Size child_size(50, 50); |
| content_child_layer->SetBounds(child_size); |
| content_child_layer->SetPosition(gfx::PointF(20.f, 0.f)); |
| |
| gfx::Size mask_size(50, 50); |
| mask_layer->SetBounds(mask_size); |
| mask_client_.set_bounds(mask_size); |
| mask_layer_id_ = mask_layer->id(); |
| |
| layer_tree_host()->SetRootLayer(root); |
| LayerTreeTest::SetupTree(); |
| client_.set_bounds(root->bounds()); |
| } |
| |
| void BeginTest() override { PostSetNeedsCommitToMainThread(); } |
| |
| DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, |
| LayerTreeHostImpl::FrameData* frame_data, |
| DrawResult draw_result) override { |
| EXPECT_EQ(3u, frame_data->render_passes.size()); |
| viz::CompositorRenderPass* root_pass = |
| frame_data->render_passes.back().get(); |
| EXPECT_EQ(2u, root_pass->quad_list.size()); |
| |
| // There's a solid color quad under everything. |
| EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, |
| root_pass->quad_list.back()->material); |
| |
| // The surface is clipped to 10x20. |
| EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass, |
| root_pass->quad_list.front()->material); |
| const viz::CompositorRenderPassDrawQuad* render_pass_quad = |
| viz::CompositorRenderPassDrawQuad::MaterialCast( |
| root_pass->quad_list.front()); |
| gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect( |
| render_pass_quad->shared_quad_state->quad_to_target_transform, |
| render_pass_quad->rect); |
| EXPECT_EQ(gfx::Rect(20, 10, 10, 20).ToString(), |
| rect_in_target_space.ToString()); |
| |
| // We use kDstIn blend mode instead of the mask feature of RenderPass. |
| EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect); |
| viz::CompositorRenderPass* mask_pass = frame_data->render_passes[1].get(); |
| EXPECT_EQ(SkBlendMode::kDstIn, |
| mask_pass->quad_list.front()->shared_quad_state->blend_mode); |
| EndTest(); |
| return draw_result; |
| } |
| |
| int mask_layer_id_; |
| FakeContentLayerClient client_; |
| FakeContentLayerClient mask_client_; |
| }; |
| |
| SINGLE_AND_MULTI_THREAD_TEST_F( |
| LayerTreeTestMaskLayerForSurfaceWithClippedLayer); |
| |
| class LayerTreeTestMaskLayerForSurfaceWithDifferentScale |
| : public LayerTreeTest { |
| protected: |
| void SetupTree() override { |
| // The masked layer has bounds 50x50, but it has a child that causes |
| // the surface bounds to be larger. It also has a parent that clips the |
| // masked layer and its surface. |
| |
| scoped_refptr<Layer> root = Layer::Create(); |
| |
| scoped_refptr<Layer> clipping_scaling_layer = Layer::Create(); |
| root->AddChild(clipping_scaling_layer); |
| |
| scoped_refptr<FakePictureLayer> content_layer = |
| FakePictureLayer::Create(&client_); |
| clipping_scaling_layer->AddChild(content_layer); |
| |
| scoped_refptr<FakePictureLayer> content_child_layer = |
| FakePictureLayer::Create(&client_); |
| content_layer->AddChild(content_child_layer); |
| |
| mask_client_.set_fill_with_nonsolid_color(true); |
| auto mask_layer = FakePictureLayer::Create(&mask_client_); |
| content_layer->SetMaskLayer(mask_layer); |
| |
| gfx::Size root_size(100, 100); |
| root->SetBounds(root_size); |
| |
| gfx::Transform scale; |
| scale.Scale(2, 2); |
| gfx::PointF clipping_origin(20.f, 10.f); |
| gfx::Size clipping_size(10, 20); |
| clipping_scaling_layer->SetBounds(clipping_size); |
| clipping_scaling_layer->SetPosition(clipping_origin); |
| // This changes scale between contributing layer and render surface to 2. |
| clipping_scaling_layer->SetTransform(scale); |
| clipping_scaling_layer->SetMasksToBounds(true); |
| |
| gfx::Size layer_size(50, 50); |
| content_layer->SetBounds(layer_size); |
| content_layer->SetPosition(gfx::PointF() - |
| clipping_origin.OffsetFromOrigin()); |
| |
| gfx::Size child_size(50, 50); |
| content_child_layer->SetBounds(child_size); |
| content_child_layer->SetPosition(gfx::PointF(20.f, 0.f)); |
| |
| gfx::Size mask_size(50, 50); |
| mask_layer->SetBounds(mask_size); |
| mask_client_.set_bounds(mask_size); |
| mask_layer_id_ = mask_layer->id(); |
| |
| layer_tree_host()->SetRootLayer(root); |
| LayerTreeTest::SetupTree(); |
| client_.set_bounds(root->bounds()); |
| } |
| |
| void BeginTest() override { PostSetNeedsCommitToMainThread(); } |
| |
| DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, |
| LayerTreeHostImpl::FrameData* frame_data, |
| DrawResult draw_result) override { |
| EXPECT_EQ(3u, frame_data->render_passes.size()); |
| viz::CompositorRenderPass* root_pass = |
| frame_data->render_passes.back().get(); |
| EXPECT_EQ(2u, root_pass->quad_list.size()); |
| |
| // There's a solid color quad under everything. |
| EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, |
| root_pass->quad_list.back()->material); |
| |
| // The surface is clipped to 10x20, and then scaled by 2, which ends up |
| // being 20x40. |
| EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass, |
| root_pass->quad_list.front()->material); |
| const viz::CompositorRenderPassDrawQuad* render_pass_quad = |
| viz::CompositorRenderPassDrawQuad::MaterialCast( |
| root_pass->quad_list.front()); |
| gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect( |
| render_pass_quad->shared_quad_state->quad_to_target_transform, |
| render_pass_quad->rect); |
| EXPECT_EQ(gfx::Rect(20, 10, 20, 40).ToString(), |
| rect_in_target_space.ToString()); |
| gfx::Rect visible_rect_in_target_space = MathUtil::MapEnclosingClippedRect( |
| render_pass_quad->shared_quad_state->quad_to_target_transform, |
| render_pass_quad->visible_rect); |
| EXPECT_EQ(gfx::Rect(20, 10, 20, 40).ToString(), |
| visible_rect_in_target_space.ToString()); |
| |
| // We use kDstIn blend mode instead of the mask feature of RenderPass. |
| EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect); |
| viz::CompositorRenderPass* mask_pass = frame_data->render_passes[1].get(); |
| EXPECT_EQ(SkBlendMode::kDstIn, |
| mask_pass->quad_list.front()->shared_quad_state->blend_mode); |
| EndTest(); |
| return draw_result; |
| } |
| |
| int mask_layer_id_; |
| FakeContentLayerClient client_; |
| FakeContentLayerClient mask_client_; |
| }; |
| |
| SINGLE_AND_MULTI_THREAD_TEST_F( |
| LayerTreeTestMaskLayerForSurfaceWithDifferentScale); |
| |
| class LayerTreeTestMaskLayerWithScaling : public LayerTreeTest { |
| protected: |
| void SetupTree() override { |
| // Root |
| // | |
| // +-- Scaling Layer (adds a 2x scale) |
| // | |
| // +-- Content Layer |
| // +--Mask |
| |
| scoped_refptr<Layer> root = Layer::Create(); |
| |
| scoped_refptr<Layer> scaling_layer = Layer::Create(); |
| root->AddChild(scaling_layer); |
| |
| scoped_refptr<FakePictureLayer> content_layer = |
| FakePictureLayer::Create(&client_); |
| scaling_layer->AddChild(content_layer); |
| |
| mask_client_.set_fill_with_nonsolid_color(true); |
| auto mask_layer = FakePictureLayer::Create(&mask_client_); |
| content_layer->SetMaskLayer(mask_layer); |
| |
| gfx::Size root_size(100, 100); |
| root->SetBounds(root_size); |
| |
| gfx::Size scaling_layer_size(50, 50); |
| scaling_layer->SetBounds(scaling_layer_size); |
| gfx::Transform scale; |
| scale.Scale(2.f, 2.f); |
| scaling_layer->SetTransform(scale); |
| |
| content_layer->SetBounds(scaling_layer_size); |
| mask_layer->SetBounds(scaling_layer_size); |
| mask_client_.set_bounds(scaling_layer_size); |
| |
| layer_tree_host()->SetRootLayer(root); |
| LayerTreeTest::SetupTree(); |
| client_.set_bounds(root->bounds()); |
| } |
| |
| void BeginTest() override { PostSetNeedsCommitToMainThread(); } |
| |
| DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, |
| LayerTreeHostImpl::FrameData* frame_data, |
| DrawResult draw_result) override { |
| EXPECT_EQ(3u, frame_data->render_passes.size()); |
| viz::CompositorRenderPass* root_pass = |
| frame_data->render_passes.back().get(); |
| EXPECT_EQ(2u, root_pass->quad_list.size()); |
| |
| // There's a solid color quad under everything. |
| EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, |
| root_pass->quad_list.back()->material); |
| |
| EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass, |
| root_pass->quad_list.front()->material); |
| const viz::CompositorRenderPassDrawQuad* render_pass_quad = |
| viz::CompositorRenderPassDrawQuad::MaterialCast( |
| root_pass->quad_list.front()); |
| gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect( |
| render_pass_quad->shared_quad_state->quad_to_target_transform, |
| render_pass_quad->rect); |
| |
| // We use kDstIn blend mode instead of the mask feature of RenderPass. |
| EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect); |
| viz::CompositorRenderPass* mask_pass = frame_data->render_passes[1].get(); |
| EXPECT_EQ(SkBlendMode::kDstIn, |
| mask_pass->quad_list.front()->shared_quad_state->blend_mode); |
| |
| switch (host_impl->active_tree()->source_frame_number()) { |
| case 0: |
| // Check that the tree scaling is correctly taken into account for the |
| // mask, that should fully map onto the quad. |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(), |
| rect_in_target_space.ToString()); |
| break; |
| case 1: |
| // Applying a DSF should change the render surface size, but won't |
| // affect which part of the mask is used. |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 200).ToString(), |
| rect_in_target_space.ToString()); |
| EndTest(); |
| break; |
| } |
| return draw_result; |
| } |
| |
| void DidCommit() override { |
| switch (layer_tree_host()->SourceFrameNumber()) { |
| case 1: |
| gfx::Size double_root_size(200, 200); |
| GenerateNewLocalSurfaceId(); |
| layer_tree_host()->SetViewportRectAndScale( |
| gfx::Rect(double_root_size), 2.f, GetCurrentLocalSurfaceId()); |
| break; |
| } |
| } |
| |
| FakeContentLayerClient client_; |
| FakeContentLayerClient mask_client_; |
| }; |
| |
| SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestMaskLayerWithScaling); |
| |
| class LayerTreeTestMaskWithNonExactTextureSize : public LayerTreeTest { |
| protected: |
| void SetupTree() override { |
| // The masked layer has bounds 100x100, but is allocated a 120x150 texture. |
| |
| scoped_refptr<Layer> root = Layer::Create(); |
| |
| scoped_refptr<FakePictureLayer> content_layer = |
| FakePictureLayer::Create(&client_); |
| root->AddChild(content_layer); |
| |
| mask_client_.set_fill_with_nonsolid_color(true); |
| auto mask_layer = FakePictureLayer::Create(&mask_client_); |
| content_layer->SetMaskLayer(mask_layer); |
| |
| gfx::Size root_size(100, 100); |
| root->SetBounds(root_size); |
| |
| gfx::Size layer_size(100, 100); |
| content_layer->SetBounds(layer_size); |
| |
| gfx::Size mask_size(100, 100); |
| gfx::Size mask_texture_size(120, 150); |
| mask_layer->SetBounds(mask_size); |
| mask_client_.set_bounds(mask_size); |
| mask_layer->set_fixed_tile_size(mask_texture_size); |
| |
| layer_tree_host()->SetRootLayer(root); |
| LayerTreeTest::SetupTree(); |
| client_.set_bounds(root->bounds()); |
| } |
| |
| void BeginTest() override { PostSetNeedsCommitToMainThread(); } |
| |
| DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, |
| LayerTreeHostImpl::FrameData* frame_data, |
| DrawResult draw_result) override { |
| EXPECT_EQ(3u, frame_data->render_passes.size()); |
| viz::CompositorRenderPass* root_pass = |
| frame_data->render_passes.back().get(); |
| EXPECT_EQ(2u, root_pass->quad_list.size()); |
| |
| // There's a solid color quad under everything. |
| EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, |
| root_pass->quad_list.back()->material); |
| |
| // The surface is 100x100 |
| EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass, |
| root_pass->quad_list.front()->material); |
| const viz::CompositorRenderPassDrawQuad* render_pass_quad = |
| viz::CompositorRenderPassDrawQuad::MaterialCast( |
| root_pass->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(), |
| render_pass_quad->rect.ToString()); |
| |
| // We use kDstIn blend mode instead of the mask feature of RenderPass. |
| EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect); |
| viz::CompositorRenderPass* mask_pass = frame_data->render_passes[1].get(); |
| EXPECT_EQ(SkBlendMode::kDstIn, |
| mask_pass->quad_list.front()->shared_quad_state->blend_mode); |
| EndTest(); |
| return draw_result; |
| } |
| |
| int mask_layer_id_; |
| FakeContentLayerClient client_; |
| FakeContentLayerClient mask_client_; |
| }; |
| |
| SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestMaskWithNonExactTextureSize); |
| |
| } // namespace |
| } // namespace cc |