// Copyright 2012 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/trees/layer_tree_host.h" #include "base/bind.h" #include "base/callback.h" #include "base/location.h" #include "base/message_loop/message_loop_proxy.h" #include "base/time/time.h" #include "cc/test/fake_content_layer.h" #include "cc/test/fake_content_layer_client.h" #include "cc/test/fake_painted_scrollbar_layer.h" #include "cc/test/fake_picture_layer.h" #include "cc/test/layer_tree_test.h" #include "cc/trees/damage_tracker.h" #include "cc/trees/layer_tree_impl.h" namespace cc { namespace { // These tests deal with damage tracking. class LayerTreeHostDamageTest : public LayerTreeTest {}; // LayerTreeHost::SetNeedsRedraw should damage the whole viewport. class LayerTreeHostDamageTestSetNeedsRedraw : public LayerTreeHostDamageTest { virtual void SetupTree() OVERRIDE { // Viewport is 10x10. scoped_refptr root = FakeContentLayer::Create(&client_); root->SetBounds(gfx::Size(10, 10)); layer_tree_host()->SetRootLayer(root); LayerTreeHostDamageTest::SetupTree(); } virtual void BeginTest() OVERRIDE { draw_count_ = 0; PostSetNeedsCommitToMainThread(); } virtual void DidCommitAndDrawFrame() OVERRIDE { switch (layer_tree_host()->source_frame_number()) { case 1: layer_tree_host()->SetNeedsRedraw(); break; } } virtual DrawResult PrepareToDrawOnThread( LayerTreeHostImpl* impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) OVERRIDE { EXPECT_EQ(DRAW_SUCCESS, draw_result); RenderSurfaceImpl* root_surface = impl->active_tree()->root_layer()->render_surface(); gfx::RectF root_damage = root_surface->damage_tracker()->current_damage_rect(); switch (draw_count_) { case 0: // The first frame has full damage. EXPECT_EQ(gfx::RectF(10.f, 10.f).ToString(), root_damage.ToString()); break; case 1: // The second frame has full damage. EXPECT_EQ(gfx::RectF(10.f, 10.f).ToString(), root_damage.ToString()); EndTest(); break; case 2: NOTREACHED(); } ++draw_count_; return draw_result; } virtual void AfterTest() OVERRIDE {} int draw_count_; FakeContentLayerClient client_; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDamageTestSetNeedsRedraw); // LayerTreeHost::SetViewportSize should damage the whole viewport. class LayerTreeHostDamageTestSetViewportSize : public LayerTreeHostDamageTest { virtual void SetupTree() OVERRIDE { // Viewport is 10x10. scoped_refptr root = FakeContentLayer::Create(&client_); root->SetBounds(gfx::Size(10, 10)); layer_tree_host()->SetRootLayer(root); LayerTreeHostDamageTest::SetupTree(); } virtual void BeginTest() OVERRIDE { draw_count_ = 0; PostSetNeedsCommitToMainThread(); } virtual void DidCommitAndDrawFrame() OVERRIDE { switch (layer_tree_host()->source_frame_number()) { case 1: layer_tree_host()->SetViewportSize(gfx::Size(15, 15)); break; } } virtual DrawResult PrepareToDrawOnThread( LayerTreeHostImpl* impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) OVERRIDE { EXPECT_EQ(DRAW_SUCCESS, draw_result); RenderSurfaceImpl* root_surface = impl->active_tree()->root_layer()->render_surface(); gfx::RectF root_damage = root_surface->damage_tracker()->current_damage_rect(); switch (draw_count_) { case 0: // The first frame has full damage. EXPECT_EQ(gfx::RectF(10.f, 10.f).ToString(), root_damage.ToString()); break; case 1: // The second frame has full damage. EXPECT_EQ(gfx::RectF(15.f, 15.f).ToString(), root_damage.ToString()); EndTest(); break; case 2: NOTREACHED(); } ++draw_count_; return draw_result; } virtual void AfterTest() OVERRIDE {} int draw_count_; FakeContentLayerClient client_; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDamageTestSetViewportSize); class LayerTreeHostDamageTestNoDamageDoesNotSwap : public LayerTreeHostDamageTest { virtual void BeginTest() OVERRIDE { expect_swap_and_succeed_ = 0; did_swaps_ = 0; did_swap_and_succeed_ = 0; PostSetNeedsCommitToMainThread(); } virtual void SetupTree() OVERRIDE { scoped_refptr root = FakeContentLayer::Create(&client_); root->SetBounds(gfx::Size(10, 10)); // Most of the layer isn't visible. content_ = FakeContentLayer::Create(&client_); content_->SetBounds(gfx::Size(2000, 100)); root->AddChild(content_); layer_tree_host()->SetRootLayer(root); LayerTreeHostDamageTest::SetupTree(); } virtual DrawResult PrepareToDrawOnThread( LayerTreeHostImpl* host_impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) OVERRIDE { EXPECT_EQ(DRAW_SUCCESS, draw_result); int source_frame = host_impl->active_tree()->source_frame_number(); switch (source_frame) { case 0: // The first frame has damage, so we should draw and swap. ++expect_swap_and_succeed_; break; case 1: // The second frame has no damage, so we should not draw and swap. break; case 2: // The third frame has damage again, so we should draw and swap. ++expect_swap_and_succeed_; break; case 3: // The fourth frame has no visible damage, so we should not draw and // swap. EndTest(); break; } return draw_result; } virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl, bool result) OVERRIDE { ++did_swaps_; if (result) ++did_swap_and_succeed_; EXPECT_EQ(expect_swap_and_succeed_, did_swap_and_succeed_); } virtual void DidCommit() OVERRIDE { int next_frame = layer_tree_host()->source_frame_number(); switch (next_frame) { case 1: layer_tree_host()->SetNeedsCommit(); break; case 2: // Cause visible damage. content_->SetNeedsDisplayRect( gfx::Rect(layer_tree_host()->device_viewport_size())); break; case 3: // Cause non-visible damage. content_->SetNeedsDisplayRect(gfx::Rect(1990, 1990, 10, 10)); layer_tree_host()->SetNeedsCommit(); break; } } virtual void AfterTest() OVERRIDE { EXPECT_EQ(4, did_swaps_); EXPECT_EQ(2, expect_swap_and_succeed_); EXPECT_EQ(expect_swap_and_succeed_, did_swap_and_succeed_); } FakeContentLayerClient client_; scoped_refptr content_; int expect_swap_and_succeed_; int did_swaps_; int did_swap_and_succeed_; }; SINGLE_AND_MULTI_THREAD_NOIMPL_TEST_F( LayerTreeHostDamageTestNoDamageDoesNotSwap); class LayerTreeHostDamageTestForcedFullDamage : public LayerTreeHostDamageTest { virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); } virtual void SetupTree() OVERRIDE { root_ = FakeContentLayer::Create(&client_); child_ = FakeContentLayer::Create(&client_); root_->SetBounds(gfx::Size(500, 500)); child_->SetPosition(gfx::Point(100, 100)); child_->SetBounds(gfx::Size(30, 30)); root_->AddChild(child_); layer_tree_host()->SetRootLayer(root_); LayerTreeHostDamageTest::SetupTree(); } virtual DrawResult PrepareToDrawOnThread( LayerTreeHostImpl* host_impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) OVERRIDE { EXPECT_EQ(DRAW_SUCCESS, draw_result); RenderSurfaceImpl* root_surface = host_impl->active_tree()->root_layer()->render_surface(); gfx::RectF root_damage = root_surface->damage_tracker()->current_damage_rect(); root_damage.Intersect(root_surface->content_rect()); int source_frame = host_impl->active_tree()->source_frame_number(); switch (source_frame) { case 0: // The first frame draws and clears any damage. EXPECT_EQ(gfx::RectF(root_surface->content_rect()).ToString(), root_damage.ToString()); EXPECT_FALSE(frame_data->has_no_damage); break; case 1: // If we get a frame without damage then we don't draw. EXPECT_EQ(gfx::RectF().ToString(), root_damage.ToString()); EXPECT_TRUE(frame_data->has_no_damage); // Then we set full damage for the next frame. host_impl->SetFullRootLayerDamage(); break; case 2: // The whole frame should be damaged as requested. EXPECT_EQ(gfx::RectF(root_surface->content_rect()).ToString(), root_damage.ToString()); EXPECT_FALSE(frame_data->has_no_damage); // Just a part of the next frame should be damaged. child_damage_rect_ = gfx::RectF(10, 11, 12, 13); break; case 3: // The update rect in the child should be damaged and the damaged area // should match the invalidation. EXPECT_EQ(gfx::RectF(100+10, 100+11, 12, 13).ToString(), root_damage.ToString()); // TODO(danakj): Remove this when impl side painting is always on. if (delegating_renderer() || host_impl->settings().impl_side_painting) { // When using a delegating renderer, or using impl side painting, the // entire child is considered damaged as we need to replace its // resources with newly created ones. The damaged area is kept as it // is, but entire child is painted. // The paint rect should match the layer bounds. gfx::RectF paint_rect = child_->LastPaintRect(); paint_rect.set_origin(child_->position()); EXPECT_EQ(gfx::RectF(100, 100, 30, 30).ToString(), paint_rect.ToString()); } EXPECT_FALSE(frame_data->has_no_damage); // If we damage part of the frame, but also damage the full // frame, then the whole frame should be damaged. child_damage_rect_ = gfx::RectF(10, 11, 12, 13); host_impl->SetFullRootLayerDamage(); break; case 4: // The whole frame is damaged. EXPECT_EQ(gfx::RectF(root_surface->content_rect()).ToString(), root_damage.ToString()); EXPECT_FALSE(frame_data->has_no_damage); EndTest(); break; } return draw_result; } virtual void DidCommitAndDrawFrame() OVERRIDE { if (!TestEnded()) layer_tree_host()->SetNeedsCommit(); if (!child_damage_rect_.IsEmpty()) { child_->SetNeedsDisplayRect(child_damage_rect_); child_damage_rect_ = gfx::RectF(); } } virtual void AfterTest() OVERRIDE {} FakeContentLayerClient client_; scoped_refptr root_; scoped_refptr child_; gfx::RectF child_damage_rect_; }; SINGLE_AND_MULTI_THREAD_NOIMPL_TEST_F(LayerTreeHostDamageTestForcedFullDamage); class LayerTreeHostScrollbarDamageTest : public LayerTreeHostDamageTest { virtual void SetupTree() OVERRIDE { scoped_refptr root_layer = Layer::Create(); root_layer->SetBounds(gfx::Size(400, 400)); root_layer->SetMasksToBounds(true); layer_tree_host()->SetRootLayer(root_layer); scoped_refptr scroll_clip_layer = Layer::Create(); scoped_refptr content_layer = FakeContentLayer::Create(&client_); content_layer->SetScrollClipLayerId(scroll_clip_layer->id()); content_layer->SetScrollOffset(gfx::Vector2d(10, 20)); content_layer->SetBounds(gfx::Size(100, 200)); scroll_clip_layer->SetBounds( gfx::Size(content_layer->bounds().width() - 30, content_layer->bounds().height() - 50)); scroll_clip_layer->AddChild(content_layer); root_layer->AddChild(scroll_clip_layer); scoped_refptr scrollbar_layer = FakePaintedScrollbarLayer::Create(false, true, content_layer->id()); scrollbar_layer->SetPosition(gfx::Point(300, 300)); scrollbar_layer->SetBounds(gfx::Size(10, 100)); scrollbar_layer->ToScrollbarLayer()->SetClipLayer(scroll_clip_layer->id()); scrollbar_layer->ToScrollbarLayer()->SetScrollLayer(content_layer->id()); root_layer->AddChild(scrollbar_layer); gfx::RectF content_rect(content_layer->position(), content_layer->bounds()); gfx::RectF scrollbar_rect(scrollbar_layer->position(), scrollbar_layer->bounds()); EXPECT_FALSE(content_rect.Intersects(scrollbar_rect)); LayerTreeHostDamageTest::SetupTree(); } private: FakeContentLayerClient client_; }; class LayerTreeHostDamageTestScrollbarDoesDamage : public LayerTreeHostScrollbarDamageTest { virtual void BeginTest() OVERRIDE { did_swaps_ = 0; PostSetNeedsCommitToMainThread(); } virtual DrawResult PrepareToDrawOnThread( LayerTreeHostImpl* host_impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) OVERRIDE { EXPECT_EQ(DRAW_SUCCESS, draw_result); RenderSurfaceImpl* root_surface = host_impl->active_tree()->root_layer()->render_surface(); gfx::RectF root_damage = root_surface->damage_tracker()->current_damage_rect(); root_damage.Intersect(root_surface->content_rect()); switch (did_swaps_) { case 0: // The first frame has damage, so we should draw and swap. break; case 1: // The second frame should not damage the scrollbars. EXPECT_FALSE(root_damage.Intersects(gfx::Rect(300, 300, 10, 100))); break; case 2: // The third frame should damage the scrollbars. EXPECT_TRUE(root_damage.Contains(gfx::Rect(300, 300, 10, 100))); break; case 3: // The fourth frame should damage the scrollbars. EXPECT_TRUE(root_damage.Contains(gfx::Rect(300, 300, 10, 100))); EndTest(); break; } return draw_result; } virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl, bool result) OVERRIDE { ++did_swaps_; EXPECT_TRUE(result); LayerImpl* root = host_impl->active_tree()->root_layer(); LayerImpl* scroll_clip_layer = root->children()[0]; LayerImpl* scroll_layer = scroll_clip_layer->children()[0]; switch (did_swaps_) { case 1: // Test that modifying the position of the content layer (not // scrolling) won't damage the scrollbar. scroll_layer->SetPosition(gfx::Point(1, 1)); scroll_layer->SetScrollOffset(scroll_layer->scroll_offset()); host_impl->SetNeedsRedraw(); break; case 2: scroll_layer->ScrollBy(gfx::Vector2dF(10.f, 10.f)); host_impl->SetNeedsRedraw(); break; case 3: scroll_layer->SetBounds(gfx::Size(root->bounds().width() + 60, root->bounds().height() + 100)); host_impl->SetNeedsRedraw(); break; } } virtual void AfterTest() OVERRIDE { EXPECT_EQ(4, did_swaps_); } int did_swaps_; }; MULTI_THREAD_TEST_F(LayerTreeHostDamageTestScrollbarDoesDamage); class LayerTreeHostDamageTestScrollbarCommitDoesNoDamage : public LayerTreeHostScrollbarDamageTest { virtual void BeginTest() OVERRIDE { did_swaps_ = 0; PostSetNeedsCommitToMainThread(); } virtual DrawResult PrepareToDrawOnThread( LayerTreeHostImpl* host_impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) OVERRIDE { EXPECT_EQ(DRAW_SUCCESS, draw_result); RenderSurfaceImpl* root_surface = host_impl->active_tree()->root_layer()->render_surface(); gfx::RectF root_damage = root_surface->damage_tracker()->current_damage_rect(); root_damage.Intersect(root_surface->content_rect()); int frame = host_impl->active_tree()->source_frame_number(); switch (did_swaps_) { case 0: // The first frame has damage, so we should draw and swap. EXPECT_EQ(0, frame); break; case 1: // The second frame has scrolled, so the scrollbar should be damaged. EXPECT_EQ(0, frame); EXPECT_TRUE(root_damage.Contains(gfx::Rect(300, 300, 10, 100))); break; case 2: // The third frame (after the commit) has no changes, so it shouldn't. EXPECT_EQ(1, frame); EXPECT_FALSE(root_damage.Intersects(gfx::Rect(300, 300, 10, 100))); break; default: NOTREACHED(); break; } return draw_result; } virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl, bool result) OVERRIDE { ++did_swaps_; EXPECT_TRUE(result); LayerImpl* root = host_impl->active_tree()->root_layer(); LayerImpl* scroll_clip_layer = root->children()[0]; LayerImpl* scroll_layer = scroll_clip_layer->children()[0]; switch (did_swaps_) { case 1: // Scroll on the thread. This should damage the scrollbar for the // next draw on the thread. scroll_layer->ScrollBy(gfx::Vector2dF(10.f, 10.f)); host_impl->SetNeedsRedraw(); break; case 2: // Forcibly send the scroll to the main thread. PostSetNeedsCommitToMainThread(); break; case 3: // First swap after second commit. EndTest(); break; default: NOTREACHED(); break; } } virtual void AfterTest() OVERRIDE { EXPECT_EQ(3, did_swaps_); } int did_swaps_; }; MULTI_THREAD_TEST_F(LayerTreeHostDamageTestScrollbarCommitDoesNoDamage); class LayerTreeHostDamageTestVisibleTilesStillTriggerDraws : public LayerTreeHostDamageTest { virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE { settings->impl_side_painting = true; } virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); } virtual void SetupTree() OVERRIDE { scoped_refptr root = FakePictureLayer::Create(&client_); root->SetBounds(gfx::Size(500, 500)); layer_tree_host()->SetRootLayer(root); LayerTreeHostDamageTest::SetupTree(); swap_count_ = 0; prepare_to_draw_count_ = 0; update_visible_tile_count_ = 0; } virtual DrawResult PrepareToDrawOnThread( LayerTreeHostImpl* host_impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) OVERRIDE { EXPECT_EQ(DRAW_SUCCESS, draw_result); prepare_to_draw_count_++; switch (prepare_to_draw_count_) { case 1: // Detect that we have an incomplete tile, during the first frame. // The first frame should have damage. frame_data->contains_incomplete_tile = true; DCHECK(!frame_data->has_no_damage); break; case 2: // Make a no-damage frame. We early out and can't detect // incomplete tiles, even if they still exist. frame_data->contains_incomplete_tile = false; frame_data->has_no_damage = true; break; case 3: // Trigger the last swap for the completed tile. frame_data->contains_incomplete_tile = false; frame_data->has_no_damage = false; EndTest(); break; default: NOTREACHED(); break; } return draw_result; } virtual void UpdateVisibleTilesOnThread( LayerTreeHostImpl* host_impl) OVERRIDE { // Simulate creating some visible tiles (that trigger prepare-to-draws). // The first we make into a no-damage-frame during prepare-to-draw (see // above). This is to ensure we still get UpdateVisibleTiles calls after // a no-damage or aborted frame. update_visible_tile_count_++; switch (update_visible_tile_count_) { case 3: case 6: host_impl->DidInitializeVisibleTileForTesting(); break; case 7: NOTREACHED(); break; } } virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl, bool didSwap) OVERRIDE { if (!didSwap) return; ++swap_count_; } virtual void AfterTest() OVERRIDE { // We should keep getting update-visible-tiles calls // until we report there are no more incomplete-tiles. EXPECT_EQ(update_visible_tile_count_, 6); // First frame, plus two triggered by DidInitializeVisibleTile() EXPECT_EQ(prepare_to_draw_count_, 3); // First swap, plus final swap (contained damage). EXPECT_EQ(swap_count_, 2); } FakeContentLayerClient client_; int swap_count_; int prepare_to_draw_count_; int update_visible_tile_count_; }; MULTI_THREAD_TEST_F(LayerTreeHostDamageTestVisibleTilesStillTriggerDraws); } // namespace } // namespace cc