blob: 4d9c77ad14d1a20b5aa36403bede390098ed33c5 [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 <stddef.h>
#include <string>
#include <vector>
#include "base/auto_reset.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/run_loop.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "cc/test/scheduler_test_common.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/test/begin_frame_args_test.h"
#include "components/viz/test/fake_delay_based_time_source.h"
#include "components/viz/test/fake_external_begin_frame_source.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#define EXPECT_ACTIONS(...) \
EXPECT_THAT(client_->Actions(), ::testing::ElementsAre(__VA_ARGS__))
#define EXPECT_NO_ACTION() EXPECT_THAT(client_->Actions(), ::testing::IsEmpty())
#define EXPECT_SCOPED(statements) \
{ \
SCOPED_TRACE(""); \
statements; \
}
namespace cc {
namespace {
base::TimeDelta kSlowDuration = base::TimeDelta::FromSeconds(1);
base::TimeDelta kFastDuration = base::TimeDelta::FromMilliseconds(1);
class FakeSchedulerClient : public SchedulerClient,
public viz::FakeExternalBeginFrameSource::Client {
public:
FakeSchedulerClient() { Reset(); }
void Reset() {
actions_.clear();
states_.clear();
will_begin_impl_frame_causes_redraw_ = false;
will_begin_impl_frame_requests_one_begin_impl_frame_ = false;
invalidate_needs_redraw_ = true;
draw_will_happen_ = true;
swap_will_happen_if_draw_happens_ = true;
num_draws_ = 0;
last_begin_main_frame_args_ = viz::BeginFrameArgs();
last_begin_frame_ack_ = viz::BeginFrameAck();
}
void set_scheduler(TestScheduler* scheduler) { scheduler_ = scheduler; }
bool needs_begin_frames() { return scheduler_->begin_frames_expected(); }
int num_draws() const { return num_draws_; }
bool invalidate_needs_redraw() const { return invalidate_needs_redraw_; }
const std::vector<std::string> Actions() const {
return std::vector<std::string>(actions_.begin(), actions_.end());
}
std::string StateForAction(int i) const { return states_[i]->ToString(); }
base::TimeTicks posted_begin_impl_frame_deadline() const {
return posted_begin_impl_frame_deadline_;
}
int ActionIndex(const char* action) const {
for (size_t i = 0; i < actions_.size(); i++)
if (!strcmp(actions_[i], action))
return base::checked_cast<int>(i);
return -1;
}
bool HasAction(const char* action) const { return ActionIndex(action) >= 0; }
void SetWillBeginImplFrameRequestsOneBeginImplFrame(bool request) {
will_begin_impl_frame_requests_one_begin_impl_frame_ = request;
}
void SetWillBeginImplFrameCausesRedraw(bool causes_redraw) {
will_begin_impl_frame_causes_redraw_ = causes_redraw;
}
void SetInvalidateNeedsRedraw(bool needs_redraw) {
invalidate_needs_redraw_ = needs_redraw;
}
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 SetAutomaticSubmitCompositorFrameAck(bool automatic_ack) {
automatic_ack_ = automatic_ack;
}
void SetWillBeginImplFrameMightHaveDamage(bool might_have_damage) {
will_begin_impl_frame_might_have_damage_ = might_have_damage;
}
// SchedulerClient implementation.
bool WillBeginImplFrame(const viz::BeginFrameArgs& args) override {
EXPECT_FALSE(inside_begin_impl_frame_);
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
inside_begin_impl_frame_ = true;
PushAction("WillBeginImplFrame");
if (will_begin_impl_frame_requests_one_begin_impl_frame_)
scheduler_->SetNeedsOneBeginImplFrame();
if (will_begin_impl_frame_causes_redraw_)
scheduler_->SetNeedsRedraw();
return will_begin_impl_frame_might_have_damage_;
}
void DidFinishImplFrame() override {
EXPECT_TRUE(inside_begin_impl_frame_);
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
inside_begin_impl_frame_ = false;
}
void DidNotProduceFrame(const viz::BeginFrameAck& ack) override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
last_begin_frame_ack_ = ack;
}
void ScheduledActionSendBeginMainFrame(
const viz::BeginFrameArgs& args) override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("ScheduledActionSendBeginMainFrame");
last_begin_main_frame_args_ = args;
}
const viz::BeginFrameArgs& last_begin_main_frame_args() {
return last_begin_main_frame_args_;
}
const viz::BeginFrameAck& last_begin_frame_ack() {
return last_begin_frame_ack_;
}
DrawResult ScheduledActionDrawIfPossible() override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("ScheduledActionDrawIfPossible");
num_draws_++;
DrawResult result =
draw_will_happen_ ? DRAW_SUCCESS : DRAW_ABORTED_CHECKERBOARD_ANIMATIONS;
bool swap_will_happen =
draw_will_happen_ && swap_will_happen_if_draw_happens_;
if (swap_will_happen) {
last_begin_frame_ack_ = scheduler_->CurrentBeginFrameAckForActiveTree();
scheduler_->DidSubmitCompositorFrame();
if (automatic_ack_)
scheduler_->DidReceiveCompositorFrameAck();
}
return result;
}
DrawResult ScheduledActionDrawForced() override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("ScheduledActionDrawForced");
last_begin_frame_ack_ = scheduler_->CurrentBeginFrameAckForActiveTree();
return DRAW_SUCCESS;
}
void ScheduledActionCommit() override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("ScheduledActionCommit");
scheduler_->DidCommit();
}
void ScheduledActionActivateSyncTree() override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("ScheduledActionActivateSyncTree");
}
void ScheduledActionBeginLayerTreeFrameSinkCreation() override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("ScheduledActionBeginLayerTreeFrameSinkCreation");
}
void ScheduledActionPrepareTiles() override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("ScheduledActionPrepareTiles");
scheduler_->WillPrepareTiles();
scheduler_->DidPrepareTiles();
}
void ScheduledActionInvalidateLayerTreeFrameSink(bool needs_redraw) override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
invalidate_needs_redraw_ = needs_redraw;
actions_.push_back("ScheduledActionInvalidateLayerTreeFrameSink");
states_.push_back(scheduler_->AsValue());
}
void ScheduledActionPerformImplSideInvalidation() override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("ScheduledActionPerformImplSideInvalidation");
}
void SendBeginMainFrameNotExpectedSoon() override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("SendBeginMainFrameNotExpectedSoon");
}
void ScheduledActionBeginMainFrameNotExpectedUntil(
base::TimeTicks time) override {
EXPECT_FALSE(inside_action_);
base::AutoReset<bool> mark_inside(&inside_action_, true);
PushAction("ScheduledActionBeginMainFrameNotExpectedUntil");
}
bool IsInsideBeginImplFrame() const { return inside_begin_impl_frame_; }
base::Callback<bool(void)> InsideBeginImplFrame(bool state) {
return base::Bind(&FakeSchedulerClient::InsideBeginImplFrameCallback,
base::Unretained(this), state);
}
bool IsCurrentFrame(int last_frame_number) const {
return scheduler_->current_frame_number() == last_frame_number;
}
base::Callback<bool(void)> FrameHasNotAdvancedCallback() {
return base::Bind(&FakeSchedulerClient::IsCurrentFrame,
base::Unretained(this),
scheduler_->current_frame_number());
}
void PushAction(const char* description) {
actions_.push_back(description);
states_.push_back(scheduler_->AsValue());
}
// FakeExternalBeginFrameSource::Client implementation.
void OnAddObserver(viz::BeginFrameObserver* obs) override {
PushAction("AddObserver(this)");
}
void OnRemoveObserver(viz::BeginFrameObserver* obs) override {
PushAction("RemoveObserver(this)");
}
size_t CompositedAnimationsCount() const override { return 0; }
size_t MainThreadAnimationsCount() const override { return 0; }
bool CurrentFrameHadRAF() const override { return false; }
bool NextFrameHasPendingRAF() const override { return false; }
protected:
bool InsideBeginImplFrameCallback(bool state) {
return inside_begin_impl_frame_ == state;
}
bool inside_action_ = false;
bool inside_begin_impl_frame_ = false;
bool will_begin_impl_frame_causes_redraw_;
bool will_begin_impl_frame_requests_one_begin_impl_frame_;
bool invalidate_needs_redraw_ = true;
bool draw_will_happen_;
bool swap_will_happen_if_draw_happens_;
bool automatic_ack_ = true;
bool will_begin_impl_frame_might_have_damage_ = true;
int num_draws_;
viz::BeginFrameArgs last_begin_main_frame_args_;
viz::BeginFrameAck last_begin_frame_ack_;
base::TimeTicks posted_begin_impl_frame_deadline_;
std::vector<const char*> actions_;
std::vector<std::unique_ptr<base::trace_event::ConvertableToTraceFormat>>
states_;
TestScheduler* scheduler_ = nullptr;
};
enum BeginFrameSourceType {
EXTERNAL_BFS,
UNTHROTTLED_BFS,
THROTTLED_BFS,
};
class SchedulerTestTaskRunner : public base::TestMockTimeTaskRunner {
public:
SchedulerTestTaskRunner()
: base::TestMockTimeTaskRunner(
base::TestMockTimeTaskRunner::Type::kStandalone) {
AdvanceMockTickClock(base::TimeDelta::FromMicroseconds(110000));
}
void RunUntilTime(base::TimeTicks end_time) {
FastForwardBy(end_time - NowTicks());
}
// Runs all tasks posted before this call.
void RunPendingTasks() {
base::circular_deque<base::TestPendingTask> tasks = TakePendingTasks();
while (!tasks.empty()) {
base::TestPendingTask task = std::move(tasks.front());
tasks.pop_front();
// Set clock to the beginning of task and run it.
AdvanceMockTickClock(task.GetTimeToRun() - NowTicks());
std::move(task.task).Run();
}
}
// Runs tasks while condition is met.
// Condition is being checked when task exists and before it gets selected.
void RunTasksWhile(base::RepeatingCallback<bool()> condition) {
run_condition_ = condition;
FastForwardUntilNoTasksRemain();
run_condition_ = base::nullopt;
// We've moved all the pending tasks away to break the execution loop,
// now we should restore them.
while (!tasks_to_requeue_.empty()) {
base::TestPendingTask task = std::move(tasks_to_requeue_.front());
tasks_to_requeue_.pop_front();
PostDelayedTask(task.location, std::move(task.task),
task.GetTimeToRun() - NowTicks());
}
}
protected:
void OnBeforeSelectingTask() override {
// Avoid potential infinite loops.
ASSERT_LT(++task_count_, 100u);
if (run_condition_ && HasPendingTask() && !run_condition_->Run()) {
// Execution will not continue because we move all the pending tasks away.
tasks_to_requeue_ = TakePendingTasks();
}
}
private:
~SchedulerTestTaskRunner() override = default; // Ref-counted.
size_t task_count_ = 0u;
base::Optional<base::RepeatingCallback<bool()>> run_condition_;
base::circular_deque<base::TestPendingTask> tasks_to_requeue_;
};
class SchedulerTest : public testing::Test {
public:
SchedulerTest()
: task_runner_(base::MakeRefCounted<SchedulerTestTaskRunner>()),
fake_external_begin_frame_source_(nullptr),
fake_compositor_timing_history_(nullptr) {}
~SchedulerTest() override = default;
protected:
TestScheduler* CreateScheduler(BeginFrameSourceType bfs_type) {
viz::BeginFrameSource* frame_source = nullptr;
unthrottled_frame_source_.reset(new viz::BackToBackBeginFrameSource(
std::make_unique<viz::FakeDelayBasedTimeSource>(
task_runner_->GetMockTickClock(), task_runner_.get())));
fake_external_begin_frame_source_.reset(
new viz::FakeExternalBeginFrameSource(1.0, false));
fake_external_begin_frame_source_->SetClient(client_.get());
synthetic_frame_source_ = std::make_unique<viz::DelayBasedBeginFrameSource>(
std::make_unique<viz::FakeDelayBasedTimeSource>(
task_runner_->GetMockTickClock(), task_runner_.get()),
viz::BeginFrameSource::kNotRestartableId);
switch (bfs_type) {
case EXTERNAL_BFS:
frame_source = fake_external_begin_frame_source_.get();
break;
case UNTHROTTLED_BFS:
frame_source = unthrottled_frame_source_.get();
break;
case THROTTLED_BFS:
frame_source = synthetic_frame_source_.get();
break;
}
DCHECK(frame_source);
std::unique_ptr<FakeCompositorTimingHistory>
fake_compositor_timing_history = FakeCompositorTimingHistory::Create(
scheduler_settings_.using_synchronous_renderer_compositor);
fake_compositor_timing_history_ = fake_compositor_timing_history.get();
scheduler_.reset(new TestScheduler(
task_runner_->GetMockTickClock(), client_.get(), scheduler_settings_, 0,
task_runner_.get(), std::move(fake_compositor_timing_history)));
client_->set_scheduler(scheduler_.get());
scheduler_->SetBeginFrameSource(frame_source);
// Use large estimates by default to avoid latency recovery in most tests.
fake_compositor_timing_history_->SetAllEstimatesTo(kSlowDuration);
return scheduler_.get();
}
void SetUpScheduler(BeginFrameSourceType bfs_type,
std::unique_ptr<FakeSchedulerClient> client) {
client_ = std::move(client);
CreateScheduler(bfs_type);
EXPECT_SCOPED(InitializeLayerTreeFrameSinkAndFirstCommit());
}
void SetUpScheduler(BeginFrameSourceType bfs_type) {
SetUpScheduler(bfs_type, std::make_unique<FakeSchedulerClient>());
}
void SetUpSchedulerWithNoLayerTreeFrameSink(BeginFrameSourceType bfs_type) {
client_ = std::make_unique<FakeSchedulerClient>();
CreateScheduler(bfs_type);
}
// As this function contains EXPECT macros, to allow debugging it should be
// called inside EXPECT_SCOPED like so;
// EXPECT_SCOPED(
// client.InitializeLayerTreeFrameSinkAndFirstCommit(scheduler));
void InitializeLayerTreeFrameSinkAndFirstCommit() {
TRACE_EVENT0(
"cc", "SchedulerUnitTest::InitializeLayerTreeFrameSinkAndFirstCommit");
DCHECK(scheduler_);
// Check the client doesn't have any actions queued when calling this
// function.
EXPECT_NO_ACTION();
EXPECT_FALSE(scheduler_->begin_frames_expected());
// Start the initial LayerTreeFrameSink creation.
scheduler_->SetVisible(true);
scheduler_->SetCanDraw(true);
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation");
client_->Reset();
// We don't see anything happening until the first impl frame.
scheduler_->DidCreateAndInitializeLayerTreeFrameSink();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_TRUE(scheduler_->begin_frames_expected());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
{
SCOPED_TRACE("Do first frame to commit after initialize.");
AdvanceFrame();
task_runner_->AdvanceMockTickClock(base::TimeDelta::FromMilliseconds(1));
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
scheduler_->NotifyReadyToDraw();
EXPECT_FALSE(scheduler_->CommitPending());
if (scheduler_settings_.using_synchronous_renderer_compositor) {
scheduler_->SetNeedsRedraw();
bool resourceless_software_draw = false;
bool skip_draw = false;
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
} else {
// Run the posted deadline task.
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
}
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
}
client_->Reset();
{
SCOPED_TRACE(
"Run second frame so Scheduler calls SetNeedsBeginFrame(false).");
AdvanceFrame();
if (!scheduler_settings_.using_synchronous_renderer_compositor) {
// Run the posted deadline task.
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
}
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
}
EXPECT_FALSE(scheduler_->begin_frames_expected());
if (scheduler_->begin_frame_source() ==
fake_external_begin_frame_source_.get()) {
// Expect the last viz::BeginFrameAck to be for last BeginFrame, which
// didn't cause damage.
uint64_t last_begin_frame_number =
fake_external_begin_frame_source_->next_begin_frame_number() - 1;
bool has_damage = false;
EXPECT_EQ(
viz::BeginFrameAck(fake_external_begin_frame_source_->source_id(),
last_begin_frame_number, has_damage),
client_->last_begin_frame_ack());
}
client_->Reset();
}
// As this function contains EXPECT macros, to allow debugging it should be
// called inside EXPECT_SCOPED like so;
// EXPECT_SCOPED(client.AdvanceFrame());
void AdvanceFrame(bool animate_only = false) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug.scheduler.frames"),
"FakeSchedulerClient::AdvanceFrame");
// Send the next BeginFrame message if using an external source, otherwise
// it will be already in the task queue.
if (scheduler_->begin_frame_source() ==
fake_external_begin_frame_source_.get()) {
EXPECT_TRUE(scheduler_->begin_frames_expected());
SendNextBeginFrame(animate_only);
} else {
task_runner_->RunTasksWhile(client_->FrameHasNotAdvancedCallback());
}
}
viz::BeginFrameArgs SendNextBeginFrame(bool animate_only = false) {
DCHECK_EQ(scheduler_->begin_frame_source(),
fake_external_begin_frame_source_.get());
// Creep the time forward so that any viz::BeginFrameArgs is not equal to
// the last one otherwise we violate the viz::BeginFrameSource contract.
task_runner_->AdvanceMockTickClock(viz::BeginFrameArgs::DefaultInterval());
viz::BeginFrameArgs args =
fake_external_begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, task_runner_->GetMockTickClock());
args.animate_only = animate_only;
fake_external_begin_frame_source_->TestOnBeginFrame(args);
return args;
}
viz::FakeExternalBeginFrameSource* fake_external_begin_frame_source() const {
return fake_external_begin_frame_source_.get();
}
void SetShouldDeferInvalidationForMainFrame(bool defer) {
// Set the CompositorTimingHistory so that main thread is identified to be
// fast or slow.
base::TimeDelta delta;
if (!defer)
delta = base::TimeDelta::FromSeconds(1);
fake_compositor_timing_history_
->SetBeginMainFrameStartToReadyToCommitDurationEstimate(delta);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationCriticalEstimate(delta);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationNotCriticalEstimate(delta);
fake_compositor_timing_history_->SetDrawDurationEstimate(base::TimeDelta());
}
void AdvanceAndMissOneFrame();
void CheckMainFrameSkippedAfterLateCommit(bool expect_send_begin_main_frame);
void ImplFrameSkippedAfterLateAck(bool receive_ack_before_deadline);
void ImplFrameNotSkippedAfterLateAck();
void BeginFramesNotFromClient(BeginFrameSourceType bfs_type);
void BeginFramesNotFromClient_IsDrawThrottled(BeginFrameSourceType bfs_type);
bool BeginMainFrameOnCriticalPath(TreePriority tree_priority,
ScrollHandlerState scroll_handler_state,
base::TimeDelta durations);
scoped_refptr<SchedulerTestTaskRunner> task_runner_;
std::unique_ptr<viz::FakeExternalBeginFrameSource>
fake_external_begin_frame_source_;
std::unique_ptr<viz::SyntheticBeginFrameSource> synthetic_frame_source_;
std::unique_ptr<viz::SyntheticBeginFrameSource> unthrottled_frame_source_;
SchedulerSettings scheduler_settings_;
std::unique_ptr<FakeSchedulerClient> client_;
std::unique_ptr<TestScheduler> scheduler_;
FakeCompositorTimingHistory* fake_compositor_timing_history_;
};
TEST_F(SchedulerTest, InitializeLayerTreeFrameSinkDoesNotBeginImplFrame) {
SetUpSchedulerWithNoLayerTreeFrameSink(EXTERNAL_BFS);
scheduler_->SetVisible(true);
scheduler_->SetCanDraw(true);
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation");
client_->Reset();
scheduler_->DidCreateAndInitializeLayerTreeFrameSink();
EXPECT_NO_ACTION();
}
TEST_F(SchedulerTest, Stop) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// No scheduled actions are performed after Stop. WillBeginImplFrame is only
// a notification and not an action performed by the scheduler.
scheduler_->Stop();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
client_->Reset();
}
TEST_F(SchedulerTest, VideoNeedsBeginFrames) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetVideoNeedsBeginFrames(true);
EXPECT_ACTIONS("AddObserver(this)");
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
// WillBeginImplFrame is responsible for sending BeginFrames to video.
EXPECT_ACTIONS("WillBeginImplFrame");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
client_->Reset();
scheduler_->SetVideoNeedsBeginFrames(false);
EXPECT_NO_ACTION();
client_->Reset();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_ACTIONS("RemoveObserver(this)");
EXPECT_FALSE(scheduler_->begin_frames_expected());
}
TEST_F(SchedulerTest, RequestCommit) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame on the next BeginImplFrame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// If we don't swap on the deadline, we wait for the next BeginFrame.
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_NO_ACTION();
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// NotifyReadyToCommit should trigger the commit.
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// NotifyReadyToActivate should trigger the activation.
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// BeginImplFrame should prepare the draw.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// BeginImplFrame deadline should draw.
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// The following BeginImplFrame deadline should SetNeedsBeginFrame(false)
// to avoid excessive toggles.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("RemoveObserver(this)");
client_->Reset();
}
TEST_F(SchedulerTest, RequestCommitAfterSetDeferCommit) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetDeferCommits(true);
scheduler_->SetNeedsBeginMainFrame();
EXPECT_NO_ACTION();
client_->Reset();
task_runner_->RunPendingTasks();
// There are no pending tasks or actions.
EXPECT_NO_ACTION();
EXPECT_FALSE(scheduler_->begin_frames_expected());
client_->Reset();
scheduler_->SetDeferCommits(false);
EXPECT_ACTIONS("AddObserver(this)");
// Start new BeginMainFrame after defer commit is off.
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
}
TEST_F(SchedulerTest, DeferCommitWithRedraw) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetDeferCommits(true);
scheduler_->SetNeedsBeginMainFrame();
EXPECT_NO_ACTION();
// The SetNeedsRedraw will override the SetDeferCommits(true), to allow a
// begin frame to be needed.
client_->Reset();
scheduler_->SetNeedsRedraw();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
AdvanceFrame();
// BeginMainFrame is not sent during the defer commit is on.
EXPECT_ACTIONS("WillBeginImplFrame");
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
AdvanceFrame();
EXPECT_ACTIONS("WillBeginImplFrame");
}
TEST_F(SchedulerTest, RequestCommitAfterBeginMainFrameSent) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// Now SetNeedsBeginMainFrame again. Calling here means we need a second
// commit.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_NO_ACTION();
client_->Reset();
// Finish the first commit.
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Activate it.
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// Because we just swapped, the Scheduler should also request the next
// BeginImplFrame from the LayerTreeFrameSink.
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// Since another commit is needed, the next BeginImplFrame should initiate
// the second commit.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Finishing the commit before the deadline should post a new deadline task
// to trigger the deadline early.
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// On the next BeginImplFrame, verify we go back to a quiescent state and
// no longer request BeginImplFrames.
EXPECT_SCOPED(AdvanceFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_FALSE(scheduler_->begin_frames_expected());
client_->Reset();
}
class SchedulerClientThatsetNeedsDrawInsideDraw : public FakeSchedulerClient {
public:
SchedulerClientThatsetNeedsDrawInsideDraw()
: FakeSchedulerClient(), request_redraws_(false) {}
void SetRequestRedrawsInsideDraw(bool enable) { request_redraws_ = enable; }
DrawResult ScheduledActionDrawIfPossible() override {
// Only SetNeedsRedraw the first time this is called
if (request_redraws_) {
scheduler_->SetNeedsRedraw();
}
return FakeSchedulerClient::ScheduledActionDrawIfPossible();
}
DrawResult ScheduledActionDrawForced() override {
NOTREACHED();
return DRAW_SUCCESS;
}
private:
bool request_redraws_;
};
// Tests for two different situations:
// 1. the scheduler dropping SetNeedsRedraw requests that happen inside
// a ScheduledActionDraw
// 2. the scheduler drawing twice inside a single tick
TEST_F(SchedulerTest, RequestRedrawInsideDraw) {
SchedulerClientThatsetNeedsDrawInsideDraw* client =
new SchedulerClientThatsetNeedsDrawInsideDraw;
SetUpScheduler(EXTERNAL_BFS, base::WrapUnique(client));
client->SetRequestRedrawsInsideDraw(true);
scheduler_->SetNeedsRedraw();
EXPECT_TRUE(scheduler_->RedrawPending());
EXPECT_TRUE(client->needs_begin_frames());
EXPECT_EQ(0, client->num_draws());
EXPECT_SCOPED(AdvanceFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client->num_draws());
EXPECT_TRUE(scheduler_->RedrawPending());
EXPECT_TRUE(client->needs_begin_frames());
client->SetRequestRedrawsInsideDraw(false);
EXPECT_SCOPED(AdvanceFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(2, client_->num_draws());
EXPECT_FALSE(scheduler_->RedrawPending());
EXPECT_TRUE(client->needs_begin_frames());
// We stop requesting BeginImplFrames after a BeginImplFrame where we don't
// swap.
EXPECT_SCOPED(AdvanceFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(2, client->num_draws());
EXPECT_FALSE(scheduler_->RedrawPending());
EXPECT_FALSE(client->needs_begin_frames());
}
// Test that requesting redraw inside a failed draw doesn't lose the request.
TEST_F(SchedulerTest, RequestRedrawInsideFailedDraw) {
SchedulerClientThatsetNeedsDrawInsideDraw* client =
new SchedulerClientThatsetNeedsDrawInsideDraw;
SetUpScheduler(EXTERNAL_BFS, base::WrapUnique(client));
client->SetRequestRedrawsInsideDraw(true);
client->SetDrawWillHappen(false);
scheduler_->SetNeedsRedraw();
EXPECT_TRUE(scheduler_->RedrawPending());
EXPECT_TRUE(client->needs_begin_frames());
EXPECT_EQ(0, client->num_draws());
// Fail the draw.
EXPECT_SCOPED(AdvanceFrame());
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_frames());
client->SetRequestRedrawsInsideDraw(false);
// Fail the draw again.
EXPECT_SCOPED(AdvanceFrame());
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_frames());
// Draw successfully.
client->SetDrawWillHappen(true);
EXPECT_SCOPED(AdvanceFrame());
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_frames());
}
class SchedulerClientThatSetNeedsBeginMainFrameInsideDraw
: public FakeSchedulerClient {
public:
SchedulerClientThatSetNeedsBeginMainFrameInsideDraw()
: set_needs_commit_on_next_draw_(false) {}
DrawResult ScheduledActionDrawIfPossible() override {
// Only SetNeedsBeginMainFrame the first time this is called
if (set_needs_commit_on_next_draw_) {
scheduler_->SetNeedsBeginMainFrame();
set_needs_commit_on_next_draw_ = false;
}
return FakeSchedulerClient::ScheduledActionDrawIfPossible();
}
DrawResult ScheduledActionDrawForced() override {
NOTREACHED();
return DRAW_SUCCESS;
}
void SetNeedsBeginMainFrameOnNextDraw() {
set_needs_commit_on_next_draw_ = true;
}
private:
bool set_needs_commit_on_next_draw_;
};
// Tests for the scheduler infinite-looping on SetNeedsBeginMainFrame requests
// that happen inside a ScheduledActionDraw
TEST_F(SchedulerTest, RequestCommitInsideDraw) {
SchedulerClientThatSetNeedsBeginMainFrameInsideDraw* client =
new SchedulerClientThatSetNeedsBeginMainFrameInsideDraw;
SetUpScheduler(EXTERNAL_BFS, base::WrapUnique(client));
EXPECT_FALSE(client->needs_begin_frames());
scheduler_->SetNeedsRedraw();
EXPECT_TRUE(scheduler_->RedrawPending());
EXPECT_EQ(0, client->num_draws());
EXPECT_TRUE(client->needs_begin_frames());
client->SetNeedsBeginMainFrameOnNextDraw();
EXPECT_SCOPED(AdvanceFrame());
client->SetNeedsBeginMainFrameOnNextDraw();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client->num_draws());
EXPECT_TRUE(scheduler_->CommitPending());
EXPECT_TRUE(client->needs_begin_frames());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_SCOPED(AdvanceFrame());
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_frames());
// We stop requesting BeginImplFrames after a BeginImplFrame where we don't
// swap.
EXPECT_SCOPED(AdvanceFrame());
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_frames());
}
// Tests that when a draw fails then the pending commit should not be dropped.
TEST_F(SchedulerTest, RequestCommitInsideFailedDraw) {
SchedulerClientThatsetNeedsDrawInsideDraw* client =
new SchedulerClientThatsetNeedsDrawInsideDraw;
SetUpScheduler(EXTERNAL_BFS, base::WrapUnique(client));
client->SetDrawWillHappen(false);
scheduler_->SetNeedsRedraw();
EXPECT_TRUE(scheduler_->RedrawPending());
EXPECT_TRUE(client->needs_begin_frames());
EXPECT_EQ(0, client->num_draws());
// Fail the draw.
EXPECT_SCOPED(AdvanceFrame());
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_frames());
// Fail the draw again.
EXPECT_SCOPED(AdvanceFrame());
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_frames());
// Draw successfully.
client->SetDrawWillHappen(true);
EXPECT_SCOPED(AdvanceFrame());
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_frames());
}
TEST_F(SchedulerTest, NoSwapWhenDrawFails) {
SchedulerClientThatSetNeedsBeginMainFrameInsideDraw* client =
new SchedulerClientThatSetNeedsBeginMainFrameInsideDraw;
SetUpScheduler(EXTERNAL_BFS, base::WrapUnique(client));
scheduler_->SetNeedsRedraw();
EXPECT_TRUE(scheduler_->RedrawPending());
EXPECT_TRUE(client->needs_begin_frames());
EXPECT_EQ(0, client->num_draws());
// Draw successfully, this starts a new frame.
client->SetNeedsBeginMainFrameOnNextDraw();
EXPECT_SCOPED(AdvanceFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client->num_draws());
scheduler_->SetNeedsRedraw();
EXPECT_TRUE(scheduler_->RedrawPending());
EXPECT_TRUE(client->needs_begin_frames());
// Fail to draw, this should not start a frame.
client->SetDrawWillHappen(false);
client->SetNeedsBeginMainFrameOnNextDraw();
EXPECT_SCOPED(AdvanceFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(2, client->num_draws());
}
class SchedulerClientNeedsPrepareTilesInDraw : public FakeSchedulerClient {
public:
DrawResult ScheduledActionDrawIfPossible() override {
scheduler_->SetNeedsPrepareTiles();
return FakeSchedulerClient::ScheduledActionDrawIfPossible();
}
};
// Test prepare tiles is independant of draws.
TEST_F(SchedulerTest, PrepareTiles) {
SchedulerClientNeedsPrepareTilesInDraw* client =
new SchedulerClientNeedsPrepareTilesInDraw;
SetUpScheduler(EXTERNAL_BFS, base::WrapUnique(client));
// Request both draw and prepare tiles. PrepareTiles shouldn't
// be trigged until BeginImplFrame.
client->Reset();
scheduler_->SetNeedsPrepareTiles();
scheduler_->SetNeedsRedraw();
EXPECT_TRUE(scheduler_->RedrawPending());
EXPECT_TRUE(scheduler_->PrepareTilesPending());
EXPECT_TRUE(client->needs_begin_frames());
EXPECT_EQ(0, client->num_draws());
EXPECT_FALSE(client->HasAction("ScheduledActionPrepareTiles"));
EXPECT_FALSE(client->HasAction("ScheduledActionDrawIfPossible"));
// We have no immediate actions to perform, so the BeginImplFrame should post
// the deadline task.
client->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
// On the deadline, the actions should have occured in the right order.
client->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client->num_draws());
EXPECT_TRUE(client->HasAction("ScheduledActionDrawIfPossible"));
EXPECT_TRUE(client->HasAction("ScheduledActionPrepareTiles"));
EXPECT_LT(client->ActionIndex("ScheduledActionDrawIfPossible"),
client->ActionIndex("ScheduledActionPrepareTiles"));
EXPECT_FALSE(scheduler_->RedrawPending());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// Request a draw. We don't need a PrepareTiles yet.
client->Reset();
scheduler_->SetNeedsRedraw();
EXPECT_TRUE(scheduler_->RedrawPending());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
EXPECT_TRUE(client->needs_begin_frames());
EXPECT_EQ(0, client->num_draws());
// We have no immediate actions to perform, so the BeginImplFrame should post
// the deadline task.
client->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
// Draw. The draw will trigger SetNeedsPrepareTiles, and
// then the PrepareTiles action will be triggered after the Draw.
// Afterwards, neither a draw nor PrepareTiles are pending.
client->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client->num_draws());
EXPECT_TRUE(client->HasAction("ScheduledActionDrawIfPossible"));
EXPECT_TRUE(client->HasAction("ScheduledActionPrepareTiles"));
EXPECT_LT(client->ActionIndex("ScheduledActionDrawIfPossible"),
client->ActionIndex("ScheduledActionPrepareTiles"));
EXPECT_FALSE(scheduler_->RedrawPending());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// We need a BeginImplFrame where we don't swap to go idle.
client->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("RemoveObserver(this)");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_EQ(0, client->num_draws());
// Now trigger a PrepareTiles outside of a draw. We will then need
// a begin-frame for the PrepareTiles, but we don't need a draw.
client->Reset();
EXPECT_FALSE(client->needs_begin_frames());
scheduler_->SetNeedsPrepareTiles();
EXPECT_TRUE(client->needs_begin_frames());
EXPECT_TRUE(scheduler_->PrepareTilesPending());
EXPECT_FALSE(scheduler_->RedrawPending());
// BeginImplFrame. There will be no draw, only PrepareTiles.
client->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(0, client->num_draws());
EXPECT_FALSE(client->HasAction("ScheduledActionDrawIfPossible"));
EXPECT_TRUE(client->HasAction("ScheduledActionPrepareTiles"));
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
}
// Test that PrepareTiles only happens once per frame. If an external caller
// initiates it, then the state machine should not PrepareTiles on that frame.
TEST_F(SchedulerTest, PrepareTilesOncePerFrame) {
SetUpScheduler(EXTERNAL_BFS);
// If DidPrepareTiles during a frame, then PrepareTiles should not occur
// again.
scheduler_->SetNeedsPrepareTiles();
scheduler_->SetNeedsRedraw();
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->PrepareTilesPending());
scheduler_->WillPrepareTiles();
scheduler_->DidPrepareTiles(); // An explicit PrepareTiles.
EXPECT_FALSE(scheduler_->PrepareTilesPending());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client_->num_draws());
EXPECT_TRUE(client_->HasAction("ScheduledActionDrawIfPossible"));
EXPECT_FALSE(client_->HasAction("ScheduledActionPrepareTiles"));
EXPECT_FALSE(scheduler_->RedrawPending());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// Next frame without DidPrepareTiles should PrepareTiles with draw.
scheduler_->SetNeedsPrepareTiles();
scheduler_->SetNeedsRedraw();
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client_->num_draws());
EXPECT_TRUE(client_->HasAction("ScheduledActionDrawIfPossible"));
EXPECT_TRUE(client_->HasAction("ScheduledActionPrepareTiles"));
EXPECT_LT(client_->ActionIndex("ScheduledActionDrawIfPossible"),
client_->ActionIndex("ScheduledActionPrepareTiles"));
EXPECT_FALSE(scheduler_->RedrawPending());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// If we get another DidPrepareTiles within the same frame, we should
// not PrepareTiles on the next frame.
scheduler_->WillPrepareTiles();
scheduler_->DidPrepareTiles(); // An explicit PrepareTiles.
scheduler_->SetNeedsPrepareTiles();
scheduler_->SetNeedsRedraw();
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->PrepareTilesPending());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client_->num_draws());
EXPECT_TRUE(client_->HasAction("ScheduledActionDrawIfPossible"));
EXPECT_FALSE(client_->HasAction("ScheduledActionPrepareTiles"));
EXPECT_FALSE(scheduler_->RedrawPending());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// If we get another DidPrepareTiles, we should not PrepareTiles on the next
// frame. This verifies we don't alternate calling PrepareTiles once and
// twice.
EXPECT_TRUE(scheduler_->PrepareTilesPending());
scheduler_->WillPrepareTiles();
scheduler_->DidPrepareTiles(); // An explicit PrepareTiles.
EXPECT_FALSE(scheduler_->PrepareTilesPending());
scheduler_->SetNeedsPrepareTiles();
scheduler_->SetNeedsRedraw();
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->PrepareTilesPending());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client_->num_draws());
EXPECT_TRUE(client_->HasAction("ScheduledActionDrawIfPossible"));
EXPECT_FALSE(client_->HasAction("ScheduledActionPrepareTiles"));
EXPECT_FALSE(scheduler_->RedrawPending());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// Next frame without DidPrepareTiles should PrepareTiles with draw.
scheduler_->SetNeedsPrepareTiles();
scheduler_->SetNeedsRedraw();
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client_->num_draws());
EXPECT_TRUE(client_->HasAction("ScheduledActionDrawIfPossible"));
EXPECT_TRUE(client_->HasAction("ScheduledActionPrepareTiles"));
EXPECT_LT(client_->ActionIndex("ScheduledActionDrawIfPossible"),
client_->ActionIndex("ScheduledActionPrepareTiles"));
EXPECT_FALSE(scheduler_->RedrawPending());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
}
TEST_F(SchedulerTest, DidPrepareTilesPreventsPrepareTilesForOneFrame) {
std::unique_ptr<SchedulerClientNeedsPrepareTilesInDraw> client =
base::WrapUnique(new SchedulerClientNeedsPrepareTilesInDraw);
SetUpScheduler(EXTERNAL_BFS, std::move(client));
client_->Reset();
scheduler_->SetNeedsRedraw();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_ACTIONS("ScheduledActionDrawIfPossible",
"ScheduledActionPrepareTiles");
// We don't want to hinder scheduled prepare tiles for more than one frame
// even if we call unscheduled prepare tiles many times.
for (int i = 0; i < 10; i++) {
scheduler_->WillPrepareTiles();
scheduler_->DidPrepareTiles();
}
client_->Reset();
scheduler_->SetNeedsRedraw();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
// No scheduled prepare tiles because we've already counted a prepare tiles in
// between frames.
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
client_->Reset();
scheduler_->SetNeedsRedraw();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
// Resume scheduled prepare tiles.
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_ACTIONS("ScheduledActionDrawIfPossible",
"ScheduledActionPrepareTiles");
}
TEST_F(SchedulerTest, TriggerBeginFrameDeadlineEarly) {
SchedulerClientNeedsPrepareTilesInDraw* client =
new SchedulerClientNeedsPrepareTilesInDraw;
SetUpScheduler(EXTERNAL_BFS, base::WrapUnique(client));
scheduler_->SetNeedsRedraw();
EXPECT_SCOPED(AdvanceFrame());
// The deadline should be zero since there is no work other than drawing
// pending.
EXPECT_EQ(base::TimeTicks(), client->posted_begin_impl_frame_deadline());
}
TEST_F(SchedulerTest, WaitForReadyToDrawDoNotPostDeadline) {
SchedulerClientNeedsPrepareTilesInDraw* client =
new SchedulerClientNeedsPrepareTilesInDraw;
scheduler_settings_.commit_to_active_tree = true;
SetUpScheduler(EXTERNAL_BFS, base::WrapUnique(client));
// SetNeedsBeginMainFrame should begin the frame on the next BeginImplFrame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// Begin new frame.
EXPECT_SCOPED(AdvanceFrame());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
client_->Reset();
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
client_->Reset();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
// Scheduler won't post deadline in the mode.
client_->Reset();
task_runner_->RunPendingTasks(); // Try to run posted deadline.
// There is no posted deadline.
EXPECT_NO_ACTION();
// Scheduler received ready to draw signal, and posted deadline.
scheduler_->NotifyReadyToDraw();
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(1, client_->num_draws());
EXPECT_TRUE(client_->HasAction("ScheduledActionDrawIfPossible"));
}
TEST_F(SchedulerTest, WaitForReadyToDrawCancelledWhenLostLayerTreeFrameSink) {
SchedulerClientNeedsPrepareTilesInDraw* client =
new SchedulerClientNeedsPrepareTilesInDraw;
scheduler_settings_.commit_to_active_tree = true;
SetUpScheduler(EXTERNAL_BFS, base::WrapUnique(client));
// SetNeedsBeginMainFrame should begin the frame on the next BeginImplFrame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// Begin new frame.
EXPECT_SCOPED(AdvanceFrame());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
client_->Reset();
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
client_->Reset();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
// Scheduler won't post deadline in the mode.
client_->Reset();
task_runner_->RunPendingTasks(); // Try to run posted deadline.
// There is no posted deadline.
EXPECT_NO_ACTION();
// Scheduler loses LayerTreeFrameSink, and stops waiting for ready to draw
// signal.
client_->Reset();
scheduler_->DidLoseLayerTreeFrameSink();
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation",
"RemoveObserver(this)");
}
void SchedulerTest::AdvanceAndMissOneFrame() {
// Impl thread hits deadline before commit finishes.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame", "ScheduledActionCommit",
"ScheduledActionActivateSyncTree");
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
client_->Reset();
}
void SchedulerTest::CheckMainFrameSkippedAfterLateCommit(
bool expect_send_begin_main_frame) {
AdvanceAndMissOneFrame();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_EQ(expect_send_begin_main_frame,
scheduler_->MainThreadMissedLastDeadline());
EXPECT_TRUE(client_->HasAction("WillBeginImplFrame"));
EXPECT_EQ(expect_send_begin_main_frame,
client_->HasAction("ScheduledActionSendBeginMainFrame"));
}
TEST_F(SchedulerTest, MainFrameNotSkippedAfterLateBeginFrame) {
// If a begin frame is delivered extremely late (because the browser has
// some contention), make sure that the main frame is not skipped even
// if it can activate before the deadline.
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
AdvanceAndMissOneFrame();
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->SetNeedsBeginMainFrame();
// Advance frame and create a begin frame.
task_runner_->AdvanceMockTickClock(viz::BeginFrameArgs::DefaultInterval());
viz::BeginFrameArgs args =
fake_external_begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, task_runner_->GetMockTickClock());
// Deliver this begin frame super late.
task_runner_->AdvanceMockTickClock(viz::BeginFrameArgs::DefaultInterval() *
100);
fake_external_begin_frame_source_->TestOnBeginFrame(args);
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_EQ(true, scheduler_->MainThreadMissedLastDeadline());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame",
"ScheduledActionDrawIfPossible");
}
TEST_F(SchedulerTest, MainFrameSkippedAfterLateCommit) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
bool expect_send_begin_main_frame = false;
EXPECT_SCOPED(
CheckMainFrameSkippedAfterLateCommit(expect_send_begin_main_frame));
}
// Response times of BeginMainFrame's without the critical path flag set
// should not affect whether we recover latency or not.
TEST_F(SchedulerTest,
MainFrameSkippedAfterLateCommit_LongMainFrameQueueDurationNotCritical) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationNotCriticalEstimate(kSlowDuration);
bool expect_send_begin_main_frame = false;
EXPECT_SCOPED(
CheckMainFrameSkippedAfterLateCommit(expect_send_begin_main_frame));
}
// Response times of BeginMainFrame's with the critical path flag set
// should affect whether we recover latency or not.
TEST_F(SchedulerTest,
MainFrameNotSkippedAfterLateCommit_LongMainFrameQueueDurationCritical) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationCriticalEstimate(kSlowDuration);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationNotCriticalEstimate(kSlowDuration);
bool expect_send_begin_main_frame = true;
EXPECT_SCOPED(
CheckMainFrameSkippedAfterLateCommit(expect_send_begin_main_frame));
}
TEST_F(SchedulerTest,
MainFrameNotSkippedAfterLateCommitInPreferImplLatencyMode) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetTreePrioritiesAndScrollState(
SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
bool expect_send_begin_main_frame = true;
EXPECT_SCOPED(
CheckMainFrameSkippedAfterLateCommit(expect_send_begin_main_frame));
}
TEST_F(SchedulerTest,
MainFrameNotSkippedAfterLateCommit_CommitEstimateTooLong) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_
->SetBeginMainFrameStartToReadyToCommitDurationEstimate(kSlowDuration);
bool expect_send_begin_main_frame = true;
EXPECT_SCOPED(
CheckMainFrameSkippedAfterLateCommit(expect_send_begin_main_frame));
}
TEST_F(SchedulerTest,
MainFrameNotSkippedAfterLateCommit_ReadyToActivateEstimateTooLong) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_->SetCommitToReadyToActivateDurationEstimate(
kSlowDuration);
bool expect_send_begin_main_frame = true;
EXPECT_SCOPED(
CheckMainFrameSkippedAfterLateCommit(expect_send_begin_main_frame));
}
TEST_F(SchedulerTest,
MainFrameNotSkippedAfterLateCommit_ActivateEstimateTooLong) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_->SetActivateDurationEstimate(kSlowDuration);
bool expect_send_begin_main_frame = true;
EXPECT_SCOPED(
CheckMainFrameSkippedAfterLateCommit(expect_send_begin_main_frame));
}
TEST_F(SchedulerTest, MainFrameNotSkippedAfterLateCommit_DrawEstimateTooLong) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_->SetDrawDurationEstimate(kSlowDuration);
bool expect_send_begin_main_frame = true;
EXPECT_SCOPED(
CheckMainFrameSkippedAfterLateCommit(expect_send_begin_main_frame));
}
// If the BeginMainFrame aborts, it doesn't actually insert a frame into the
// queue, which means there is no latency to recover.
TEST_F(SchedulerTest, MainFrameNotSkippedAfterLateBeginMainFrameAbort) {
SetUpScheduler(EXTERNAL_BFS);
// Use fast estimates so we think we can recover latency if needed.
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
// Impl thread hits deadline before BeginMainFrame aborts.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame");
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
// After aborting the frame, make sure we don't skip the
// next BeginMainFrame.
client_->Reset();
scheduler_->BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES);
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(client_->HasAction("WillBeginImplFrame"));
EXPECT_TRUE(client_->HasAction("ScheduledActionSendBeginMainFrame"));
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
}
// If the BeginMainFrame aborts, it doesn't actually insert a frame into the
// queue, which means there is no latency to recover.
TEST_F(SchedulerTest, MainFrameNotSkippedAfterCanDrawChanges) {
SetUpScheduler(EXTERNAL_BFS);
// Use fast estimates so we think we can recover latency if needed.
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
// Impl thread hits deadline before BeginMainFrame aborts.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame");
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
// Make us abort the upcoming draw.
client_->Reset();
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionCommit", "ScheduledActionActivateSyncTree");
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->SetCanDraw(false);
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
// Make CanDraw true after activation.
client_->Reset();
scheduler_->SetCanDraw(true);
EXPECT_NO_ACTION();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
// Make sure we don't skip the next BeginMainFrame.
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(client_->HasAction("WillBeginImplFrame"));
EXPECT_TRUE(client_->HasAction("ScheduledActionSendBeginMainFrame"));
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
}
TEST_F(SchedulerTest, MainFrameNotSkippedWhenNoTimingHistory) {
SetUpScheduler(EXTERNAL_BFS);
// Use fast estimates so we think we can recover latency if needed.
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
// Impl thread hits deadline before BeginMainFrame commits.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame");
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
// Commit after the deadline.
client_->Reset();
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionCommit", "ScheduledActionActivateSyncTree");
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
// Clear the timing history. Make sure we don't skip the main frame until we
// have history from at least one frame.
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
scheduler_->ClearHistory();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
}
void SchedulerTest::ImplFrameSkippedAfterLateAck(
bool receive_ack_before_deadline) {
// To get into a high latency state, this test disables automatic swap acks.
client_->SetAutomaticSubmitCompositorFrameAck(false);
// Draw and swap for first BeginFrame
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
scheduler_->SetNeedsRedraw();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame");
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionCommit", "ScheduledActionActivateSyncTree",
"ScheduledActionDrawIfPossible");
// Verify we skip every other frame if the swap ack consistently
// comes back late.
for (int i = 0; i < 10; i++) {
// Not calling scheduler_->DidReceiveCompositorFrameAck() until after next
// BeginImplFrame puts the impl thread in high latency mode.
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
scheduler_->SetNeedsRedraw();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
// Verify that we skip the BeginImplFrame
EXPECT_NO_ACTION();
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
// Verify that we do not perform any actions after we are no longer
// swap throttled.
client_->Reset();
if (receive_ack_before_deadline) {
// It shouldn't matter if the swap ack comes back before the deadline...
scheduler_->DidReceiveCompositorFrameAck();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
} else {
// ... or after the deadline.
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
scheduler_->DidReceiveCompositorFrameAck();
}
EXPECT_NO_ACTION();
// Verify that we start the next BeginImplFrame and continue normally
// after having just skipped a BeginImplFrame.
client_->Reset();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionCommit", "ScheduledActionActivateSyncTree",
"ScheduledActionDrawIfPossible");
}
}
TEST_F(SchedulerTest,
ImplFrameSkippedAfterLateAck_FastEstimates_SubmitAckThenDeadline) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
bool receive_ack_before_deadline = true;
EXPECT_SCOPED(ImplFrameSkippedAfterLateAck(receive_ack_before_deadline));
}
TEST_F(SchedulerTest,
ImplFrameSkippedAfterLateAck_FastEstimates_DeadlineThenSubmitAck) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
bool receive_ack_before_deadline = false;
EXPECT_SCOPED(ImplFrameSkippedAfterLateAck(receive_ack_before_deadline));
}
TEST_F(SchedulerTest,
ImplFrameSkippedAfterLateAck_LongMainFrameQueueDurationNotCritical) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationNotCriticalEstimate(kSlowDuration);
bool receive_ack_before_deadline = false;
EXPECT_SCOPED(ImplFrameSkippedAfterLateAck(receive_ack_before_deadline));
}
TEST_F(SchedulerTest, ImplFrameSkippedAfterLateAck_ImplLatencyTakesPriority) {
SetUpScheduler(EXTERNAL_BFS);
// Even if every estimate related to the main thread is slow, we should
// still expect to recover impl thread latency if the draw is fast and we
// are in impl latency takes priority.
scheduler_->SetTreePrioritiesAndScrollState(
SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER);
fake_compositor_timing_history_->SetAllEstimatesTo(kSlowDuration);
fake_compositor_timing_history_->SetDrawDurationEstimate(kFastDuration);
bool receive_ack_before_deadline = false;
EXPECT_SCOPED(ImplFrameSkippedAfterLateAck(receive_ack_before_deadline));
}
TEST_F(SchedulerTest,
ImplFrameSkippedAfterLateAck_OnlyImplSideUpdatesExpected) {
// This tests that we recover impl thread latency when there are no commits.
SetUpScheduler(EXTERNAL_BFS);
// To get into a high latency state, this test disables automatic swap acks.
client_->SetAutomaticSubmitCompositorFrameAck(false);
// Even if every estimate related to the main thread is slow, we should
// still expect to recover impl thread latency if there are no commits from
// the main thread.
fake_compositor_timing_history_->SetAllEstimatesTo(kSlowDuration);
fake_compositor_timing_history_->SetDrawDurationEstimate(kFastDuration);
// Draw and swap for first BeginFrame
client_->Reset();
scheduler_->SetNeedsRedraw();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame");
client_->Reset();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
// Verify we skip every other frame if the swap ack consistently
// comes back late.
for (int i = 0; i < 10; i++) {
// Not calling scheduler_->DidReceiveCompositorFrameAck() until after next
// BeginImplFrame puts the impl thread in high latency mode.
client_->Reset();
scheduler_->SetNeedsRedraw();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
// Verify that we skip the BeginImplFrame
EXPECT_NO_ACTION();
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
// Verify that we do not perform any actions after we are no longer
// swap throttled.
client_->Reset();
scheduler_->DidReceiveCompositorFrameAck();
EXPECT_NO_ACTION();
// Verify that we start the next BeginImplFrame and continue normally
// after having just skipped a BeginImplFrame.
client_->Reset();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame");
client_->Reset();
// Deadline should be immediate.
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunUntilTime(task_runner_->NowTicks());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
}
}
void SchedulerTest::ImplFrameNotSkippedAfterLateAck() {
// To get into a high latency state, this test disables automatic swap acks.
client_->SetAutomaticSubmitCompositorFrameAck(false);
// Draw and swap for first BeginFrame
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame");
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionCommit", "ScheduledActionActivateSyncTree",
"ScheduledActionDrawIfPossible");
// Verify impl thread consistently operates in high latency mode
// without skipping any frames.
for (int i = 0; i < 10; i++) {
// Not calling scheduler_->DidReceiveCompositorFrameAck() until after next
// frame
// puts the impl thread in high latency mode.
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
client_->Reset();
scheduler_->DidReceiveCompositorFrameAck();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
// Verify that we don't skip the actions of the BeginImplFrame
EXPECT_ACTIONS("ScheduledActionSendBeginMainFrame", "ScheduledActionCommit",
"ScheduledActionActivateSyncTree",
"ScheduledActionDrawIfPossible");
}
}
TEST_F(SchedulerTest,
ImplFrameNotSkippedAfterLateAck_MainFrameQueueDurationCriticalTooLong) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationCriticalEstimate(kSlowDuration);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationNotCriticalEstimate(kSlowDuration);
EXPECT_SCOPED(ImplFrameNotSkippedAfterLateAck());
}
TEST_F(SchedulerTest, ImplFrameNotSkippedAfterLateAck_CommitEstimateTooLong) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_
->SetBeginMainFrameStartToReadyToCommitDurationEstimate(kSlowDuration);
EXPECT_SCOPED(ImplFrameNotSkippedAfterLateAck());
}
TEST_F(SchedulerTest,
ImplFrameNotSkippedAfterLateAck_ReadyToActivateEstimateTooLong) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_->SetCommitToReadyToActivateDurationEstimate(
kSlowDuration);
EXPECT_SCOPED(ImplFrameNotSkippedAfterLateAck());
}
TEST_F(SchedulerTest, ImplFrameNotSkippedAfterLateAck_ActivateEstimateTooLong) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_->SetActivateDurationEstimate(kSlowDuration);
EXPECT_SCOPED(ImplFrameNotSkippedAfterLateAck());
}
TEST_F(SchedulerTest, ImplFrameNotSkippedAfterLateAck_DrawEstimateTooLong) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_->SetDrawDurationEstimate(kSlowDuration);
EXPECT_SCOPED(ImplFrameNotSkippedAfterLateAck());
}
TEST_F(SchedulerTest, MainFrameThenImplFrameSkippedAfterLateCommitAndLateAck) {
// Set up client with custom estimates.
// This test starts off with expensive estimates to prevent latency recovery
// initially, then lowers the estimates to enable it once both the main
// and impl threads are in a high latency mode.
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kSlowDuration);
// To get into a high latency state, this test disables automatic swap acks.
client_->SetAutomaticSubmitCompositorFrameAck(false);
// Impl thread hits deadline before commit finishes to make
// MainThreadMissedLastDeadline true
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame", "ScheduledActionCommit",
"ScheduledActionActivateSyncTree");
// Draw and swap for first commit, start second commit.
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame",
"ScheduledActionDrawIfPossible", "ScheduledActionCommit",
"ScheduledActionActivateSyncTree");
// Don't call scheduler_->DidReceiveCompositorFrameAck() until after next
// frame
// to put the impl thread in a high latency mode.
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("WillBeginImplFrame");
// Note: BeginMainFrame and swap are skipped here because of
// swap ack backpressure, not because of latency recovery.
EXPECT_FALSE(client_->HasAction("ScheduledActionSendBeginMainFrame"));
EXPECT_FALSE(client_->HasAction("ScheduledActionDrawIfPossible"));
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
// Lower estimates so that the scheduler will attempt latency recovery.
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
// Now that both threads are in a high latency mode, make sure we
// skip the BeginMainFrame, then the BeginImplFrame, but not both
// at the same time.
// Verify we skip BeginMainFrame first.
client_->Reset();
// Previous commit request is still outstanding.
EXPECT_TRUE(scheduler_->NeedsBeginMainFrame());
EXPECT_TRUE(scheduler_->IsDrawThrottled());
SendNextBeginFrame();
EXPECT_TRUE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->DidReceiveCompositorFrameAck();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionDrawIfPossible");
// Verify we skip the BeginImplFrame second.
client_->Reset();
// Previous commit request is still outstanding.
EXPECT_TRUE(scheduler_->NeedsBeginMainFrame());
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->DidReceiveCompositorFrameAck();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_NO_ACTION();
// Then verify we operate in a low latency mode.
client_->Reset();
// Previous commit request is still outstanding.
EXPECT_TRUE(scheduler_->NeedsBeginMainFrame());
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
SendNextBeginFrame();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
scheduler_->DidReceiveCompositorFrameAck();
EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame",
"ScheduledActionCommit", "ScheduledActionActivateSyncTree",
"ScheduledActionDrawIfPossible");
}
TEST_F(
SchedulerTest,
Deadlock_CommitMakesProgressWhileSwapTrottledAndActiveTreeNeedsFirstDraw) {
// NPAPI plugins on Windows block the Browser UI thread on the Renderer main
// thread. This prevents the scheduler from receiving any pending swap acks.
scheduler_settings_.main_frame_while_submit_frame_throttled_enabled = true;
SetUpScheduler(EXTERNAL_BFS);
// Disables automatic swap acks so this test can force swap ack throttling
// to simulate a blocked Browser ui thread.
client_->SetAutomaticSubmitCompositorFrameAck(false);
// Get a new active tree in main-thread high latency mode and put us
// in a swap throttled state.
client_->Reset();
EXPECT_FALSE(scheduler_->CommitPending());
scheduler_->SetNeedsBeginMainFrame();
scheduler_->SetNeedsRedraw();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(scheduler_->CommitPending());
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_FALSE(scheduler_->CommitPending());
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame",
"ScheduledActionDrawIfPossible", "ScheduledActionCommit",
"ScheduledActionActivateSyncTree");
// Make sure that we can finish the next commit even while swap throttled.
client_->Reset();
EXPECT_FALSE(scheduler_->CommitPending());
scheduler_->SetNeedsBeginMainFrame();
EXPECT_SCOPED(AdvanceFrame());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame",
"ScheduledActionCommit");
// Make sure we do not send a BeginMainFrame while swap throttled and
// we have both a pending tree and an active tree.
client_->Reset();
EXPECT_FALSE(scheduler_->CommitPending());
scheduler_->SetNeedsBeginMainFrame();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_FALSE(scheduler_->CommitPending());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("WillBeginImplFrame");
}
TEST_F(
SchedulerTest,
CommitMakesProgressWhenIdleAndHasPendingTreeAndActiveTreeNeedsFirstDraw) {
// This verifies we don't block commits longer than we need to
// for performance reasons - not deadlock reasons.
// 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.
scheduler_settings_.main_frame_while_submit_frame_throttled_enabled = true;
scheduler_settings_.main_frame_before_activation_enabled = true;
SetUpScheduler(EXTERNAL_BFS);
// Disables automatic swap acks so this test can force swap ack throttling
// to simulate a blocked Browser ui thread.
client_->SetAutomaticSubmitCompositorFrameAck(false);
// Start a new commit in main-thread high latency mode and hold off on
// activation.
client_->Reset();
EXPECT_FALSE(scheduler_->CommitPending());
scheduler_->SetNeedsBeginMainFrame();
scheduler_->SetNeedsRedraw();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(scheduler_->CommitPending());
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
scheduler_->DidReceiveCompositorFrameAck();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_FALSE(scheduler_->CommitPending());
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame",
"ScheduledActionDrawIfPossible", "ScheduledActionCommit");
// Start another commit while we still have an active tree.
client_->Reset();
EXPECT_FALSE(scheduler_->CommitPending());
scheduler_->SetNeedsBeginMainFrame();
scheduler_->SetNeedsRedraw();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(scheduler_->CommitPending());
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
scheduler_->DidReceiveCompositorFrameAck();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame",
"ScheduledActionDrawIfPossible");
// Can't commit yet because there's still a pending tree.
client_->Reset();
scheduler_->NotifyReadyToCommit();
EXPECT_NO_ACTION();
// Activate the pending tree, which also unblocks the commit immediately
// while we are in an idle state.
client_->Reset();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree", "ScheduledActionCommit");
}
void SchedulerTest::BeginFramesNotFromClient(BeginFrameSourceType bfs_type) {
SetUpScheduler(bfs_type);
// SetNeedsBeginMainFrame should begin the frame on the next BeginImplFrame
// without calling SetNeedsBeginFrame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_NO_ACTION();
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Can't run the deadline task because it can race with begin frame for the
// SyntheticBFS case.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// NotifyReadyToCommit should trigger the commit.
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
client_->Reset();
// NotifyReadyToActivate should trigger the activation.
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
client_->Reset();
// BeginImplFrame deadline should draw. The following BeginImplFrame deadline
// should SetNeedsBeginFrame(false) to avoid excessive toggles.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("ScheduledActionDrawIfPossible", "WillBeginImplFrame");
client_->Reset();
// Make sure SetNeedsBeginFrame isn't called on the client
// when the BeginFrame is no longer needed.
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_NO_ACTION();
client_->Reset();
}
TEST_F(SchedulerTest, SyntheticBeginFrames) {
BeginFramesNotFromClient(THROTTLED_BFS);
}
TEST_F(SchedulerTest, UnthrottledBeginFrames) {
BeginFramesNotFromClient(UNTHROTTLED_BFS);
}
void SchedulerTest::BeginFramesNotFromClient_IsDrawThrottled(
BeginFrameSourceType bfs_type) {
SetUpScheduler(bfs_type);
// Set the draw duration estimate to zero so that deadlines are accurate.
fake_compositor_timing_history_->SetDrawDurationEstimate(base::TimeDelta());
// To test swap ack throttling, this test disables automatic swap acks.
client_->SetAutomaticSubmitCompositorFrameAck(false);
// SetNeedsBeginMainFrame should begin the frame on the next BeginImplFrame.
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_NO_ACTION();
client_->Reset();
// Trigger the first BeginImplFrame and BeginMainFrame
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// NotifyReadyToCommit should trigger the pending commit.
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
client_->Reset();
// NotifyReadyToActivate should trigger the activation and draw.
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
client_->Reset();
// Swapping will put us into a swap throttled state.
// Run posted deadline.
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// While swap throttled, BeginFrames should trigger BeginImplFrames,
// but not a BeginMainFrame or draw.
scheduler_->SetNeedsBeginMainFrame();
scheduler_->SetNeedsRedraw();
EXPECT_SCOPED(AdvanceFrame()); // Run posted BeginFrame.
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
base::TimeTicks before_deadline, after_deadline;
// The deadline is set to the regular deadline.
before_deadline = task_runner_->NowTicks();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
after_deadline = task_runner_->NowTicks();
// We can't do an equality comparison here because the scheduler uses a fudge
// factor that's an internal implementation detail.
EXPECT_GT(after_deadline, before_deadline);
EXPECT_LT(after_deadline,
before_deadline + viz::BeginFrameArgs::DefaultInterval());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
EXPECT_SCOPED(AdvanceFrame()); // Run posted BeginFrame.
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Take us out of a swap throttled state.
scheduler_->DidReceiveCompositorFrameAck();
EXPECT_ACTIONS("ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// The deadline is set to the regular deadline.
before_deadline = task_runner_->NowTicks();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
after_deadline = task_runner_->NowTicks();
// We can't do an equality comparison here because the scheduler uses a fudge
// factor that's an internal implementation detail.
EXPECT_GT(after_deadline, before_deadline);
EXPECT_LT(after_deadline,
before_deadline + viz::BeginFrameArgs::DefaultInterval());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
}
TEST_F(SchedulerTest, SyntheticBeginFrames_IsDrawThrottled) {
BeginFramesNotFromClient_IsDrawThrottled(THROTTLED_BFS);
}
TEST_F(SchedulerTest, UnthrottledBeginFrames_IsDrawThrottled) {
BeginFramesNotFromClient_IsDrawThrottled(UNTHROTTLED_BFS);
}
TEST_F(SchedulerTest,
DidLoseLayerTreeFrameSinkAfterLayerTreeFrameSinkIsInitialized) {
SetUpSchedulerWithNoLayerTreeFrameSink(EXTERNAL_BFS);
scheduler_->SetVisible(true);
scheduler_->SetCanDraw(true);
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation");
client_->Reset();
scheduler_->DidCreateAndInitializeLayerTreeFrameSink();
EXPECT_NO_ACTION();
scheduler_->DidLoseLayerTreeFrameSink();
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation");
}
TEST_F(SchedulerTest, DidLoseLayerTreeFrameSinkAfterBeginFrameStarted) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->DidLoseLayerTreeFrameSink();
// RemoveObserver(this) is not called until the end of the frame.
EXPECT_NO_ACTION();
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit", "ScheduledActionActivateSyncTree");
client_->Reset();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation",
"RemoveObserver(this)");
}
TEST_F(SchedulerTest,
DidLoseLayerTreeFrameSinkAfterBeginFrameStartedWithHighLatency) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->DidLoseLayerTreeFrameSink();
// Do nothing when impl frame is in deadine pending state.
EXPECT_NO_ACTION();
client_->Reset();
// Run posted deadline.
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
// OnBeginImplFrameDeadline didn't schedule LayerTreeFrameSink creation
// because
// main frame is not yet completed.
EXPECT_ACTIONS("RemoveObserver(this)");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// BeginImplFrame is not started.
client_->Reset();
task_runner_->RunUntilTime(task_runner_->NowTicks() +
base::TimeDelta::FromMilliseconds(10));
EXPECT_NO_ACTION();
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit", "ScheduledActionActivateSyncTree",
"ScheduledActionBeginLayerTreeFrameSinkCreation");
}
TEST_F(SchedulerTest, DidLoseLayerTreeFrameSinkAfterReadyToCommit) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
client_->Reset();
scheduler_->DidLoseLayerTreeFrameSink();
// Sync tree should be forced to activate.
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
// RemoveObserver(this) is not called until the end of the frame.
client_->Reset();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation",
"RemoveObserver(this)");
}
TEST_F(SchedulerTest, DidLoseLayerTreeFrameSinkAfterSetNeedsPrepareTiles) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsPrepareTiles();
scheduler_->SetNeedsRedraw();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->DidLoseLayerTreeFrameSink();
// RemoveObserver(this) is not called until the end of the frame.
EXPECT_NO_ACTION();
client_->Reset();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionPrepareTiles",
"ScheduledActionBeginLayerTreeFrameSinkCreation",
"RemoveObserver(this)");
}
TEST_F(SchedulerTest, DidLoseLayerTreeFrameSinkWithDelayBasedBeginFrameSource) {
SetUpScheduler(THROTTLED_BFS);
// SetNeedsBeginMainFrame should begin the frame on the next BeginImplFrame.
EXPECT_FALSE(scheduler_->begin_frames_expected());
scheduler_->SetNeedsBeginMainFrame();
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
AdvanceFrame();
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
// NotifyReadyToCommit should trigger the commit.
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
EXPECT_TRUE(scheduler_->begin_frames_expected());
// NotifyReadyToActivate should trigger the activation.
client_->Reset();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
scheduler_->DidLoseLayerTreeFrameSink();
// RemoveObserver(this) is not called until the end of the frame.
EXPECT_NO_ACTION();
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation");
EXPECT_FALSE(scheduler_->begin_frames_expected());
}
TEST_F(SchedulerTest, DidLoseLayerTreeFrameSinkWhenIdle) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
client_->Reset();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
client_->Reset();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
// Idle time between BeginFrames.
client_->Reset();
scheduler_->DidLoseLayerTreeFrameSink();
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation",
"RemoveObserver(this)");
}
TEST_F(SchedulerTest, ScheduledActionActivateAfterBecomingInvisible) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->SetVisible(false);
task_runner_->RunPendingTasks(); // Run posted deadline.
// Sync tree should be forced to activate.
EXPECT_ACTIONS("ScheduledActionActivateSyncTree", "RemoveObserver(this)");
}
TEST_F(SchedulerTest, ScheduledActionActivateAfterBeginFrameSourcePaused) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
fake_external_begin_frame_source_->SetPaused(true);
task_runner_->RunPendingTasks(); // Run posted deadline.
// Sync tree should be forced to activate.
// Pausing the begin frame source aborts the draw. Then
// ProactiveBeginFrameWanted is no longer true, so the scheduler stops
// listening for begin frames.
EXPECT_ACTIONS("ScheduledActionActivateSyncTree", "RemoveObserver(this)");
}
// Tests to ensure frame sources can be successfully changed while drawing.
TEST_F(SchedulerTest, SwitchFrameSourceToUnthrottled) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsRedraw should begin the frame on the next BeginImplFrame.
scheduler_->SetNeedsRedraw();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
scheduler_->SetNeedsRedraw();
// Switch to an unthrottled frame source.
scheduler_->SetBeginFrameSource(unthrottled_frame_source_.get());
client_->Reset();
// Unthrottled frame source will immediately begin a new frame.
task_runner_->RunPendingTasks(); // Run posted BeginFrame.
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// If we don't swap on the deadline, we wait for the next BeginFrame.
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
}
// Tests to ensure frame sources can be successfully changed while a frame
// deadline is pending.
TEST_F(SchedulerTest, SwitchFrameSourceToUnthrottledBeforeDeadline) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsRedraw should begin the frame on the next BeginImplFrame.
scheduler_->SetNeedsRedraw();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
// Switch to an unthrottled frame source before the frame deadline is hit.
scheduler_->SetBeginFrameSource(unthrottled_frame_source_.get());
client_->Reset();
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible",
// Unthrottled frame source will immediately begin a new frame.
"WillBeginImplFrame");
scheduler_->SetNeedsRedraw();
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
}
// Tests to ensure that the active frame source can successfully be changed from
// unthrottled to throttled.
TEST_F(SchedulerTest, SwitchFrameSourceToThrottled) {
SetUpScheduler(UNTHROTTLED_BFS);
scheduler_->SetNeedsRedraw();
EXPECT_NO_ACTION();
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted BeginFrame.
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Switch to a throttled frame source.
scheduler_->SetBeginFrameSource(fake_external_begin_frame_source_.get());
client_->Reset();
// SetNeedsRedraw should begin the frame on the next BeginImplFrame.
scheduler_->SetNeedsRedraw();
task_runner_->RunPendingTasks();
EXPECT_NO_ACTION();
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
}
TEST_F(SchedulerTest, SwitchFrameSourceToNullInsideDeadline) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsRedraw();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
client_->Reset();
// Switch to a null frame source.
scheduler_->SetBeginFrameSource(nullptr);
EXPECT_ACTIONS("RemoveObserver(this)");
client_->Reset();
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(scheduler_->begin_frames_expected());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// AdvanceFrame helper can't be used here because there's no deadline posted.
scheduler_->SetNeedsRedraw();
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_NO_ACTION();
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_NO_ACTION();
client_->Reset();
// Switch back to the same source, make sure frames continue to be produced.
scheduler_->SetBeginFrameSource(fake_external_begin_frame_source_.get());
EXPECT_ACTIONS("AddObserver(this)");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
task_runner_->RunPendingTasks();
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
}
// This test maskes sure that switching a frame source when not observing
// such as when not visible also works.
TEST_F(SchedulerTest, SwitchFrameSourceWhenNotObserving) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame on the next BeginImplFrame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// Begin new frame.
EXPECT_SCOPED(AdvanceFrame());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
client_->Reset();
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
client_->Reset();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
// Scheduler loses LayerTreeFrameSink, and stops waiting for ready to draw
// signal.
client_->Reset();
scheduler_->DidLoseLayerTreeFrameSink();
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
task_runner_->RunPendingTasks();
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation",
"RemoveObserver(this)");
// Changing begin frame source doesn't do anything.
// The unthrottled source doesn't print Add/RemoveObserver like the fake one.
client_->Reset();
scheduler_->SetBeginFrameSource(unthrottled_frame_source_.get());
EXPECT_NO_ACTION();
client_->Reset();
scheduler_->DidCreateAndInitializeLayerTreeFrameSink();
EXPECT_NO_ACTION();
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_NO_ACTION();
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
}
// Tests to ensure that we send a ScheduledActionBeginMainFrameNotExpectedUntil
// when expected.
TEST_F(SchedulerTest, ScheduledActionBeginMainFrameNotExpectedUntil) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsRedraw();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
scheduler_->SetMainThreadWantsBeginMainFrameNotExpected(true);
task_runner_->RunPendingTasks();
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionBeginMainFrameNotExpectedUntil",
"ScheduledActionDrawIfPossible");
}
// Tests to ensure that we send a BeginMainFrameNotExpectedSoon when expected.
TEST_F(SchedulerTest, SendBeginMainFrameNotExpectedSoon_Requested) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame on the next BeginImplFrame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// Trigger a frame draw.
EXPECT_SCOPED(AdvanceFrame());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
task_runner_->RunPendingTasks();
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame",
"ScheduledActionCommit", "ScheduledActionActivateSyncTree",
"ScheduledActionDrawIfPossible");
client_->Reset();
scheduler_->SetMainThreadWantsBeginMainFrameNotExpected(true);
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionBeginMainFrameNotExpectedUntil");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// The BeginImplFrame deadline should SetNeedsBeginFrame(false) and send a
// SendBeginMainFrameNotExpectedSoon.
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("RemoveObserver(this)", "SendBeginMainFrameNotExpectedSoon");
client_->Reset();
}
// Tests to ensure that we dont't send a BeginMainFrameNotExpectedSoon when
// possible but not requested.
TEST_F(SchedulerTest, SendBeginMainFrameNotExpectedSoon_Unrequested) {
SetUpScheduler(EXTERNAL_BFS);
// SetNeedsBeginMainFrame should begin the frame on the next BeginImplFrame.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// Trigger a frame draw.
EXPECT_SCOPED(AdvanceFrame());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
task_runner_->RunPendingTasks();
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame",
"ScheduledActionCommit", "ScheduledActionActivateSyncTree",
"ScheduledActionDrawIfPossible");
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// The BeginImplFrame deadline should SetNeedsBeginFrame(false), but doesn't
// send a SendBeginMainFrameNotExpectedSoon as it's not been requested by the
// main thread.
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("RemoveObserver(this)");
client_->Reset();
scheduler_->SetMainThreadWantsBeginMainFrameNotExpected(true);
EXPECT_ACTIONS("SendBeginMainFrameNotExpectedSoon");
}
TEST_F(SchedulerTest, SynchronousCompositorAnimation) {
scheduler_settings_.using_synchronous_renderer_compositor = true;
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsOneBeginImplFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// Testing the case where animation ticks a fling scroll.
client_->SetWillBeginImplFrameCausesRedraw(true);
// The animation isn't done so it'll cause another tick in the future.
client_->SetWillBeginImplFrameRequestsOneBeginImplFrame(true);
// Next vsync.
AdvanceFrame();
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionInvalidateLayerTreeFrameSink");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Android onDraw. This doesn't consume the single begin frame request.
scheduler_->SetNeedsRedraw();
bool resourceless_software_draw = false;
bool skip_draw = false;
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// The animation inside of WillBeginImplFrame changes stuff on the screen, but
// ends here, so does not cause another frame to happen.
client_->SetWillBeginImplFrameCausesRedraw(true);
// Next vsync.
AdvanceFrame();
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionInvalidateLayerTreeFrameSink");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Android onDraw.
scheduler_->SetNeedsRedraw();
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Idle on next vsync, as the animation has completed.
AdvanceFrame();
EXPECT_ACTIONS("WillBeginImplFrame", "RemoveObserver(this)");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
}
TEST_F(SchedulerTest, SynchronousCompositorOnDrawDuringIdle) {
scheduler_settings_.using_synchronous_renderer_compositor = true;
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsRedraw();
bool resourceless_software_draw = false;
bool skip_draw = false;
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("AddObserver(this)", "ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Idle on next vsync.
AdvanceFrame();
EXPECT_ACTIONS("WillBeginImplFrame", "RemoveObserver(this)");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
}
TEST_F(SchedulerTest, SetNeedsOneBeginImplFrame) {
SetUpScheduler(EXTERNAL_BFS);
EXPECT_FALSE(scheduler_->begin_frames_expected());
// Request a frame, should kick the source.
scheduler_->SetNeedsOneBeginImplFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// The incoming WillBeginImplFrame will request another one.
client_->SetWillBeginImplFrameRequestsOneBeginImplFrame(true);
// Next vsync, the first requested frame happens.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// We don't request another frame here.
// Next vsync, the second requested frame happens (the one requested inside
// the previous frame's begin impl frame step).
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// End that frame's deadline.
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// Scheduler shuts down the source now that no begin frame is requested.
EXPECT_ACTIONS("RemoveObserver(this)");
}
TEST_F(SchedulerTest, AbortEarlyIfNoDamage) {
SetUpScheduler(EXTERNAL_BFS);
// WillBeginImplFrame will return false, so draws should never be scheduled
// and client_->num_draws() should stay at 0.
client_->SetWillBeginImplFrameMightHaveDamage(false);
scheduler_->SetNeedsRedraw();
EXPECT_EQ(0, client_->num_draws());
EXPECT_ACTIONS("AddObserver(this)");
EXPECT_SCOPED(AdvanceFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
// Should not try to schedule a draw. (ScheduledActionDrawIfPossible should
// not appear.)
// When the frame is aborted, the scheduler does not ask for a proactive begin
// frame, so stop listening for begin frames.
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"RemoveObserver(this)");
EXPECT_EQ(0, client_->num_draws());
scheduler_->SetNeedsRedraw();
EXPECT_SCOPED(AdvanceFrame());
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_EQ(0, client_->num_draws());
}
TEST_F(SchedulerTest, SkipDraw) {
scheduler_settings_.using_synchronous_renderer_compositor = true;
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsOneBeginImplFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
client_->SetWillBeginImplFrameCausesRedraw(true);
// Next vsync.
AdvanceFrame();
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionInvalidateLayerTreeFrameSink");
EXPECT_TRUE(client_->invalidate_needs_redraw());
client_->Reset();
// Android onDraw. This doesn't consume the single begin frame request.
scheduler_->SetNeedsPrepareTiles();
bool resourceless_software_draw = false;
bool skip_draw = false;
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("ScheduledActionDrawIfPossible",
"ScheduledActionPrepareTiles");
client_->Reset();
// Next vsync.
scheduler_->SetNeedsPrepareTiles();
AdvanceFrame();
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionInvalidateLayerTreeFrameSink");
EXPECT_FALSE(client_->invalidate_needs_redraw());
client_->Reset();
// Android onDraw.
scheduler_->SetNeedsRedraw();
scheduler_->SetNeedsPrepareTiles();
client_->SetInvalidateNeedsRedraw(false);
skip_draw = true;
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("ScheduledActionPrepareTiles");
client_->Reset();
}
TEST_F(SchedulerTest, SynchronousCompositorCommitAndVerifyBeginFrameAcks) {
scheduler_settings_.using_synchronous_renderer_compositor = true;
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// Next vsync.
viz::BeginFrameArgs args = SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
bool has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
EXPECT_NO_ACTION();
// Next vsync.
args = SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
client_->Reset();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
client_->Reset();
// Next vsync.
args = SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionInvalidateLayerTreeFrameSink");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// No damage, since not drawn yet.
// TODO(eseckler): In the future, |has_damage = false| will prevent us from
// filtering this ack (in CompositorExternalBeginFrameSource) and instead
// forwarding the one attached to the later submitted CompositorFrame.
has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
// Android onDraw.
scheduler_->SetNeedsRedraw();
bool resourceless_software_draw = false;
bool skip_draw = false;
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Idle on next vsync.
args = SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame", "RemoveObserver(this)");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
}
class SchedulerClientSetNeedsPrepareTilesOnDraw : public FakeSchedulerClient {
public:
SchedulerClientSetNeedsPrepareTilesOnDraw() : FakeSchedulerClient() {}
protected:
DrawResult ScheduledActionDrawIfPossible() override {
scheduler_->SetNeedsPrepareTiles();
return FakeSchedulerClient::ScheduledActionDrawIfPossible();
}
};
TEST_F(SchedulerTest, SynchronousCompositorPrepareTilesOnDraw) {
scheduler_settings_.using_synchronous_renderer_compositor = true;
std::unique_ptr<FakeSchedulerClient> client =
base::WrapUnique(new SchedulerClientSetNeedsPrepareTilesOnDraw);
SetUpScheduler(EXTERNAL_BFS, std::move(client));
scheduler_->SetNeedsRedraw();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// Next vsync.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionInvalidateLayerTreeFrameSink");
client_->Reset();
// Android onDraw.
scheduler_->SetNeedsRedraw();
bool resourceless_software_draw = false;
bool skip_draw = false;
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("ScheduledActionDrawIfPossible",
"ScheduledActionPrepareTiles");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
client_->Reset();
// Android onDraw.
scheduler_->SetNeedsRedraw();
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("ScheduledActionDrawIfPossible",
"ScheduledActionPrepareTiles");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
client_->Reset();
// Next vsync.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
EXPECT_ACTIONS("WillBeginImplFrame", "RemoveObserver(this)");
EXPECT_FALSE(scheduler_->begin_frames_expected());
client_->Reset();
}
TEST_F(SchedulerTest, SetNeedsRedrawFromWillBeginImplFrame) {
client_ = std::make_unique<FakeSchedulerClient>();
CreateScheduler(EXTERNAL_BFS);
scheduler_->SetVisible(true);
scheduler_->SetCanDraw(true);
client_->SetWillBeginImplFrameCausesRedraw(true);
scheduler_->DidCreateAndInitializeLayerTreeFrameSink();
scheduler_->SetNeedsBeginMainFrame();
AdvanceFrame();
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation",
"AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame");
EXPECT_TRUE(scheduler_->RedrawPending());
// WillBeginFrame calls Scheduler::SetNeedsRedraw, which could try to run
// another action. If none of the EXPECT_FALSE(inside_action_)s in
// FakeSchedulerClient fail, we know we didn't re-enter the scheduler.
}
TEST_F(SchedulerTest, SynchronousCompositorSendBeginMainFrameWhileIdle) {
scheduler_settings_.using_synchronous_renderer_compositor = true;
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsRedraw();
EXPECT_ACTIONS("AddObserver(this)");
client_->Reset();
// Next vsync.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionInvalidateLayerTreeFrameSink");
client_->Reset();
// Android onDraw.
scheduler_->SetNeedsRedraw();
bool resourceless_software_draw = false;
bool skip_draw = false;
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
client_->Reset();
// Simulate SetNeedsBeginMainFrame due to input event.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("ScheduledActionSendBeginMainFrame");
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
client_->Reset();
scheduler_->NotifyReadyToActivate();
EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
client_->Reset();
// Next vsync.
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionInvalidateLayerTreeFrameSink");
client_->Reset();
// Android onDraw.
scheduler_->SetNeedsRedraw();
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_FALSE(scheduler_->PrepareTilesPending());
client_->Reset();
// Simulate SetNeedsBeginMainFrame due to input event.
scheduler_->SetNeedsBeginMainFrame();
EXPECT_ACTIONS("ScheduledActionSendBeginMainFrame");
client_->Reset();
}
TEST_F(SchedulerTest, SynchronousCompositorResourcelessOnDrawWhenInvisible) {
scheduler_settings_.using_synchronous_renderer_compositor = true;
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetVisible(false);
scheduler_->SetNeedsRedraw();
bool resourceless_software_draw = true;
bool skip_draw = false;
scheduler_->OnDrawForLayerTreeFrameSink(resourceless_software_draw,
skip_draw);
// SynchronousCompositor has to draw regardless of visibility.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
}
TEST_F(SchedulerTest, AuthoritativeVSyncInterval) {
SetUpScheduler(THROTTLED_BFS);
base::TimeDelta initial_interval = scheduler_->BeginImplFrameInterval();
base::TimeDelta authoritative_interval =
base::TimeDelta::FromMilliseconds(33);
scheduler_->SetNeedsBeginMainFrame();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_EQ(initial_interval, scheduler_->BeginImplFrameInterval());
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
scheduler_->NotifyReadyToActivate();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
// Test changing the interval on the frame source external to the scheduler.
synthetic_frame_source_->OnUpdateVSyncParameters(task_runner_->NowTicks(),
authoritative_interval);
EXPECT_SCOPED(AdvanceFrame());
// At the next BeginFrame, authoritative interval is used instead of previous
// interval.
EXPECT_NE(initial_interval, scheduler_->BeginImplFrameInterval());
EXPECT_EQ(authoritative_interval, scheduler_->BeginImplFrameInterval());
}
TEST_F(SchedulerTest, ImplLatencyTakesPriority) {
SetUpScheduler(THROTTLED_BFS);
scheduler_->SetTreePrioritiesAndScrollState(
SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER);
scheduler_->SetCriticalBeginMainFrameToActivateIsFast(true);
EXPECT_TRUE(scheduler_->ImplLatencyTakesPriority());
scheduler_->SetCriticalBeginMainFrameToActivateIsFast(false);
EXPECT_TRUE(scheduler_->ImplLatencyTakesPriority());
scheduler_->SetTreePrioritiesAndScrollState(
SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER);
scheduler_->SetCriticalBeginMainFrameToActivateIsFast(true);
EXPECT_FALSE(scheduler_->ImplLatencyTakesPriority());
scheduler_->SetCriticalBeginMainFrameToActivateIsFast(false);
EXPECT_TRUE(scheduler_->ImplLatencyTakesPriority());
scheduler_->SetTreePrioritiesAndScrollState(
SAME_PRIORITY_FOR_BOTH_TREES,
ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER);
scheduler_->SetCriticalBeginMainFrameToActivateIsFast(true);
EXPECT_FALSE(scheduler_->ImplLatencyTakesPriority());
scheduler_->SetCriticalBeginMainFrameToActivateIsFast(false);
EXPECT_FALSE(scheduler_->ImplLatencyTakesPriority());
scheduler_->SetTreePrioritiesAndScrollState(
SAME_PRIORITY_FOR_BOTH_TREES,
ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER);
scheduler_->SetCriticalBeginMainFrameToActivateIsFast(true);
EXPECT_FALSE(scheduler_->ImplLatencyTakesPriority());
scheduler_->SetCriticalBeginMainFrameToActivateIsFast(false);
EXPECT_FALSE(scheduler_->ImplLatencyTakesPriority());
}
TEST_F(SchedulerTest, NoLayerTreeFrameSinkCreationWhileCommitPending) {
SetUpScheduler(THROTTLED_BFS);
// SetNeedsBeginMainFrame should begin the frame.
scheduler_->SetNeedsBeginMainFrame();
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
// Lose the LayerTreeFrameSink and trigger the deadline.
client_->Reset();
scheduler_->DidLoseLayerTreeFrameSink();
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_NO_ACTION();
// The scheduler should not trigger the LayerTreeFrameSink creation till the
// commit is aborted.
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_NO_ACTION();
// Abort the commit.
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->BeginMainFrameAborted(
CommitEarlyOutReason::ABORTED_LAYER_TREE_FRAME_SINK_LOST);
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation");
}
TEST_F(SchedulerTest, ImplSideInvalidationInsideImplFrame) {
SetUpScheduler(EXTERNAL_BFS);
// Request an impl-side invalidation. Ensure that it runs before the deadline.
bool needs_first_draw_on_activation = true;
scheduler_->SetNeedsImplSideInvalidation(needs_first_draw_on_activation);
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionPerformImplSideInvalidation");
}
TEST_F(SchedulerTest, ImplSideInvalidationsMergedWithCommit) {
SetUpScheduler(EXTERNAL_BFS);
// Request a main frame and invalidation, the only action run should be
// sending the main frame.
SetShouldDeferInvalidationForMainFrame(true);
scheduler_->SetNeedsBeginMainFrame();
bool needs_first_draw_on_activation = true;
scheduler_->SetNeedsImplSideInvalidation(needs_first_draw_on_activation);
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
// Respond with a commit. The scheduler should only perform the commit
// actions since the impl-side invalidation request will be merged with the
// commit.
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
EXPECT_FALSE(scheduler_->needs_impl_side_invalidation());
}
TEST_F(SchedulerTest, AbortedCommitsTriggerImplSideInvalidations) {
SetUpScheduler(EXTERNAL_BFS);
// Request a main frame and invalidation, with a fast main thread so we wait
// for it to respond.
SetShouldDeferInvalidationForMainFrame(true);
scheduler_->SetNeedsBeginMainFrame();
bool needs_first_draw_on_activation = true;
scheduler_->SetNeedsImplSideInvalidation(needs_first_draw_on_activation);
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
// Abort the main frame and request another one, the impl-side invalidations
// should not be blocked on the main frame.
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES);
EXPECT_ACTIONS("ScheduledActionPerformImplSideInvalidation");
}
TEST_F(SchedulerTest, InvalidationNotBlockedOnMainFrame) {
SetUpScheduler(EXTERNAL_BFS);
// Request a main frame and invalidation, with a slow main thread so the
// invalidation is not blocked on a commit.
SetShouldDeferInvalidationForMainFrame(false);
scheduler_->SetNeedsBeginMainFrame();
bool needs_first_draw_on_activation = true;
scheduler_->SetNeedsImplSideInvalidation(needs_first_draw_on_activation);
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame",
"ScheduledActionPerformImplSideInvalidation");
}
// The three letters appeneded to each version of this test mean the following:s
// tree_priority: B = both trees same priority; A = active tree priority;
// scroll_handler_state: H = affects scroll handler; N = does not affect scroll
// handler;
// durations: F = fast durations; S = slow durations
bool SchedulerTest::BeginMainFrameOnCriticalPath(
TreePriority tree_priority,
ScrollHandlerState scroll_handler_state,
base::TimeDelta durations) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(durations);
client_->Reset();
scheduler_->SetTreePrioritiesAndScrollState(tree_priority,
scroll_handler_state);
scheduler_->SetNeedsBeginMainFrame();
EXPECT_FALSE(client_->last_begin_main_frame_args().IsValid());
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(client_->last_begin_main_frame_args().IsValid());
return client_->last_begin_main_frame_args().on_critical_path;
}
TEST_F(SchedulerTest, BeginMainFrameOnCriticalPath_BNF) {
EXPECT_TRUE(BeginMainFrameOnCriticalPath(
SAME_PRIORITY_FOR_BOTH_TREES,
ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER,
kFastDuration));
}
TEST_F(SchedulerTest, BeginMainFrameOnCriticalPath_BNS) {
EXPECT_TRUE(BeginMainFrameOnCriticalPath(
SAME_PRIORITY_FOR_BOTH_TREES,
ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER,
kSlowDuration));
}
TEST_F(SchedulerTest, BeginMainFrameOnCriticalPath_BHF) {
EXPECT_TRUE(BeginMainFrameOnCriticalPath(
SAME_PRIORITY_FOR_BOTH_TREES,
ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER, kFastDuration));
}
TEST_F(SchedulerTest, BeginMainFrameOnCriticalPath_BHS) {
EXPECT_TRUE(BeginMainFrameOnCriticalPath(
SAME_PRIORITY_FOR_BOTH_TREES,
ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER, kSlowDuration));
}
TEST_F(SchedulerTest, BeginMainFrameOnCriticalPath_ANF) {
EXPECT_FALSE(BeginMainFrameOnCriticalPath(
SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER,
kFastDuration));
}
TEST_F(SchedulerTest, BeginMainFrameOnCriticalPath_ANS) {
EXPECT_FALSE(BeginMainFrameOnCriticalPath(
SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER,
kSlowDuration));
}
TEST_F(SchedulerTest, BeginMainFrameOnCriticalPath_AHF) {
EXPECT_TRUE(BeginMainFrameOnCriticalPath(
SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER, kFastDuration));
}
TEST_F(SchedulerTest, BeginMainFrameOnCriticalPath_AHS) {
EXPECT_FALSE(BeginMainFrameOnCriticalPath(
SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER, kSlowDuration));
}
TEST_F(SchedulerTest, BeginFrameAckForFinishedImplFrame) {
// Sets up scheduler and sends two BeginFrames, both finished.
SetUpScheduler(EXTERNAL_BFS);
// Run a successful redraw and verify that a new ack is sent.
scheduler_->SetNeedsRedraw();
client_->Reset();
viz::BeginFrameArgs args = SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
// Successful draw caused damage.
bool has_damage = true;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
// Request another redraw, but fail it. Verify that a new ack is sent.
scheduler_->SetNeedsRedraw();
client_->Reset();
args = SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
client_->SetDrawWillHappen(false);
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible",
// Failed draw triggers SendBeginMainFrame.
"ScheduledActionSendBeginMainFrame");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
// Failed draw: no damage.
has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
}
TEST_F(SchedulerTest, BeginFrameAckForSkippedImplFrame) {
SetUpScheduler(EXTERNAL_BFS);
// To get into a high latency state, this test disables automatic swap acks.
client_->SetAutomaticSubmitCompositorFrameAck(false);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
// Run a successful redraw that submits a compositor frame but doesn't receive
// a swap ack. Verify that a viz::BeginFrameAck is sent for it.
scheduler_->SetNeedsRedraw();
client_->Reset();
viz::BeginFrameArgs args = SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
task_runner_->RunPendingTasks(); // Run posted deadline.
EXPECT_ACTIONS("ScheduledActionDrawIfPossible");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
// Successful draw caused damage.
bool has_damage = true;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
// Request another redraw that will be skipped because the swap ack is still
// missing. Verify that a new viz::BeginFrameAck is sent.
scheduler_->SetNeedsRedraw();
client_->Reset();
args = SendNextBeginFrame();
EXPECT_NO_ACTION();
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_TRUE(scheduler_->begin_frames_expected());
// Skipped draw: no damage.
has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
}
TEST_F(SchedulerTest, BeginFrameAckForBeginFrameBeforeLastDeadline) {
SetUpScheduler(EXTERNAL_BFS);
// Request tile preparation to schedule a proactive BeginFrame.
scheduler_->SetNeedsPrepareTiles();
client_->Reset();
SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
// Until tiles were prepared, further proactive BeginFrames are expected.
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// Send the next BeginFrame before the previous one's deadline was executed.
// This should trigger the previous BeginFrame's deadline synchronously,
// during which tiles will be prepared. As a result of that, no further
// BeginFrames will be needed, and the new BeginFrame should be dropped.
viz::BeginFrameArgs args = SendNextBeginFrame();
EXPECT_ACTIONS("ScheduledActionPrepareTiles", "RemoveObserver(this)");
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
EXPECT_FALSE(scheduler_->begin_frames_expected());
// Latest ack should be for the dropped BeginFrame.
bool has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
}
TEST_F(SchedulerTest, BeginFrameAckForDroppedBeginFrame) {
SetUpScheduler(EXTERNAL_BFS);
// Request a single BeginFrame.
scheduler_->SetNeedsOneBeginImplFrame();
EXPECT_TRUE(scheduler_->begin_frames_expected());
client_->Reset();
// First BeginFrame is handled by StateMachine.
viz::BeginFrameArgs first_args = SendNextBeginFrame();
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
// State machine is no longer interested in BeginFrames, but scheduler is
// still observing the source.
EXPECT_TRUE(scheduler_->begin_frames_expected());
EXPECT_FALSE(scheduler_->BeginFrameNeeded());
client_->Reset();
// Send the next BeginFrame before the previous one's deadline was executed.
// The BeginFrame should be dropped immediately, since the state machine is
// not expecting any BeginFrames.
viz::BeginFrameArgs second_args = SendNextBeginFrame();
EXPECT_NO_ACTION();
// Latest ack should be for the dropped BeginFrame.
bool has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(second_args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
task_runner_->RunPendingTasks(); // Run deadline of prior BeginFrame.
EXPECT_ACTIONS("RemoveObserver(this)");
// We'd expect an out-of-order ack for the prior BeginFrame.
has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(first_args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
}
TEST_F(SchedulerTest, BeginFrameAckForLateMissedBeginFrame) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsRedraw();
client_->Reset();
// Send a missed BeginFrame with a passed deadline.
task_runner_->AdvanceMockTickClock(viz::BeginFrameArgs::DefaultInterval());
viz::BeginFrameArgs args =
fake_external_begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, task_runner_->GetMockTickClock());
args.type = viz::BeginFrameArgs::MISSED;
task_runner_->AdvanceMockTickClock(viz::BeginFrameArgs::DefaultInterval());
EXPECT_GT(task_runner_->NowTicks(), args.deadline);
fake_external_begin_frame_source_->TestOnBeginFrame(args);
EXPECT_NO_ACTION();
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
// Latest ack should be for the missed BeginFrame that was too late: no
// damage.
bool has_damage = false;
EXPECT_EQ(viz::BeginFrameAck(args, has_damage),
client_->last_begin_frame_ack());
client_->Reset();
}
TEST_F(SchedulerTest, CriticalBeginMainFrameToActivateIsFast) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsRedraw();
base::TimeDelta estimate_duration = base::TimeDelta::FromMilliseconds(1);
fake_compositor_timing_history_->SetAllEstimatesTo(estimate_duration);
// If we have a scroll handler but the critical main frame is slow, we should
// still prioritize impl thread latency.
scheduler_->SetTreePrioritiesAndScrollState(
SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER);
scheduler_->SetNeedsRedraw();
// An interval of 2ms makes sure that the main frame is considered slow.
base::TimeDelta interval = base::TimeDelta::FromMilliseconds(2);
task_runner_->AdvanceMockTickClock(interval);
viz::BeginFrameArgs args = viz::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, 0u, 1u, task_runner_->NowTicks(),
task_runner_->NowTicks() + interval, interval,
viz::BeginFrameArgs::NORMAL);
fake_external_begin_frame_source_->TestOnBeginFrame(args);
EXPECT_TRUE(scheduler_->ImplLatencyTakesPriority());
task_runner_->RunPendingTasks(); // Run posted deadline to finish the frame.
ASSERT_FALSE(client_->IsInsideBeginImplFrame());
// Set an interval of 10ms. The bmf_to_activate_interval should be 1*4 = 4ms,
// to account for queue + main_frame + pending_tree + activation durations.
// With a draw time of 1ms and fudge factor of 1ms, the interval available for
// the main frame to be activated is 8ms, so it should be considered fast.
scheduler_->SetNeedsRedraw();
interval = base::TimeDelta::FromMilliseconds(10);
task_runner_->AdvanceMockTickClock(interval);
args = viz::BeginFrameArgs::Create(BEGINFRAME_FROM_HERE, 0u, 2u,
task_runner_->NowTicks(),
task_runner_->NowTicks() + interval,
interval, viz::BeginFrameArgs::NORMAL);
fake_external_begin_frame_source_->TestOnBeginFrame(args);
EXPECT_FALSE(scheduler_->ImplLatencyTakesPriority());
task_runner_->RunPendingTasks(); // Run posted deadline to finish the frame.
ASSERT_FALSE(client_->IsInsideBeginImplFrame());
// Increase the draw duration to decrease the time available for the main
// frame. This should prioritize the impl thread.
scheduler_->SetNeedsRedraw();
fake_compositor_timing_history_->SetDrawDurationEstimate(
base::TimeDelta::FromMilliseconds(7));
task_runner_->AdvanceMockTickClock(interval);
args = viz::BeginFrameArgs::Create(BEGINFRAME_FROM_HERE, 0u, 3u,
task_runner_->NowTicks(),
task_runner_->NowTicks() + interval,
interval, viz::BeginFrameArgs::NORMAL);
fake_external_begin_frame_source_->TestOnBeginFrame(args);
EXPECT_TRUE(scheduler_->ImplLatencyTakesPriority());
}
TEST_F(SchedulerTest, WaitForAllPipelineStagesSkipsMissedBeginFrames) {
scheduler_settings_.wait_for_all_pipeline_stages_before_draw = true;
client_ = std::make_unique<FakeSchedulerClient>();
CreateScheduler(EXTERNAL_BFS);
// Initialize frame sink so that state machine Scheduler and state machine
// need BeginFrames.
scheduler_->SetVisible(true);
scheduler_->SetCanDraw(true);
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation");
client_->Reset();
scheduler_->DidCreateAndInitializeLayerTreeFrameSink();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_TRUE(scheduler_->begin_frames_expected());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
base::TimeDelta interval = base::TimeDelta::FromMilliseconds(16);
task_runner_->AdvanceMockTickClock(interval);
viz::BeginFrameArgs args = viz::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, 0u, 1u, task_runner_->NowTicks(),
task_runner_->NowTicks() + interval, interval,
viz::BeginFrameArgs::MISSED);
fake_external_begin_frame_source_->TestOnBeginFrame(args);
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
}
TEST_F(
SchedulerTest,
WaitForAllPipelineStagesUsesMissedBeginFramesWithSurfaceSynchronization) {
scheduler_settings_.wait_for_all_pipeline_stages_before_draw = true;
scheduler_settings_.enable_surface_synchronization = true;
client_ = std::make_unique<FakeSchedulerClient>();
CreateScheduler(EXTERNAL_BFS);
// Initialize frame sink so that Scheduler and state machine need BeginFrames.
scheduler_->SetVisible(true);
scheduler_->SetCanDraw(true);
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation");
client_->Reset();
scheduler_->DidCreateAndInitializeLayerTreeFrameSink();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_TRUE(scheduler_->begin_frames_expected());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// Uses MISSED BeginFrames even after the deadline has passed.
base::TimeDelta interval = base::TimeDelta::FromMilliseconds(16);
task_runner_->AdvanceMockTickClock(interval);
base::TimeTicks timestamp = task_runner_->NowTicks();
// Deadline should have passed after this.
task_runner_->AdvanceMockTickClock(interval * 2);
viz::BeginFrameArgs args = viz::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, 0u, 1u, timestamp, timestamp + interval, interval,
viz::BeginFrameArgs::MISSED);
fake_external_begin_frame_source_->TestOnBeginFrame(args);
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
}
TEST_F(SchedulerTest, WaitForAllPipelineStagesAlwaysObservesBeginFrames) {
scheduler_settings_.wait_for_all_pipeline_stages_before_draw = true;
scheduler_settings_.enable_surface_synchronization = true;
client_ = std::make_unique<FakeSchedulerClient>();
CreateScheduler(EXTERNAL_BFS);
// Initialize frame sink, request a main frame but defer commits, so that
// state machine is idle.
scheduler_->SetVisible(true);
scheduler_->SetCanDraw(true);
EXPECT_ACTIONS("ScheduledActionBeginLayerTreeFrameSinkCreation");
client_->Reset();
scheduler_->DidCreateAndInitializeLayerTreeFrameSink();
scheduler_->SetDeferCommits(true);
scheduler_->SetNeedsBeginMainFrame();
EXPECT_TRUE(scheduler_->begin_frames_expected());
EXPECT_FALSE(client_->IsInsideBeginImplFrame());
client_->Reset();
// In full-pipe mode, the SchedulerStateMachine always wants BeginFrames, even
// if it is otherwise idle.
EXPECT_TRUE(scheduler_->begin_frames_expected());
EXPECT_TRUE(scheduler_->BeginFrameNeeded());
// Scheduler begins a frame even if otherwise idle.
base::TimeDelta interval = base::TimeDelta::FromMilliseconds(16);
task_runner_->AdvanceMockTickClock(interval);
base::TimeTicks timestamp = task_runner_->NowTicks();
viz::BeginFrameArgs args = viz::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, 0u, 1u, timestamp, timestamp + interval, interval,
viz::BeginFrameArgs::NORMAL);
fake_external_begin_frame_source_->TestOnBeginFrame(args);
EXPECT_ACTIONS("WillBeginImplFrame");
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
// BeginFrame deadline is blocked because commits are deferred.
task_runner_->RunPendingTasks();
EXPECT_ACTIONS();
EXPECT_TRUE(client_->IsInsideBeginImplFrame());
client_->Reset();
}
TEST_F(SchedulerTest, CriticalBeginMainFrameIsFast_CommitEstimateSlow) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_->SetCommitDurationEstimate(kSlowDuration);
EXPECT_SCOPED(AdvanceFrame());
EXPECT_FALSE(scheduler_->state_machine()
.critical_begin_main_frame_to_activate_is_fast());
}
TEST_F(SchedulerTest, CriticalBeginMainFrameIsFast_CommitEstimateFast) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(scheduler_->state_machine()
.critical_begin_main_frame_to_activate_is_fast());
}
TEST_F(SchedulerTest, ShouldDeferInvalidation_AllEstimatesFast) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
EXPECT_SCOPED(AdvanceFrame());
EXPECT_TRUE(scheduler_->state_machine()
.should_defer_invalidation_for_fast_main_frame());
}
TEST_F(SchedulerTest, ShouldDeferInvalidation_BMFStartToReadyToCommitSlow) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_
->SetBeginMainFrameStartToReadyToCommitDurationEstimate(kSlowDuration);
EXPECT_SCOPED(AdvanceFrame());
EXPECT_FALSE(scheduler_->state_machine()
.should_defer_invalidation_for_fast_main_frame());
}
TEST_F(SchedulerTest, ShouldDeferInvalidation_BMFQueueDurationCriticalSlow) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationCriticalEstimate(kSlowDuration);
EXPECT_SCOPED(AdvanceFrame());
EXPECT_FALSE(scheduler_->state_machine()
.should_defer_invalidation_for_fast_main_frame());
}
TEST_F(SchedulerTest, ShouldDeferInvalidation_BMFQueueDurationNotCriticalSlow) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
scheduler_->SetTreePrioritiesAndScrollState(
TreePriority::SMOOTHNESS_TAKES_PRIORITY,
ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
fake_compositor_timing_history_
->SetBeginMainFrameQueueDurationNotCriticalEstimate(kSlowDuration);
EXPECT_SCOPED(AdvanceFrame());
EXPECT_FALSE(scheduler_->state_machine()
.should_defer_invalidation_for_fast_main_frame());
}
TEST_F(SchedulerTest, SlowMainThreadButEstimatedFastTriggersInvalidations) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
scheduler_->SetNeedsImplSideInvalidation(true);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
// Main thread is estimated fast, invalidation will be deferred.
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
// Draw deadline.
client_->Reset();
task_runner_->RunPendingTasks();
EXPECT_ACTIONS();
// Next frame. The invalidation should not be throttled.
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionPerformImplSideInvalidation");
}
TEST_F(SchedulerTest,
SlowMainThreadRasterButEstimatedFastDoesNotTriggersInvalidations) {
SetUpScheduler(EXTERNAL_BFS);
scheduler_->SetNeedsBeginMainFrame();
scheduler_->SetNeedsImplSideInvalidation(true);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
// Main thread is estimated fast, invalidation will be deferred.
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
// Commit before deadline but not ready to activate.
client_->Reset();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("ScheduledActionCommit");
// Draw deadline.
client_->Reset();
task_runner_->RunPendingTasks();
EXPECT_ACTIONS();
// Next frame. The invalidation should still be throttled.
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame");
}
TEST_F(SchedulerTest, SynchronousCompositorImplSideInvalidation) {
// Synchronous compositor doesn't have a deadline and our heuristics can't
// work. We should never be prioritizing impl-side invalidations over main
// frames.
scheduler_settings_.using_synchronous_renderer_compositor = true;
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kSlowDuration);
scheduler_->SetNeedsBeginMainFrame();
const bool needs_first_draw_on_activation = true;
scheduler_->SetNeedsImplSideInvalidation(needs_first_draw_on_activation);
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
}
TEST_F(SchedulerTest, DontSkipMainFrameAfterClearingHistory) {
// Set up a fast estimate for the main frame and make it miss the deadline.
scheduler_settings_.main_frame_before_activation_enabled = true;
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
client_->Reset();
scheduler_->SetNeedsBeginMainFrame();
EXPECT_SCOPED(AdvanceFrame());
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
"ScheduledActionSendBeginMainFrame");
// Now commit during the second frame, since the main thread missed the last
// deadline but we have a fast estimate, we would want to skip the next main
// frame.
client_->Reset();
EXPECT_SCOPED(AdvanceFrame());
scheduler_->SetNeedsBeginMainFrame();
scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
scheduler_->NotifyReadyToCommit();
EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionCommit");
// But during the commit, the history is cleared. So the main frame should not
// be skipped.
client_->Reset();
scheduler_->ClearHistory();
EXPECT_ACTIONS("ScheduledActionSendBeginMainFrame");
}
TEST_F(SchedulerTest, NoInvalidationForAnimateOnlyFrames) {
SetUpScheduler(EXTERNAL_BFS);
fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration);
client_->Reset();
scheduler_->SetNeedsImplSideInvalidation(true);
bool animate_only = true;
EXPECT_SCOPED(AdvanceFrame(animate_only));
EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame");
client_->Reset();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS();
// Now send a frame which requires full updates.
animate_only = false;
EXPECT_SCOPED(AdvanceFrame(animate_only));
EXPECT_ACTIONS("WillBeginImplFrame",
"ScheduledActionPerformImplSideInvalidation");
client_->Reset();
task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true));
EXPECT_ACTIONS();
}
} // namespace
} // namespace cc