[go: nahoru, domu]

blob: 717d25896d13eb8b4d136faba16aad301a623484 [file] [log] [blame]
// Copyright 2015 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 "components/viz/service/display/display_scheduler.h"
#include <set>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/test/null_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "cc/test/scheduler_test_common.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/surfaces/surface_info.h"
#include "components/viz/service/display/display.h"
#include "components/viz/service/display/display_resource_provider_software.h"
#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
#include "components/viz/test/begin_frame_args_test.h"
#include "components/viz/test/fake_external_begin_frame_source.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace viz {
namespace {
constexpr base::TimeDelta k1Usec = base::Microseconds(1);
constexpr base::TimeDelta kVSyncInterval = base::Milliseconds(16);
const int kMaxPendingSwaps = 1;
static constexpr FrameSinkId kArbitraryFrameSinkId(1, 1);
class TestDisplayDamageTracker : public DisplayDamageTracker {
public:
using DisplayDamageTracker::DisplayDamageTracker;
~TestDisplayDamageTracker() override = default;
void SurfaceDamagedForTest(const SurfaceId& surface_id,
const BeginFrameAck& ack,
bool display_damaged) {
if (display_damaged)
undrawn_surfaces_.insert(surface_id);
ProcessSurfaceDamage(surface_id, ack, display_damaged);
}
void ClearUndrawnSurfaces() { undrawn_surfaces_.clear(); }
void SetRootFrameMissingForTest(bool missing) {
SetRootFrameMissing(missing);
}
// DisplayDamageTracker overrides
bool SurfaceHasUnackedFrame(const SurfaceId& surface_id) const override {
return base::Contains(undrawn_surfaces_, surface_id);
}
void UpdateRootFrameMissing() override {
// We don't create actual surfaces, so make sure they are not missing
SetRootFrameMissing(false);
}
private:
std::set<SurfaceId> undrawn_surfaces_;
};
class FakeDisplaySchedulerClient : public DisplaySchedulerClient {
public:
explicit FakeDisplaySchedulerClient(TestDisplayDamageTracker* damage_tracker)
: damage_tracker_(std::move(damage_tracker)),
draw_and_swap_count_(0),
next_draw_and_swap_fails_(false) {}
~FakeDisplaySchedulerClient() override {}
bool DrawAndSwap(base::TimeTicks frame_time,
base::TimeTicks expected_display_time) override {
draw_and_swap_count_++;
bool success = !next_draw_and_swap_fails_;
next_draw_and_swap_fails_ = false;
if (success)
damage_tracker_->ClearUndrawnSurfaces();
return success;
}
void DidFinishFrame(const BeginFrameAck& ack) override {
last_begin_frame_ack_ = ack;
}
base::TimeDelta GetEstimatedDisplayDrawTime(
const base::TimeDelta interval,
double percentile) const override {
return estimated_display_draw_time_;
}
void OnObservingBeginFrameSourceChanged(bool observing) override {}
int draw_and_swap_count() const { return draw_and_swap_count_; }
void SetNextDrawAndSwapFails() { next_draw_and_swap_fails_ = true; }
const BeginFrameAck& last_begin_frame_ack() { return last_begin_frame_ack_; }
void set_estimated_display_draw_time(
base::TimeDelta estimated_display_draw_time) {
estimated_display_draw_time_ = estimated_display_draw_time;
}
protected:
raw_ptr<TestDisplayDamageTracker> damage_tracker_ = nullptr;
int draw_and_swap_count_;
bool next_draw_and_swap_fails_;
BeginFrameAck last_begin_frame_ack_;
base::TimeDelta estimated_display_draw_time_;
};
class TestDisplayScheduler : public DisplayScheduler {
public:
TestDisplayScheduler(DisplayDamageTracker* damage_tracker,
BeginFrameSource* begin_frame_source,
SurfaceManager* surface_manager,
base::SingleThreadTaskRunner* task_runner,
int max_pending_swaps,
bool wait_for_all_surfaces_before_draw)
: DisplayScheduler(begin_frame_source,
task_runner,
PendingSwapParams(max_pending_swaps),
/*hint_session_factory=*/nullptr,
wait_for_all_surfaces_before_draw),
scheduler_begin_frame_deadline_count_(0) {
SetDamageTracker(damage_tracker);
}
base::TimeTicks DesiredBeginFrameDeadlineTimeForTest() {
BeginFrameDeadlineMode deadline_mode = AdjustedBeginFrameDeadlineMode();
return DesiredBeginFrameDeadlineTime(deadline_mode,
current_begin_frame_args_);
}
void BeginFrameDeadlineForTest() {
// Ensure that any missed BeginFrames were handled by the scheduler. We need
// to run the scheduled task ourselves since the NullTaskRunner won't.
if (!missed_begin_frame_task_.IsCancelled())
missed_begin_frame_task_.callback().Run();
OnBeginFrameDeadline();
}
void ScheduleBeginFrameDeadline() override {
scheduler_begin_frame_deadline_count_++;
DisplayScheduler::ScheduleBeginFrameDeadline();
}
int scheduler_begin_frame_deadline_count() {
return scheduler_begin_frame_deadline_count_;
}
bool inside_begin_frame_deadline_interval() {
return inside_begin_frame_deadline_interval_;
}
base::TimeTicks current_frame_time() const {
return current_begin_frame_args_.frame_time;
}
bool has_pending_surfaces() { return has_pending_surfaces_; }
bool is_swap_throttled() const {
return pending_swaps_ >= pending_swap_params_.max_pending_swaps;
}
protected:
int scheduler_begin_frame_deadline_count_;
};
class DisplaySchedulerTest : public testing::Test {
public:
explicit DisplaySchedulerTest(bool wait_for_all_surfaces_before_draw = false)
: wait_for_all_surfaces_before_draw_(wait_for_all_surfaces_before_draw),
fake_begin_frame_source_(0.f, false),
task_runner_(new base::NullTaskRunner),
surface_manager_(nullptr, 4u),
resource_provider_(&shared_bitmap_manager_),
aggregator_(&surface_manager_, &resource_provider_, false, false),
damage_tracker_(
std::make_unique<TestDisplayDamageTracker>(&surface_manager_,
&aggregator_)),
client_(damage_tracker_.get()) {
now_src_.Advance(base::Microseconds(10000));
}
~DisplaySchedulerTest() override {
}
void SetUp() override;
void SetNewRootSurface(SurfaceId surface_id) {
damage_tracker_->SetNewRootSurface(surface_id);
}
void AdvanceTimeAndBeginFrameForTest(
const std::vector<SurfaceId>& observing_surfaces) {
now_src_.Advance(base::Microseconds(10000));
// FakeBeginFrameSource deals with |source_id| and |sequence_number|.
last_begin_frame_args_ = fake_begin_frame_source_.CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, &now_src_);
fake_begin_frame_source_.TestOnBeginFrame(last_begin_frame_args_);
for (const auto& surface_id : observing_surfaces) {
damage_tracker_->OnSurfaceDamageExpected(surface_id,
last_begin_frame_args_);
}
}
void SurfaceDamaged(const SurfaceId& surface_id) {
damage_tracker_->SurfaceDamagedForTest(surface_id,
AckForCurrentBeginFrame(), true);
}
protected:
base::SimpleTestTickClock& now_src() { return now_src_; }
FakeDisplaySchedulerClient& client() { return client_; }
DisplayScheduler* scheduler() { return scheduler_.get(); }
BeginFrameAck AckForCurrentBeginFrame() {
DCHECK(last_begin_frame_args_.IsValid());
return BeginFrameAck(last_begin_frame_args_, true);
}
bool wait_for_all_surfaces_before_draw_;
FakeExternalBeginFrameSource fake_begin_frame_source_;
BeginFrameArgs last_begin_frame_args_;
base::SimpleTestTickClock now_src_;
scoped_refptr<base::NullTaskRunner> task_runner_;
SurfaceManager surface_manager_;
ServerSharedBitmapManager shared_bitmap_manager_;
DisplayResourceProviderSoftware resource_provider_;
SurfaceAggregator aggregator_;
std::unique_ptr<TestDisplayDamageTracker> damage_tracker_;
FakeDisplaySchedulerClient client_;
std::unique_ptr<TestDisplayScheduler> scheduler_;
};
void DisplaySchedulerTest::SetUp() {
scheduler_ = std::make_unique<TestDisplayScheduler>(
damage_tracker_.get(), &fake_begin_frame_source_, &surface_manager_,
task_runner_.get(), kMaxPendingSwaps, wait_for_all_surfaces_before_draw_);
damage_tracker_->SetRootFrameMissingForTest(false);
scheduler_->SetClient(&client_);
}
TEST_F(DisplaySchedulerTest, ResizeHasLateDeadlineUntilNewRootSurface) {
SurfaceId root_surface_id1(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId root_surface_id2(
kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(3, base::UnguessableToken::Create()));
base::TimeTicks late_deadline;
scheduler_->SetVisible(true);
// Go trough an initial BeginFrame cycle with the root surface.
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
SetNewRootSurface(root_surface_id1);
scheduler_->BeginFrameDeadlineForTest();
// Resize on the next begin frame cycle should cause the deadline to wait
// for a new root surface.
AdvanceTimeAndBeginFrameForTest({root_surface_id1});
late_deadline = now_src().NowTicks() + BeginFrameArgs::DefaultInterval();
SurfaceDamaged(sid1);
EXPECT_GT(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
damage_tracker_->DisplayResized();
EXPECT_EQ(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
damage_tracker_->OnSurfaceMarkedForDestruction(root_surface_id1);
SetNewRootSurface(root_surface_id2);
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
// Verify deadline goes back to normal after resize.
late_deadline = now_src().NowTicks() + BeginFrameArgs::DefaultInterval();
AdvanceTimeAndBeginFrameForTest({root_surface_id2, sid1});
SurfaceDamaged(sid1);
EXPECT_GT(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
SurfaceDamaged(root_surface_id2);
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
}
TEST_F(DisplaySchedulerTest, ResizeHasLateDeadlineUntilDamagedSurface) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
base::TimeTicks late_deadline;
scheduler_->SetVisible(true);
// Go trough an initial BeginFrame cycle with the root surface.
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
SetNewRootSurface(root_surface_id);
scheduler_->BeginFrameDeadlineForTest();
// Resize on the next begin frame cycle should cause the deadline to wait
// for a new root surface.
AdvanceTimeAndBeginFrameForTest({root_surface_id});
late_deadline = now_src().NowTicks() + BeginFrameArgs::DefaultInterval();
SurfaceDamaged(sid1);
EXPECT_GT(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
damage_tracker_->DisplayResized();
EXPECT_EQ(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
SurfaceDamaged(root_surface_id);
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
// Verify deadline goes back to normal after resize.
AdvanceTimeAndBeginFrameForTest({root_surface_id, sid1});
late_deadline = now_src().NowTicks() + BeginFrameArgs::DefaultInterval();
SurfaceDamaged(sid1);
EXPECT_GT(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
SurfaceDamaged(root_surface_id);
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
}
TEST_F(DisplaySchedulerTest, SurfaceDamaged) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
SurfaceId sid2(kArbitraryFrameSinkId,
LocalSurfaceId(3, base::UnguessableToken::Create()));
scheduler_->SetVisible(true);
SetNewRootSurface(root_surface_id);
EXPECT_EQ(BeginFrameAck(), client_.last_begin_frame_ack());
// Set surface1 as active via SurfaceDamageExpected().
AdvanceTimeAndBeginFrameForTest({sid1});
EXPECT_EQ(BeginFrameAck(), client_.last_begin_frame_ack());
// Damage only from surface 2 (inactive) does not trigger deadline early.
SurfaceDamaged(sid2);
EXPECT_TRUE(scheduler_->has_pending_surfaces());
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Damage from surface 1 triggers deadline early.
SurfaceDamaged(sid1);
EXPECT_FALSE(scheduler_->has_pending_surfaces());
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(BeginFrameAck(last_begin_frame_args_, true),
client_.last_begin_frame_ack());
// Set both surface 1 and 2 as active via SurfaceDamageExpected().
AdvanceTimeAndBeginFrameForTest({sid1, sid2});
// Deadline doesn't trigger early until surface 1 and 2 are both damaged.
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
SurfaceDamaged(sid1);
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
SurfaceDamaged(sid2);
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(BeginFrameAck(last_begin_frame_args_, true),
client_.last_begin_frame_ack());
// Surface damage with |!has_damage| triggers early deadline if other damage
// exists.
AdvanceTimeAndBeginFrameForTest({sid1, sid2});
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
SurfaceDamaged(sid2);
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
BeginFrameAck ack = AckForCurrentBeginFrame();
ack.has_damage = false;
damage_tracker_->SurfaceDamagedForTest(sid1, ack, false);
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
// Surface damage with |!has_damage| does not trigger early deadline if no
// other damage exists.
AdvanceTimeAndBeginFrameForTest({sid1});
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
ack = AckForCurrentBeginFrame();
ack.has_damage = false;
damage_tracker_->SurfaceDamagedForTest(sid1, ack, false);
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(BeginFrameAck(last_begin_frame_args_, false),
client_.last_begin_frame_ack());
// System should be idle now.
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
EXPECT_FALSE(scheduler_->inside_begin_frame_deadline_interval());
// Surface damage with |!display_damaged| does not affect needs_draw and
// scheduler stays idle.
damage_tracker_->SurfaceDamagedForTest(sid1, AckForCurrentBeginFrame(),
false);
EXPECT_FALSE(scheduler_->inside_begin_frame_deadline_interval());
// Deadline should trigger early if child surfaces are idle and
// we get damage on the root surface.
damage_tracker_->OnSurfaceDamageExpected(root_surface_id,
last_begin_frame_args_);
EXPECT_FALSE(scheduler_->inside_begin_frame_deadline_interval());
SurfaceDamaged(root_surface_id);
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
}
class DisplaySchedulerWaitForAllSurfacesTest : public DisplaySchedulerTest {
public:
DisplaySchedulerWaitForAllSurfacesTest()
: DisplaySchedulerTest(true /* wait_for_all_surfaces_before_draw */) {}
};
TEST_F(DisplaySchedulerWaitForAllSurfacesTest, WaitForAllSurfacesBeforeDraw) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
SurfaceId sid2(kArbitraryFrameSinkId,
LocalSurfaceId(3, base::UnguessableToken::Create()));
scheduler_->SetVisible(true);
SetNewRootSurface(root_surface_id);
// Set surface1 as active via SurfaceDamageExpected().
AdvanceTimeAndBeginFrameForTest({sid1});
// Deadline is blocked indefinitely until surface 1 is damaged.
EXPECT_EQ(base::TimeTicks::Max(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Damage only from surface 2 (inactive) does not change deadline.
SurfaceDamaged(sid2);
EXPECT_TRUE(scheduler_->has_pending_surfaces());
EXPECT_EQ(base::TimeTicks::Max(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Damage from surface 1 triggers deadline immediately.
SurfaceDamaged(sid1);
EXPECT_FALSE(scheduler_->has_pending_surfaces());
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
// Surface damage with |!has_damage| triggers immediate deadline if other
// damage exists.
AdvanceTimeAndBeginFrameForTest({sid1, sid2});
EXPECT_EQ(base::TimeTicks::Max(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
SurfaceDamaged(sid2);
EXPECT_EQ(base::TimeTicks::Max(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
BeginFrameAck ack = AckForCurrentBeginFrame();
ack.has_damage = false;
damage_tracker_->SurfaceDamagedForTest(sid1, ack, false);
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
// Surface damage with |!has_damage| also triggers immediate deadline even if
// no other damage exists.
AdvanceTimeAndBeginFrameForTest({sid1});
EXPECT_EQ(base::TimeTicks::Max(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
ack = AckForCurrentBeginFrame();
ack.has_damage = false;
damage_tracker_->SurfaceDamagedForTest(sid1, ack, false);
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Stray BeginFrameAcks for older BeginFrames are ignored.
ack.frame_id.sequence_number--;
damage_tracker_->SurfaceDamagedForTest(sid1, ack, false);
// If the acknowledgment above was not ignored and instead updated the surface
// state for sid1, the surface would become a pending surface again, and the
// deadline would no longer be immediate. Since it is ignored, we are
// expecting the deadline to remain immedate.
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
// System should be idle now because we had a frame without damage. Restore it
// to active state (DisplayScheduler observing BeginFrames) for the next test.
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
EXPECT_FALSE(scheduler_->inside_begin_frame_deadline_interval());
SurfaceDamaged(sid1);
scheduler_->BeginFrameDeadlineForTest();
// BeginFrame without expected surface damage triggers immediate deadline.
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
EXPECT_TRUE(scheduler_->inside_begin_frame_deadline_interval());
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->BeginFrameDeadlineForTest();
}
TEST_F(DisplaySchedulerTest, OutputSurfaceLost) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
scheduler_->SetVisible(true);
SetNewRootSurface(root_surface_id);
// DrawAndSwap normally.
AdvanceTimeAndBeginFrameForTest({sid1});
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
EXPECT_EQ(0, client_.draw_and_swap_count());
SurfaceDamaged(sid1);
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(1, client_.draw_and_swap_count());
// Deadline triggers immediately on OutputSurfaceLost.
AdvanceTimeAndBeginFrameForTest({sid1});
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
scheduler_->OutputSurfaceLost();
EXPECT_GE(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Deadline does not DrawAndSwap after OutputSurfaceLost.
EXPECT_EQ(1, client_.draw_and_swap_count());
SurfaceDamaged(sid1);
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(1, client_.draw_and_swap_count());
}
TEST_F(DisplaySchedulerTest, VisibleWithoutDamageNoTicks) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
scheduler_->SetVisible(true);
// When becoming visible, don't start listening for begin frames until there
// is some damage.
EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
SetNewRootSurface(root_surface_id);
EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
}
TEST_F(DisplaySchedulerTest, VisibleWithDamageTicks) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
SetNewRootSurface(root_surface_id);
// When there is damage, start listening for begin frames once becoming
// visible.
EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
scheduler_->SetVisible(true);
EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
}
TEST_F(DisplaySchedulerTest, Visibility) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
// Set the root surface.
SetNewRootSurface(root_surface_id);
scheduler_->SetVisible(true);
EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
// DrawAndSwap normally.
AdvanceTimeAndBeginFrameForTest({sid1});
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
EXPECT_EQ(0, client_.draw_and_swap_count());
SurfaceDamaged(sid1);
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(1, client_.draw_and_swap_count());
AdvanceTimeAndBeginFrameForTest({sid1});
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Become not visible.
scheduler_->SetVisible(false);
// It will stop listening for begin frames after the current deadline.
EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
// Deadline does not DrawAndSwap when not visible.
EXPECT_EQ(1, client_.draw_and_swap_count());
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(1, client_.draw_and_swap_count());
// Now it stops listening for begin frames.
EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
// Does not start listening for begin frames when becoming visible without
// damage.
scheduler_->SetVisible(true);
EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
scheduler_->SetVisible(false);
// Does not start listening for begin frames when damage arrives.
SurfaceDamaged(sid1);
EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
// But does when becoming visible with damage again.
scheduler_->SetVisible(true);
EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
}
TEST_F(DisplaySchedulerTest, ResizeCausesSwap) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
scheduler_->SetVisible(true);
SetNewRootSurface(root_surface_id);
// DrawAndSwap normally.
AdvanceTimeAndBeginFrameForTest({sid1});
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
EXPECT_EQ(0, client_.draw_and_swap_count());
SurfaceDamaged(sid1);
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(1, client_.draw_and_swap_count());
damage_tracker_->DisplayResized();
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
// DisplayResizedd should trigger a swap to happen.
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(2, client_.draw_and_swap_count());
}
TEST_F(DisplaySchedulerTest, RootFrameMissing) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
base::TimeTicks late_deadline;
scheduler_->SetVisible(true);
SetNewRootSurface(root_surface_id);
// DrawAndSwap normally.
AdvanceTimeAndBeginFrameForTest({sid1});
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
EXPECT_EQ(0, client_.draw_and_swap_count());
SurfaceDamaged(sid1);
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(1, client_.draw_and_swap_count());
// Deadline triggers late while root frame is missing.
AdvanceTimeAndBeginFrameForTest({sid1});
late_deadline = now_src().NowTicks() + BeginFrameArgs::DefaultInterval();
SurfaceDamaged(sid1);
EXPECT_GT(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
damage_tracker_->SetRootFrameMissingForTest(true);
EXPECT_EQ(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Deadline does not DrawAndSwap while root frame is missing.
EXPECT_EQ(1, client_.draw_and_swap_count());
SurfaceDamaged(sid1);
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(1, client_.draw_and_swap_count());
// Deadline triggers normally when root frame is not missing.
AdvanceTimeAndBeginFrameForTest({sid1, root_surface_id});
EXPECT_FALSE(scheduler_->inside_begin_frame_deadline_interval());
SurfaceDamaged(sid1);
// The deadline is not updated because the display scheduler does not receive
// a BeginFrame while the root frame is missing.
EXPECT_EQ(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
damage_tracker_->SetRootFrameMissingForTest(false);
EXPECT_TRUE(scheduler_->inside_begin_frame_deadline_interval());
SurfaceDamaged(root_surface_id);
EXPECT_EQ(base::TimeTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
EXPECT_EQ(1, client_.draw_and_swap_count());
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(2, client_.draw_and_swap_count());
}
TEST_F(DisplaySchedulerTest, DidSwapBuffers) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
SurfaceId sid2(kArbitraryFrameSinkId,
LocalSurfaceId(3, base::UnguessableToken::Create()));
scheduler_->SetVisible(true);
SetNewRootSurface(root_surface_id);
// Set surface 1 and 2 as active.
AdvanceTimeAndBeginFrameForTest({sid1, sid2});
// DrawAndSwap normally.
EXPECT_LT(now_src().NowTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
EXPECT_EQ(0, client_.draw_and_swap_count());
SurfaceDamaged(sid1);
SurfaceDamaged(sid2);
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(1, client_.draw_and_swap_count());
scheduler_->DidSwapBuffers();
// Deadline triggers late when swap throttled.
AdvanceTimeAndBeginFrameForTest({sid1, sid2});
base::TimeTicks late_deadline =
now_src().NowTicks() + BeginFrameArgs::DefaultInterval();
// Damage surface 1, but not surface 2 so we avoid triggering deadline
// early because all surfaces are ready.
SurfaceDamaged(sid1);
EXPECT_EQ(late_deadline, scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Don't draw and swap in deadline while swap throttled.
EXPECT_EQ(1, client_.draw_and_swap_count());
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(1, client_.draw_and_swap_count());
// Deadline triggers normally once not swap throttled.
// Damage from previous BeginFrame should cary over, so don't damage again.
scheduler_->DidReceiveSwapBuffersAck();
AdvanceTimeAndBeginFrameForTest({sid2});
base::TimeTicks expected_deadline =
last_begin_frame_args_.deadline -
BeginFrameArgs::DefaultEstimatedDisplayDrawTime(
last_begin_frame_args_.interval);
EXPECT_EQ(expected_deadline,
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Still waiting for surface 2. Once it updates, deadline should trigger
// immediately again.
SurfaceDamaged(sid2);
EXPECT_EQ(base::TimeTicks(),
scheduler_->DesiredBeginFrameDeadlineTimeForTest());
// Draw and swap now that we aren't throttled.
EXPECT_EQ(1, client_.draw_and_swap_count());
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(2, client_.draw_and_swap_count());
}
// This test verfies that we try to reschedule the deadline
// after any event that may change what deadline we want.
TEST_F(DisplaySchedulerTest, ScheduleBeginFrameDeadline) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
SurfaceId sid1(kArbitraryFrameSinkId,
LocalSurfaceId(2, base::UnguessableToken::Create()));
int count = 1;
EXPECT_EQ(count, scheduler_->scheduler_begin_frame_deadline_count());
scheduler_->SetVisible(true);
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
scheduler_->SetVisible(true);
EXPECT_EQ(count, scheduler_->scheduler_begin_frame_deadline_count());
scheduler_->SetVisible(false);
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
// Set the root surface while not visible.
SetNewRootSurface(root_surface_id);
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
scheduler_->SetVisible(true);
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
// Set the root surface while visible.
SetNewRootSurface(root_surface_id);
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
scheduler_->BeginFrameDeadlineForTest();
scheduler_->DidSwapBuffers();
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
scheduler_->DidReceiveSwapBuffersAck();
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
damage_tracker_->DisplayResized();
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
SetNewRootSurface(root_surface_id);
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
SurfaceDamaged(sid1);
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
damage_tracker_->SetRootFrameMissingForTest(true);
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
scheduler_->OutputSurfaceLost();
EXPECT_EQ(++count, scheduler_->scheduler_begin_frame_deadline_count());
}
TEST_F(DisplaySchedulerTest, SetNeedsOneBeginFrame) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
scheduler_->SetVisible(true);
SetNewRootSurface(root_surface_id);
// Make system idle.
AdvanceTimeAndBeginFrameForTest({root_surface_id});
SurfaceDamaged(root_surface_id);
scheduler_->BeginFrameDeadlineForTest();
AdvanceTimeAndBeginFrameForTest({root_surface_id});
scheduler_->BeginFrameDeadlineForTest();
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
EXPECT_FALSE(scheduler_->inside_begin_frame_deadline_interval());
// SetNeedsOneBeginFrame should make DisplayScheduler active for just a single
// BeginFrame.
scheduler_->SetNeedsOneBeginFrame(false);
EXPECT_TRUE(scheduler_->inside_begin_frame_deadline_interval());
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(BeginFrameAck(last_begin_frame_args_, false),
client_.last_begin_frame_ack());
// System should be idle again.
AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
EXPECT_FALSE(scheduler_->inside_begin_frame_deadline_interval());
}
TEST_F(DisplaySchedulerTest, GpuBusyNotifications) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
scheduler_->SetVisible(true);
SetNewRootSurface(root_surface_id);
// Swap one frame, since max pending swaps is 1 it puts us in a swap throttled
// state.
AdvanceTimeAndBeginFrameForTest({root_surface_id});
EXPECT_EQ(scheduler_->current_frame_time(),
last_begin_frame_args_.frame_time);
EXPECT_EQ(client().draw_and_swap_count(), 0);
SurfaceDamaged(root_surface_id);
scheduler_->BeginFrameDeadlineForTest();
EXPECT_EQ(client().draw_and_swap_count(), 1);
scheduler_->DidSwapBuffers();
EXPECT_TRUE(scheduler_->is_swap_throttled());
// The next vsync should not be blocked from the swap throttling.
EXPECT_FALSE(fake_begin_frame_source_.RequestCallbackOnGpuAvailable());
// Send the next vsync, after which vsyncs will be blocked on busy gpu.
EXPECT_TRUE(fake_begin_frame_source_.RequestCallbackOnGpuAvailable());
// Ack the pending swap buffers, we should no longer be marked gpu busy.
scheduler_->DidReceiveSwapBuffersAck();
EXPECT_FALSE(fake_begin_frame_source_.RequestCallbackOnGpuAvailable());
}
TEST_F(DisplaySchedulerTest, OnBeginFrameDeadlineNoClient) {
SurfaceId root_surface_id(
kArbitraryFrameSinkId,
LocalSurfaceId(1, base::UnguessableToken::Create()));
scheduler_->SetVisible(true);
SetNewRootSurface(root_surface_id);
AdvanceTimeAndBeginFrameForTest({root_surface_id});
SurfaceDamaged(root_surface_id);
// During teardown, we may get a BeginFrameDeadline while |client_| is null.
// This should not crash.
scheduler_->SetClient(nullptr);
scheduler_->BeginFrameDeadlineForTest();
}
// Tests that when there is no dynamic scheduler adjustment, that the deadline
// is not shifted.
TEST_F(DisplaySchedulerTest, DefaultBeginFrameArgsDeadline) {
const base::TimeTicks frame_time = base::TimeTicks() + k1Usec;
const base::TimeTicks next_frame_time = frame_time + kVSyncInterval;
BeginFrameArgs args =
fake_begin_frame_source_.CreateBeginFrameArgsWithGenerator(
frame_time, next_frame_time, kVSyncInterval);
EXPECT_EQ(args.deadline, next_frame_time);
}
// Tests the DisplayScheduler when we enable dynamic adjustments of begin
// frames.
class DynamicDisplaySchedulerTest : public DisplaySchedulerTest {
public:
DynamicDisplaySchedulerTest();
~DynamicDisplaySchedulerTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
DynamicDisplaySchedulerTest::DynamicDisplaySchedulerTest() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kDynamicSchedulerForClients, {{"percentile", "90"}});
client_.set_estimated_display_draw_time(base::Milliseconds(2));
}
// Tests that when we are dynamically adjusting begin frames, that the deadline
// is shifted.
TEST_F(DynamicDisplaySchedulerTest, DynamicBeginFrameArgsDeadline) {
const base::TimeTicks frame_time = base::TimeTicks() + k1Usec;
const base::TimeTicks next_frame_time = frame_time + kVSyncInterval;
BeginFrameArgs args =
fake_begin_frame_source_.CreateBeginFrameArgsWithGenerator(
frame_time, next_frame_time, kVSyncInterval);
EXPECT_LT(args.deadline, next_frame_time);
EXPECT_GT(args.deadline, args.frame_time);
// We expect that the deadlines will be offset by the `client_` estimate of
// draw time.
EXPECT_EQ(args.deadline,
next_frame_time -
client_.GetEstimatedDisplayDrawTime(kVSyncInterval, 0.0));
}
} // namespace
} // namespace viz