// 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/scheduler/scheduler.h" #include #include #include "base/logging.h" #include "base/memory/scoped_vector.h" #include "cc/test/scheduler_test_common.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #define EXPECT_ACTION(action, client, action_index, expected_num_actions) \ EXPECT_EQ(expected_num_actions, client.num_actions_()); \ ASSERT_LT(action_index, client.num_actions_()); \ do { \ EXPECT_STREQ(action, client.Action(action_index)); \ for (int i = expected_num_actions; i < client.num_actions_(); ++i) \ ADD_FAILURE() << "Unexpected action: " << client.Action(i) << \ " with state:\n" << client.StateForAction(action_index); \ } while (false) #define EXPECT_SINGLE_ACTION(action, client) \ EXPECT_ACTION(action, client, 0, 1) namespace cc { namespace { void InitializeOutputSurfaceAndFirstCommit(Scheduler* scheduler) { scheduler->DidCreateAndInitializeOutputSurface(); scheduler->SetNeedsCommit(); scheduler->FinishCommit(); // Go through the motions to draw the commit. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); // We need another BeginImplFrame so Scheduler calls // SetNeedsBeginImplFrame(false). scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); } class FakeSchedulerClient : public SchedulerClient { public: FakeSchedulerClient() : needs_begin_impl_frame_(false) { Reset(); } void Reset() { actions_.clear(); states_.clear(); draw_will_happen_ = true; swap_will_happen_if_draw_happens_ = true; num_draws_ = 0; } Scheduler* CreateScheduler(const SchedulerSettings& settings) { scheduler_ = Scheduler::Create(this, settings); return scheduler_.get(); } bool needs_begin_impl_frame() { return needs_begin_impl_frame_; } int num_draws() const { return num_draws_; } int num_actions_() const { return static_cast(actions_.size()); } const char* Action(int i) const { return actions_[i]; } base::Value& StateForAction(int i) const { return *states_[i]; } int ActionIndex(const char* action) const { for (size_t i = 0; i < actions_.size(); i++) if (!strcmp(actions_[i], action)) return i; return -1; } bool HasAction(const char* action) const { return ActionIndex(action) >= 0; } void SetDrawWillHappen(bool draw_will_happen) { draw_will_happen_ = draw_will_happen; } void SetSwapWillHappenIfDrawHappens(bool swap_will_happen_if_draw_happens) { swap_will_happen_if_draw_happens_ = swap_will_happen_if_draw_happens; } // Scheduler Implementation. virtual void SetNeedsBeginImplFrame(bool enable) OVERRIDE { actions_.push_back("SetNeedsBeginImplFrame"); states_.push_back(scheduler_->StateAsValue().release()); needs_begin_impl_frame_ = enable; } virtual void ScheduledActionSendBeginMainFrame() OVERRIDE { actions_.push_back("ScheduledActionSendBeginMainFrame"); states_.push_back(scheduler_->StateAsValue().release()); } virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible() OVERRIDE { actions_.push_back("ScheduledActionDrawAndSwapIfPossible"); states_.push_back(scheduler_->StateAsValue().release()); num_draws_++; bool did_readback = false; return DrawSwapReadbackResult( draw_will_happen_, draw_will_happen_ && swap_will_happen_if_draw_happens_, did_readback); } virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapForced() OVERRIDE { actions_.push_back("ScheduledActionDrawAndSwapForced"); states_.push_back(scheduler_->StateAsValue().release()); bool did_draw = true; bool did_swap = swap_will_happen_if_draw_happens_; bool did_readback = false; return DrawSwapReadbackResult(did_draw, did_swap, did_readback); } virtual DrawSwapReadbackResult ScheduledActionDrawAndReadback() OVERRIDE { actions_.push_back("ScheduledActionDrawAndReadback"); states_.push_back(scheduler_->StateAsValue().release()); bool did_draw = true; bool did_swap = false; bool did_readback = true; return DrawSwapReadbackResult(did_draw, did_swap, did_readback); } virtual void ScheduledActionCommit() OVERRIDE { actions_.push_back("ScheduledActionCommit"); states_.push_back(scheduler_->StateAsValue().release()); } virtual void ScheduledActionUpdateVisibleTiles() OVERRIDE { actions_.push_back("ScheduledActionUpdateVisibleTiles"); states_.push_back(scheduler_->StateAsValue().release()); } virtual void ScheduledActionActivatePendingTree() OVERRIDE { actions_.push_back("ScheduledActionActivatePendingTree"); states_.push_back(scheduler_->StateAsValue().release()); } virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE { actions_.push_back("ScheduledActionBeginOutputSurfaceCreation"); states_.push_back(scheduler_->StateAsValue().release()); } virtual void ScheduledActionAcquireLayerTexturesForMainThread() OVERRIDE { actions_.push_back("ScheduledActionAcquireLayerTexturesForMainThread"); states_.push_back(scheduler_->StateAsValue().release()); } virtual void ScheduledActionManageTiles() OVERRIDE { actions_.push_back("ScheduledActionManageTiles"); states_.push_back(scheduler_->StateAsValue().release()); } virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {} virtual base::TimeDelta DrawDurationEstimate() OVERRIDE { return base::TimeDelta(); } virtual base::TimeDelta BeginMainFrameToCommitDurationEstimate() OVERRIDE { return base::TimeDelta(); } virtual base::TimeDelta CommitToActivateDurationEstimate() OVERRIDE { return base::TimeDelta(); } virtual void PostBeginImplFrameDeadline(const base::Closure& closure, base::TimeTicks deadline) OVERRIDE { actions_.push_back("PostBeginImplFrameDeadlineTask"); states_.push_back(scheduler_->StateAsValue().release()); } virtual void DidBeginImplFrameDeadline() OVERRIDE {} protected: bool needs_begin_impl_frame_; bool draw_will_happen_; bool swap_will_happen_if_draw_happens_; int num_draws_; std::vector actions_; ScopedVector states_; scoped_ptr scheduler_; }; TEST(SchedulerTest, InitializeOutputSurfaceDoesNotBeginImplFrame) { FakeSchedulerClient client; SchedulerSettings default_scheduler_settings; Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client); client.Reset(); scheduler->DidCreateAndInitializeOutputSurface(); EXPECT_EQ(0, client.num_actions_()); } void RequestCommit(bool deadline_scheduling_enabled) { FakeSchedulerClient client; SchedulerSettings scheduler_settings; scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled; Scheduler* scheduler = client.CreateScheduler(scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client); InitializeOutputSurfaceAndFirstCommit(scheduler); // SetNeedsCommit should begin the frame on the next BeginImplFrame. client.Reset(); scheduler->SetNeedsCommit(); EXPECT_TRUE(client.needs_begin_impl_frame()); if (deadline_scheduling_enabled) { EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); } else { EXPECT_EQ(client.num_actions_(), 2); EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame")); EXPECT_TRUE(client.HasAction("SetNeedsBeginImplFrame")); } client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); if (deadline_scheduling_enabled) { EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2); EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2); } else { EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); } EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); // If we don't swap on the deadline, we need to request another // BeginImplFrame. scheduler->OnBeginImplFrameDeadline(); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); // FinishCommit should commit scheduler->FinishCommit(); EXPECT_SINGLE_ACTION("ScheduledActionCommit", client); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); // BeginImplFrame should prepare the draw. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); // BeginImplFrame deadline should draw. scheduler->OnBeginImplFrameDeadline(); EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); // The following BeginImplFrame deadline should SetNeedsBeginImplFrame(false) // to avoid excessive toggles. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); EXPECT_FALSE(client.needs_begin_impl_frame()); client.Reset(); } TEST(SchedulerTest, RequestCommit) { bool deadline_scheduling_enabled = false; RequestCommit(deadline_scheduling_enabled); } TEST(SchedulerTest, RequestCommit_Deadline) { bool deadline_scheduling_enabled = true; RequestCommit(deadline_scheduling_enabled); } void RequestCommitAfterBeginMainFrameSent( bool deadline_scheduling_enabled) { FakeSchedulerClient client; SchedulerSettings scheduler_settings; scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled; Scheduler* scheduler = client.CreateScheduler(scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client); InitializeOutputSurfaceAndFirstCommit(scheduler); client.Reset(); // SetNeedsCommit should begin the frame. scheduler->SetNeedsCommit(); if (deadline_scheduling_enabled) { EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); } else { EXPECT_EQ(client.num_actions_(), 2); EXPECT_TRUE(client.HasAction("SetNeedsBeginImplFrame")); EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame")); } client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); if (deadline_scheduling_enabled) { EXPECT_EQ(client.num_actions_(), 2); EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame")); EXPECT_TRUE(client.HasAction("PostBeginImplFrameDeadlineTask")); } else { EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); } EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); // Now SetNeedsCommit again. Calling here means we need a second commit. scheduler->SetNeedsCommit(); EXPECT_EQ(client.num_actions_(), 0); client.Reset(); // Finish the first commit. scheduler->FinishCommit(); EXPECT_ACTION("ScheduledActionCommit", client, 0, 2); EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2); client.Reset(); scheduler->OnBeginImplFrameDeadline(); if (deadline_scheduling_enabled) { EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2); } else { EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 3); EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 3); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 2, 3); } // Because we just swapped, the Scheduler should also request the next // BeginImplFrame from the OutputSurface. EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); // Since another commit is needed, the next BeginImplFrame should initiate // the second commit. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); if (deadline_scheduling_enabled) { EXPECT_EQ(client.num_actions_(), 2); EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame")); EXPECT_TRUE(client.HasAction("PostBeginImplFrameDeadlineTask")); } else { EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); } client.Reset(); // Finishing the commit before the deadline should post a new deadline task // to trigger the deadline early. scheduler->FinishCommit(); EXPECT_ACTION("ScheduledActionCommit", client, 0, 2); EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); // On the next BeginImplFrame, verify we go back to a quiescent state and // no longer request BeginImplFrames. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_FALSE(client.needs_begin_impl_frame()); client.Reset(); } TEST(SchedulerTest, RequestCommitAfterBeginMainFrameSent) { bool deadline_scheduling_enabled = false; RequestCommitAfterBeginMainFrameSent(deadline_scheduling_enabled); } TEST(SchedulerTest, RequestCommitAfterBeginMainFrameSent_Deadline) { bool deadline_scheduling_enabled = true; RequestCommitAfterBeginMainFrameSent(deadline_scheduling_enabled); } void TextureAcquisitionCausesCommitInsteadOfDraw( bool deadline_scheduling_enabled) { FakeSchedulerClient client; SchedulerSettings scheduler_settings; scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled; Scheduler* scheduler = client.CreateScheduler(scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client); InitializeOutputSurfaceAndFirstCommit(scheduler); client.Reset(); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_FALSE(client.needs_begin_impl_frame()); client.Reset(); scheduler->SetMainThreadNeedsLayerTextures(); EXPECT_SINGLE_ACTION("ScheduledActionAcquireLayerTexturesForMainThread", client); // We should request a BeginImplFrame in anticipation of a draw. client.Reset(); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); EXPECT_TRUE(client.needs_begin_impl_frame()); // No draw happens since the textures are acquired by the main thread. client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); scheduler->SetNeedsCommit(); if (deadline_scheduling_enabled) { EXPECT_EQ(0, client.num_actions_()); } else { EXPECT_SINGLE_ACTION("ScheduledActionSendBeginMainFrame", client); } client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); if (deadline_scheduling_enabled) { EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2); EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2); } else { EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); } // Commit will release the texture. client.Reset(); scheduler->FinishCommit(); EXPECT_ACTION("ScheduledActionCommit", client, 0, 2); EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2); EXPECT_TRUE(scheduler->RedrawPending()); // Now we can draw again after the commit happens. client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); // Make sure we stop requesting BeginImplFrames if we don't swap. client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); EXPECT_FALSE(client.needs_begin_impl_frame()); } TEST(SchedulerTest, TextureAcquisitionCausesCommitInsteadOfDraw) { bool deadline_scheduling_enabled = false; TextureAcquisitionCausesCommitInsteadOfDraw(deadline_scheduling_enabled); } TEST(SchedulerTest, TextureAcquisitionCausesCommitInsteadOfDraw_Deadline) { bool deadline_scheduling_enabled = true; TextureAcquisitionCausesCommitInsteadOfDraw(deadline_scheduling_enabled); } void TextureAcquisitionCollision(bool deadline_scheduling_enabled) { FakeSchedulerClient client; SchedulerSettings scheduler_settings; scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled; Scheduler* scheduler = client.CreateScheduler(scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client); InitializeOutputSurfaceAndFirstCommit(scheduler); client.Reset(); scheduler->SetNeedsCommit(); if (deadline_scheduling_enabled) { EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); } else { EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2); } client.Reset(); scheduler->SetMainThreadNeedsLayerTextures(); EXPECT_SINGLE_ACTION( "ScheduledActionAcquireLayerTexturesForMainThread", client); client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); if (deadline_scheduling_enabled) { EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2); EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2); } else { EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); } client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); // Although the compositor cannot draw because textures are locked by main // thread, we continue requesting SetNeedsBeginImplFrame in anticipation of // the unlock. EXPECT_TRUE(client.needs_begin_impl_frame()); // Trigger the commit scheduler->FinishCommit(); EXPECT_TRUE(client.needs_begin_impl_frame()); // Between commit and draw, texture acquisition for main thread delayed, // and main thread blocks. client.Reset(); scheduler->SetMainThreadNeedsLayerTextures(); EXPECT_EQ(0, client.num_actions_()); // No implicit commit is expected. client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 3); EXPECT_ACTION( "ScheduledActionAcquireLayerTexturesForMainThread", client, 1, 3); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 2, 3); EXPECT_TRUE(client.needs_begin_impl_frame()); // The compositor should not draw because textures are locked by main // thread. client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); EXPECT_FALSE(client.needs_begin_impl_frame()); // The impl thread need an explicit commit from the main thread to lock // the textures. client.Reset(); scheduler->SetNeedsCommit(); if (deadline_scheduling_enabled) { EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client); } else { EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2); } EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); if (deadline_scheduling_enabled) { EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2); EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2); } else { EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); } client.Reset(); // Trigger the commit, which will trigger the deadline task early. scheduler->FinishCommit(); EXPECT_ACTION("ScheduledActionCommit", client, 0, 2); EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); // Verify we draw on the next BeginImplFrame deadline scheduler->OnBeginImplFrameDeadline(); EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2); EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2); EXPECT_TRUE(client.needs_begin_impl_frame()); client.Reset(); } TEST(SchedulerTest, TextureAcquisitionCollision) { bool deadline_scheduling_enabled = false; TextureAcquisitionCollision(deadline_scheduling_enabled); } TEST(SchedulerTest, TextureAcquisitionCollision_Deadline) { bool deadline_scheduling_enabled = true; TextureAcquisitionCollision(deadline_scheduling_enabled); } void VisibilitySwitchWithTextureAcquisition(bool deadline_scheduling_enabled) { FakeSchedulerClient client; SchedulerSettings scheduler_settings; scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled; Scheduler* scheduler = client.CreateScheduler(scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client); client.Reset(); scheduler->DidCreateAndInitializeOutputSurface(); scheduler->SetNeedsCommit(); if (deadline_scheduling_enabled) { scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); } scheduler->FinishCommit(); scheduler->SetMainThreadNeedsLayerTextures(); scheduler->SetNeedsCommit(); client.Reset(); // Verify that pending texture acquisition fires when visibility // is lost in order to avoid a deadlock. scheduler->SetVisible(false); EXPECT_SINGLE_ACTION("ScheduledActionAcquireLayerTexturesForMainThread", client); client.Reset(); scheduler->SetVisible(true); EXPECT_EQ(0, client.num_actions_()); EXPECT_TRUE(client.needs_begin_impl_frame()); // Regaining visibility with textures acquired by main thread while // compositor is waiting for first draw should result in a request // for a new frame in order to escape a deadlock. client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2); EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2); } TEST(SchedulerTest, VisibilitySwitchWithTextureAcquisition) { bool deadline_scheduling_enabled = false; VisibilitySwitchWithTextureAcquisition(deadline_scheduling_enabled); } TEST(SchedulerTest, VisibilitySwitchWithTextureAcquisition_Deadline) { bool deadline_scheduling_enabled = true; VisibilitySwitchWithTextureAcquisition(deadline_scheduling_enabled); } class SchedulerClientThatsetNeedsDrawInsideDraw : public FakeSchedulerClient { public: virtual void ScheduledActionSendBeginMainFrame() OVERRIDE {} virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible() OVERRIDE { // Only SetNeedsRedraw the first time this is called if (!num_draws_) scheduler_->SetNeedsRedraw(); return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible(); } virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapForced() OVERRIDE { NOTREACHED(); bool did_draw = true; bool did_swap = true; bool did_readback = false; return DrawSwapReadbackResult(did_draw, did_swap, did_readback); } virtual void ScheduledActionCommit() OVERRIDE {} virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE {} virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {} }; // Tests for two different situations: // 1. the scheduler dropping SetNeedsRedraw requests that happen inside // a ScheduledActionDrawAndSwap // 2. the scheduler drawing twice inside a single tick TEST(SchedulerTest, RequestRedrawInsideDraw) { SchedulerClientThatsetNeedsDrawInsideDraw client; SchedulerSettings default_scheduler_settings; Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); InitializeOutputSurfaceAndFirstCommit(scheduler); client.Reset(); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); EXPECT_EQ(0, client.num_draws()); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(1, client.num_draws()); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(2, client.num_draws()); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); // We stop requesting BeginImplFrames after a BeginImplFrame where we don't // swap. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(2, client.num_draws()); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_FALSE(client.needs_begin_impl_frame()); } // Test that requesting redraw inside a failed draw doesn't lose the request. TEST(SchedulerTest, RequestRedrawInsideFailedDraw) { SchedulerClientThatsetNeedsDrawInsideDraw client; SchedulerSettings default_scheduler_settings; Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); InitializeOutputSurfaceAndFirstCommit(scheduler); client.Reset(); client.SetDrawWillHappen(false); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); EXPECT_EQ(0, client.num_draws()); // Fail the draw. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(1, client.num_draws()); // We have a commit pending and the draw failed, and we didn't lose the redraw // request. EXPECT_TRUE(scheduler->CommitPending()); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); // Fail the draw again. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(2, client.num_draws()); EXPECT_TRUE(scheduler->CommitPending()); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); // Draw successfully. client.SetDrawWillHappen(true); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(3, client.num_draws()); EXPECT_TRUE(scheduler->CommitPending()); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); } class SchedulerClientThatSetNeedsCommitInsideDraw : public FakeSchedulerClient { public: SchedulerClientThatSetNeedsCommitInsideDraw() : set_needs_commit_on_next_draw_(false) {} virtual void ScheduledActionSendBeginMainFrame() OVERRIDE {} virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible() OVERRIDE { // Only SetNeedsCommit the first time this is called if (set_needs_commit_on_next_draw_) { scheduler_->SetNeedsCommit(); set_needs_commit_on_next_draw_ = false; } return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible(); } virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapForced() OVERRIDE { NOTREACHED(); bool did_draw = true; bool did_swap = false; bool did_readback = false; return DrawSwapReadbackResult(did_draw, did_swap, did_readback); } virtual void ScheduledActionCommit() OVERRIDE {} virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE {} virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {} void SetNeedsCommitOnNextDraw() { set_needs_commit_on_next_draw_ = true; } private: bool set_needs_commit_on_next_draw_; }; // Tests for the scheduler infinite-looping on SetNeedsCommit requests that // happen inside a ScheduledActionDrawAndSwap TEST(SchedulerTest, RequestCommitInsideDraw) { SchedulerClientThatSetNeedsCommitInsideDraw client; SchedulerSettings default_scheduler_settings; Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); InitializeOutputSurfaceAndFirstCommit(scheduler); client.Reset(); EXPECT_FALSE(client.needs_begin_impl_frame()); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_EQ(0, client.num_draws()); EXPECT_TRUE(client.needs_begin_impl_frame()); client.SetNeedsCommitOnNextDraw(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); client.SetNeedsCommitOnNextDraw(); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(1, client.num_draws()); EXPECT_TRUE(scheduler->CommitPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); scheduler->FinishCommit(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(2, client.num_draws()); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_FALSE(scheduler->CommitPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); // We stop requesting BeginImplFrames after a BeginImplFrame where we don't // swap. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(2, client.num_draws()); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_FALSE(scheduler->CommitPending()); EXPECT_FALSE(client.needs_begin_impl_frame()); } // Tests that when a draw fails then the pending commit should not be dropped. TEST(SchedulerTest, RequestCommitInsideFailedDraw) { SchedulerClientThatsetNeedsDrawInsideDraw client; SchedulerSettings default_scheduler_settings; Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); InitializeOutputSurfaceAndFirstCommit(scheduler); client.Reset(); client.SetDrawWillHappen(false); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); EXPECT_EQ(0, client.num_draws()); // Fail the draw. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(1, client.num_draws()); // We have a commit pending and the draw failed, and we didn't lose the commit // request. EXPECT_TRUE(scheduler->CommitPending()); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); // Fail the draw again. scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(2, client.num_draws()); EXPECT_TRUE(scheduler->CommitPending()); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); // Draw successfully. client.SetDrawWillHappen(true); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(3, client.num_draws()); EXPECT_TRUE(scheduler->CommitPending()); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); } TEST(SchedulerTest, NoSwapWhenDrawFails) { SchedulerClientThatSetNeedsCommitInsideDraw client; SchedulerSettings default_scheduler_settings; Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); InitializeOutputSurfaceAndFirstCommit(scheduler); client.Reset(); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); EXPECT_EQ(0, client.num_draws()); // Draw successfully, this starts a new frame. client.SetNeedsCommitOnNextDraw(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(1, client.num_draws()); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); // Fail to draw, this should not start a frame. client.SetDrawWillHappen(false); client.SetNeedsCommitOnNextDraw(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(2, client.num_draws()); } TEST(SchedulerTest, NoSwapWhenSwapFailsDuringForcedCommit) { FakeSchedulerClient client; SchedulerSettings default_scheduler_settings; Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings); // Tell the client that it will fail to swap. client.SetDrawWillHappen(true); client.SetSwapWillHappenIfDrawHappens(false); // Get the compositor to do a ScheduledActionDrawAndReadback. scheduler->SetCanDraw(true); scheduler->SetNeedsRedraw(); scheduler->SetNeedsForcedCommitForReadback(); scheduler->FinishCommit(); EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndReadback")); } TEST(SchedulerTest, BackToBackReadbackAllowed) { // Some clients call readbacks twice in a row before the replacement // commit comes in. Make sure it is allowed. FakeSchedulerClient client; SchedulerSettings default_scheduler_settings; Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings); // Get the compositor to do 2 ScheduledActionDrawAndReadbacks before // the replacement commit comes in. scheduler->SetCanDraw(true); scheduler->SetNeedsRedraw(); scheduler->SetNeedsForcedCommitForReadback(); scheduler->FinishCommit(); EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndReadback")); client.Reset(); scheduler->SetNeedsForcedCommitForReadback(); scheduler->FinishCommit(); EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndReadback")); // The replacement commit comes in after 2 readbacks. client.Reset(); scheduler->FinishCommit(); } class SchedulerClientNeedsManageTilesInDraw : public FakeSchedulerClient { public: virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible() OVERRIDE { scheduler_->SetNeedsManageTiles(); return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible(); } }; // Test manage tiles is independant of draws. TEST(SchedulerTest, ManageTiles) { SchedulerClientNeedsManageTilesInDraw client; SchedulerSettings default_scheduler_settings; Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings); scheduler->SetCanStart(); scheduler->SetVisible(true); scheduler->SetCanDraw(true); InitializeOutputSurfaceAndFirstCommit(scheduler); // Request both draw and manage tiles. ManageTiles shouldn't // be trigged until BeginImplFrame. client.Reset(); scheduler->SetNeedsManageTiles(); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_TRUE(scheduler->ManageTilesPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); EXPECT_EQ(0, client.num_draws()); EXPECT_FALSE(client.HasAction("ScheduledActionManageTiles")); EXPECT_FALSE(client.HasAction("ScheduledActionDrawAndSwapIfPossible")); // We have no immediate actions to perform, so the BeginImplFrame should post // the deadline task. client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); // On the deadline, he actions should have occured in the right order. client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(1, client.num_draws()); EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapIfPossible")); EXPECT_TRUE(client.HasAction("ScheduledActionManageTiles")); EXPECT_LT(client.ActionIndex("ScheduledActionDrawAndSwapIfPossible"), client.ActionIndex("ScheduledActionManageTiles")); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_FALSE(scheduler->ManageTilesPending()); // Request a draw. We don't need a ManageTiles yet. client.Reset(); scheduler->SetNeedsRedraw(); EXPECT_TRUE(scheduler->RedrawPending()); EXPECT_FALSE(scheduler->ManageTilesPending()); EXPECT_TRUE(client.needs_begin_impl_frame()); EXPECT_EQ(0, client.num_draws()); // We have no immediate actions to perform, so the BeginImplFrame should post // the deadline task. client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); // Draw. The draw will trigger SetNeedsManageTiles, and // then the ManageTiles action will be triggered after the Draw. // Afterwards, neither a draw nor ManageTiles are pending. client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(1, client.num_draws()); EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapIfPossible")); EXPECT_TRUE(client.HasAction("ScheduledActionManageTiles")); EXPECT_LT(client.ActionIndex("ScheduledActionDrawAndSwapIfPossible"), client.ActionIndex("ScheduledActionManageTiles")); EXPECT_FALSE(scheduler->RedrawPending()); EXPECT_FALSE(scheduler->ManageTilesPending()); // We need a BeginImplFrame where we don't swap to go idle. client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);; EXPECT_EQ(0, client.num_draws()); // Now trigger a ManageTiles outside of a draw. We will then need // a begin-frame for the ManageTiles, but we don't need a draw. client.Reset(); EXPECT_FALSE(client.needs_begin_impl_frame()); scheduler->SetNeedsManageTiles(); EXPECT_TRUE(client.needs_begin_impl_frame()); EXPECT_TRUE(scheduler->ManageTilesPending()); EXPECT_FALSE(scheduler->RedrawPending()); // BeginImplFrame. There will be no draw, only ManageTiles. client.Reset(); scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting()); EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client); client.Reset(); scheduler->OnBeginImplFrameDeadline(); EXPECT_EQ(0, client.num_draws()); EXPECT_FALSE(client.HasAction("ScheduledActionDrawAndSwapIfPossible")); EXPECT_TRUE(client.HasAction("ScheduledActionManageTiles")); } } // namespace } // namespace cc