blob: 921b94af19f0d2e605434276663c7a8aab70d998 [file] [log] [blame]
// 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 <string>
#include <vector>
#include "base/logging.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/test_simple_task_runner.h"
#include "base/time/time.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 {
class FakeSchedulerClient;
void InitializeOutputSurfaceAndFirstCommit(Scheduler* scheduler,
FakeSchedulerClient* client);
class FakeSchedulerClient : public SchedulerClient {
public:
FakeSchedulerClient() : needs_begin_frame_(false), automatic_swap_ack_(true) {
Reset();
}
void Reset() {
actions_.clear();
states_.clear();
draw_will_happen_ = true;
swap_will_happen_if_draw_happens_ = true;
num_draws_ = 0;
log_anticipated_draw_time_change_ = false;
}
Scheduler* CreateScheduler(const SchedulerSettings& settings) {
task_runner_ = new base::TestSimpleTaskRunner;
scheduler_ = Scheduler::Create(this, settings, 0, task_runner_);
return scheduler_.get();
}
// Most tests don't care about DidAnticipatedDrawTimeChange, so only record it
// for tests that do.
void set_log_anticipated_draw_time_change(bool log) {
log_anticipated_draw_time_change_ = log;
}
bool needs_begin_frame() { return needs_begin_frame_; }
int num_draws() const { return num_draws_; }
int num_actions_() const { return static_cast<int>(actions_.size()); }
const char* Action(int i) const { return actions_[i]; }
base::Value& StateForAction(int i) const { return *states_[i]; }
base::TimeTicks posted_begin_impl_frame_deadline() const {
return posted_begin_impl_frame_deadline_;
}
base::TestSimpleTaskRunner& task_runner() { return *task_runner_; }
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;
}
void SetAutomaticSwapAck(bool automatic_swap_ack) {
automatic_swap_ack_ = automatic_swap_ack;
}
// SchedulerClient implementation.
virtual void SetNeedsBeginFrame(bool enable) OVERRIDE {
actions_.push_back("SetNeedsBeginFrame");
states_.push_back(scheduler_->StateAsValue().release());
needs_begin_frame_ = enable;
}
virtual void WillBeginImplFrame(const BeginFrameArgs& args) OVERRIDE {
actions_.push_back("WillBeginImplFrame");
states_.push_back(scheduler_->StateAsValue().release());
}
virtual void ScheduledActionSendBeginMainFrame() OVERRIDE {
actions_.push_back("ScheduledActionSendBeginMainFrame");
states_.push_back(scheduler_->StateAsValue().release());
}
virtual void ScheduledActionAnimate() OVERRIDE {
actions_.push_back("ScheduledActionAnimate");
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;
DrawSwapReadbackResult::DrawResult result =
draw_will_happen_
? DrawSwapReadbackResult::DRAW_SUCCESS
: DrawSwapReadbackResult::DRAW_ABORTED_CHECKERBOARD_ANIMATIONS;
bool swap_will_happen =
draw_will_happen_ && swap_will_happen_if_draw_happens_;
if (swap_will_happen) {
scheduler_->DidSwapBuffers();
if (automatic_swap_ack_)
scheduler_->DidSwapBuffersComplete();
}
return DrawSwapReadbackResult(
result,
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_request_swap = swap_will_happen_if_draw_happens_;
bool did_readback = false;
return DrawSwapReadbackResult(
DrawSwapReadbackResult::DRAW_SUCCESS, did_request_swap, did_readback);
}
virtual DrawSwapReadbackResult ScheduledActionDrawAndReadback() OVERRIDE {
actions_.push_back("ScheduledActionDrawAndReadback");
states_.push_back(scheduler_->StateAsValue().release());
bool did_request_swap = false;
bool did_readback = true;
return DrawSwapReadbackResult(
DrawSwapReadbackResult::DRAW_SUCCESS, did_request_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 ScheduledActionManageTiles() OVERRIDE {
actions_.push_back("ScheduledActionManageTiles");
states_.push_back(scheduler_->StateAsValue().release());
}
virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {
if (log_anticipated_draw_time_change_)
actions_.push_back("DidAnticipatedDrawTimeChange");
}
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 DidBeginImplFrameDeadline() OVERRIDE {}
protected:
bool needs_begin_frame_;
bool draw_will_happen_;
bool swap_will_happen_if_draw_happens_;
bool automatic_swap_ack_;
int num_draws_;
bool log_anticipated_draw_time_change_;
base::TimeTicks posted_begin_impl_frame_deadline_;
std::vector<const char*> actions_;
ScopedVector<base::Value> states_;
scoped_ptr<Scheduler> scheduler_;
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
};
void InitializeOutputSurfaceAndFirstCommit(Scheduler* scheduler,
FakeSchedulerClient* client) {
bool client_initiates_begin_frame =
scheduler->settings().begin_frame_scheduling_enabled &&
scheduler->settings().throttle_frame_production;
scheduler->DidCreateAndInitializeOutputSurface();
scheduler->SetNeedsCommit();
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
// Go through the motions to draw the commit.
if (client_initiates_begin_frame)
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
else
client->task_runner().RunPendingTasks(); // Run posted BeginFrame.
// Run the posted deadline task.
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client->task_runner().RunPendingTasks();
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
// We need another BeginImplFrame so Scheduler calls
// SetNeedsBeginFrame(false).
if (client_initiates_begin_frame)
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
else
client->task_runner().RunPendingTasks(); // Run posted BeginFrame.
// Run the posted deadline task.
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client->task_runner().RunPendingTasks();
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
}
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_());
}
TEST(SchedulerTest, RequestCommit) {
FakeSchedulerClient client;
SchedulerSettings scheduler_settings;
Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->SetCanDraw(true);
EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
InitializeOutputSurfaceAndFirstCommit(scheduler, &client);
// SetNeedsCommit should begin the frame on the next BeginImplFrame.
client.Reset();
scheduler->SetNeedsCommit();
EXPECT_TRUE(client.needs_begin_frame());
EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client);
client.Reset();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// If we don't swap on the deadline, we wait for the next BeginFrame.
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(0, client.num_actions_());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// NotifyReadyToCommit should trigger the commit.
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_SINGLE_ACTION("ScheduledActionCommit", client);
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// BeginImplFrame should prepare the draw.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// BeginImplFrame deadline should draw.
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 1);
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// The following BeginImplFrame deadline should SetNeedsBeginFrame(false)
// to avoid excessive toggles.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_SINGLE_ACTION("WillBeginImplFrame", client);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client);
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
}
TEST(SchedulerTest, RequestCommitAfterBeginMainFrameSent) {
FakeSchedulerClient client;
SchedulerSettings scheduler_settings;
Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->SetCanDraw(true);
EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
InitializeOutputSurfaceAndFirstCommit(scheduler, &client);
client.Reset();
// SetNeedsCommit should begin the frame.
scheduler->SetNeedsCommit();
EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client);
client.Reset();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_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->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_SINGLE_ACTION("ScheduledActionCommit", client);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_ACTION("ScheduledActionAnimate", client, 0, 2);
EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 1, 2);
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
// Because we just swapped, the Scheduler should also request the next
// BeginImplFrame from the OutputSurface.
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// Since another commit is needed, the next BeginImplFrame should initiate
// the second commit.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
// Finishing the commit before the deadline should post a new deadline task
// to trigger the deadline early.
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_SINGLE_ACTION("ScheduledActionCommit", client);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_ACTION("ScheduledActionAnimate", client, 0, 2);
EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 1, 2);
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// On the next BeginImplFrame, verify we go back to a quiescent state and
// no longer request BeginImplFrames.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
}
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_request_swap = true;
bool did_readback = false;
return DrawSwapReadbackResult(
DrawSwapReadbackResult::DRAW_SUCCESS, did_request_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);
client.Reset();
scheduler->SetNeedsRedraw();
EXPECT_TRUE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_frame());
EXPECT_EQ(0, client.num_draws());
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client.num_draws());
EXPECT_TRUE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_frame());
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(2, client.num_draws());
EXPECT_FALSE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_frame());
// We stop requesting BeginImplFrames after a BeginImplFrame where we don't
// swap.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(2, client.num_draws());
EXPECT_FALSE(scheduler->RedrawPending());
EXPECT_FALSE(client.needs_begin_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);
client.Reset();
client.SetDrawWillHappen(false);
scheduler->SetNeedsRedraw();
EXPECT_TRUE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_frame());
EXPECT_EQ(0, client.num_draws());
// Fail the draw.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
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_frame());
// Fail the draw again.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(2, client.num_draws());
EXPECT_TRUE(scheduler->CommitPending());
EXPECT_TRUE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_frame());
// Draw successfully.
client.SetDrawWillHappen(true);
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(3, client.num_draws());
EXPECT_TRUE(scheduler->CommitPending());
EXPECT_FALSE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_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_request_swap = false;
bool did_readback = false;
return DrawSwapReadbackResult(
DrawSwapReadbackResult::DRAW_SUCCESS, did_request_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);
client.Reset();
EXPECT_FALSE(client.needs_begin_frame());
scheduler->SetNeedsRedraw();
EXPECT_TRUE(scheduler->RedrawPending());
EXPECT_EQ(0, client.num_draws());
EXPECT_TRUE(client.needs_begin_frame());
client.SetNeedsCommitOnNextDraw();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.SetNeedsCommitOnNextDraw();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client.num_draws());
EXPECT_TRUE(scheduler->CommitPending());
EXPECT_TRUE(client.needs_begin_frame());
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(2, client.num_draws());
EXPECT_FALSE(scheduler->RedrawPending());
EXPECT_FALSE(scheduler->CommitPending());
EXPECT_TRUE(client.needs_begin_frame());
// We stop requesting BeginImplFrames after a BeginImplFrame where we don't
// swap.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(2, client.num_draws());
EXPECT_FALSE(scheduler->RedrawPending());
EXPECT_FALSE(scheduler->CommitPending());
EXPECT_FALSE(client.needs_begin_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);
client.Reset();
client.SetDrawWillHappen(false);
scheduler->SetNeedsRedraw();
EXPECT_TRUE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_frame());
EXPECT_EQ(0, client.num_draws());
// Fail the draw.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
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_frame());
// Fail the draw again.
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(2, client.num_draws());
EXPECT_TRUE(scheduler->CommitPending());
EXPECT_TRUE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_frame());
// Draw successfully.
client.SetDrawWillHappen(true);
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(3, client.num_draws());
EXPECT_TRUE(scheduler->CommitPending());
EXPECT_FALSE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_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);
client.Reset();
scheduler->SetNeedsRedraw();
EXPECT_TRUE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_frame());
EXPECT_EQ(0, client.num_draws());
// Draw successfully, this starts a new frame.
client.SetNeedsCommitOnNextDraw();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client.num_draws());
scheduler->SetNeedsRedraw();
EXPECT_TRUE(scheduler->RedrawPending());
EXPECT_TRUE(client.needs_begin_frame());
// Fail to draw, this should not start a frame.
client.SetDrawWillHappen(false);
client.SetNeedsCommitOnNextDraw();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
client.task_runner().RunPendingTasks(); // Run posted deadline.
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->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
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->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndReadback"));
client.Reset();
scheduler->SetNeedsForcedCommitForReadback();
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndReadback"));
// The replacement commit comes in after 2 readbacks.
client.Reset();
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
}
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, &client);
// 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_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->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
// On the deadline, he actions should have occured in the right order.
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
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());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
// 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_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->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
// 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();
client.task_runner().RunPendingTasks(); // Run posted deadline.
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());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
// We need a BeginImplFrame where we don't swap to go idle.
client.Reset();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_SINGLE_ACTION("WillBeginImplFrame", client);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client);
EXPECT_FALSE(client.needs_begin_frame());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
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_frame());
scheduler->SetNeedsManageTiles();
EXPECT_TRUE(client.needs_begin_frame());
EXPECT_TRUE(scheduler->ManageTilesPending());
EXPECT_FALSE(scheduler->RedrawPending());
// BeginImplFrame. There will be no draw, only ManageTiles.
client.Reset();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_SINGLE_ACTION("WillBeginImplFrame", client);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(0, client.num_draws());
EXPECT_FALSE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));
EXPECT_TRUE(client.HasAction("ScheduledActionManageTiles"));
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
}
// Test that ManageTiles only happens once per frame. If an external caller
// initiates it, then the state machine should not ManageTiles on that frame.
TEST(SchedulerTest, ManageTilesOncePerFrame) {
FakeSchedulerClient client;
SchedulerSettings default_scheduler_settings;
Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->SetCanDraw(true);
InitializeOutputSurfaceAndFirstCommit(scheduler, &client);
// If DidManageTiles during a frame, then ManageTiles should not occur again.
scheduler->SetNeedsManageTiles();
scheduler->SetNeedsRedraw();
client.Reset();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(scheduler->ManageTilesPending());
scheduler->DidManageTiles(); // An explicit ManageTiles.
EXPECT_FALSE(scheduler->ManageTilesPending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client.num_draws());
EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));
EXPECT_FALSE(client.HasAction("ScheduledActionManageTiles"));
EXPECT_FALSE(scheduler->RedrawPending());
EXPECT_FALSE(scheduler->ManageTilesPending());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
// Next frame without DidManageTiles should ManageTiles with draw.
scheduler->SetNeedsManageTiles();
scheduler->SetNeedsRedraw();
client.Reset();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
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());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
scheduler->DidManageTiles(); // Corresponds to ScheduledActionManageTiles
// If we get another DidManageTiles within the same frame, we should
// not ManageTiles on the next frame.
scheduler->DidManageTiles(); // An explicit ManageTiles.
scheduler->SetNeedsManageTiles();
scheduler->SetNeedsRedraw();
client.Reset();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(scheduler->ManageTilesPending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client.num_draws());
EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));
EXPECT_FALSE(client.HasAction("ScheduledActionManageTiles"));
EXPECT_FALSE(scheduler->RedrawPending());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
// If we get another DidManageTiles, we should not ManageTiles on the next
// frame. This verifies we don't alternate calling ManageTiles once and twice.
EXPECT_TRUE(scheduler->ManageTilesPending());
scheduler->DidManageTiles(); // An explicit ManageTiles.
EXPECT_FALSE(scheduler->ManageTilesPending());
scheduler->SetNeedsManageTiles();
scheduler->SetNeedsRedraw();
client.Reset();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(scheduler->ManageTilesPending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client.num_draws());
EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));
EXPECT_FALSE(client.HasAction("ScheduledActionManageTiles"));
EXPECT_FALSE(scheduler->RedrawPending());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
// Next frame without DidManageTiles should ManageTiles with draw.
scheduler->SetNeedsManageTiles();
scheduler->SetNeedsRedraw();
client.Reset();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
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());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
scheduler->DidManageTiles(); // Corresponds to ScheduledActionManageTiles
}
TEST(SchedulerTest, TriggerBeginFrameDeadlineEarly) {
SchedulerClientNeedsManageTilesInDraw client;
SchedulerSettings default_scheduler_settings;
Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->SetCanDraw(true);
InitializeOutputSurfaceAndFirstCommit(scheduler, &client);
client.Reset();
scheduler->SetNeedsRedraw();
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
// The deadline should be zero since there is no work other than drawing
// pending.
EXPECT_EQ(base::TimeTicks(), client.posted_begin_impl_frame_deadline());
}
class SchedulerClientWithFixedEstimates : public FakeSchedulerClient {
public:
SchedulerClientWithFixedEstimates(
base::TimeDelta draw_duration,
base::TimeDelta begin_main_frame_to_commit_duration,
base::TimeDelta commit_to_activate_duration)
: draw_duration_(draw_duration),
begin_main_frame_to_commit_duration_(
begin_main_frame_to_commit_duration),
commit_to_activate_duration_(commit_to_activate_duration) {}
virtual base::TimeDelta DrawDurationEstimate() OVERRIDE {
return draw_duration_;
}
virtual base::TimeDelta BeginMainFrameToCommitDurationEstimate() OVERRIDE {
return begin_main_frame_to_commit_duration_;
}
virtual base::TimeDelta CommitToActivateDurationEstimate() OVERRIDE {
return commit_to_activate_duration_;
}
private:
base::TimeDelta draw_duration_;
base::TimeDelta begin_main_frame_to_commit_duration_;
base::TimeDelta commit_to_activate_duration_;
};
void MainFrameInHighLatencyMode(int64 begin_main_frame_to_commit_estimate_in_ms,
int64 commit_to_activate_estimate_in_ms,
bool smoothness_takes_priority,
bool should_send_begin_main_frame) {
// Set up client with specified estimates (draw duration is set to 1).
SchedulerClientWithFixedEstimates client(
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMilliseconds(
begin_main_frame_to_commit_estimate_in_ms),
base::TimeDelta::FromMilliseconds(commit_to_activate_estimate_in_ms));
SchedulerSettings default_scheduler_settings;
Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->SetCanDraw(true);
scheduler->SetSmoothnessTakesPriority(smoothness_takes_priority);
InitializeOutputSurfaceAndFirstCommit(scheduler, &client);
// Impl thread hits deadline before commit finishes.
client.Reset();
scheduler->SetNeedsCommit();
EXPECT_FALSE(scheduler->MainThreadIsInHighLatencyMode());
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_FALSE(scheduler->MainThreadIsInHighLatencyMode());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_TRUE(scheduler->MainThreadIsInHighLatencyMode());
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_TRUE(scheduler->MainThreadIsInHighLatencyMode());
EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame"));
client.Reset();
scheduler->SetNeedsCommit();
EXPECT_TRUE(scheduler->MainThreadIsInHighLatencyMode());
scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
EXPECT_TRUE(scheduler->MainThreadIsInHighLatencyMode());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(scheduler->MainThreadIsInHighLatencyMode(),
should_send_begin_main_frame);
EXPECT_EQ(client.HasAction("ScheduledActionSendBeginMainFrame"),
should_send_begin_main_frame);
}
TEST(SchedulerTest,
SkipMainFrameIfHighLatencyAndCanCommitAndActivateBeforeDeadline) {
// Set up client so that estimates indicate that we can commit and activate
// before the deadline (~8ms by default).
MainFrameInHighLatencyMode(1, 1, false, false);
}
TEST(SchedulerTest, NotSkipMainFrameIfHighLatencyAndCanCommitTooLong) {
// Set up client so that estimates indicate that the commit cannot finish
// before the deadline (~8ms by default).
MainFrameInHighLatencyMode(10, 1, false, true);
}
TEST(SchedulerTest, NotSkipMainFrameIfHighLatencyAndCanActivateTooLong) {
// Set up client so that estimates indicate that the activate cannot finish
// before the deadline (~8ms by default).
MainFrameInHighLatencyMode(1, 10, false, true);
}
TEST(SchedulerTest, NotSkipMainFrameInPreferSmoothnessMode) {
// Set up client so that estimates indicate that we can commit and activate
// before the deadline (~8ms by default), but also enable smoothness takes
// priority mode.
MainFrameInHighLatencyMode(1, 1, true, true);
}
TEST(SchedulerTest, PollForCommitCompletion) {
// Since we are simulating a long commit, set up a client with draw duration
// estimates that prevent skipping main frames to get to low latency mode.
SchedulerClientWithFixedEstimates client(
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMilliseconds(32),
base::TimeDelta::FromMilliseconds(32));
client.set_log_anticipated_draw_time_change(true);
SchedulerSettings default_scheduler_settings;
Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
scheduler->SetCanDraw(true);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->DidCreateAndInitializeOutputSurface();
scheduler->SetNeedsCommit();
EXPECT_TRUE(scheduler->CommitPending());
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
scheduler->SetNeedsRedraw();
BeginFrameArgs frame_args = BeginFrameArgs::CreateForTesting();
frame_args.interval = base::TimeDelta::FromMilliseconds(1000);
scheduler->BeginFrame(frame_args);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
scheduler->DidSwapBuffers();
scheduler->DidSwapBuffersComplete();
// At this point, we've drawn a frame. Start another commit, but hold off on
// the NotifyReadyToCommit for now.
EXPECT_FALSE(scheduler->CommitPending());
scheduler->SetNeedsCommit();
scheduler->BeginFrame(frame_args);
EXPECT_TRUE(scheduler->CommitPending());
// Draw and swap the frame, but don't ack the swap to simulate the Browser
// blocking on the renderer.
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
scheduler->DidSwapBuffers();
// Spin the event loop a few times and make sure we get more
// DidAnticipateDrawTimeChange calls every time.
int actions_so_far = client.num_actions_();
// Does three iterations to make sure that the timer is properly repeating.
for (int i = 0; i < 3; ++i) {
EXPECT_EQ((frame_args.interval * 2).InMicroseconds(),
client.task_runner().NextPendingTaskDelay().InMicroseconds())
<< *scheduler->StateAsValue();
client.task_runner().RunPendingTasks();
EXPECT_GT(client.num_actions_(), actions_so_far);
EXPECT_STREQ(client.Action(client.num_actions_() - 1),
"DidAnticipatedDrawTimeChange");
actions_so_far = client.num_actions_();
}
// Do the same thing after BeginMainFrame starts but still before activation.
scheduler->NotifyBeginMainFrameStarted();
for (int i = 0; i < 3; ++i) {
EXPECT_EQ((frame_args.interval * 2).InMicroseconds(),
client.task_runner().NextPendingTaskDelay().InMicroseconds())
<< *scheduler->StateAsValue();
client.task_runner().RunPendingTasks();
EXPECT_GT(client.num_actions_(), actions_so_far);
EXPECT_STREQ(client.Action(client.num_actions_() - 1),
"DidAnticipatedDrawTimeChange");
actions_so_far = client.num_actions_();
}
}
TEST(SchedulerTest, BeginRetroFrame) {
FakeSchedulerClient client;
SchedulerSettings scheduler_settings;
Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->SetCanDraw(true);
InitializeOutputSurfaceAndFirstCommit(scheduler, &client);
// SetNeedsCommit should begin the frame on the next BeginImplFrame.
client.Reset();
scheduler->SetNeedsCommit();
EXPECT_TRUE(client.needs_begin_frame());
EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client);
client.Reset();
// Create a BeginFrame with a long deadline to avoid race conditions.
// This is the first BeginFrame, which will be handled immediately.
BeginFrameArgs args = BeginFrameArgs::CreateForTesting();
args.deadline += base::TimeDelta::FromHours(1);
scheduler->BeginFrame(args);
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// Queue BeginFrames while we are still handling the previous BeginFrame.
args.frame_time += base::TimeDelta::FromSeconds(1);
scheduler->BeginFrame(args);
args.frame_time += base::TimeDelta::FromSeconds(1);
scheduler->BeginFrame(args);
// If we don't swap on the deadline, we wait for the next BeginImplFrame.
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(0, client.num_actions_());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// NotifyReadyToCommit should trigger the commit.
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_SINGLE_ACTION("ScheduledActionCommit", client);
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// BeginImplFrame should prepare the draw.
client.task_runner().RunPendingTasks(); // Run posted BeginRetroFrame.
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// BeginImplFrame deadline should draw.
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 1);
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// The following BeginImplFrame deadline should SetNeedsBeginFrame(false)
// to avoid excessive toggles.
client.task_runner().RunPendingTasks(); // Run posted BeginRetroFrame.
EXPECT_SINGLE_ACTION("WillBeginImplFrame", client);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client);
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
}
TEST(SchedulerTest, BeginRetroFrame_SwapThrottled) {
FakeSchedulerClient client;
SchedulerSettings scheduler_settings;
Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->SetCanDraw(true);
InitializeOutputSurfaceAndFirstCommit(scheduler, &client);
// To test swap ack throttling, this test disables automatic swap acks.
scheduler->SetMaxSwapsPending(1);
client.SetAutomaticSwapAck(false);
// SetNeedsCommit should begin the frame on the next BeginImplFrame.
client.Reset();
scheduler->SetNeedsCommit();
EXPECT_TRUE(client.needs_begin_frame());
EXPECT_SINGLE_ACTION("SetNeedsBeginFrame", client);
client.Reset();
// Create a BeginFrame with a long deadline to avoid race conditions.
// This is the first BeginFrame, which will be handled immediately.
BeginFrameArgs args = BeginFrameArgs::CreateForTesting();
args.deadline += base::TimeDelta::FromHours(1);
scheduler->BeginFrame(args);
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// Queue BeginFrame while we are still handling the previous BeginFrame.
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
args.frame_time += base::TimeDelta::FromSeconds(1);
scheduler->BeginFrame(args);
EXPECT_EQ(0, client.num_actions_());
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
// NotifyReadyToCommit should trigger the pending commit and draw.
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_SINGLE_ACTION("ScheduledActionCommit", client);
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// Swapping will put us into a swap throttled state.
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_ACTION("ScheduledActionAnimate", client, 0, 2);
EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 1, 2);
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// While swap throttled, BeginRetroFrames should trigger BeginImplFrames
// but not a BeginMainFrame or draw.
scheduler->SetNeedsCommit();
client.task_runner().RunPendingTasks(); // Run posted BeginRetroFrame.
EXPECT_ACTION("WillBeginImplFrame", client, 0, 1);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// Queue BeginFrame while we are still handling the previous BeginFrame.
args.frame_time += base::TimeDelta::FromSeconds(1);
scheduler->BeginFrame(args);
EXPECT_EQ(0, client.num_actions_());
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// Take us out of a swap throttled state.
scheduler->DidSwapBuffersComplete();
EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 1);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
// BeginImplFrame deadline should draw.
scheduler->SetNeedsRedraw();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_ACTION("ScheduledActionAnimate", client, 0, 2);
EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 1, 2);
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_TRUE(client.needs_begin_frame());
client.Reset();
}
void BeginFramesNotFromClient(bool begin_frame_scheduling_enabled,
bool throttle_frame_production) {
FakeSchedulerClient client;
SchedulerSettings scheduler_settings;
scheduler_settings.begin_frame_scheduling_enabled =
begin_frame_scheduling_enabled;
scheduler_settings.throttle_frame_production = throttle_frame_production;
Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->SetCanDraw(true);
InitializeOutputSurfaceAndFirstCommit(scheduler, &client);
// SetNeedsCommit should begin the frame on the next BeginImplFrame
// without calling SetNeedsBeginFrame.
client.Reset();
scheduler->SetNeedsCommit();
EXPECT_FALSE(client.needs_begin_frame());
EXPECT_EQ(0, client.num_actions_());
client.Reset();
// When the client-driven BeginFrame are disabled, the scheduler posts it's
// own BeginFrame tasks.
client.task_runner().RunPendingTasks(); // Run posted BeginFrame.
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// If we don't swap on the deadline, we wait for the next BeginFrame.
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(0, client.num_actions_());
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// NotifyReadyToCommit should trigger the commit.
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_SINGLE_ACTION("ScheduledActionCommit", client);
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// BeginImplFrame should prepare the draw.
client.task_runner().RunPendingTasks(); // Run posted BeginFrame.
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionAnimate", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// BeginImplFrame deadline should draw.
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 1);
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// The following BeginImplFrame deadline should SetNeedsBeginFrame(false)
// to avoid excessive toggles.
client.task_runner().RunPendingTasks(); // Run posted BeginFrame.
EXPECT_SINGLE_ACTION("WillBeginImplFrame", client);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
client.Reset();
// Make sure SetNeedsBeginFrame isn't called on the client
// when the BeginFrame is no longer needed.
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(0, client.num_actions_());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
}
TEST(SchedulerTest, SyntheticBeginFrames) {
bool begin_frame_scheduling_enabled = false;
bool throttle_frame_production = true;
BeginFramesNotFromClient(begin_frame_scheduling_enabled,
throttle_frame_production);
}
TEST(SchedulerTest, VSyncThrottlingDisabled) {
bool begin_frame_scheduling_enabled = true;
bool throttle_frame_production = false;
BeginFramesNotFromClient(begin_frame_scheduling_enabled,
throttle_frame_production);
}
TEST(SchedulerTest, SyntheticBeginFrames_And_VSyncThrottlingDisabled) {
bool begin_frame_scheduling_enabled = false;
bool throttle_frame_production = false;
BeginFramesNotFromClient(begin_frame_scheduling_enabled,
throttle_frame_production);
}
void BeginFramesNotFromClient_SwapThrottled(bool begin_frame_scheduling_enabled,
bool throttle_frame_production) {
FakeSchedulerClient client;
SchedulerSettings scheduler_settings;
scheduler_settings.begin_frame_scheduling_enabled =
begin_frame_scheduling_enabled;
scheduler_settings.throttle_frame_production = throttle_frame_production;
Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
scheduler->SetCanStart();
scheduler->SetVisible(true);
scheduler->SetCanDraw(true);
InitializeOutputSurfaceAndFirstCommit(scheduler, &client);
// To test swap ack throttling, this test disables automatic swap acks.
scheduler->SetMaxSwapsPending(1);
client.SetAutomaticSwapAck(false);
// SetNeedsCommit should begin the frame on the next BeginImplFrame.
client.Reset();
scheduler->SetNeedsCommit();
EXPECT_FALSE(client.needs_begin_frame());
EXPECT_EQ(0, client.num_actions_());
client.Reset();
// Trigger the first BeginImplFrame and BeginMainFrame
client.task_runner().RunPendingTasks(); // Run posted BeginFrame.
EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 2);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// NotifyReadyToCommit should trigger the pending commit and draw.
scheduler->NotifyBeginMainFrameStarted();
scheduler->NotifyReadyToCommit();
EXPECT_SINGLE_ACTION("ScheduledActionCommit", client);
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// Swapping will put us into a swap throttled state.
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_ACTION("ScheduledActionAnimate", client, 0, 2);
EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 1, 2);
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// While swap throttled, BeginFrames should trigger BeginImplFrames,
// but not a BeginMainFrame or draw.
scheduler->SetNeedsCommit();
client.task_runner().RunPendingTasks(); // Run posted BeginFrame.
EXPECT_ACTION("WillBeginImplFrame", client, 0, 1);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// Take us out of a swap throttled state.
scheduler->DidSwapBuffersComplete();
EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 1);
EXPECT_TRUE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
// BeginImplFrame deadline should draw.
scheduler->SetNeedsRedraw();
client.task_runner().RunPendingTasks(); // Run posted deadline.
EXPECT_ACTION("ScheduledActionAnimate", client, 0, 2);
EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 1, 2);
EXPECT_FALSE(scheduler->BeginImplFrameDeadlinePending());
EXPECT_FALSE(client.needs_begin_frame());
client.Reset();
}
TEST(SchedulerTest, SyntheticBeginFrames_SwapThrottled) {
bool begin_frame_scheduling_enabled = false;
bool throttle_frame_production = true;
BeginFramesNotFromClient_SwapThrottled(begin_frame_scheduling_enabled,
throttle_frame_production);
}
TEST(SchedulerTest, VSyncThrottlingDisabled_SwapThrottled) {
bool begin_frame_scheduling_enabled = true;
bool throttle_frame_production = false;
BeginFramesNotFromClient_SwapThrottled(begin_frame_scheduling_enabled,
throttle_frame_production);
}
TEST(SchedulerTest,
SyntheticBeginFrames_And_VSyncThrottlingDisabled_SwapThrottled) {
bool begin_frame_scheduling_enabled = false;
bool throttle_frame_production = false;
BeginFramesNotFromClient_SwapThrottled(begin_frame_scheduling_enabled,
throttle_frame_production);
}
} // namespace
} // namespace cc