// Copyright 2011 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/layer_tree_host.h" #include "base/synchronization/lock.h" #include "cc/content_layer.h" #include "cc/content_layer_client.h" #include "cc/frame_rate_controller.h" #include "cc/layer_impl.h" #include "cc/layer_tree_host_impl.h" #include "cc/layer_tree_impl.h" #include "cc/output_surface.h" #include "cc/picture_layer.h" #include "cc/prioritized_resource.h" #include "cc/prioritized_resource_manager.h" #include "cc/resource_update_queue.h" #include "cc/single_thread_proxy.h" #include "cc/test/fake_content_layer.h" #include "cc/test/fake_content_layer_client.h" #include "cc/test/fake_layer_tree_host_client.h" #include "cc/test/fake_output_surface.h" #include "cc/test/fake_proxy.h" #include "cc/test/fake_scrollbar_layer.h" #include "cc/test/geometry_test_utils.h" #include "cc/test/layer_tree_test_common.h" #include "cc/test/occlusion_tracker_test_common.h" #include "cc/thread_proxy.h" #include "cc/timing_function.h" #include "skia/ext/refptr.h" #include "testing/gmock/include/gmock/gmock.h" #include "third_party/WebKit/Source/Platform/chromium/public/WebSize.h" #include "third_party/khronos/GLES2/gl2.h" #include "third_party/khronos/GLES2/gl2ext.h" #include "third_party/skia/include/core/SkPicture.h" #include "ui/gfx/point_conversions.h" #include "ui/gfx/size_conversions.h" #include "ui/gfx/vector2d_conversions.h" namespace cc { namespace { class LayerTreeHostTest : public ThreadedTest { }; // Shortlived layerTreeHosts shouldn't die. class LayerTreeHostTestShortlived1 : public LayerTreeHostTest { public: LayerTreeHostTestShortlived1() { } virtual void beginTest() OVERRIDE { // Kill the layerTreeHost immediately. m_layerTreeHost->SetRootLayer(NULL); m_layerTreeHost.reset(); endTest(); } virtual void afterTest() OVERRIDE { } }; // Shortlived layerTreeHosts shouldn't die with a commit in flight. class LayerTreeHostTestShortlived2 : public LayerTreeHostTest { public: LayerTreeHostTestShortlived2() { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); // Kill the layerTreeHost immediately. m_layerTreeHost->SetRootLayer(NULL); m_layerTreeHost.reset(); endTest(); } virtual void afterTest() OVERRIDE { } }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestShortlived2) // Shortlived layerTreeHosts shouldn't die with a redraw in flight. class LayerTreeHostTestShortlived3 : public LayerTreeHostTest { public: LayerTreeHostTestShortlived3() { } virtual void beginTest() OVERRIDE { postSetNeedsRedrawToMainThread(); // Kill the layerTreeHost immediately. m_layerTreeHost->SetRootLayer(NULL); m_layerTreeHost.reset(); endTest(); } virtual void afterTest() OVERRIDE { } }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestShortlived3) // Test interleaving of redraws and commits class LayerTreeHostTestCommitingWithContinuousRedraw : public LayerTreeHostTest { public: LayerTreeHostTestCommitingWithContinuousRedraw() : m_numCompleteCommits(0) , m_numDraws(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE { m_numCompleteCommits++; if (m_numCompleteCommits == 2) endTest(); } virtual void drawLayersOnThread(LayerTreeHostImpl*) OVERRIDE { if (m_numDraws == 1) postSetNeedsCommitToMainThread(); m_numDraws++; postSetNeedsRedrawToMainThread(); } virtual void afterTest() OVERRIDE { } private: int m_numCompleteCommits; int m_numDraws; }; TEST_F(LayerTreeHostTestCommitingWithContinuousRedraw, runMultiThread) { runTest(true); } // Two setNeedsCommits in a row should lead to at least 1 commit and at least 1 // draw with frame 0. class LayerTreeHostTestSetNeedsCommit1 : public LayerTreeHostTest { public: LayerTreeHostTestSetNeedsCommit1() : m_numCommits(0) , m_numDraws(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); postSetNeedsCommitToMainThread(); } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { m_numDraws++; if (!impl->active_tree()->source_frame_number()) endTest(); } virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE { m_numCommits++; } virtual void afterTest() OVERRIDE { EXPECT_GE(1, m_numCommits); EXPECT_GE(1, m_numDraws); } private: int m_numCommits; int m_numDraws; }; TEST_F(LayerTreeHostTestSetNeedsCommit1, DISABLED_runMultiThread) { runTest(true); } // A setNeedsCommit should lead to 1 commit. Issuing a second commit after that // first committed frame draws should lead to another commit. class LayerTreeHostTestSetNeedsCommit2 : public LayerTreeHostTest { public: LayerTreeHostTestSetNeedsCommit2() : m_numCommits(0) , m_numDraws(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { if (impl->active_tree()->source_frame_number() == 0) postSetNeedsCommitToMainThread(); else if (impl->active_tree()->source_frame_number() == 1) endTest(); } virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE { m_numCommits++; } virtual void afterTest() OVERRIDE { EXPECT_EQ(2, m_numCommits); EXPECT_GE(2, m_numDraws); } private: int m_numCommits; int m_numDraws; }; TEST_F(LayerTreeHostTestSetNeedsCommit2, runMultiThread) { runTest(true); } // 1 setNeedsRedraw after the first commit has completed should lead to 1 // additional draw. class LayerTreeHostTestSetNeedsRedraw : public LayerTreeHostTest { public: LayerTreeHostTestSetNeedsRedraw() : m_numCommits(0) , m_numDraws(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { EXPECT_EQ(0, impl->active_tree()->source_frame_number()); if (!m_numDraws) postSetNeedsRedrawToMainThread(); // Redraw again to verify that the second redraw doesn't commit. else endTest(); m_numDraws++; } virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE { EXPECT_EQ(0, m_numDraws); m_numCommits++; } virtual void afterTest() OVERRIDE { EXPECT_GE(2, m_numDraws); EXPECT_EQ(1, m_numCommits); } private: int m_numCommits; int m_numDraws; }; TEST_F(LayerTreeHostTestSetNeedsRedraw, runMultiThread) { runTest(true); } class LayerTreeHostTestNoExtraCommitFromInvalidate : public LayerTreeHostTest { public: LayerTreeHostTestNoExtraCommitFromInvalidate() : m_rootLayer(ContentLayer::Create(&m_client)) { } virtual void beginTest() OVERRIDE { m_rootLayer->SetAutomaticallyComputeRasterScale(false); m_rootLayer->SetIsDrawable(true); m_rootLayer->SetBounds(gfx::Size(1, 1)); m_layerTreeHost->SetRootLayer(m_rootLayer); postSetNeedsCommitToMainThread(); } virtual void didCommit() OVERRIDE { switch (m_layerTreeHost->commit_number()) { case 1: // Changing the content bounds will cause a single commit! m_rootLayer->SetRasterScale(4.0f); break; default: // No extra commits. EXPECT_EQ(2, m_layerTreeHost->commit_number()); endTest(); } } virtual void afterTest() OVERRIDE { } private: FakeContentLayerClient m_client; scoped_refptr m_rootLayer; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestNoExtraCommitFromInvalidate) class LayerTreeHostTestCompositeAndReadback : public LayerTreeHostTest { public: LayerTreeHostTestCompositeAndReadback() : m_numCommits(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void didCommit() OVERRIDE { m_numCommits++; if (m_numCommits == 1) { char pixels[4]; m_layerTreeHost->CompositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1)); } else if (m_numCommits == 2) { // This is inside the readback. We should get another commit after it. } else if (m_numCommits == 3) { endTest(); } else { NOTREACHED(); } } virtual void afterTest() OVERRIDE { } private: int m_numCommits; }; TEST_F(LayerTreeHostTestCompositeAndReadback, runMultiThread) { runTest(true); } class LayerTreeHostTestCompositeAndReadbackBeforePreviousCommitDraws : public LayerTreeHostTest { public: LayerTreeHostTestCompositeAndReadbackBeforePreviousCommitDraws() : m_numCommits(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void didCommit() OVERRIDE { m_numCommits++; if (m_numCommits == 1) { m_layerTreeHost->SetNeedsCommit(); } else if (m_numCommits == 2) { char pixels[4]; m_layerTreeHost->CompositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1)); } else if (m_numCommits == 3) { // This is inside the readback. We should get another commit after it. } else if (m_numCommits == 4) { endTest(); } else { NOTREACHED(); } } virtual void afterTest() OVERRIDE { } private: int m_numCommits; }; TEST_F(LayerTreeHostTestCompositeAndReadbackBeforePreviousCommitDraws, runMultiThread) { runTest(true); } // If the layerTreeHost says it can't draw, then we should not try to draw. class LayerTreeHostTestCanDrawBlocksDrawing : public LayerTreeHostTest { public: LayerTreeHostTestCanDrawBlocksDrawing() : m_numCommits(0) , m_done(false) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { if (m_done) return; // Only the initial draw should bring us here. EXPECT_TRUE(impl->CanDraw()); EXPECT_EQ(0, impl->active_tree()->source_frame_number()); } virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { if (m_done) return; if (m_numCommits >= 1) { // After the first commit, we should not be able to draw. EXPECT_FALSE(impl->CanDraw()); } } virtual void didCommit() OVERRIDE { m_numCommits++; if (m_numCommits == 1) { // Make the viewport empty so the host says it can't draw. m_layerTreeHost->SetViewportSize(gfx::Size(0, 0), gfx::Size(0, 0)); } else if (m_numCommits == 2) { char pixels[4]; m_layerTreeHost->CompositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1)); } else if (m_numCommits == 3) { // Let it draw so we go idle and end the test. m_layerTreeHost->SetViewportSize(gfx::Size(1, 1), gfx::Size(1, 1)); m_done = true; endTest(); } } virtual void afterTest() OVERRIDE { } private: int m_numCommits; bool m_done; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestCanDrawBlocksDrawing) // beginLayerWrite should prevent draws from executing until a commit occurs class LayerTreeHostTestWriteLayersRedraw : public LayerTreeHostTest { public: LayerTreeHostTestWriteLayersRedraw() : m_numCommits(0) , m_numDraws(0) { } virtual void beginTest() OVERRIDE { postAcquireLayerTextures(); postSetNeedsRedrawToMainThread(); // should be inhibited without blocking postSetNeedsCommitToMainThread(); } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { m_numDraws++; EXPECT_EQ(m_numDraws, m_numCommits); } virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE { m_numCommits++; endTest(); } virtual void afterTest() OVERRIDE { EXPECT_EQ(1, m_numCommits); } private: int m_numCommits; int m_numDraws; }; TEST_F(LayerTreeHostTestWriteLayersRedraw, runMultiThread) { runTest(true); } // Verify that when resuming visibility, requesting layer write permission // will not deadlock the main thread even though there are not yet any // scheduled redraws. This behavior is critical for reliably surviving tab // switching. There are no failure conditions to this test, it just passes // by not timing out. class LayerTreeHostTestWriteLayersAfterVisible : public LayerTreeHostTest { public: LayerTreeHostTestWriteLayersAfterVisible() : m_numCommits(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE { m_numCommits++; if (m_numCommits == 2) endTest(); else if (m_numCommits < 2) { postSetVisibleToMainThread(false); postSetVisibleToMainThread(true); postAcquireLayerTextures(); postSetNeedsCommitToMainThread(); } } virtual void afterTest() OVERRIDE { } private: int m_numCommits; }; TEST_F(LayerTreeHostTestWriteLayersAfterVisible, runMultiThread) { runTest(true); } // A compositeAndReadback while invisible should force a normal commit without assertion. class LayerTreeHostTestCompositeAndReadbackWhileInvisible : public LayerTreeHostTest { public: LayerTreeHostTestCompositeAndReadbackWhileInvisible() : m_numCommits(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void didCommitAndDrawFrame() OVERRIDE { m_numCommits++; if (m_numCommits == 1) { m_layerTreeHost->SetVisible(false); m_layerTreeHost->SetNeedsCommit(); m_layerTreeHost->SetNeedsCommit(); char pixels[4]; m_layerTreeHost->CompositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1)); } else endTest(); } virtual void afterTest() OVERRIDE { } private: int m_numCommits; }; TEST_F(LayerTreeHostTestCompositeAndReadbackWhileInvisible, runMultiThread) { runTest(true); } class LayerTreeHostTestAbortFrameWhenInvisible : public LayerTreeHostTest { public: LayerTreeHostTestAbortFrameWhenInvisible() { } virtual void beginTest() OVERRIDE { // Request a commit (from the main thread), which will trigger the commit flow from the impl side. m_layerTreeHost->SetNeedsCommit(); // Then mark ourselves as not visible before processing any more messages on the main thread. m_layerTreeHost->SetVisible(false); // If we make it without kicking a frame, we pass! endTestAfterDelay(1); } virtual void layout() OVERRIDE { ASSERT_FALSE(true); endTest(); } virtual void afterTest() OVERRIDE { } private: }; TEST_F(LayerTreeHostTestAbortFrameWhenInvisible, runMultiThread) { runTest(true); } // This test verifies that properties on the layer tree host are commited to the impl side. class LayerTreeHostTestCommit : public LayerTreeHostTest { public: LayerTreeHostTestCommit() { } virtual void beginTest() OVERRIDE { m_layerTreeHost->SetViewportSize(gfx::Size(20, 20), gfx::Size(20, 20)); m_layerTreeHost->set_background_color(SK_ColorGRAY); m_layerTreeHost->SetPageScaleFactorAndLimits(5, 5, 5); postSetNeedsCommitToMainThread(); } virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { EXPECT_EQ(gfx::Size(20, 20), impl->layout_viewport_size()); EXPECT_EQ(SK_ColorGRAY, impl->active_tree()->background_color()); EXPECT_EQ(5, impl->active_tree()->page_scale_factor()); endTest(); } virtual void afterTest() OVERRIDE { } }; TEST_F(LayerTreeHostTestCommit, runTest) { runTest(true); } // Verifies that startPageScaleAnimation events propagate correctly from LayerTreeHost to // LayerTreeHostImpl in the MT compositor. class LayerTreeHostTestStartPageScaleAnimation : public LayerTreeHostTest { public: LayerTreeHostTestStartPageScaleAnimation() : m_animationRequested(false) { } virtual void beginTest() OVERRIDE { m_layerTreeHost->root_layer()->SetScrollable(true); m_layerTreeHost->root_layer()->SetScrollOffset(gfx::Vector2d()); postSetNeedsCommitToMainThread(); postSetNeedsRedrawToMainThread(); } void requestStartPageScaleAnimation() { m_layerTreeHost->StartPageScaleAnimation(gfx::Vector2d(), false, 1.25, base::TimeDelta()); } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { impl->active_tree()->root_layer()->SetScrollable(true); impl->active_tree()->root_layer()->SetScrollOffset(gfx::Vector2d()); impl->active_tree()->SetPageScaleFactorAndLimits(impl->active_tree()->page_scale_factor(), 0.5, 2); // We request animation only once. if (!m_animationRequested) { impl->proxy()->MainThread()->postTask(base::Bind(&LayerTreeHostTestStartPageScaleAnimation::requestStartPageScaleAnimation, base::Unretained(this))); m_animationRequested = true; } } virtual void applyScrollAndScale(gfx::Vector2d scrollDelta, float scale) OVERRIDE { gfx::Vector2d offset = m_layerTreeHost->root_layer()->scroll_offset(); m_layerTreeHost->root_layer()->SetScrollOffset(offset + scrollDelta); m_layerTreeHost->SetPageScaleFactorAndLimits(scale, 0.5, 2); } virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { impl->ProcessScrollDeltas(); // We get one commit before the first draw, and the animation doesn't happen until the second draw. if (impl->active_tree()->source_frame_number() == 1) { EXPECT_EQ(1.25, impl->active_tree()->page_scale_factor()); endTest(); } else postSetNeedsRedrawToMainThread(); } virtual void afterTest() OVERRIDE { } private: bool m_animationRequested; }; // TODO(aelias): This test is currently broken: http://crbug.com/178295 TEST_F(LayerTreeHostTestStartPageScaleAnimation, DISABLED_runTest) { runTest(true); } class LayerTreeHostTestSetVisible : public LayerTreeHostTest { public: LayerTreeHostTestSetVisible() : m_numDraws(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); postSetVisibleToMainThread(false); postSetNeedsRedrawToMainThread(); // This is suppressed while we're invisible. postSetVisibleToMainThread(true); // Triggers the redraw. } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { EXPECT_TRUE(impl->visible()); ++m_numDraws; endTest(); } virtual void afterTest() OVERRIDE { EXPECT_EQ(1, m_numDraws); } private: int m_numDraws; }; TEST_F(LayerTreeHostTestSetVisible, runMultiThread) { runTest(true); } class TestOpacityChangeLayerDelegate : public ContentLayerClient { public: TestOpacityChangeLayerDelegate() : m_testLayer(0) { } void setTestLayer(Layer* testLayer) { m_testLayer = testLayer; } virtual void paintContents(SkCanvas*, const gfx::Rect&, gfx::RectF&) OVERRIDE { // Set layer opacity to 0. if (m_testLayer) m_testLayer->SetOpacity(0); } private: Layer* m_testLayer; }; class ContentLayerWithUpdateTracking : public ContentLayer { public: static scoped_refptr Create(ContentLayerClient* client) { return make_scoped_refptr(new ContentLayerWithUpdateTracking(client)); } int paintContentsCount() { return m_paintContentsCount; } void resetPaintContentsCount() { m_paintContentsCount = 0; } virtual void Update(ResourceUpdateQueue* queue, const OcclusionTracker* occlusion, RenderingStats* stats) OVERRIDE { ContentLayer::Update(queue, occlusion, stats); m_paintContentsCount++; } private: explicit ContentLayerWithUpdateTracking(ContentLayerClient* client) : ContentLayer(client) , m_paintContentsCount(0) { SetAnchorPoint(gfx::PointF(0, 0)); SetBounds(gfx::Size(10, 10)); SetIsDrawable(true); } virtual ~ContentLayerWithUpdateTracking() { } int m_paintContentsCount; }; // Layer opacity change during paint should not prevent compositor resources from being updated during commit. class LayerTreeHostTestOpacityChange : public LayerTreeHostTest { public: LayerTreeHostTestOpacityChange() : m_testOpacityChangeDelegate() , m_updateCheckLayer(ContentLayerWithUpdateTracking::Create(&m_testOpacityChangeDelegate)) { m_testOpacityChangeDelegate.setTestLayer(m_updateCheckLayer.get()); } virtual void beginTest() OVERRIDE { m_layerTreeHost->SetViewportSize(gfx::Size(10, 10), gfx::Size(10, 10)); m_layerTreeHost->root_layer()->AddChild(m_updateCheckLayer); postSetNeedsCommitToMainThread(); } virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE { endTest(); } virtual void afterTest() OVERRIDE { // Update() should have been called once. EXPECT_EQ(1, m_updateCheckLayer->paintContentsCount()); // clear m_updateCheckLayer so LayerTreeHost dies. m_updateCheckLayer = NULL; } private: TestOpacityChangeLayerDelegate m_testOpacityChangeDelegate; scoped_refptr m_updateCheckLayer; }; TEST_F(LayerTreeHostTestOpacityChange, runMultiThread) { runTest(true); } class NoScaleContentLayer : public ContentLayer { public: static scoped_refptr Create(ContentLayerClient* client) { return make_scoped_refptr(new NoScaleContentLayer(client)); } virtual void CalculateContentsScale( float ideal_contents_scale, bool animating_transform_to_screen, float* contents_scale_x, float* contents_scale_y, gfx::Size* contentBounds) OVERRIDE { // Skip over the ContentLayer's method to the base Layer class. Layer::CalculateContentsScale( ideal_contents_scale, animating_transform_to_screen, contents_scale_x, contents_scale_y, contentBounds); } private: explicit NoScaleContentLayer(ContentLayerClient* client) : ContentLayer(client) { } virtual ~NoScaleContentLayer() { } }; class LayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers : public LayerTreeHostTest { public: LayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers() : m_rootLayer(NoScaleContentLayer::Create(&m_client)) , m_childLayer(ContentLayer::Create(&m_client)) { } virtual void beginTest() OVERRIDE { m_layerTreeHost->SetViewportSize(gfx::Size(40, 40), gfx::Size(60, 60)); m_layerTreeHost->SetDeviceScaleFactor(1.5); EXPECT_EQ(gfx::Size(40, 40), m_layerTreeHost->layout_viewport_size()); EXPECT_EQ(gfx::Size(60, 60), m_layerTreeHost->device_viewport_size()); m_rootLayer->AddChild(m_childLayer); m_rootLayer->SetIsDrawable(true); m_rootLayer->SetBounds(gfx::Size(30, 30)); m_rootLayer->SetAnchorPoint(gfx::PointF(0, 0)); m_childLayer->SetIsDrawable(true); m_childLayer->SetPosition(gfx::Point(2, 2)); m_childLayer->SetBounds(gfx::Size(10, 10)); m_childLayer->SetAnchorPoint(gfx::PointF(0, 0)); m_layerTreeHost->SetRootLayer(m_rootLayer); ASSERT_TRUE(m_layerTreeHost->InitializeRendererIfNeeded()); ResourceUpdateQueue queue; m_layerTreeHost->UpdateLayers(&queue, std::numeric_limits::max()); postSetNeedsCommitToMainThread(); } virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { // Get access to protected methods. MockLayerTreeHostImpl* mockImpl = static_cast(impl); // Should only do one commit. EXPECT_EQ(0, impl->active_tree()->source_frame_number()); // Device scale factor should come over to impl. EXPECT_NEAR(impl->device_scale_factor(), 1.5, 0.00001); // Both layers are on impl. ASSERT_EQ(1u, impl->active_tree()->root_layer()->children().size()); // Device viewport is scaled. EXPECT_EQ(gfx::Size(40, 40), impl->layout_viewport_size()); EXPECT_EQ(gfx::Size(60, 60), impl->device_viewport_size()); LayerImpl* root = impl->active_tree()->root_layer(); LayerImpl* child = impl->active_tree()->root_layer()->children()[0]; // Positions remain in layout pixels. EXPECT_EQ(gfx::Point(0, 0), root->position()); EXPECT_EQ(gfx::Point(2, 2), child->position()); // Compute all the layer transforms for the frame. LayerTreeHostImpl::FrameData frameData; mockImpl->PrepareToDraw(&frameData); mockImpl->DidDrawAllLayers(frameData); const MockLayerTreeHostImpl::LayerList& renderSurfaceLayerList = *frameData.render_surface_layer_list; // Both layers should be drawing into the root render surface. ASSERT_EQ(1u, renderSurfaceLayerList.size()); ASSERT_EQ(root->render_surface(), renderSurfaceLayerList[0]->render_surface()); ASSERT_EQ(2u, root->render_surface()->layer_list().size()); // The root render surface is the size of the viewport. EXPECT_RECT_EQ(gfx::Rect(0, 0, 60, 60), root->render_surface()->content_rect()); // The content bounds of the child should be scaled. gfx::Size childBoundsScaled = gfx::ToCeiledSize(gfx::ScaleSize(child->bounds(), 1.5)); EXPECT_EQ(childBoundsScaled, child->content_bounds()); gfx::Transform scaleTransform; scaleTransform.Scale(impl->device_scale_factor(), impl->device_scale_factor()); // The root layer is scaled by 2x. gfx::Transform rootScreenSpaceTransform = scaleTransform; gfx::Transform rootDrawTransform = scaleTransform; EXPECT_EQ(rootDrawTransform, root->draw_transform()); EXPECT_EQ(rootScreenSpaceTransform, root->screen_space_transform()); // The child is at position 2,2, which is transformed to 3,3 after the scale gfx::Transform childScreenSpaceTransform; childScreenSpaceTransform.Translate(3, 3); gfx::Transform childDrawTransform = childScreenSpaceTransform; EXPECT_TRANSFORMATION_MATRIX_EQ(childDrawTransform, child->draw_transform()); EXPECT_TRANSFORMATION_MATRIX_EQ(childScreenSpaceTransform, child->screen_space_transform()); endTest(); } virtual void afterTest() OVERRIDE { m_rootLayer = NULL; m_childLayer = NULL; } private: FakeContentLayerClient m_client; scoped_refptr m_rootLayer; scoped_refptr m_childLayer; }; TEST_F(LayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers, runMultiThread) { runTest(true); } // Verify atomicity of commits and reuse of textures. class LayerTreeHostTestAtomicCommit : public LayerTreeHostTest { public: LayerTreeHostTestAtomicCommit() { // Make sure partial texture updates are turned off. m_settings.maxPartialTextureUpdates = 0; // Linear fade animator prevents scrollbars from drawing immediately. m_settings.useLinearFadeScrollbarAnimator = false; } virtual void setupTree() OVERRIDE { m_layer = FakeContentLayer::Create(&m_client); m_layer->SetBounds(gfx::Size(10, 20)); bool paint_scrollbar = true; bool has_thumb = false; m_scrollbar = FakeScrollbarLayer::Create( paint_scrollbar, has_thumb, m_layer->id()); m_scrollbar->SetPosition(gfx::Point(0, 10)); m_scrollbar->SetBounds(gfx::Size(10, 10)); m_layer->AddChild(m_scrollbar); m_layerTreeHost->SetRootLayer(m_layer); LayerTreeHostTest::setupTree(); } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { ASSERT_EQ(0u, m_layerTreeHost->settings().maxPartialTextureUpdates); TestWebGraphicsContext3D* context = static_cast(impl->output_surface()->context3d()); switch (impl->active_tree()->source_frame_number()) { case 0: // Number of textures should be one for each layer ASSERT_EQ(2, context->NumTextures()); // Number of textures used for commit should be one for each layer. EXPECT_EQ(2, context->NumUsedTextures()); // Verify that used texture is correct. EXPECT_TRUE(context->UsedTexture(context->TextureAt(0))); EXPECT_TRUE(context->UsedTexture(context->TextureAt(1))); context->ResetUsedTextures(); postSetNeedsCommitToMainThread(); break; case 1: // Number of textures should be doubled as the first textures // are used by impl thread and cannot by used for update. ASSERT_EQ(4, context->NumTextures()); // Number of textures used for commit should still be one for each layer. EXPECT_EQ(2, context->NumUsedTextures()); // First textures should not have been used. EXPECT_FALSE(context->UsedTexture(context->TextureAt(0))); EXPECT_FALSE(context->UsedTexture(context->TextureAt(1))); // New textures should have been used. EXPECT_TRUE(context->UsedTexture(context->TextureAt(2))); EXPECT_TRUE(context->UsedTexture(context->TextureAt(3))); context->ResetUsedTextures(); postSetNeedsCommitToMainThread(); break; case 2: endTest(); break; default: NOTREACHED(); break; } } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { TestWebGraphicsContext3D* context = static_cast(impl->output_surface()->context3d()); // Number of textures used for draw should always be one for each layer. EXPECT_EQ(2, context->NumUsedTextures()); context->ResetUsedTextures(); } virtual void layout() OVERRIDE { m_layer->SetNeedsDisplay(); m_scrollbar->SetNeedsDisplay(); } virtual void afterTest() OVERRIDE { } private: FakeContentLayerClient m_client; scoped_refptr m_layer; scoped_refptr m_scrollbar; }; TEST_F(LayerTreeHostTestAtomicCommit, runMultiThread) { runTest(true); } static void setLayerPropertiesForTesting(Layer* layer, Layer* parent, const gfx::Transform& transform, const gfx::PointF& anchor, const gfx::PointF& position, const gfx::Size& bounds, bool opaque) { layer->RemoveAllChildren(); if (parent) parent->AddChild(layer); layer->SetTransform(transform); layer->SetAnchorPoint(anchor); layer->SetPosition(position); layer->SetBounds(bounds); layer->SetContentsOpaque(opaque); } class LayerTreeHostTestAtomicCommitWithPartialUpdate : public LayerTreeHostTest { public: LayerTreeHostTestAtomicCommitWithPartialUpdate() : m_numCommits(0) { // Allow one partial texture update. m_settings.maxPartialTextureUpdates = 1; // Linear fade animator prevents scrollbars from drawing immediately. m_settings.useLinearFadeScrollbarAnimator = false; } virtual void setupTree() OVERRIDE { parent_ = FakeContentLayer::Create(&m_client); parent_->SetBounds(gfx::Size(10, 20)); m_child = FakeContentLayer::Create(&m_client); m_child->SetPosition(gfx::Point(0, 10)); m_child->SetBounds(gfx::Size(3, 10)); bool paint_scrollbar = true; bool has_thumb = false; m_scrollbarWithPaints = FakeScrollbarLayer::Create( paint_scrollbar, has_thumb, parent_->id()); m_scrollbarWithPaints->SetPosition(gfx::Point(3, 10)); m_scrollbarWithPaints->SetBounds(gfx::Size(3, 10)); paint_scrollbar = false; m_scrollbarWithoutPaints = FakeScrollbarLayer::Create( paint_scrollbar, has_thumb, parent_->id()); m_scrollbarWithoutPaints->SetPosition(gfx::Point(6, 10)); m_scrollbarWithoutPaints->SetBounds(gfx::Size(3, 10)); parent_->AddChild(m_child); parent_->AddChild(m_scrollbarWithPaints); parent_->AddChild(m_scrollbarWithoutPaints); m_layerTreeHost->SetRootLayer(parent_); LayerTreeHostTest::setupTree(); } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { ASSERT_EQ(1u, m_layerTreeHost->settings().maxPartialTextureUpdates); TestWebGraphicsContext3D* context = static_cast(impl->output_surface()->context3d()); switch (impl->active_tree()->source_frame_number()) { case 0: // Number of textures should be one for each layer. ASSERT_EQ(4, context->NumTextures()); // Number of textures used for commit should be one for each layer. EXPECT_EQ(4, context->NumUsedTextures()); // Verify that used textures are correct. EXPECT_TRUE(context->UsedTexture(context->TextureAt(0))); EXPECT_TRUE(context->UsedTexture(context->TextureAt(1))); EXPECT_TRUE(context->UsedTexture(context->TextureAt(2))); EXPECT_TRUE(context->UsedTexture(context->TextureAt(3))); context->ResetUsedTextures(); postSetNeedsCommitToMainThread(); break; case 1: // Number of textures should be two for each content layer and one // for each scrollbar, since they always do a partial update. ASSERT_EQ(6, context->NumTextures()); // Number of textures used for commit should be one for each content // layer, and one for the scrollbar layer that paints. EXPECT_EQ(3, context->NumUsedTextures()); // First content textures should not have been used. EXPECT_FALSE(context->UsedTexture(context->TextureAt(0))); EXPECT_FALSE(context->UsedTexture(context->TextureAt(1))); // The non-painting scrollbar's texture wasn't updated. EXPECT_FALSE(context->UsedTexture(context->TextureAt(2))); // The painting scrollbar's partial update texture was used. EXPECT_TRUE(context->UsedTexture(context->TextureAt(3))); // New textures should have been used. EXPECT_TRUE(context->UsedTexture(context->TextureAt(4))); EXPECT_TRUE(context->UsedTexture(context->TextureAt(5))); context->ResetUsedTextures(); postSetNeedsCommitToMainThread(); break; case 2: // Number of textures should be two for each content layer and one // for each scrollbar, since they always do a partial update. ASSERT_EQ(6, context->NumTextures()); // Number of textures used for commit should be one for each content // layer, and one for the scrollbar layer that paints. EXPECT_EQ(3, context->NumUsedTextures()); // The non-painting scrollbar's texture wasn't updated. EXPECT_FALSE(context->UsedTexture(context->TextureAt(2))); // The painting scrollbar does a partial update. EXPECT_TRUE(context->UsedTexture(context->TextureAt(3))); // One content layer does a partial update also. EXPECT_TRUE(context->UsedTexture(context->TextureAt(4))); EXPECT_FALSE(context->UsedTexture(context->TextureAt(5))); context->ResetUsedTextures(); postSetNeedsCommitToMainThread(); break; case 3: // No textures should be used for commit. EXPECT_EQ(0, context->NumUsedTextures()); context->ResetUsedTextures(); postSetNeedsCommitToMainThread(); break; case 4: // Number of textures used for commit should be two. One for the // content layer, and one for the painting scrollbar. The // non-painting scrollbar doesn't update its texture. EXPECT_EQ(2, context->NumUsedTextures()); context->ResetUsedTextures(); postSetNeedsCommitToMainThread(); break; case 5: endTest(); break; default: NOTREACHED(); break; } } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { TestWebGraphicsContext3D* context = static_cast(impl->output_surface()->context3d()); // Number of textures used for drawing should one per layer except for // frame 3 where the viewport only contains one layer. if (impl->active_tree()->source_frame_number() == 3) EXPECT_EQ(1, context->NumUsedTextures()); else EXPECT_EQ(4, context->NumUsedTextures()); context->ResetUsedTextures(); } virtual void layout() OVERRIDE { switch (m_numCommits++) { case 0: case 1: parent_->SetNeedsDisplay(); m_child->SetNeedsDisplay(); m_scrollbarWithPaints->SetNeedsDisplay(); m_scrollbarWithoutPaints->SetNeedsDisplay(); break; case 2: // Damage part of layers. parent_->SetNeedsDisplayRect(gfx::RectF(0, 0, 5, 5)); m_child->SetNeedsDisplayRect(gfx::RectF(0, 0, 5, 5)); m_scrollbarWithPaints->SetNeedsDisplayRect(gfx::RectF(0, 0, 5, 5)); m_scrollbarWithoutPaints->SetNeedsDisplayRect(gfx::RectF(0, 0, 5, 5)); break; case 3: m_child->SetNeedsDisplay(); m_scrollbarWithPaints->SetNeedsDisplay(); m_scrollbarWithoutPaints->SetNeedsDisplay(); m_layerTreeHost->SetViewportSize(gfx::Size(10, 10), gfx::Size(10, 10)); break; case 4: m_layerTreeHost->SetViewportSize(gfx::Size(10, 20), gfx::Size(10, 20)); break; case 5: break; default: NOTREACHED(); break; } } virtual void afterTest() OVERRIDE { } private: FakeContentLayerClient m_client; scoped_refptr parent_; scoped_refptr m_child; scoped_refptr m_scrollbarWithPaints; scoped_refptr m_scrollbarWithoutPaints; int m_numCommits; }; TEST_F(LayerTreeHostTestAtomicCommitWithPartialUpdate, runMultiThread) { runTest(true); } class LayerTreeHostTestFinishAllRendering : public LayerTreeHostTest { public: LayerTreeHostTestFinishAllRendering() : m_once(false) , m_drawCount(0) { } virtual void beginTest() OVERRIDE { m_layerTreeHost->SetNeedsRedraw(); postSetNeedsCommitToMainThread(); } virtual void didCommitAndDrawFrame() OVERRIDE { if (m_once) return; m_once = true; m_layerTreeHost->SetNeedsRedraw(); m_layerTreeHost->AcquireLayerTextures(); { base::AutoLock lock(m_lock); m_drawCount = 0; } m_layerTreeHost->FinishAllRendering(); { base::AutoLock lock(m_lock); EXPECT_EQ(0, m_drawCount); } endTest(); } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { base::AutoLock lock(m_lock); ++m_drawCount; } virtual void afterTest() OVERRIDE { } private: bool m_once; base::Lock m_lock; int m_drawCount; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestFinishAllRendering) class LayerTreeHostTestCompositeAndReadbackCleanup : public LayerTreeHostTest { public: LayerTreeHostTestCompositeAndReadbackCleanup() { } virtual void beginTest() OVERRIDE { Layer* rootLayer = m_layerTreeHost->root_layer(); char pixels[4]; m_layerTreeHost->CompositeAndReadback(static_cast(&pixels), gfx::Rect(0, 0, 1, 1)); EXPECT_FALSE(rootLayer->render_surface()); endTest(); } virtual void afterTest() OVERRIDE { } }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestCompositeAndReadbackCleanup) class LayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit : public LayerTreeHostTest { public: LayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit() : m_rootLayer(ContentLayerWithUpdateTracking::Create(&m_fakeDelegate)) , m_surfaceLayer1(ContentLayerWithUpdateTracking::Create(&m_fakeDelegate)) , m_replicaLayer1(ContentLayerWithUpdateTracking::Create(&m_fakeDelegate)) , m_surfaceLayer2(ContentLayerWithUpdateTracking::Create(&m_fakeDelegate)) , m_replicaLayer2(ContentLayerWithUpdateTracking::Create(&m_fakeDelegate)) { } virtual void initializeSettings(LayerTreeSettings& settings) OVERRIDE { settings.cacheRenderPassContents = true; } virtual void beginTest() OVERRIDE { m_layerTreeHost->SetViewportSize(gfx::Size(100, 100), gfx::Size(100, 100)); m_rootLayer->SetBounds(gfx::Size(100, 100)); m_surfaceLayer1->SetBounds(gfx::Size(100, 100)); m_surfaceLayer1->SetForceRenderSurface(true); m_surfaceLayer1->SetOpacity(0.5); m_surfaceLayer2->SetBounds(gfx::Size(100, 100)); m_surfaceLayer2->SetForceRenderSurface(true); m_surfaceLayer2->SetOpacity(0.5); m_surfaceLayer1->SetReplicaLayer(m_replicaLayer1.get()); m_surfaceLayer2->SetReplicaLayer(m_replicaLayer2.get()); m_rootLayer->AddChild(m_surfaceLayer1); m_surfaceLayer1->AddChild(m_surfaceLayer2); m_layerTreeHost->SetRootLayer(m_rootLayer); postSetNeedsCommitToMainThread(); } virtual void drawLayersOnThread(LayerTreeHostImpl* hostImpl) OVERRIDE { Renderer* renderer = hostImpl->renderer(); RenderPass::Id surface1RenderPassId = hostImpl->active_tree()->root_layer()->children()[0]->render_surface()->RenderPassId(); RenderPass::Id surface2RenderPassId = hostImpl->active_tree()->root_layer()->children()[0]->children()[0]->render_surface()->RenderPassId(); switch (hostImpl->active_tree()->source_frame_number()) { case 0: EXPECT_TRUE(renderer->HaveCachedResourcesForRenderPassId(surface1RenderPassId)); EXPECT_TRUE(renderer->HaveCachedResourcesForRenderPassId(surface2RenderPassId)); // Reduce the memory limit to only fit the root layer and one render surface. This // prevents any contents drawing into surfaces from being allocated. hostImpl->SetManagedMemoryPolicy(ManagedMemoryPolicy(100 * 100 * 4 * 2)); break; case 1: EXPECT_FALSE(renderer->HaveCachedResourcesForRenderPassId(surface1RenderPassId)); EXPECT_FALSE(renderer->HaveCachedResourcesForRenderPassId(surface2RenderPassId)); endTest(); break; } } virtual void afterTest() OVERRIDE { EXPECT_EQ(2, m_rootLayer->paintContentsCount()); EXPECT_EQ(2, m_surfaceLayer1->paintContentsCount()); EXPECT_EQ(2, m_surfaceLayer2->paintContentsCount()); // Clear layer references so LayerTreeHost dies. m_rootLayer = NULL; m_surfaceLayer1 = NULL; m_replicaLayer1 = NULL; m_surfaceLayer2 = NULL; m_replicaLayer2 = NULL; } private: FakeContentLayerClient m_fakeDelegate; scoped_refptr m_rootLayer; scoped_refptr m_surfaceLayer1; scoped_refptr m_replicaLayer1; scoped_refptr m_surfaceLayer2; scoped_refptr m_replicaLayer2; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit) class EvictionTestLayer : public Layer { public: static scoped_refptr Create() { return make_scoped_refptr(new EvictionTestLayer()); } virtual void Update(ResourceUpdateQueue*, const OcclusionTracker*, RenderingStats*) OVERRIDE; virtual bool DrawsContent() const OVERRIDE { return true; } virtual scoped_ptr CreateLayerImpl(LayerTreeImpl* treeImpl) OVERRIDE; virtual void PushPropertiesTo(LayerImpl*) OVERRIDE; virtual void SetTexturePriorities(const PriorityCalculator&) OVERRIDE; bool haveBackingTexture() const { return m_texture.get() ? m_texture->haveBackingTexture() : false; } private: EvictionTestLayer() : Layer() { } virtual ~EvictionTestLayer() { } void createTextureIfNeeded() { if (m_texture.get()) return; m_texture = PrioritizedResource::create(layer_tree_host()->contents_texture_manager()); m_texture->setDimensions(gfx::Size(10, 10), GL_RGBA); m_bitmap.setConfig(SkBitmap::kARGB_8888_Config, 10, 10); } scoped_ptr m_texture; SkBitmap m_bitmap; }; class EvictionTestLayerImpl : public LayerImpl { public: static scoped_ptr Create(LayerTreeImpl* treeImpl, int id) { return make_scoped_ptr(new EvictionTestLayerImpl(treeImpl, id)); } virtual ~EvictionTestLayerImpl() { } virtual void AppendQuads(QuadSink* quad_sink, AppendQuadsData* append_quads_data) OVERRIDE { ASSERT_TRUE(m_hasTexture); ASSERT_NE(0u, layer_tree_impl()->resource_provider()->num_resources()); } void setHasTexture(bool hasTexture) { m_hasTexture = hasTexture; } private: EvictionTestLayerImpl(LayerTreeImpl* treeImpl, int id) : LayerImpl(treeImpl, id) , m_hasTexture(false) { } bool m_hasTexture; }; void EvictionTestLayer::SetTexturePriorities(const PriorityCalculator&) { createTextureIfNeeded(); if (!m_texture.get()) return; m_texture->setRequestPriority(PriorityCalculator::uiPriority(true)); } void EvictionTestLayer::Update(ResourceUpdateQueue* queue, const OcclusionTracker*, RenderingStats*) { createTextureIfNeeded(); if (!m_texture.get()) return; gfx::Rect fullRect(0, 0, 10, 10); ResourceUpdate upload = ResourceUpdate::Create( m_texture.get(), &m_bitmap, fullRect, fullRect, gfx::Vector2d()); queue->appendFullUpload(upload); } scoped_ptr EvictionTestLayer::CreateLayerImpl(LayerTreeImpl* treeImpl) { return EvictionTestLayerImpl::Create(treeImpl, layer_id_).PassAs(); } void EvictionTestLayer::PushPropertiesTo(LayerImpl* layerImpl) { Layer::PushPropertiesTo(layerImpl); EvictionTestLayerImpl* testLayerImpl = static_cast(layerImpl); testLayerImpl->setHasTexture(m_texture->haveBackingTexture()); } class LayerTreeHostTestEvictTextures : public LayerTreeHostTest { public: LayerTreeHostTestEvictTextures() : m_layer(EvictionTestLayer::Create()) , m_implForEvictTextures(0) , m_numCommits(0) { } virtual void beginTest() OVERRIDE { m_layerTreeHost->SetRootLayer(m_layer); m_layerTreeHost->SetViewportSize(gfx::Size(10, 20), gfx::Size(10, 20)); gfx::Transform identityMatrix; setLayerPropertiesForTesting(m_layer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(10, 20), true); postSetNeedsCommitToMainThread(); } void postEvictTextures() { DCHECK(ImplThread()); ImplThread()->postTask(base::Bind(&LayerTreeHostTestEvictTextures::evictTexturesOnImplThread, base::Unretained(this))); } void evictTexturesOnImplThread() { DCHECK(m_implForEvictTextures); m_implForEvictTextures->EnforceManagedMemoryPolicy(ManagedMemoryPolicy(0)); } // Commit 1: Just commit and draw normally, then post an eviction at the end // that will trigger a commit. // Commit 2: Triggered by the eviction, let it go through and then set // needsCommit. // Commit 3: Triggered by the setNeedsCommit. In layout(), post an eviction // task, which will be handled before the commit. Don't set needsCommit, it // should have been posted. A frame should not be drawn (note, // didCommitAndDrawFrame may be called anyway). // Commit 4: Triggered by the eviction, let it go through and then set // needsCommit. // Commit 5: Triggered by the setNeedsCommit, post an eviction task in // layout(), a frame should not be drawn but a commit will be posted. // Commit 6: Triggered by the eviction, post an eviction task in // layout(), which will be a noop, letting the commit (which recreates the // textures) go through and draw a frame, then end the test. // // Commits 1+2 test the eviction recovery path where eviction happens outside // of the beginFrame/commit pair. // Commits 3+4 test the eviction recovery path where eviction happens inside // the beginFrame/commit pair. // Commits 5+6 test the path where an eviction happens during the eviction // recovery path. virtual void didCommitAndDrawFrame() OVERRIDE { switch (m_numCommits) { case 1: EXPECT_TRUE(m_layer->haveBackingTexture()); postEvictTextures(); break; case 2: EXPECT_TRUE(m_layer->haveBackingTexture()); m_layerTreeHost->SetNeedsCommit(); break; case 3: break; case 4: EXPECT_TRUE(m_layer->haveBackingTexture()); m_layerTreeHost->SetNeedsCommit(); break; case 5: break; case 6: EXPECT_TRUE(m_layer->haveBackingTexture()); endTest(); break; default: NOTREACHED(); break; } } virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { m_implForEvictTextures = impl; } virtual void layout() OVERRIDE { ++m_numCommits; switch (m_numCommits) { case 1: case 2: break; case 3: postEvictTextures(); break; case 4: // We couldn't check in didCommitAndDrawFrame on commit 3, so check here. EXPECT_FALSE(m_layer->haveBackingTexture()); break; case 5: postEvictTextures(); break; case 6: // We couldn't check in didCommitAndDrawFrame on commit 5, so check here. EXPECT_FALSE(m_layer->haveBackingTexture()); postEvictTextures(); break; default: NOTREACHED(); break; } } virtual void afterTest() OVERRIDE { } private: FakeContentLayerClient m_client; scoped_refptr m_layer; LayerTreeHostImpl* m_implForEvictTextures; int m_numCommits; }; TEST_F(LayerTreeHostTestEvictTextures, runMultiThread) { runTest(true); } class LayerTreeHostTestContinuousCommit : public LayerTreeHostTest { public: LayerTreeHostTestContinuousCommit() : m_numCommitComplete(0) , m_numDrawLayers(0) { } virtual void beginTest() OVERRIDE { m_layerTreeHost->SetViewportSize(gfx::Size(10, 10), gfx::Size(10, 10)); m_layerTreeHost->root_layer()->SetBounds(gfx::Size(10, 10)); postSetNeedsCommitToMainThread(); } virtual void didCommit() OVERRIDE { if (m_numDrawLayers == 2) return; postSetNeedsCommitToMainThread(); } virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE { if (m_numDrawLayers == 1) m_numCommitComplete++; } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { m_numDrawLayers++; if (m_numDrawLayers == 2) endTest(); } virtual void afterTest() OVERRIDE { // Check that we didn't commit twice between first and second draw. EXPECT_EQ(1, m_numCommitComplete); } private: int m_numCommitComplete; int m_numDrawLayers; }; TEST_F(LayerTreeHostTestContinuousCommit, runMultiThread) { runTest(true); } class LayerTreeHostTestContinuousInvalidate : public LayerTreeHostTest { public: LayerTreeHostTestContinuousInvalidate() : m_numCommitComplete(0) , m_numDrawLayers(0) { } virtual void beginTest() OVERRIDE { m_layerTreeHost->SetViewportSize(gfx::Size(10, 10), gfx::Size(10, 10)); m_layerTreeHost->root_layer()->SetBounds(gfx::Size(10, 10)); m_contentLayer = ContentLayer::Create(&m_fakeDelegate); m_contentLayer->SetBounds(gfx::Size(10, 10)); m_contentLayer->SetPosition(gfx::PointF(0, 0)); m_contentLayer->SetAnchorPoint(gfx::PointF(0, 0)); m_contentLayer->SetIsDrawable(true); m_layerTreeHost->root_layer()->AddChild(m_contentLayer); postSetNeedsCommitToMainThread(); } virtual void didCommit() OVERRIDE { if (m_numDrawLayers == 2) return; m_contentLayer->SetNeedsDisplay(); } virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE { if (m_numDrawLayers == 1) m_numCommitComplete++; } virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { m_numDrawLayers++; if (m_numDrawLayers == 2) endTest(); } virtual void afterTest() OVERRIDE { // Check that we didn't commit twice between first and second draw. EXPECT_EQ(1, m_numCommitComplete); // Clear layer references so LayerTreeHost dies. m_contentLayer = NULL; } private: FakeContentLayerClient m_fakeDelegate; scoped_refptr m_contentLayer; int m_numCommitComplete; int m_numDrawLayers; }; TEST_F(LayerTreeHostTestContinuousInvalidate, runMultiThread) { runTest(true); } class LayerTreeHostTestDeferCommits : public LayerTreeHostTest { public: LayerTreeHostTestDeferCommits() : m_numCommitsDeferred(0) , m_numCompleteCommits(0) { } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void didDeferCommit() OVERRIDE { m_numCommitsDeferred++; m_layerTreeHost->SetDeferCommits(false); } virtual void didCommit() OVERRIDE { m_numCompleteCommits++; switch (m_numCompleteCommits) { case 1: EXPECT_EQ(0, m_numCommitsDeferred); m_layerTreeHost->SetDeferCommits(true); postSetNeedsCommitToMainThread(); break; case 2: endTest(); break; default: NOTREACHED(); break; } } virtual void afterTest() OVERRIDE { EXPECT_EQ(1, m_numCommitsDeferred); EXPECT_EQ(2, m_numCompleteCommits); } private: int m_numCommitsDeferred; int m_numCompleteCommits; }; TEST_F(LayerTreeHostTestDeferCommits, runMultiThread) { runTest(true); } class LayerTreeHostWithProxy : public LayerTreeHost { public: LayerTreeHostWithProxy(FakeLayerImplTreeHostClient* client, const LayerTreeSettings& settings, scoped_ptr proxy) : LayerTreeHost(client, settings) { EXPECT_TRUE(InitializeForTesting(proxy.Pass())); } private: FakeLayerImplTreeHostClient m_client; }; TEST(LayerTreeHostTest, LimitPartialUpdates) { // When partial updates are not allowed, max updates should be 0. { FakeLayerImplTreeHostClient client; scoped_ptr proxy = make_scoped_ptr(new FakeProxy(scoped_ptr())); proxy->GetRendererCapabilities().allow_partial_texture_updates = false; proxy->SetMaxPartialTextureUpdates(5); LayerTreeSettings settings; settings.maxPartialTextureUpdates = 10; LayerTreeHostWithProxy host(&client, settings, proxy.PassAs()); EXPECT_TRUE(host.InitializeRendererIfNeeded()); EXPECT_EQ(0u, host.settings().maxPartialTextureUpdates); } // When partial updates are allowed, max updates should be limited by the proxy. { FakeLayerImplTreeHostClient client; scoped_ptr proxy = make_scoped_ptr(new FakeProxy(scoped_ptr())); proxy->GetRendererCapabilities().allow_partial_texture_updates = true; proxy->SetMaxPartialTextureUpdates(5); LayerTreeSettings settings; settings.maxPartialTextureUpdates = 10; LayerTreeHostWithProxy host(&client, settings, proxy.PassAs()); EXPECT_TRUE(host.InitializeRendererIfNeeded()); EXPECT_EQ(5u, host.settings().maxPartialTextureUpdates); } // When partial updates are allowed, max updates should also be limited by the settings. { FakeLayerImplTreeHostClient client; scoped_ptr proxy = make_scoped_ptr(new FakeProxy(scoped_ptr())); proxy->GetRendererCapabilities().allow_partial_texture_updates = true; proxy->SetMaxPartialTextureUpdates(20); LayerTreeSettings settings; settings.maxPartialTextureUpdates = 10; LayerTreeHostWithProxy host(&client, settings, proxy.PassAs()); EXPECT_TRUE(host.InitializeRendererIfNeeded()); EXPECT_EQ(10u, host.settings().maxPartialTextureUpdates); } } TEST(LayerTreeHostTest, PartialUpdatesWithGLRenderer) { bool useSoftwareRendering = false; bool useDelegatingRenderer = false; FakeLayerImplTreeHostClient client(useSoftwareRendering, useDelegatingRenderer); LayerTreeSettings settings; settings.maxPartialTextureUpdates = 4; scoped_ptr host = LayerTreeHost::Create(&client, settings, scoped_ptr()); EXPECT_TRUE(host->InitializeRendererIfNeeded()); EXPECT_EQ(4u, host->settings().maxPartialTextureUpdates); } TEST(LayerTreeHostTest, PartialUpdatesWithSoftwareRenderer) { bool useSoftwareRendering = true; bool useDelegatingRenderer = false; FakeLayerImplTreeHostClient client(useSoftwareRendering, useDelegatingRenderer); LayerTreeSettings settings; settings.maxPartialTextureUpdates = 4; scoped_ptr host = LayerTreeHost::Create(&client, settings, scoped_ptr()); EXPECT_TRUE(host->InitializeRendererIfNeeded()); EXPECT_EQ(4u, host->settings().maxPartialTextureUpdates); } TEST(LayerTreeHostTest, PartialUpdatesWithDelegatingRendererAndGLContent) { bool useSoftwareRendering = false; bool useDelegatingRenderer = true; FakeLayerImplTreeHostClient client(useSoftwareRendering, useDelegatingRenderer); LayerTreeSettings settings; settings.maxPartialTextureUpdates = 4; scoped_ptr host = LayerTreeHost::Create(&client, settings, scoped_ptr()); EXPECT_TRUE(host->InitializeRendererIfNeeded()); EXPECT_EQ(0u, host->settings().maxPartialTextureUpdates); } TEST(LayerTreeHostTest, PartialUpdatesWithDelegatingRendererAndSoftwareContent) { bool useSoftwareRendering = true; bool useDelegatingRenderer = true; FakeLayerImplTreeHostClient client(useSoftwareRendering, useDelegatingRenderer); LayerTreeSettings settings; settings.maxPartialTextureUpdates = 4; scoped_ptr host = LayerTreeHost::Create(&client, settings, scoped_ptr()); EXPECT_TRUE(host->InitializeRendererIfNeeded()); EXPECT_EQ(0u, host->settings().maxPartialTextureUpdates); } class LayerTreeHostTestCapturePicture : public LayerTreeHostTest { public: LayerTreeHostTestCapturePicture() : bounds_(gfx::Size(100, 100)) , m_layer(PictureLayer::Create(&m_contentClient)) { m_settings.implSidePainting = true; } class FillRectContentLayerClient : public ContentLayerClient { public: virtual void paintContents(SkCanvas* canvas, const gfx::Rect& clip, gfx::RectF& opaque) OVERRIDE { SkPaint paint; paint.setColor(SK_ColorGREEN); SkRect rect = SkRect::MakeWH(canvas->getDeviceSize().width(), canvas->getDeviceSize().height()); opaque = gfx::RectF(rect.width(), rect.height()); canvas->drawRect(rect, paint); } }; virtual void beginTest() OVERRIDE { m_layer->SetIsDrawable(true); m_layer->SetBounds(bounds_); m_layerTreeHost->SetViewportSize(bounds_, bounds_); m_layerTreeHost->SetRootLayer(m_layer); EXPECT_TRUE(m_layerTreeHost->InitializeRendererIfNeeded()); postSetNeedsCommitToMainThread(); } virtual void didCommitAndDrawFrame() OVERRIDE { m_picture = m_layerTreeHost->CapturePicture(); endTest(); } virtual void afterTest() OVERRIDE { EXPECT_EQ(bounds_, gfx::Size(m_picture->width(), m_picture->height())); SkBitmap bitmap; bitmap.setConfig(SkBitmap::kARGB_8888_Config, bounds_.width(), bounds_.height()); bitmap.allocPixels(); bitmap.eraseARGB(0, 0, 0, 0); SkCanvas canvas(bitmap); m_picture->draw(&canvas); bitmap.lockPixels(); SkColor* pixels = reinterpret_cast(bitmap.getPixels()); EXPECT_EQ(SK_ColorGREEN, pixels[0]); bitmap.unlockPixels(); } private: gfx::Size bounds_; FillRectContentLayerClient m_contentClient; scoped_refptr m_layer; skia::RefPtr m_picture; }; MULTI_THREAD_TEST_F(LayerTreeHostTestCapturePicture); class LayerTreeHostTestMaxPendingFrames : public LayerTreeHostTest { public: LayerTreeHostTestMaxPendingFrames() : LayerTreeHostTest() { } virtual scoped_ptr createOutputSurface() OVERRIDE { if (m_delegatingRenderer) return FakeOutputSurface::CreateDelegating3d().PassAs(); return FakeOutputSurface::Create3d().PassAs(); } virtual void beginTest() OVERRIDE { postSetNeedsCommitToMainThread(); } virtual void drawLayersOnThread(LayerTreeHostImpl* hostImpl) OVERRIDE { DCHECK(hostImpl->proxy()->HasImplThread()); const ThreadProxy* proxy = static_cast(hostImpl->proxy()); if (m_delegatingRenderer) EXPECT_EQ(1, proxy->MaxFramesPendingForTesting()); else EXPECT_EQ(FrameRateController::kDefaultMaxFramesPending, proxy->MaxFramesPendingForTesting()); endTest(); } virtual void afterTest() OVERRIDE { } protected: bool m_delegatingRenderer; }; TEST_F(LayerTreeHostTestMaxPendingFrames, DelegatingRenderer) { m_delegatingRenderer = true; runTest(true); } TEST_F(LayerTreeHostTestMaxPendingFrames, GLRenderer) { m_delegatingRenderer = false; runTest(true); } class LayerTreeHostTestShutdownWithOnlySomeResourcesEvicted : public LayerTreeHostTest { public: LayerTreeHostTestShutdownWithOnlySomeResourcesEvicted() : m_rootLayer(FakeContentLayer::Create(&m_client)) , m_childLayer1(FakeContentLayer::Create(&m_client)) , m_childLayer2(FakeContentLayer::Create(&m_client)) , m_numCommits(0) { } virtual void beginTest() OVERRIDE { m_layerTreeHost->SetViewportSize(gfx::Size(100, 100), gfx::Size(100, 100)); m_rootLayer->SetBounds(gfx::Size(100, 100)); m_childLayer1->SetBounds(gfx::Size(100, 100)); m_childLayer2->SetBounds(gfx::Size(100, 100)); m_rootLayer->AddChild(m_childLayer1); m_rootLayer->AddChild(m_childLayer2); m_layerTreeHost->SetRootLayer(m_rootLayer); postSetNeedsCommitToMainThread(); } virtual void didSetVisibleOnImplTree(LayerTreeHostImpl* hostImpl, bool visible) OVERRIDE { // One backing should remain unevicted. EXPECT_EQ( 100 * 100 * 4 * 1, m_layerTreeHost->contents_texture_manager()->memoryUseBytes()); // Make sure that contents textures are marked as having been // purged. EXPECT_TRUE(hostImpl->active_tree()->ContentsTexturesPurged()); // End the test in this state. endTest(); } virtual void commitCompleteOnThread(LayerTreeHostImpl* hostImpl) OVERRIDE { ++m_numCommits; switch(m_numCommits) { case 1: // All three backings should have memory. EXPECT_EQ( 100 * 100 * 4 * 3, m_layerTreeHost->contents_texture_manager()->memoryUseBytes()); // Set a new policy that will kick out 1 of the 3 resources. // Because a resource was evicted, a commit will be kicked off. hostImpl->SetManagedMemoryPolicy(ManagedMemoryPolicy( 100 * 100 * 4 * 2, ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING, 100 * 100 * 4 * 1, ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING)); break; case 2: // Only two backings should have memory. EXPECT_EQ( 100 * 100 * 4 * 2, m_layerTreeHost->contents_texture_manager()->memoryUseBytes()); // Become backgrounded, which will cause 1 more resource to be // evicted. postSetVisibleToMainThread(false); break; default: // No further commits should happen because this is not visible // anymore. NOTREACHED(); break; } } virtual void afterTest() OVERRIDE { } private: FakeContentLayerClient m_client; scoped_refptr m_rootLayer; scoped_refptr m_childLayer1; scoped_refptr m_childLayer2; int m_numCommits; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestShutdownWithOnlySomeResourcesEvicted) } // namespace } // namespace cc