diff options
Diffstat (limited to 'chromium/cc/trees/layer_tree_host_unittest_context.cc')
-rw-r--r-- | chromium/cc/trees/layer_tree_host_unittest_context.cc | 1905 |
1 files changed, 1905 insertions, 0 deletions
diff --git a/chromium/cc/trees/layer_tree_host_unittest_context.cc b/chromium/cc/trees/layer_tree_host_unittest_context.cc new file mode 100644 index 00000000000..3404c1ea9df --- /dev/null +++ b/chromium/cc/trees/layer_tree_host_unittest_context.cc @@ -0,0 +1,1905 @@ +// 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/basictypes.h" +#include "cc/debug/test_context_provider.h" +#include "cc/debug/test_web_graphics_context_3d.h" +#include "cc/layers/content_layer.h" +#include "cc/layers/heads_up_display_layer.h" +#include "cc/layers/io_surface_layer.h" +#include "cc/layers/layer_impl.h" +#include "cc/layers/picture_layer.h" +#include "cc/layers/scrollbar_layer.h" +#include "cc/layers/texture_layer.h" +#include "cc/layers/texture_layer_impl.h" +#include "cc/layers/video_layer.h" +#include "cc/layers/video_layer_impl.h" +#include "cc/output/filter_operations.h" +#include "cc/test/fake_content_layer.h" +#include "cc/test/fake_content_layer_client.h" +#include "cc/test/fake_content_layer_impl.h" +#include "cc/test/fake_delegated_renderer_layer.h" +#include "cc/test/fake_delegated_renderer_layer_impl.h" +#include "cc/test/fake_layer_tree_host_client.h" +#include "cc/test/fake_output_surface.h" +#include "cc/test/fake_scoped_ui_resource.h" +#include "cc/test/fake_scrollbar.h" +#include "cc/test/fake_scrollbar_layer.h" +#include "cc/test/fake_video_frame_provider.h" +#include "cc/test/layer_tree_test.h" +#include "cc/test/render_pass_test_common.h" +#include "cc/trees/layer_tree_host_impl.h" +#include "cc/trees/layer_tree_impl.h" +#include "cc/trees/single_thread_proxy.h" +#include "gpu/GLES2/gl2extchromium.h" +#include "media/base/media.h" + +using media::VideoFrame; +using WebKit::WebGraphicsContext3D; + +namespace cc { +namespace { + +// These tests deal with losing the 3d graphics context. +class LayerTreeHostContextTest : public LayerTreeTest { + public: + LayerTreeHostContextTest() + : LayerTreeTest(), + context3d_(NULL), + times_to_fail_create_(0), + times_to_fail_initialize_(0), + times_to_lose_on_create_(0), + times_to_lose_during_commit_(0), + times_to_lose_during_draw_(0), + times_to_fail_recreate_(0), + times_to_fail_reinitialize_(0), + times_to_lose_on_recreate_(0), + times_to_fail_create_offscreen_(0), + times_to_fail_recreate_offscreen_(0), + times_to_expect_create_failed_(0), + times_create_failed_(0), + times_offscreen_created_(0), + committed_at_least_once_(false), + context_should_support_io_surface_(false), + fallback_context_works_(false) { + media::InitializeMediaLibraryForTesting(); + } + + void LoseContext() { + context3d_->loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB, + GL_INNOCENT_CONTEXT_RESET_ARB); + context3d_ = NULL; + } + + virtual scoped_ptr<TestWebGraphicsContext3D> CreateContext3d() { + return TestWebGraphicsContext3D::Create(); + } + + virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback) + OVERRIDE { + if (times_to_fail_create_) { + --times_to_fail_create_; + ExpectCreateToFail(); + return scoped_ptr<OutputSurface>(); + } + + scoped_ptr<TestWebGraphicsContext3D> context3d = CreateContext3d(); + context3d_ = context3d.get(); + + if (context_should_support_io_surface_) { + context3d_->set_have_extension_io_surface(true); + context3d_->set_have_extension_egl_image(true); + } + + if (times_to_fail_initialize_ && !(fallback && fallback_context_works_)) { + --times_to_fail_initialize_; + // Make the context get lost during reinitialization. + // The number of times MakeCurrent succeeds is not important, and + // can be changed if needed to make this pass with future changes. + context3d_->set_times_make_current_succeeds(2); + ExpectCreateToFail(); + } else if (times_to_lose_on_create_) { + --times_to_lose_on_create_; + LoseContext(); + ExpectCreateToFail(); + } + + if (delegating_renderer()) { + return FakeOutputSurface::CreateDelegating3d( + context3d.PassAs<WebGraphicsContext3D>()).PassAs<OutputSurface>(); + } + return FakeOutputSurface::Create3d( + context3d.PassAs<WebGraphicsContext3D>()).PassAs<OutputSurface>(); + } + + scoped_ptr<TestWebGraphicsContext3D> CreateOffscreenContext3d() { + if (!context3d_) + return scoped_ptr<TestWebGraphicsContext3D>(); + + ++times_offscreen_created_; + + if (times_to_fail_create_offscreen_) { + --times_to_fail_create_offscreen_; + ExpectCreateToFail(); + return scoped_ptr<TestWebGraphicsContext3D>(); + } + + scoped_ptr<TestWebGraphicsContext3D> offscreen_context3d = + TestWebGraphicsContext3D::Create().Pass(); + DCHECK(offscreen_context3d); + context3d_->add_share_group_context(offscreen_context3d.get()); + + return offscreen_context3d.Pass(); + } + + virtual scoped_refptr<cc::ContextProvider> + OffscreenContextProviderForMainThread() OVERRIDE { + DCHECK(!HasImplThread()); + + if (!offscreen_contexts_main_thread_.get() || + offscreen_contexts_main_thread_->DestroyedOnMainThread()) { + offscreen_contexts_main_thread_ = TestContextProvider::Create( + base::Bind(&LayerTreeHostContextTest::CreateOffscreenContext3d, + base::Unretained(this))); + if (offscreen_contexts_main_thread_.get() && + !offscreen_contexts_main_thread_->BindToCurrentThread()) + offscreen_contexts_main_thread_ = NULL; + } + return offscreen_contexts_main_thread_; + } + + virtual scoped_refptr<cc::ContextProvider> + OffscreenContextProviderForCompositorThread() OVERRIDE { + DCHECK(HasImplThread()); + + if (!offscreen_contexts_compositor_thread_.get() || + offscreen_contexts_compositor_thread_->DestroyedOnMainThread()) { + offscreen_contexts_compositor_thread_ = TestContextProvider::Create( + base::Bind(&LayerTreeHostContextTest::CreateOffscreenContext3d, + base::Unretained(this))); + } + return offscreen_contexts_compositor_thread_; + } + + virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, + LayerTreeHostImpl::FrameData* frame, + bool result) OVERRIDE { + EXPECT_TRUE(result); + if (!times_to_lose_during_draw_) + return result; + + --times_to_lose_during_draw_; + context3d_->set_times_make_current_succeeds(0); + + times_to_fail_create_ = times_to_fail_recreate_; + times_to_fail_recreate_ = 0; + times_to_fail_initialize_ = times_to_fail_reinitialize_; + times_to_fail_reinitialize_ = 0; + times_to_lose_on_create_ = times_to_lose_on_recreate_; + times_to_lose_on_recreate_ = 0; + times_to_fail_create_offscreen_ = times_to_fail_recreate_offscreen_; + times_to_fail_recreate_offscreen_ = 0; + + return result; + } + + virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE { + committed_at_least_once_ = true; + + if (!times_to_lose_during_commit_) + return; + --times_to_lose_during_commit_; + LoseContext(); + + times_to_fail_create_ = times_to_fail_recreate_; + times_to_fail_recreate_ = 0; + times_to_fail_initialize_ = times_to_fail_reinitialize_; + times_to_fail_reinitialize_ = 0; + times_to_lose_on_create_ = times_to_lose_on_recreate_; + times_to_lose_on_recreate_ = 0; + times_to_fail_create_offscreen_ = times_to_fail_recreate_offscreen_; + times_to_fail_recreate_offscreen_ = 0; + } + + virtual void DidFailToInitializeOutputSurface() OVERRIDE { + ++times_create_failed_; + } + + virtual void TearDown() OVERRIDE { + LayerTreeTest::TearDown(); + EXPECT_EQ(times_to_expect_create_failed_, times_create_failed_); + } + + void ExpectCreateToFail() { + ++times_to_expect_create_failed_; + } + + protected: + TestWebGraphicsContext3D* context3d_; + int times_to_fail_create_; + int times_to_fail_initialize_; + int times_to_lose_on_create_; + int times_to_lose_during_commit_; + int times_to_lose_during_draw_; + int times_to_fail_recreate_; + int times_to_fail_reinitialize_; + int times_to_lose_on_recreate_; + int times_to_fail_create_offscreen_; + int times_to_fail_recreate_offscreen_; + int times_to_expect_create_failed_; + int times_create_failed_; + int times_offscreen_created_; + bool committed_at_least_once_; + bool context_should_support_io_surface_; + bool fallback_context_works_; + + scoped_refptr<TestContextProvider> offscreen_contexts_main_thread_; + scoped_refptr<TestContextProvider> offscreen_contexts_compositor_thread_; +}; + +class LayerTreeHostContextTestLostContextSucceeds + : public LayerTreeHostContextTest { + public: + LayerTreeHostContextTestLostContextSucceeds() + : LayerTreeHostContextTest(), + test_case_(0), + num_losses_(0), + recovered_context_(true), + first_initialized_(false) {} + + virtual void BeginTest() OVERRIDE { + PostSetNeedsCommitToMainThread(); + } + + virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE { + EXPECT_TRUE(succeeded); + + if (first_initialized_) + ++num_losses_; + else + first_initialized_ = true; + + recovered_context_ = true; + } + + virtual void AfterTest() OVERRIDE { + EXPECT_EQ(11u, test_case_); + EXPECT_EQ(8 + 10 + 10 + 1, num_losses_); + } + + virtual void DidCommitAndDrawFrame() OVERRIDE { + // If the last frame had a context loss, then we'll commit again to + // recover. + if (!recovered_context_) + return; + if (times_to_lose_during_commit_) + return; + if (times_to_lose_during_draw_) + return; + + recovered_context_ = false; + if (NextTestCase()) + InvalidateAndSetNeedsCommit(); + else + EndTest(); + } + + virtual void InvalidateAndSetNeedsCommit() { + // Cause damage so we try to draw. + layer_tree_host()->root_layer()->SetNeedsDisplay(); + layer_tree_host()->SetNeedsCommit(); + } + + bool NextTestCase() { + static const TestCase kTests[] = { + // Losing the context and failing to recreate it (or losing it again + // immediately) a small number of times should succeed. + { 1, // times_to_lose_during_commit + 0, // times_to_lose_during_draw + 3, // times_to_fail_reinitialize + 0, // times_to_fail_recreate + 0, // times_to_lose_on_recreate + 0, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + { 0, // times_to_lose_during_commit + 1, // times_to_lose_during_draw + 3, // times_to_fail_reinitialize + 0, // times_to_fail_recreate + 0, // times_to_lose_on_recreate + 0, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + { 1, // times_to_lose_during_commit + 0, // times_to_lose_during_draw + 0, // times_to_fail_reinitialize + 3, // times_to_fail_recreate + 0, // times_to_lose_on_recreate + 0, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + { 0, // times_to_lose_during_commit + 1, // times_to_lose_during_draw + 0, // times_to_fail_reinitialize + 3, // times_to_fail_recreate + 0, // times_to_lose_on_recreate + 0, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + { 1, // times_to_lose_during_commit + 0, // times_to_lose_during_draw + 0, // times_to_fail_reinitialize + 0, // times_to_fail_recreate + 3, // times_to_lose_on_recreate + 0, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + { 0, // times_to_lose_during_commit + 1, // times_to_lose_during_draw + 0, // times_to_fail_reinitialize + 0, // times_to_fail_recreate + 3, // times_to_lose_on_recreate + 0, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + { 1, // times_to_lose_during_commit + 0, // times_to_lose_during_draw + 0, // times_to_fail_reinitialize + 0, // times_to_fail_recreate + 0, // times_to_lose_on_recreate + 3, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + { 0, // times_to_lose_during_commit + 1, // times_to_lose_during_draw + 0, // times_to_fail_reinitialize + 0, // times_to_fail_recreate + 0, // times_to_lose_on_recreate + 3, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + // Losing the context and recreating it any number of times should + // succeed. + { 10, // times_to_lose_during_commit + 0, // times_to_lose_during_draw + 0, // times_to_fail_reinitialize + 0, // times_to_fail_recreate + 0, // times_to_lose_on_recreate + 0, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + { 0, // times_to_lose_during_commit + 10, // times_to_lose_during_draw + 0, // times_to_fail_reinitialize + 0, // times_to_fail_recreate + 0, // times_to_lose_on_recreate + 0, // times_to_fail_recreate_offscreen + false, // fallback_context_works + }, + // Losing the context, failing to reinitialize it, and making a fallback + // context should work. + { 0, // times_to_lose_during_commit + 1, // times_to_lose_during_draw + 10, // times_to_fail_reinitialize + 0, // times_to_fail_recreate + 0, // times_to_lose_on_recreate + 0, // times_to_fail_recreate_offscreen + true, // fallback_context_works + }, + }; + + if (test_case_ >= arraysize(kTests)) + return false; + + times_to_lose_during_commit_ = + kTests[test_case_].times_to_lose_during_commit; + times_to_lose_during_draw_ = + kTests[test_case_].times_to_lose_during_draw; + times_to_fail_reinitialize_ = kTests[test_case_].times_to_fail_reinitialize; + times_to_fail_recreate_ = kTests[test_case_].times_to_fail_recreate; + times_to_lose_on_recreate_ = kTests[test_case_].times_to_lose_on_recreate; + times_to_fail_recreate_offscreen_ = + kTests[test_case_].times_to_fail_recreate_offscreen; + fallback_context_works_ = kTests[test_case_].fallback_context_works; + ++test_case_; + return true; + } + + struct TestCase { + int times_to_lose_during_commit; + int times_to_lose_during_draw; + int times_to_fail_reinitialize; + int times_to_fail_recreate; + int times_to_lose_on_recreate; + int times_to_fail_recreate_offscreen; + bool fallback_context_works; + }; + + protected: + size_t test_case_; + int num_losses_; + bool recovered_context_; + bool first_initialized_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostContextTestLostContextSucceeds); + +class LayerTreeHostContextTestLostContextSucceedsWithContent + : public LayerTreeHostContextTestLostContextSucceeds { + public: + LayerTreeHostContextTestLostContextSucceedsWithContent() + : LayerTreeHostContextTestLostContextSucceeds() {} + + virtual void SetupTree() OVERRIDE { + root_ = Layer::Create(); + root_->SetBounds(gfx::Size(10, 10)); + root_->SetAnchorPoint(gfx::PointF()); + root_->SetIsDrawable(true); + + content_ = FakeContentLayer::Create(&client_); + content_->SetBounds(gfx::Size(10, 10)); + content_->SetAnchorPoint(gfx::PointF()); + content_->SetIsDrawable(true); + if (use_surface_) { + content_->SetForceRenderSurface(true); + // Filters require us to create an offscreen context. + FilterOperations filters; + filters.Append(FilterOperation::CreateGrayscaleFilter(0.5f)); + content_->SetFilters(filters); + content_->SetBackgroundFilters(filters); + } + + root_->AddChild(content_); + + layer_tree_host()->SetRootLayer(root_); + LayerTreeHostContextTest::SetupTree(); + } + + virtual void InvalidateAndSetNeedsCommit() OVERRIDE { + // Invalidate the render surface so we don't try to use a cached copy of the + // surface. We want to make sure to test the drawing paths for drawing to + // a child surface. + content_->SetNeedsDisplay(); + LayerTreeHostContextTestLostContextSucceeds::InvalidateAndSetNeedsCommit(); + } + + virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE { + FakeContentLayerImpl* content_impl = static_cast<FakeContentLayerImpl*>( + host_impl->active_tree()->root_layer()->children()[0]); + // Even though the context was lost, we should have a resource. The + // TestWebGraphicsContext3D ensures that this resource is created with + // the active context. + EXPECT_TRUE(content_impl->HaveResourceForTileAt(0, 0)); + + cc::ContextProvider* contexts = + host_impl->resource_provider()->offscreen_context_provider(); + if (use_surface_) { + EXPECT_TRUE(contexts->Context3d()); + // TODO(danakj): Make a fake GrContext. + // EXPECT_TRUE(contexts->GrContext()); + } else { + EXPECT_FALSE(contexts); + } + } + + virtual void AfterTest() OVERRIDE { + LayerTreeHostContextTestLostContextSucceeds::AfterTest(); + if (use_surface_) { + // 1 create to start with + + // 6 from test cases that fail on initializing the renderer (after the + // offscreen context is created) + + // 6 from test cases that lose the offscreen context directly + + // 4 from test cases that create a fallback + + // All the test cases that recreate both contexts only once + // per time it is lost. + EXPECT_EQ(6 + 6 + 1 + 4 + num_losses_, times_offscreen_created_); + } else { + EXPECT_EQ(0, times_offscreen_created_); + } + } + + protected: + bool use_surface_; + FakeContentLayerClient client_; + scoped_refptr<Layer> root_; + scoped_refptr<ContentLayer> content_; +}; + +TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent, + NoSurface_SingleThread_DirectRenderer) { + use_surface_ = false; + RunTest(false, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent, + NoSurface_SingleThread_DelegatingRenderer) { + use_surface_ = false; + RunTest(false, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent, + NoSurface_MultiThread_DirectRenderer_MainThreadPaint) { + use_surface_ = false; + RunTest(true, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent, + NoSurface_MultiThread_DirectRenderer_ImplSidePaint) { + use_surface_ = false; + RunTest(true, false, true); +} + +TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent, + NoSurface_MultiThread_DelegatingRenderer_MainThreadPaint) { + use_surface_ = false; + RunTest(true, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent, + NoSurface_MultiThread_DelegatingRenderer_ImplSidePaint) { + use_surface_ = false; + RunTest(true, true, true); +} + +// Surfaces don't exist with a delegating renderer. +TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent, + WithSurface_SingleThread_DirectRenderer) { + use_surface_ = true; + RunTest(false, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent, + WithSurface_MultiThread_DirectRenderer_MainThreadPaint) { + use_surface_ = true; + RunTest(true, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent, + WithSurface_MultiThread_DirectRenderer_ImplSidePaint) { + use_surface_ = true; + RunTest(true, false, true); +} + +class LayerTreeHostContextTestOffscreenContextFails + : public LayerTreeHostContextTest { + public: + virtual void SetupTree() OVERRIDE { + root_ = Layer::Create(); + root_->SetBounds(gfx::Size(10, 10)); + root_->SetAnchorPoint(gfx::PointF()); + root_->SetIsDrawable(true); + + content_ = FakeContentLayer::Create(&client_); + content_->SetBounds(gfx::Size(10, 10)); + content_->SetAnchorPoint(gfx::PointF()); + content_->SetIsDrawable(true); + content_->SetForceRenderSurface(true); + // Filters require us to create an offscreen context. + FilterOperations filters; + filters.Append(FilterOperation::CreateGrayscaleFilter(0.5f)); + content_->SetFilters(filters); + content_->SetBackgroundFilters(filters); + + root_->AddChild(content_); + + layer_tree_host()->SetRootLayer(root_); + LayerTreeHostContextTest::SetupTree(); + } + + virtual void BeginTest() OVERRIDE { + times_to_fail_create_offscreen_ = 1; + PostSetNeedsCommitToMainThread(); + } + + virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE { + cc::ContextProvider* contexts = + host_impl->resource_provider()->offscreen_context_provider(); + EXPECT_FALSE(contexts); + + // This did not lead to create failure. + times_to_expect_create_failed_ = 0; + EndTest(); + } + + virtual void AfterTest() OVERRIDE {} + + protected: + FakeContentLayerClient client_; + scoped_refptr<Layer> root_; + scoped_refptr<ContentLayer> content_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostContextTestOffscreenContextFails); + +class LayerTreeHostContextTestLostContextFails + : public LayerTreeHostContextTest { + public: + LayerTreeHostContextTestLostContextFails() + : LayerTreeHostContextTest(), + num_commits_(0), + first_initialized_(false) { + times_to_lose_during_commit_ = 1; + } + + virtual void BeginTest() OVERRIDE { + PostSetNeedsCommitToMainThread(); + } + + virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE { + if (first_initialized_) { + EXPECT_FALSE(succeeded); + EndTest(); + } else { + first_initialized_ = true; + } + } + + virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE { + LayerTreeHostContextTest::CommitCompleteOnThread(host_impl); + + ++num_commits_; + if (num_commits_ == 1) { + // When the context is ok, we should have these things. + EXPECT_TRUE(host_impl->output_surface()); + EXPECT_TRUE(host_impl->renderer()); + EXPECT_TRUE(host_impl->resource_provider()); + return; + } + + // When context recreation fails we shouldn't be left with any of them. + EXPECT_FALSE(host_impl->output_surface()); + EXPECT_FALSE(host_impl->renderer()); + EXPECT_FALSE(host_impl->resource_provider()); + } + + virtual void AfterTest() OVERRIDE {} + + private: + int num_commits_; + bool first_initialized_; +}; + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailReinitialize100_SingleThread_DirectRenderer) { + times_to_fail_reinitialize_ = 100; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 0; + RunTest(false, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailReinitialize100_SingleThread_DelegatingRenderer) { + times_to_fail_reinitialize_ = 100; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 0; + RunTest(false, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailReinitialize100_MultiThread_DirectRenderer_MainThreadPaint) { + times_to_fail_reinitialize_ = 100; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 0; + RunTest(true, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailReinitialize100_MultiThread_DirectRenderer_ImplSidePaint) { + times_to_fail_reinitialize_ = 100; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 0; + RunTest(true, false, true); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailReinitialize100_MultiThread_DelegatingRenderer_MainThreadPaint) { + times_to_fail_reinitialize_ = 100; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 0; + RunTest(true, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailReinitialize100_MultiThread_DelegatingRenderer_ImplSidePaint) { + times_to_fail_reinitialize_ = 100; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 0; + RunTest(true, true, true); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailRecreate100_SingleThread_DirectRenderer) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 100; + times_to_lose_on_recreate_ = 0; + RunTest(false, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailRecreate100_SingleThread_DelegatingRenderer) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 100; + times_to_lose_on_recreate_ = 0; + RunTest(false, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailRecreate100_MultiThread_DirectRenderer_MainThreadPaint) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 100; + times_to_lose_on_recreate_ = 0; + RunTest(true, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailRecreate100_MultiThread_DirectRenderer_ImplSidePaint) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 100; + times_to_lose_on_recreate_ = 0; + RunTest(true, false, true); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailRecreate100_MultiThread_DelegatingRenderer_MainThreadPaint) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 100; + times_to_lose_on_recreate_ = 0; + RunTest(true, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + FailRecreate100_MultiThread_DelegatingRenderer_ImplSidePaint) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 100; + times_to_lose_on_recreate_ = 0; + RunTest(true, true, true); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + LoseOnRecreate100_SingleThread_DirectRenderer) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 100; + RunTest(false, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + LoseOnRecreate100_SingleThread_DelegatingRenderer) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 100; + RunTest(false, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + LoseOnRecreate100_MultiThread_DirectRenderer_MainThreadPaint) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 100; + RunTest(true, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + LoseOnRecreate100_MultiThread_DirectRenderer_ImplSidePaint) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 100; + RunTest(true, false, true); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + LoseOnRecreate100_MultiThread_DelegatingRenderer_MainThreadPaint) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 100; + RunTest(true, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextFails, + LoseOnRecreate100_MultiThread_DelegatingRenderer_ImplSidePaint) { + times_to_fail_reinitialize_ = 0; + times_to_fail_recreate_ = 0; + times_to_lose_on_recreate_ = 100; + RunTest(true, true, true); +} + +class LayerTreeHostContextTestFinishAllRenderingAfterLoss + : public LayerTreeHostContextTest { + public: + virtual void BeginTest() OVERRIDE { + // Lose the context until the compositor gives up on it. + first_initialized_ = false; + times_to_lose_during_commit_ = 1; + times_to_fail_reinitialize_ = 10; + PostSetNeedsCommitToMainThread(); + } + + virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE { + if (first_initialized_) { + EXPECT_FALSE(succeeded); + layer_tree_host()->FinishAllRendering(); + EndTest(); + } else { + first_initialized_ = true; + } + } + + virtual void AfterTest() OVERRIDE {} + + private: + bool first_initialized_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F( + LayerTreeHostContextTestFinishAllRenderingAfterLoss); + +class LayerTreeHostContextTestLostContextAndEvictTextures + : public LayerTreeHostContextTest { + public: + LayerTreeHostContextTestLostContextAndEvictTextures() + : LayerTreeHostContextTest(), + layer_(FakeContentLayer::Create(&client_)), + impl_host_(0), + num_commits_(0) {} + + virtual void SetupTree() OVERRIDE { + layer_->SetBounds(gfx::Size(10, 20)); + layer_tree_host()->SetRootLayer(layer_); + LayerTreeHostContextTest::SetupTree(); + } + + virtual void BeginTest() OVERRIDE { + PostSetNeedsCommitToMainThread(); + } + + void PostEvictTextures() { + if (HasImplThread()) { + ImplThreadTaskRunner()->PostTask( + FROM_HERE, + base::Bind( + &LayerTreeHostContextTestLostContextAndEvictTextures:: + EvictTexturesOnImplThread, + base::Unretained(this))); + } else { + DebugScopedSetImplThread impl(proxy()); + EvictTexturesOnImplThread(); + } + } + + void EvictTexturesOnImplThread() { + impl_host_->EvictTexturesForTesting(); + if (lose_after_evict_) + LoseContext(); + } + + virtual void DidCommitAndDrawFrame() OVERRIDE { + if (num_commits_ > 1) + return; + EXPECT_TRUE(layer_->HaveBackingAt(0, 0)); + PostEvictTextures(); + } + + virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerTreeHostContextTest::CommitCompleteOnThread(impl); + if (num_commits_ > 1) + return; + ++num_commits_; + if (!lose_after_evict_) + LoseContext(); + impl_host_ = impl; + } + + virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE { + EXPECT_TRUE(succeeded); + EndTest(); + } + + virtual void AfterTest() OVERRIDE {} + + protected: + bool lose_after_evict_; + FakeContentLayerClient client_; + scoped_refptr<FakeContentLayer> layer_; + LayerTreeHostImpl* impl_host_; + int num_commits_; +}; + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseAfterEvict_SingleThread_DirectRenderer) { + lose_after_evict_ = true; + RunTest(false, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseAfterEvict_SingleThread_DelegatingRenderer) { + lose_after_evict_ = true; + RunTest(false, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseAfterEvict_MultiThread_DirectRenderer_MainThreadPaint) { + lose_after_evict_ = true; + RunTest(true, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseAfterEvict_MultiThread_DirectRenderer_ImplSidePaint) { + lose_after_evict_ = true; + RunTest(true, false, true); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseAfterEvict_MultiThread_DelegatingRenderer_MainThreadPaint) { + lose_after_evict_ = true; + RunTest(true, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseAfterEvict_MultiThread_DelegatingRenderer_ImplSidePaint) { + lose_after_evict_ = true; + RunTest(true, true, true); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseBeforeEvict_SingleThread_DirectRenderer) { + lose_after_evict_ = false; + RunTest(false, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseBeforeEvict_SingleThread_DelegatingRenderer) { + lose_after_evict_ = false; + RunTest(false, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseBeforeEvict_MultiThread_DirectRenderer_MainThreadPaint) { + lose_after_evict_ = false; + RunTest(true, false, false); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseBeforeEvict_MultiThread_DirectRenderer_ImplSidePaint) { + lose_after_evict_ = false; + RunTest(true, false, true); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseBeforeEvict_MultiThread_DelegatingRenderer_MainThreadPaint) { + lose_after_evict_ = false; + RunTest(true, true, false); +} + +TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures, + LoseBeforeEvict_MultiThread_DelegatingRenderer_ImplSidePaint) { + lose_after_evict_ = false; + RunTest(true, true, true); +} + +class LayerTreeHostContextTestLostContextWhileUpdatingResources + : public LayerTreeHostContextTest { + public: + LayerTreeHostContextTestLostContextWhileUpdatingResources() + : parent_(FakeContentLayer::Create(&client_)), + num_children_(50), + times_to_lose_on_end_query_(3) {} + + virtual scoped_ptr<TestWebGraphicsContext3D> CreateContext3d() OVERRIDE { + scoped_ptr<TestWebGraphicsContext3D> context = + LayerTreeHostContextTest::CreateContext3d(); + if (times_to_lose_on_end_query_) { + --times_to_lose_on_end_query_; + context->set_times_end_query_succeeds(5); + } + return context.Pass(); + } + + virtual void SetupTree() OVERRIDE { + parent_->SetBounds(gfx::Size(num_children_, 1)); + + for (int i = 0; i < num_children_; i++) { + scoped_refptr<FakeContentLayer> child = + FakeContentLayer::Create(&client_); + child->SetPosition(gfx::PointF(i, 0.f)); + child->SetBounds(gfx::Size(1, 1)); + parent_->AddChild(child); + } + + layer_tree_host()->SetRootLayer(parent_); + LayerTreeHostContextTest::SetupTree(); + } + + virtual void BeginTest() OVERRIDE { + PostSetNeedsCommitToMainThread(); + } + + virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerTreeHostContextTest::CommitCompleteOnThread(impl); + EndTest(); + } + + virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE { + EXPECT_TRUE(succeeded); + } + + virtual void AfterTest() OVERRIDE { + EXPECT_EQ(0, times_to_lose_on_end_query_); + } + + private: + FakeContentLayerClient client_; + scoped_refptr<FakeContentLayer> parent_; + int num_children_; + int times_to_lose_on_end_query_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F( + LayerTreeHostContextTestLostContextWhileUpdatingResources); + +class LayerTreeHostContextTestLayersNotified + : public LayerTreeHostContextTest { + public: + LayerTreeHostContextTestLayersNotified() + : LayerTreeHostContextTest(), + num_commits_(0) {} + + virtual void SetupTree() OVERRIDE { + root_ = FakeContentLayer::Create(&client_); + child_ = FakeContentLayer::Create(&client_); + grandchild_ = FakeContentLayer::Create(&client_); + + root_->AddChild(child_); + child_->AddChild(grandchild_); + + layer_tree_host()->SetRootLayer(root_); + LayerTreeHostContextTest::SetupTree(); + } + + virtual void BeginTest() OVERRIDE { + PostSetNeedsCommitToMainThread(); + } + + virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE { + LayerTreeHostContextTest::DidActivateTreeOnThread(host_impl); + + FakeContentLayerImpl* root = static_cast<FakeContentLayerImpl*>( + host_impl->active_tree()->root_layer()); + FakeContentLayerImpl* child = static_cast<FakeContentLayerImpl*>( + root->children()[0]); + FakeContentLayerImpl* grandchild = static_cast<FakeContentLayerImpl*>( + child->children()[0]); + + ++num_commits_; + switch (num_commits_) { + case 1: + EXPECT_EQ(0u, root->lost_output_surface_count()); + EXPECT_EQ(0u, child->lost_output_surface_count()); + EXPECT_EQ(0u, grandchild->lost_output_surface_count()); + // Lose the context and struggle to recreate it. + LoseContext(); + times_to_fail_create_ = 1; + break; + case 2: + EXPECT_EQ(1u, root->lost_output_surface_count()); + EXPECT_EQ(1u, child->lost_output_surface_count()); + EXPECT_EQ(1u, grandchild->lost_output_surface_count()); + // Lose the context and again during recreate. + LoseContext(); + times_to_lose_on_create_ = 1; + break; + case 3: + EXPECT_EQ(3u, root->lost_output_surface_count()); + EXPECT_EQ(3u, child->lost_output_surface_count()); + EXPECT_EQ(3u, grandchild->lost_output_surface_count()); + // Lose the context and again during reinitialization. + LoseContext(); + times_to_fail_initialize_ = 1; + break; + case 4: + EXPECT_EQ(5u, root->lost_output_surface_count()); + EXPECT_EQ(5u, child->lost_output_surface_count()); + EXPECT_EQ(5u, grandchild->lost_output_surface_count()); + EndTest(); + break; + default: + NOTREACHED(); + } + } + + virtual void AfterTest() OVERRIDE {} + + private: + int num_commits_; + + FakeContentLayerClient client_; + scoped_refptr<FakeContentLayer> root_; + scoped_refptr<FakeContentLayer> child_; + scoped_refptr<FakeContentLayer> grandchild_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostContextTestLayersNotified); + +class LayerTreeHostContextTestDontUseLostResources + : public LayerTreeHostContextTest { + public: + virtual void SetupTree() OVERRIDE { + scoped_refptr<Layer> root_ = Layer::Create(); + root_->SetBounds(gfx::Size(10, 10)); + root_->SetAnchorPoint(gfx::PointF()); + root_->SetIsDrawable(true); + + scoped_refptr<FakeDelegatedRendererLayer> delegated_ = + FakeDelegatedRendererLayer::Create(NULL); + delegated_->SetBounds(gfx::Size(10, 10)); + delegated_->SetAnchorPoint(gfx::PointF()); + delegated_->SetIsDrawable(true); + root_->AddChild(delegated_); + + scoped_refptr<ContentLayer> content_ = ContentLayer::Create(&client_); + content_->SetBounds(gfx::Size(10, 10)); + content_->SetAnchorPoint(gfx::PointF()); + content_->SetIsDrawable(true); + root_->AddChild(content_); + + scoped_refptr<TextureLayer> texture_ = TextureLayer::Create(NULL); + texture_->SetBounds(gfx::Size(10, 10)); + texture_->SetAnchorPoint(gfx::PointF()); + texture_->SetIsDrawable(true); + root_->AddChild(texture_); + + scoped_refptr<ContentLayer> mask_ = ContentLayer::Create(&client_); + mask_->SetBounds(gfx::Size(10, 10)); + mask_->SetAnchorPoint(gfx::PointF()); + + scoped_refptr<ContentLayer> content_with_mask_ = + ContentLayer::Create(&client_); + content_with_mask_->SetBounds(gfx::Size(10, 10)); + content_with_mask_->SetAnchorPoint(gfx::PointF()); + content_with_mask_->SetIsDrawable(true); + content_with_mask_->SetMaskLayer(mask_.get()); + root_->AddChild(content_with_mask_); + + scoped_refptr<VideoLayer> video_color_ = VideoLayer::Create( + &color_frame_provider_); + video_color_->SetBounds(gfx::Size(10, 10)); + video_color_->SetAnchorPoint(gfx::PointF()); + video_color_->SetIsDrawable(true); + root_->AddChild(video_color_); + + scoped_refptr<VideoLayer> video_hw_ = VideoLayer::Create( + &hw_frame_provider_); + video_hw_->SetBounds(gfx::Size(10, 10)); + video_hw_->SetAnchorPoint(gfx::PointF()); + video_hw_->SetIsDrawable(true); + root_->AddChild(video_hw_); + + scoped_refptr<VideoLayer> video_scaled_hw_ = VideoLayer::Create( + &scaled_hw_frame_provider_); + video_scaled_hw_->SetBounds(gfx::Size(10, 10)); + video_scaled_hw_->SetAnchorPoint(gfx::PointF()); + video_scaled_hw_->SetIsDrawable(true); + root_->AddChild(video_scaled_hw_); + + if (!delegating_renderer()) { + // TODO(danakj): IOSurface layer can not be transported. crbug.com/239335 + scoped_refptr<IOSurfaceLayer> io_surface_ = IOSurfaceLayer::Create(); + io_surface_->SetBounds(gfx::Size(10, 10)); + io_surface_->SetAnchorPoint(gfx::PointF()); + io_surface_->SetIsDrawable(true); + io_surface_->SetIOSurfaceProperties(1, gfx::Size(10, 10)); + root_->AddChild(io_surface_); + } + + // Enable the hud. + LayerTreeDebugState debug_state; + debug_state.show_property_changed_rects = true; + layer_tree_host()->SetDebugState(debug_state); + + scoped_refptr<ScrollbarLayer> scrollbar_ = ScrollbarLayer::Create( + scoped_ptr<Scrollbar>(new FakeScrollbar).Pass(), + content_->id()); + scrollbar_->SetBounds(gfx::Size(10, 10)); + scrollbar_->SetAnchorPoint(gfx::PointF()); + scrollbar_->SetIsDrawable(true); + root_->AddChild(scrollbar_); + + layer_tree_host()->SetRootLayer(root_); + LayerTreeHostContextTest::SetupTree(); + } + + virtual void BeginTest() OVERRIDE { + context_should_support_io_surface_ = true; + PostSetNeedsCommitToMainThread(); + } + + virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE { + LayerTreeHostContextTest::CommitCompleteOnThread(host_impl); + + ResourceProvider* resource_provider = host_impl->resource_provider(); + + if (host_impl->active_tree()->source_frame_number() == 0) { + // Set up impl resources on the first commit. + + scoped_ptr<TestRenderPass> pass_for_quad = TestRenderPass::Create(); + pass_for_quad->SetNew( + // AppendOneOfEveryQuadType() makes a RenderPass quad with this id. + RenderPass::Id(1, 1), + gfx::Rect(0, 0, 10, 10), + gfx::Rect(0, 0, 10, 10), + gfx::Transform()); + + scoped_ptr<TestRenderPass> pass = TestRenderPass::Create(); + pass->SetNew( + RenderPass::Id(2, 1), + gfx::Rect(0, 0, 10, 10), + gfx::Rect(0, 0, 10, 10), + gfx::Transform()); + pass->AppendOneOfEveryQuadType(resource_provider, RenderPass::Id(2, 1)); + + ScopedPtrVector<RenderPass> pass_list; + pass_list.push_back(pass_for_quad.PassAs<RenderPass>()); + pass_list.push_back(pass.PassAs<RenderPass>()); + + // First child is the delegated layer. + FakeDelegatedRendererLayerImpl* delegated_impl = + static_cast<FakeDelegatedRendererLayerImpl*>( + host_impl->active_tree()->root_layer()->children()[0]); + delegated_impl->SetFrameDataForRenderPasses(&pass_list); + EXPECT_TRUE(pass_list.empty()); + + // Third child is the texture layer. + TextureLayerImpl* texture_impl = + static_cast<TextureLayerImpl*>( + host_impl->active_tree()->root_layer()->children()[2]); + texture_impl->set_texture_id( + resource_provider->GraphicsContext3D()->createTexture()); + + DCHECK(resource_provider->GraphicsContext3D()); + ResourceProvider::ResourceId texture = resource_provider->CreateResource( + gfx::Size(4, 4), + resource_provider->default_resource_type(), + ResourceProvider::TextureUsageAny); + ResourceProvider::ScopedWriteLockGL lock(resource_provider, texture); + + gpu::Mailbox mailbox; + resource_provider->GraphicsContext3D()->genMailboxCHROMIUM(mailbox.name); + unsigned sync_point = + resource_provider->GraphicsContext3D()->insertSyncPoint(); + + color_video_frame_ = VideoFrame::CreateColorFrame( + gfx::Size(4, 4), 0x80, 0x80, 0x80, base::TimeDelta()); + hw_video_frame_ = VideoFrame::WrapNativeTexture( + new VideoFrame::MailboxHolder( + mailbox, + sync_point, + VideoFrame::MailboxHolder::TextureNoLongerNeededCallback()), + GL_TEXTURE_2D, + gfx::Size(4, 4), gfx::Rect(0, 0, 4, 4), gfx::Size(4, 4), + base::TimeDelta(), + VideoFrame::ReadPixelsCB(), + base::Closure()); + scaled_hw_video_frame_ = VideoFrame::WrapNativeTexture( + new VideoFrame::MailboxHolder( + mailbox, + sync_point, + VideoFrame::MailboxHolder::TextureNoLongerNeededCallback()), + GL_TEXTURE_2D, + gfx::Size(4, 4), gfx::Rect(0, 0, 3, 2), gfx::Size(4, 4), + base::TimeDelta(), + VideoFrame::ReadPixelsCB(), + base::Closure()); + + color_frame_provider_.set_frame(color_video_frame_); + hw_frame_provider_.set_frame(hw_video_frame_); + scaled_hw_frame_provider_.set_frame(scaled_hw_video_frame_); + return; + } + + if (host_impl->active_tree()->source_frame_number() == 3) { + // On the third commit we're recovering from context loss. Hardware + // video frames should not be reused by the VideoFrameProvider, but + // software frames can be. + hw_frame_provider_.set_frame(NULL); + scaled_hw_frame_provider_.set_frame(NULL); + } + } + + virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, + LayerTreeHostImpl::FrameData* frame, + bool result) OVERRIDE { + if (host_impl->active_tree()->source_frame_number() == 2) { + // Lose the context during draw on the second commit. This will cause + // a third commit to recover. + if (context3d_) + context3d_->set_times_bind_texture_succeeds(4); + } + return true; + } + + virtual void DidCommitAndDrawFrame() OVERRIDE { + ASSERT_TRUE(layer_tree_host()->hud_layer()); + // End the test once we know the 3nd frame drew. + if (layer_tree_host()->source_frame_number() == 4) + EndTest(); + else + layer_tree_host()->SetNeedsCommit(); + } + + virtual void AfterTest() OVERRIDE {} + + private: + FakeContentLayerClient client_; + + scoped_refptr<Layer> root_; + scoped_refptr<DelegatedRendererLayer> delegated_; + scoped_refptr<ContentLayer> content_; + scoped_refptr<TextureLayer> texture_; + scoped_refptr<ContentLayer> mask_; + scoped_refptr<ContentLayer> content_with_mask_; + scoped_refptr<VideoLayer> video_color_; + scoped_refptr<VideoLayer> video_hw_; + scoped_refptr<VideoLayer> video_scaled_hw_; + scoped_refptr<IOSurfaceLayer> io_surface_; + scoped_refptr<ScrollbarLayer> scrollbar_; + + scoped_refptr<VideoFrame> color_video_frame_; + scoped_refptr<VideoFrame> hw_video_frame_; + scoped_refptr<VideoFrame> scaled_hw_video_frame_; + + FakeVideoFrameProvider color_frame_provider_; + FakeVideoFrameProvider hw_frame_provider_; + FakeVideoFrameProvider scaled_hw_frame_provider_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostContextTestDontUseLostResources); + +class LayerTreeHostContextTestLosesFirstOutputSurface + : public LayerTreeHostContextTest { + public: + LayerTreeHostContextTestLosesFirstOutputSurface() { + // Always fail. This needs to be set before LayerTreeHost is created. + times_to_lose_on_create_ = 1000; + } + + virtual void BeginTest() OVERRIDE { + PostSetNeedsCommitToMainThread(); + } + + virtual void AfterTest() OVERRIDE {} + + virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE { + EXPECT_FALSE(succeeded); + + // If we make it this far without crashing, we pass! + EndTest(); + } + + virtual void DidCommitAndDrawFrame() OVERRIDE { + EXPECT_TRUE(false); + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F( + LayerTreeHostContextTestLosesFirstOutputSurface); + +class LayerTreeHostContextTestRetriesFirstInitializationAndSucceeds + : public LayerTreeHostContextTest { + public: + virtual void AfterTest() OVERRIDE {} + + virtual void BeginTest() OVERRIDE { + times_to_fail_initialize_ = 2; + PostSetNeedsCommitToMainThread(); + } + + virtual void DidCommitAndDrawFrame() OVERRIDE { + EndTest(); + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F( + LayerTreeHostContextTestRetriesFirstInitializationAndSucceeds); + +class LayerTreeHostContextTestRetryWorksWithForcedInit + : public LayerTreeHostContextTestRetriesFirstInitializationAndSucceeds { + public: + virtual void DidFailToInitializeOutputSurface() OVERRIDE { + LayerTreeHostContextTestRetriesFirstInitializationAndSucceeds + ::DidFailToInitializeOutputSurface(); + + if (times_create_failed_ == 1) { + // CompositeAndReadback force recreates the output surface, which should + // fail. + char pixels[4]; + EXPECT_FALSE(layer_tree_host()->CompositeAndReadback( + &pixels, gfx::Rect(1, 1))); + } + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F( + LayerTreeHostContextTestRetryWorksWithForcedInit); + +class LayerTreeHostContextTestCompositeAndReadbackBeforeOutputSurfaceInit + : public LayerTreeHostContextTest { + public: + virtual void BeginTest() OVERRIDE { + // This must be called immediately after creating LTH, before the first + // OutputSurface is initialized. + ASSERT_TRUE(layer_tree_host()->output_surface_lost()); + + times_output_surface_created_ = 0; + + char pixels[4]; + bool result = layer_tree_host()->CompositeAndReadback( + &pixels, gfx::Rect(1, 1)); + EXPECT_EQ(!delegating_renderer(), result); + EXPECT_EQ(1, times_output_surface_created_); + + PostSetNeedsCommitToMainThread(); + } + + virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE { + EXPECT_TRUE(succeeded); + ++times_output_surface_created_; + } + + virtual void DidCommitAndDrawFrame() OVERRIDE { + EndTest(); + } + + virtual void AfterTest() OVERRIDE { + // Should not try to create output surface again after successfully + // created by CompositeAndReadback. + EXPECT_EQ(1, times_output_surface_created_); + } + + private: + int times_output_surface_created_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F( + LayerTreeHostContextTestCompositeAndReadbackBeforeOutputSurfaceInit); + +class ImplSidePaintingLayerTreeHostContextTest + : public LayerTreeHostContextTest { + public: + virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE { + settings->impl_side_painting = true; + } +}; + +class LayerTreeHostContextTestImplSidePainting + : public ImplSidePaintingLayerTreeHostContextTest { + public: + virtual void SetupTree() OVERRIDE { + scoped_refptr<Layer> root = Layer::Create(); + root->SetBounds(gfx::Size(10, 10)); + root->SetAnchorPoint(gfx::PointF()); + root->SetIsDrawable(true); + + scoped_refptr<PictureLayer> picture = PictureLayer::Create(&client_); + picture->SetBounds(gfx::Size(10, 10)); + picture->SetAnchorPoint(gfx::PointF()); + picture->SetIsDrawable(true); + root->AddChild(picture); + + layer_tree_host()->SetRootLayer(root); + LayerTreeHostContextTest::SetupTree(); + } + + virtual void BeginTest() OVERRIDE { + times_to_lose_during_commit_ = 1; + PostSetNeedsCommitToMainThread(); + } + + virtual void AfterTest() OVERRIDE {} + + virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE { + EXPECT_TRUE(succeeded); + EndTest(); + } + + private: + FakeContentLayerClient client_; +}; + +MULTI_THREAD_TEST_F(LayerTreeHostContextTestImplSidePainting); + +class ScrollbarLayerLostContext : public LayerTreeHostContextTest { + public: + ScrollbarLayerLostContext() : commits_(0) {} + + virtual void BeginTest() OVERRIDE { + scoped_refptr<Layer> scroll_layer = Layer::Create(); + scrollbar_layer_ = FakeScrollbarLayer::Create( + false, true, scroll_layer->id()); + scrollbar_layer_->SetBounds(gfx::Size(10, 100)); + layer_tree_host()->root_layer()->AddChild(scrollbar_layer_); + layer_tree_host()->root_layer()->AddChild(scroll_layer); + PostSetNeedsCommitToMainThread(); + } + + virtual void AfterTest() OVERRIDE {} + + virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerTreeHostContextTest::CommitCompleteOnThread(impl); + + ++commits_; + switch (commits_) { + case 1: + // First (regular) update, we should upload 2 resources (thumb, and + // backtrack). + EXPECT_EQ(1, scrollbar_layer_->update_count()); + LoseContext(); + break; + case 2: + // Second update, after the lost context, we should still upload 2 + // resources even if the contents haven't changed. + EXPECT_EQ(2, scrollbar_layer_->update_count()); + EndTest(); + break; + default: + NOTREACHED(); + } + } + + private: + int commits_; + scoped_refptr<FakeScrollbarLayer> scrollbar_layer_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(ScrollbarLayerLostContext); + +class LayerTreeHostContextTestFailsToCreateSurface + : public LayerTreeHostContextTest { + public: + LayerTreeHostContextTestFailsToCreateSurface() + : LayerTreeHostContextTest(), + failure_count_(0) { + times_to_lose_on_create_ = 10; + } + + virtual void BeginTest() OVERRIDE { + PostSetNeedsCommitToMainThread(); + } + + virtual void AfterTest() OVERRIDE {} + + virtual void DidInitializeOutputSurface(bool success) OVERRIDE { + EXPECT_FALSE(success); + EXPECT_EQ(0, failure_count_); + times_to_lose_on_create_ = 0; + failure_count_++; + // Normally, the embedder should stop trying to use the compositor at + // this point, but let's force it back into action when we shouldn't. + char pixels[4]; + EXPECT_FALSE( + layer_tree_host()->CompositeAndReadback(pixels, gfx::Rect(1, 1))); + // If we've made it this far without crashing, we've succeeded. + EndTest(); + } + + private: + int failure_count_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F( + LayerTreeHostContextTestFailsToCreateSurface); + +// Not reusing LayerTreeTest because it expects creating LTH to always succeed. +class LayerTreeHostTestCannotCreateIfCannotCreateOutputSurface + : public testing::Test, + public FakeLayerTreeHostClient { + public: + LayerTreeHostTestCannotCreateIfCannotCreateOutputSurface() + : FakeLayerTreeHostClient(FakeLayerTreeHostClient::DIRECT_3D) {} + + // FakeLayerTreeHostClient implementation. + virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback) + OVERRIDE { + return scoped_ptr<OutputSurface>(); + } + + void RunTest(bool threaded, + bool delegating_renderer, + bool impl_side_painting) { + scoped_ptr<base::Thread> impl_thread; + if (threaded) { + impl_thread.reset(new base::Thread("LayerTreeTest")); + ASSERT_TRUE(impl_thread->Start()); + ASSERT_TRUE(impl_thread->message_loop_proxy().get()); + } + + LayerTreeSettings settings; + settings.impl_side_painting = impl_side_painting; + scoped_ptr<LayerTreeHost> layer_tree_host = LayerTreeHost::Create( + this, + settings, + impl_thread ? impl_thread->message_loop_proxy() : NULL); + EXPECT_FALSE(layer_tree_host); + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F( + LayerTreeHostTestCannotCreateIfCannotCreateOutputSurface); + +class UIResourceLostTest : public LayerTreeHostContextTest { + public: + UIResourceLostTest() : time_step_(0) {} + virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); } + virtual void AfterTest() OVERRIDE {} + + protected: + int time_step_; + scoped_ptr<FakeScopedUIResource> ui_resource_; +}; + +// Losing context after an UI resource has been created. +class UIResourceLostAfterCommit : public UIResourceLostTest { + public: + virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerTreeHostContextTest::CommitCompleteOnThread(impl); + switch (time_step_) { + case 0: + ui_resource_ = FakeScopedUIResource::Create(layer_tree_host()); + // Expects a valid UIResourceId. + EXPECT_NE(0, ui_resource_->id()); + PostSetNeedsCommitToMainThread(); + break; + case 1: + // The resource should have been created on LTHI after the commit. + if (!layer_tree_host()->settings().impl_side_painting) + EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id())); + PostSetNeedsCommitToMainThread(); + break; + case 2: + LoseContext(); + break; + case 3: + // The resources should have been recreated. The bitmap callback should + // have been called once with the resource_lost flag set to true. + EXPECT_EQ(1, ui_resource_->lost_resource_count); + // Resource Id on the impl-side have been recreated as well. Note + // that the same UIResourceId persists after the context lost. + if (!layer_tree_host()->settings().impl_side_painting) + EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id())); + PostSetNeedsCommitToMainThread(); + break; + case 4: + // Release resource before ending test. + ui_resource_.reset(); + EndTest(); + break; + } + } + + virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerTreeHostContextTest::DidActivateTreeOnThread(impl); + switch (time_step_) { + case 1: + if (layer_tree_host()->settings().impl_side_painting) + EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id())); + break; + case 3: + if (layer_tree_host()->settings().impl_side_painting) + EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id())); + break; + } + ++time_step_; + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(UIResourceLostAfterCommit); + +// Losing context before UI resource requests can be commited. Three sequences +// of creation/deletion are considered: +// 1. Create one resource -> Context Lost => Expect the resource to have been +// created. +// 2. Delete an exisiting resource (test_id0_) -> create a second resource +// (test_id1_) -> Context Lost => Expect the test_id0_ to be removed and +// test_id1_ to have been created. +// 3. Create one resource -> Delete that same resource -> Context Lost => Expect +// the resource to not exist in the manager. +class UIResourceLostBeforeCommit : public UIResourceLostTest { + public: + UIResourceLostBeforeCommit() + : test_id0_(0), + test_id1_(0) {} + + virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerTreeHostContextTest::CommitCompleteOnThread(impl); + switch (time_step_) { + case 0: + // Sequence 1: + ui_resource_ = FakeScopedUIResource::Create(layer_tree_host()); + LoseContext(); + // Resource Id on the impl-side should no longer be valid after + // context is lost. + EXPECT_EQ(0u, impl->ResourceIdForUIResource(ui_resource_->id())); + break; + case 1: + // The resources should have been recreated. + EXPECT_EQ(2, ui_resource_->resource_create_count); + // "resource lost" callback was called once for the resource in the + // resource map. + EXPECT_EQ(1, ui_resource_->lost_resource_count); + // Resource Id on the impl-side have been recreated as well. Note + // that the same UIResourceId persists after the context lost. + if (!layer_tree_host()->settings().impl_side_painting) + EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id())); + PostSetNeedsCommitToMainThread(); + break; + case 2: + // Sequence 2: + // Currently one resource has been created. + test_id0_ = ui_resource_->id(); + // Delete this resource. + ui_resource_.reset(); + // Create another resource. + ui_resource_ = FakeScopedUIResource::Create(layer_tree_host()); + test_id1_ = ui_resource_->id(); + // Sanity check that two resource creations return different ids. + EXPECT_NE(test_id0_, test_id1_); + // Lose the context before commit. + LoseContext(); + break; + case 3: + if (!layer_tree_host()->settings().impl_side_painting) { + // The previous resource should have been deleted. + EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id0_)); + // The second resource should have been created. + EXPECT_NE(0u, impl->ResourceIdForUIResource(test_id1_)); + } + + // The second resource called the resource callback once and since the + // context is lost, a "resource lost" callback was also issued. + EXPECT_EQ(2, ui_resource_->resource_create_count); + EXPECT_EQ(1, ui_resource_->lost_resource_count); + // Clear the manager of resources. + ui_resource_.reset(); + PostSetNeedsCommitToMainThread(); + break; + case 4: + // Sequence 3: + ui_resource_ = FakeScopedUIResource::Create(layer_tree_host()); + test_id0_ = ui_resource_->id(); + // Sanity check the UIResourceId should not be 0. + EXPECT_NE(0, test_id0_); + // Usually ScopedUIResource are deleted from the manager in their + // destructor (so usually ui_resource_.reset()). But here we need + // ui_resource_ for the next step, so call DeleteUIResource directly. + layer_tree_host()->DeleteUIResource(test_id0_); + LoseContext(); + break; + case 5: + // Expect the resource callback to have been called once. + EXPECT_EQ(1, ui_resource_->resource_create_count); + // No "resource lost" callbacks. + EXPECT_EQ(0, ui_resource_->lost_resource_count); + if (!layer_tree_host()->settings().impl_side_painting) { + // The UI resource id should not be valid + EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id0_)); + } + PostSetNeedsCommitToMainThread(); + break; + case 6: + ui_resource_.reset(); + EndTest(); + break; + } + } + + virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerTreeHostContextTest::DidActivateTreeOnThread(impl); + switch (time_step_) { + case 1: + if (layer_tree_host()->settings().impl_side_painting) + EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id())); + break; + case 3: + if (layer_tree_host()->settings().impl_side_painting) { + EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id0_)); + EXPECT_NE(0u, impl->ResourceIdForUIResource(test_id1_)); + } + break; + case 5: + if (layer_tree_host()->settings().impl_side_painting) + EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id0_)); + break; + } + ++time_step_; + } + + private: + UIResourceId test_id0_; + UIResourceId test_id1_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(UIResourceLostBeforeCommit); + +// Losing UI resource before the pending trees is activated but after the +// commit. Impl-side-painting only. +class UIResourceLostBeforeActivateTree : public UIResourceLostTest { + virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerTreeHostContextTest::CommitCompleteOnThread(impl); + switch (time_step_) { + case 0: + ui_resource_ = FakeScopedUIResource::Create(layer_tree_host()); + PostSetNeedsCommitToMainThread(); + break; + case 2: + PostSetNeedsCommitToMainThread(); + break; + case 3: + test_id_ = ui_resource_->id(); + ui_resource_.reset(); + PostSetNeedsCommitToMainThread(); + break; + case 4: + PostSetNeedsCommitToMainThread(); + break; + case 5: + EndTest(); + break; + } + } + + virtual void WillActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE { + switch (time_step_) { + case 0: + break; + case 1: + // The resource creation callback has been called. + EXPECT_EQ(1, ui_resource_->resource_create_count); + // The resource is not yet lost (sanity check). + EXPECT_EQ(0, ui_resource_->lost_resource_count); + // The resource should not have been created yet on the impl-side. + EXPECT_EQ(0u, impl->ResourceIdForUIResource(ui_resource_->id())); + LoseContext(); + break; + case 3: + LoseContext(); + break; + } + } + + virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerTreeHostContextTest::DidActivateTreeOnThread(impl); + switch (time_step_) { + case 1: + // The pending requests on the impl-side should have been processed. + EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id())); + break; + case 2: + // The "lost resource" callback should have been called once. + EXPECT_EQ(1, ui_resource_->lost_resource_count); + break; + case 4: + // The resource is deleted and should not be in the manager. Use + // test_id_ since ui_resource_ has been deleted. + EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id_)); + break; + } + ++time_step_; + } + + private: + UIResourceId test_id_; +}; + +TEST_F(UIResourceLostBeforeActivateTree, + RunMultiThread_DirectRenderer_ImplSidePaint) { + RunTest(true, false, true); +} + +TEST_F(UIResourceLostBeforeActivateTree, + RunMultiThread_DelegatingRenderer_ImplSidePaint) { + RunTest(true, true, true); +} + +} // namespace +} // namespace cc |