// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/renderer/gpu/render_widget_compositor.h" #include "cc/output/begin_frame_args.h" #include "cc/test/failure_output_surface.h" #include "cc/trees/layer_tree_host.h" #include "components/scheduler/renderer/renderer_scheduler.h" #include "content/public/test/mock_render_thread.h" #include "content/renderer/render_widget.h" #include "content/test/fake_compositor_dependencies.h" #include "content/test/fake_renderer_scheduler.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/public/platform/WebScreenInfo.h" using testing::AllOf; using testing::Field; namespace content { namespace { class MockWebWidget : public blink::WebWidget { public: MOCK_METHOD1(beginFrame, void(const blink::WebBeginFrameArgs& args)); }; class TestRenderWidget : public RenderWidget { public: TestRenderWidget() : RenderWidget(blink::WebPopupTypeNone, blink::WebScreenInfo(), true, false, false) { webwidget_ = &mock_webwidget_; } MockWebWidget mock_webwidget_; protected: ~TestRenderWidget() override { webwidget_ = NULL; } DISALLOW_COPY_AND_ASSIGN(TestRenderWidget); }; class RenderWidgetCompositorTest : public testing::Test { public: RenderWidgetCompositorTest() : render_widget_(make_scoped_refptr(new TestRenderWidget())), compositor_deps_(make_scoped_ptr(new FakeCompositorDependencies)), render_widget_compositor_( RenderWidgetCompositor::Create(render_widget_.get(), compositor_deps_.get())) {} ~RenderWidgetCompositorTest() override {} protected: base::MessageLoop loop_; MockRenderThread render_thread_; scoped_refptr render_widget_; scoped_ptr compositor_deps_; scoped_ptr render_widget_compositor_; private: DISALLOW_COPY_AND_ASSIGN(RenderWidgetCompositorTest); }; TEST_F(RenderWidgetCompositorTest, BeginMainFrame) { base::TimeTicks frame_time(base::TimeTicks() + base::TimeDelta::FromSeconds(1)); base::TimeTicks deadline(base::TimeTicks() + base::TimeDelta::FromSeconds(2)); base::TimeDelta interval(base::TimeDelta::FromSeconds(3)); cc::BeginFrameArgs args( cc::BeginFrameArgs::Create(BEGINFRAME_FROM_HERE, frame_time, deadline, interval, cc::BeginFrameArgs::NORMAL)); EXPECT_CALL(render_widget_->mock_webwidget_, beginFrame(AllOf( Field(&blink::WebBeginFrameArgs::lastFrameTimeMonotonic, 1), Field(&blink::WebBeginFrameArgs::deadline, 2), Field(&blink::WebBeginFrameArgs::interval, 3)))); render_widget_compositor_->BeginMainFrame(args); } class RenderWidgetCompositorOutputSurface; class RenderWidgetOutputSurface : public TestRenderWidget { public: RenderWidgetOutputSurface() : compositor_(NULL) {} void SetCompositor(RenderWidgetCompositorOutputSurface* compositor); scoped_ptr CreateOutputSurface(bool fallback) override; protected: ~RenderWidgetOutputSurface() override {} private: RenderWidgetCompositorOutputSurface* compositor_; DISALLOW_COPY_AND_ASSIGN(RenderWidgetOutputSurface); }; // Verify that failing to create an output surface will cause the compositor // to attempt to repeatedly create another output surface. After enough // failures, verify that it attempts to create a fallback output surface. // The use null output surface parameter allows testing whether failures // from RenderWidget (couldn't create an output surface) vs failures from // the compositor (couldn't bind the output surface) are handled identically. class RenderWidgetCompositorOutputSurface : public RenderWidgetCompositor { public: RenderWidgetCompositorOutputSurface(RenderWidget* widget, CompositorDependencies* compositor_deps) : RenderWidgetCompositor(widget, compositor_deps), num_failures_before_success_(0), expected_successes_(0), expected_fallback_successes_(0), expected_requests_(0), num_requests_(0), num_requests_since_last_success_(0), num_successes_(0), num_fallback_successes_(0), num_failures_(0), last_create_was_fallback_(false), use_null_output_surface_(true) {} using RenderWidgetCompositor::Initialize; scoped_ptr CreateOutputSurface(bool fallback) { EXPECT_EQ(num_requests_since_last_success_ > OUTPUT_SURFACE_RETRIES_BEFORE_FALLBACK, fallback); last_create_was_fallback_ = fallback; bool success = num_failures_ >= num_failures_before_success_; if (success) { scoped_ptr context = cc::TestWebGraphicsContext3D::Create(); // Image support required for synchronous compositing. context->set_support_image(true); return cc::FakeOutputSurface::Create3d(context.Pass()); } return use_null_output_surface_ ? nullptr : make_scoped_ptr(new cc::FailureOutputSurface(false)); } // Force a new output surface to be created. void SynchronousComposite() { layer_tree_host()->DidLoseOutputSurface(); base::TimeTicks some_time; layer_tree_host()->Composite(some_time); } void RequestNewOutputSurface() override { ++num_requests_; ++num_requests_since_last_success_; RenderWidgetCompositor::RequestNewOutputSurface(); } void DidInitializeOutputSurface() override { if (last_create_was_fallback_) ++num_fallback_successes_; else ++num_successes_; if (num_requests_ == expected_requests_) { EndTest(); } else { num_requests_since_last_success_ = 0; RenderWidgetCompositor::DidInitializeOutputSurface(); // Post the synchronous composite task so that it is not called // reentrantly as a part of RequestNewOutputSurface. base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&RenderWidgetCompositorOutputSurface::SynchronousComposite, base::Unretained(this))); } } void DidFailToInitializeOutputSurface() override { ++num_failures_; if (num_requests_ == expected_requests_) { EndTest(); return; } RenderWidgetCompositor::DidFailToInitializeOutputSurface(); } void SetUp(bool use_null_output_surface, int num_failures_before_success, int expected_successes, int expected_fallback_succeses) { use_null_output_surface_ = use_null_output_surface; num_failures_before_success_ = num_failures_before_success; expected_successes_ = expected_successes; expected_fallback_successes_ = expected_fallback_succeses; expected_requests_ = num_failures_before_success_ + expected_successes_ + expected_fallback_successes_; } void EndTest() { base::MessageLoop::current()->Quit(); } void AfterTest() { EXPECT_EQ(num_failures_before_success_, num_failures_); EXPECT_EQ(expected_successes_, num_successes_); EXPECT_EQ(expected_fallback_successes_, num_fallback_successes_); EXPECT_EQ(expected_requests_, num_requests_); } private: int num_failures_before_success_; int expected_successes_; int expected_fallback_successes_; int expected_requests_; int num_requests_; int num_requests_since_last_success_; int num_successes_; int num_fallback_successes_; int num_failures_; bool last_create_was_fallback_; bool use_null_output_surface_; DISALLOW_COPY_AND_ASSIGN(RenderWidgetCompositorOutputSurface); }; class RenderWidgetCompositorOutputSurfaceTest : public testing::Test { public: RenderWidgetCompositorOutputSurfaceTest() : render_widget_(make_scoped_refptr(new RenderWidgetOutputSurface)), compositor_deps_(make_scoped_ptr(new FakeCompositorDependencies)) { // Required in order to call the synchronous LayerTreeHost::Composite. compositor_deps_->set_use_single_thread_scheduler(false); render_widget_compositor_.reset(new RenderWidgetCompositorOutputSurface( render_widget_.get(), compositor_deps_.get())); render_widget_compositor_->Initialize(); render_widget_->SetCompositor(render_widget_compositor_.get()); } void RunTest(bool use_null_output_surface, int num_failures_before_success, int expected_successes, int expected_fallback_succeses) { render_widget_compositor_->SetUp( use_null_output_surface, num_failures_before_success, expected_successes, expected_fallback_succeses); render_widget_compositor_->StartCompositor(); base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&RenderWidgetCompositorOutputSurface::SynchronousComposite, base::Unretained(render_widget_compositor_.get()))); base::MessageLoop::current()->Run(); render_widget_compositor_->AfterTest(); } protected: base::MessageLoop ye_olde_message_loope_; MockRenderThread render_thread_; scoped_refptr render_widget_; scoped_ptr compositor_deps_; scoped_ptr render_widget_compositor_; private: DISALLOW_COPY_AND_ASSIGN(RenderWidgetCompositorOutputSurfaceTest); }; scoped_ptr RenderWidgetOutputSurface::CreateOutputSurface( bool fallback) { return compositor_->CreateOutputSurface(fallback); } void RenderWidgetOutputSurface::SetCompositor( RenderWidgetCompositorOutputSurface* compositor) { compositor_ = compositor; } TEST_F(RenderWidgetCompositorOutputSurfaceTest, SucceedOnce) { RunTest(false, 0, 1, 0); } TEST_F(RenderWidgetCompositorOutputSurfaceTest, SucceedTwice) { RunTest(false, 0, 2, 0); } TEST_F(RenderWidgetCompositorOutputSurfaceTest, FailOnceNull) { static_assert( RenderWidgetCompositor::OUTPUT_SURFACE_RETRIES_BEFORE_FALLBACK >= 2, "Adjust the values of this test if this fails"); RunTest(true, 1, 1, 0); } TEST_F(RenderWidgetCompositorOutputSurfaceTest, FailOnceBind) { static_assert( RenderWidgetCompositor::OUTPUT_SURFACE_RETRIES_BEFORE_FALLBACK >= 2, "Adjust the values of this test if this fails"); RunTest(false, 1, 1, 0); } TEST_F(RenderWidgetCompositorOutputSurfaceTest, FallbackSuccessNull) { RunTest(true, RenderWidgetCompositor::OUTPUT_SURFACE_RETRIES_BEFORE_FALLBACK, 0, 1); } TEST_F(RenderWidgetCompositorOutputSurfaceTest, FallbackSuccessBind) { RunTest(false, RenderWidgetCompositor::OUTPUT_SURFACE_RETRIES_BEFORE_FALLBACK, 0, 1); } TEST_F(RenderWidgetCompositorOutputSurfaceTest, FallbackSuccessNormalSuccess) { // The first success is a fallback, but the next should not be a fallback. RunTest(false, RenderWidgetCompositor::OUTPUT_SURFACE_RETRIES_BEFORE_FALLBACK, 1, 1); } } // namespace } // namespace content