| // 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 <utility> |
| |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "cc/animation/animation_host.h" |
| #include "cc/output/begin_frame_args.h" |
| #include "cc/output/copy_output_request.h" |
| #include "cc/test/fake_layer_tree_frame_sink.h" |
| #include "cc/test/test_context_provider.h" |
| #include "cc/test/test_web_graphics_context_3d.h" |
| #include "cc/trees/layer_tree_host.h" |
| #include "content/public/common/screen_info.h" |
| #include "content/public/test/mock_render_thread.h" |
| #include "content/renderer/render_widget.h" |
| #include "content/test/fake_compositor_dependencies.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::AllOf; |
| using testing::Field; |
| |
| namespace content { |
| namespace { |
| |
| class StubRenderWidgetCompositorDelegate |
| : public RenderWidgetCompositorDelegate { |
| public: |
| // RenderWidgetCompositorDelegate implementation. |
| void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta, |
| const gfx::Vector2dF& outer_delta, |
| const gfx::Vector2dF& elastic_overscroll_delta, |
| float page_scale, |
| float top_controls_delta) override {} |
| void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel, |
| bool has_scrolled_by_touch) override {} |
| void BeginMainFrame(double frame_time_sec) override {} |
| void RequestNewLayerTreeFrameSink( |
| bool fallback, |
| const LayerTreeFrameSinkCallback& callback) override { |
| callback.Run(nullptr); |
| } |
| void DidCommitAndDrawCompositorFrame() override {} |
| void DidCommitCompositorFrame() override {} |
| void DidCompletePageScaleAnimation() override {} |
| void DidReceiveCompositorFrameAck() override {} |
| bool IsClosing() const override { return false; } |
| void RequestScheduleAnimation() override {} |
| void UpdateVisualState() override {} |
| void WillBeginCompositorFrame() override {} |
| std::unique_ptr<cc::SwapPromise> RequestCopyOfOutputForLayoutTest( |
| std::unique_ptr<cc::CopyOutputRequest> request) override { |
| return nullptr; |
| } |
| }; |
| |
| class FakeRenderWidgetCompositorDelegate |
| : public StubRenderWidgetCompositorDelegate { |
| public: |
| FakeRenderWidgetCompositorDelegate() = default; |
| |
| void RequestNewLayerTreeFrameSink( |
| bool fallback, |
| const LayerTreeFrameSinkCallback& callback) override { |
| EXPECT_EQ(num_requests_since_last_success_ > |
| RenderWidgetCompositor:: |
| LAYER_TREE_FRAME_SINK_RETRIES_BEFORE_FALLBACK, |
| fallback); |
| last_create_was_fallback_ = fallback; |
| |
| bool success = num_failures_ >= num_failures_before_success_; |
| if (!success && use_null_layer_tree_frame_sink_) { |
| callback.Run(std::unique_ptr<cc::LayerTreeFrameSink>()); |
| return; |
| } |
| |
| auto context_provider = cc::TestContextProvider::Create(); |
| if (!success) { |
| context_provider->UnboundTestContext3d()->loseContextCHROMIUM( |
| GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB); |
| } |
| callback.Run( |
| cc::FakeLayerTreeFrameSink::Create3d(std::move(context_provider))); |
| } |
| |
| void add_success() { |
| if (last_create_was_fallback_) |
| ++num_fallback_successes_; |
| else |
| ++num_successes_; |
| num_requests_since_last_success_ = 0; |
| } |
| int num_successes() const { return num_successes_; } |
| int num_fallback_successes() const { return num_fallback_successes_; } |
| |
| void add_request() { |
| ++num_requests_since_last_success_; |
| ++num_requests_; |
| } |
| int num_requests() const { return num_requests_; } |
| |
| void add_failure() { ++num_failures_; } |
| int num_failures() const { return num_failures_; } |
| |
| void set_num_failures_before_success(int n) { |
| num_failures_before_success_ = n; |
| } |
| int num_failures_before_success() const { |
| return num_failures_before_success_; |
| } |
| |
| void set_use_null_layer_tree_frame_sink(bool u) { |
| use_null_layer_tree_frame_sink_ = u; |
| } |
| bool use_null_layer_tree_frame_sink() const { |
| return use_null_layer_tree_frame_sink_; |
| } |
| |
| private: |
| int num_requests_ = 0; |
| int num_requests_since_last_success_ = 0; |
| int num_failures_ = 0; |
| int num_failures_before_success_ = 0; |
| int num_fallback_successes_ = 0; |
| int num_successes_ = 0; |
| bool last_create_was_fallback_ = false; |
| bool use_null_layer_tree_frame_sink_ = true; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakeRenderWidgetCompositorDelegate); |
| }; |
| |
| // 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 RenderWidgetLayerTreeFrameSink : public RenderWidgetCompositor { |
| public: |
| RenderWidgetLayerTreeFrameSink(FakeRenderWidgetCompositorDelegate* delegate, |
| CompositorDependencies* compositor_deps) |
| : RenderWidgetCompositor(delegate, compositor_deps), |
| delegate_(delegate) {} |
| |
| using RenderWidgetCompositor::Initialize; |
| |
| // Force a new output surface to be created. |
| void SynchronousComposite() { |
| layer_tree_host()->SetVisible(false); |
| layer_tree_host()->ReleaseLayerTreeFrameSink(); |
| layer_tree_host()->SetVisible(true); |
| |
| base::TimeTicks some_time; |
| layer_tree_host()->Composite(some_time); |
| } |
| |
| void RequestNewLayerTreeFrameSink() override { |
| delegate_->add_request(); |
| RenderWidgetCompositor::RequestNewLayerTreeFrameSink(); |
| } |
| |
| void DidInitializeLayerTreeFrameSink() override { |
| delegate_->add_success(); |
| if (delegate_->num_requests() == expected_requests_) { |
| EndTest(); |
| } else { |
| RenderWidgetCompositor::DidInitializeLayerTreeFrameSink(); |
| // Post the synchronous composite task so that it is not called |
| // reentrantly as a part of RequestNewLayerTreeFrameSink. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(&RenderWidgetLayerTreeFrameSink::SynchronousComposite, |
| base::Unretained(this))); |
| } |
| } |
| |
| void DidFailToInitializeLayerTreeFrameSink() override { |
| delegate_->add_failure(); |
| if (delegate_->num_requests() == expected_requests_) { |
| EndTest(); |
| return; |
| } |
| |
| RenderWidgetCompositor::DidFailToInitializeLayerTreeFrameSink(); |
| } |
| |
| void SetUp(int expected_successes, int expected_fallback_succeses) { |
| expected_successes_ = expected_successes; |
| expected_fallback_successes_ = expected_fallback_succeses; |
| expected_requests_ = delegate_->num_failures_before_success() + |
| expected_successes_ + expected_fallback_successes_; |
| } |
| |
| void EndTest() { base::MessageLoop::current()->QuitWhenIdle(); } |
| |
| void AfterTest() { |
| EXPECT_EQ(delegate_->num_failures_before_success(), |
| delegate_->num_failures()); |
| EXPECT_EQ(expected_successes_, delegate_->num_successes()); |
| EXPECT_EQ(expected_fallback_successes_, |
| delegate_->num_fallback_successes()); |
| EXPECT_EQ(expected_requests_, delegate_->num_requests()); |
| } |
| |
| private: |
| FakeRenderWidgetCompositorDelegate* delegate_; |
| int expected_successes_ = 0; |
| int expected_fallback_successes_ = 0; |
| int expected_requests_ = 0; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetLayerTreeFrameSink); |
| }; |
| |
| class RenderWidgetLayerTreeFrameSinkTest : public testing::Test { |
| public: |
| RenderWidgetLayerTreeFrameSinkTest() |
| : render_widget_compositor_(&compositor_delegate_, &compositor_deps_) { |
| auto animation_host = cc::AnimationHost::CreateMainInstance(); |
| |
| ScreenInfo dummy_screen_info; |
| const float initial_device_scale_factor = 1.f; |
| |
| auto layer_tree_host = RenderWidgetCompositor::CreateLayerTreeHost( |
| &render_widget_compositor_, &render_widget_compositor_, |
| animation_host.get(), &compositor_deps_, initial_device_scale_factor, |
| dummy_screen_info); |
| render_widget_compositor_.Initialize(std::move(layer_tree_host), |
| std::move(animation_host)); |
| } |
| |
| void RunTest(bool use_null_layer_tree_frame_sink, |
| int num_failures_before_success, |
| int expected_successes, |
| int expected_fallback_succeses) { |
| compositor_delegate_.set_use_null_layer_tree_frame_sink( |
| use_null_layer_tree_frame_sink); |
| compositor_delegate_.set_num_failures_before_success( |
| num_failures_before_success); |
| render_widget_compositor_.SetUp(expected_successes, |
| expected_fallback_succeses); |
| render_widget_compositor_.SetVisible(true); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(&RenderWidgetLayerTreeFrameSink::SynchronousComposite, |
| base::Unretained(&render_widget_compositor_))); |
| base::RunLoop().Run(); |
| render_widget_compositor_.AfterTest(); |
| } |
| |
| protected: |
| base::MessageLoop ye_olde_message_loope_; |
| MockRenderThread render_thread_; |
| FakeCompositorDependencies compositor_deps_; |
| FakeRenderWidgetCompositorDelegate compositor_delegate_; |
| RenderWidgetLayerTreeFrameSink render_widget_compositor_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetLayerTreeFrameSinkTest); |
| }; |
| |
| TEST_F(RenderWidgetLayerTreeFrameSinkTest, SucceedOnce) { |
| RunTest(false, 0, 1, 0); |
| } |
| |
| TEST_F(RenderWidgetLayerTreeFrameSinkTest, SucceedTwice) { |
| RunTest(false, 0, 2, 0); |
| } |
| |
| TEST_F(RenderWidgetLayerTreeFrameSinkTest, FailOnceNull) { |
| static_assert( |
| RenderWidgetCompositor::LAYER_TREE_FRAME_SINK_RETRIES_BEFORE_FALLBACK >= |
| 2, |
| "Adjust the values of this test if this fails"); |
| RunTest(true, 1, 1, 0); |
| } |
| |
| TEST_F(RenderWidgetLayerTreeFrameSinkTest, FailOnceBind) { |
| static_assert( |
| RenderWidgetCompositor::LAYER_TREE_FRAME_SINK_RETRIES_BEFORE_FALLBACK >= |
| 2, |
| "Adjust the values of this test if this fails"); |
| RunTest(false, 1, 1, 0); |
| } |
| |
| // Android doesn't support fallback frame sinks. (crbug.com/721102) |
| #ifndef OS_ANDROID |
| TEST_F(RenderWidgetLayerTreeFrameSinkTest, FallbackSuccessNull) { |
| RunTest(true, |
| RenderWidgetCompositor::LAYER_TREE_FRAME_SINK_RETRIES_BEFORE_FALLBACK, |
| 0, 1); |
| } |
| |
| TEST_F(RenderWidgetLayerTreeFrameSinkTest, FallbackSuccessBind) { |
| RunTest(false, |
| RenderWidgetCompositor::LAYER_TREE_FRAME_SINK_RETRIES_BEFORE_FALLBACK, |
| 0, 1); |
| } |
| |
| TEST_F(RenderWidgetLayerTreeFrameSinkTest, FallbackSuccessNormalSuccess) { |
| // The first success is a fallback, but the next should not be a fallback. |
| RunTest(false, |
| RenderWidgetCompositor::LAYER_TREE_FRAME_SINK_RETRIES_BEFORE_FALLBACK, |
| 1, 1); |
| } |
| #endif |
| |
| } // namespace |
| } // namespace content |