blob: 5aa90a55aee84c0fd857a04036926db479955426 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 <lib/gtest/test_loop_fixture.h>
#include "src/ui/scenic/lib/scheduling/tests/frame_scheduler_test.h"
using scheduling::Present2Info;
namespace fuchsia {
namespace images {
inline bool operator==(const fuchsia::images::PresentationInfo& a,
const fuchsia::images::PresentationInfo& b) {
return fidl::Equals(a, b);
}
} // namespace images
} // namespace fuchsia
namespace scheduling {
namespace test {
namespace {
// A MockSessionUpdater class which executes the provided function on every
// UpdateSessions call.
//
class MockSessionUpdaterWithFunctionOnUpdate : public MockSessionUpdater {
public:
MockSessionUpdaterWithFunctionOnUpdate(fit::function<void()> function)
: function_(std::move(function)) {}
// |SessionUpdater|
SessionUpdater::UpdateResults UpdateSessions(
const std::unordered_set<SessionId>& sessions_to_update, zx::time target_presentation_time,
zx::time latched_time, uint64_t trace_id = 0) override {
if (function_) {
function_();
}
return MockSessionUpdater::UpdateSessions(std::move(sessions_to_update),
target_presentation_time, latched_time, trace_id);
}
private:
fit::function<void()> function_;
};
} // namespace
// Schedule an update on the scheduler, and also add a callback in the mock updater which will be
// invoked when the frame is finished "rendering".
static void ScheduleUpdateAndCallback(const std::unique_ptr<DefaultFrameScheduler>& scheduler,
const std::unique_ptr<MockSessionUpdater>& updater,
SessionId session_id, zx::time presentation_time,
zx::time acquire_fence_time = zx::time(0)) {
scheduler->ScheduleUpdateForSession(presentation_time, session_id);
updater->AddCallback(session_id, presentation_time, acquire_fence_time);
}
// Schedule an update on the scheduler, and also add a callback in the mock updater which will be
// invoked when the frame is finished "rendering".
static void SchedulePresent2Update(const std::unique_ptr<DefaultFrameScheduler>& scheduler,
const std::unique_ptr<MockSessionUpdater>& updater,
SessionId session_id, zx::time presentation_time,
zx::time acquire_fence_time = zx::time(0),
zx::time latched_time = zx::time(0),
zx::time present_received_time = zx::time(0)) {
Present2Info info = Present2Info(session_id);
info.SetLatchedTime(latched_time);
info.SetPresentReceivedTime(present_received_time);
scheduler->ScheduleUpdateForSession(presentation_time, session_id);
updater->AddPresent2Info(std::move(info), presentation_time, acquire_fence_time);
}
static void SetSessionUpdateFailedNotExpected(FrameScheduler* scheduler,
const SessionId session_id) {
scheduler->SetOnUpdateFailedCallbackForSession(session_id, []() {
// Tests using this helper do not expect their session
// update to fail. Fail the test case.
EXPECT_FALSE(true);
});
}
// Schedule an update on the update manager, and also add a callback in the mock updater which will
// be invoked when the frame is finished "rendering".
static std::shared_ptr<const MockSessionUpdater::CallbackStatus> ScheduleUpdateAndCallback(
const std::unique_ptr<DefaultFrameScheduler::UpdateManager>& update_manager,
MockSessionUpdater* updater, SessionId session_id, zx::time presentation_time,
zx::time acquire_fence_time = zx::time(0)) {
update_manager->ScheduleUpdate(presentation_time, session_id);
return updater->AddCallback(session_id, presentation_time, acquire_fence_time);
}
TEST_F(FrameSchedulerTest, PresentTimeZero_ShouldBeScheduledBeforeNextVsync) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule an update for as soon as possible.
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, /* presentation */ zx::time(0));
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// Should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
}
TEST_F(FrameSchedulerTest, Present2WithTimeZero_ShouldBeScheduledBeforeNextVsync) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
scheduler->SetOnFramePresentedCallbackForSession(kSessionId, [](auto) {});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule an update for as soon as possible.
SchedulePresent2Update(scheduler, mock_updater_, kSessionId,
/* presentation */ zx::time(0));
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// Should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
}
TEST_F(FrameSchedulerTest, PresentBiggerThanNextVsync_ShouldBeScheduledAfterNextVsync) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
EXPECT_EQ(Now(), vsync_timing_->last_vsync_time());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule an update for in between the next two vsyncs.
const auto vsync_interval = vsync_timing_->vsync_interval();
zx::time time_after_vsync =
vsync_timing_->last_vsync_time() + (vsync_interval + vsync_interval / 2);
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId,
/* presentation time*/ time_after_vsync);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Nothing should have been scheduled yet.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Wait for one more vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// Should have been scheduled and handled now.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
}
TEST_F(FrameSchedulerTest, Present2BiggerThanNextVsync_ShouldBeScheduledAfterNextVsync) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
scheduler->SetOnFramePresentedCallbackForSession(kSessionId, [](auto) {});
EXPECT_EQ(Now(), vsync_timing_->last_vsync_time());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule an update for in between the next two vsyncs.
const auto vsync_interval = vsync_timing_->vsync_interval();
zx::time time_after_vsync =
vsync_timing_->last_vsync_time() + (vsync_interval + vsync_interval / 2);
SchedulePresent2Update(scheduler, mock_updater_, kSessionId,
/* presentation time*/ time_after_vsync);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Nothing should have been scheduled yet.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Wait for one more vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// Should have been scheduled and handled now.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
}
TEST_F(FrameSchedulerTest, SinglePresent_ShouldGetSingleRenderCall) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, Now());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
// End the pending frame.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 1u);
// Wait for a very long time.
RunLoopFor(zx::sec(10));
// No further render calls should have been made.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 1u);
}
TEST_F(FrameSchedulerTest, SinglePresent2_ShouldGetSingleRenderCall) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
uint64_t present_count = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId, [&present_count](fuchsia::scenic::scheduling::FramePresentedInfo info) {
present_count += info.presentation_infos.size();
});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
SchedulePresent2Update(scheduler, mock_updater_, kSessionId,
/* presentation time*/ Now());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(present_count, 0u);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, 0u);
// End the pending frame.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
EXPECT_EQ(present_count, 1u);
// Wait for a very long time.
RunLoopFor(zx::sec(10));
// No further render calls should have been made.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, 1u);
}
TEST_F(FrameSchedulerTest, SinglePresent_ShouldGetSingleRenderCallExactlyOnTime) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
// Set the LastVsyncTime arbitrarily in the future.
//
// We want to test our ability to schedule a frame "next time" given an arbitrary start,
// vs in a certain duration from Now() = 0, so this makes that distinction clear.
zx::time future_vsync_time =
zx::time(vsync_timing_->last_vsync_time() + vsync_timing_->vsync_interval() * 6);
vsync_timing_->set_last_vsync_time(future_vsync_time);
EXPECT_GT(vsync_timing_->last_vsync_time(), Now());
// Start the test.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId,
future_vsync_time + vsync_timing_->vsync_interval());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Wait for one vsync period.
RunLoopUntil(zx::time(future_vsync_time + vsync_timing_->vsync_interval()));
// Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
// End the pending frame.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 1u);
// Wait for a very long time.
RunLoopFor(zx::sec(10));
// No further render calls should have been made.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 1u);
}
TEST_F(FrameSchedulerTest, SinglePresent2_ShouldGetSingleRenderCallExactlyOnTime) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
uint64_t present_count = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId, [&present_count](fuchsia::scenic::scheduling::FramePresentedInfo info) {
present_count += info.presentation_infos.size();
});
// Set the LastVsyncTime arbitrarily in the future.
//
// We want to test our ability to schedule a frame "next time" given an arbitrary start,
// vs in a certain duration from Now() = 0, so this makes that distinction clear.
zx::time future_vsync_time =
zx::time(vsync_timing_->last_vsync_time() + vsync_timing_->vsync_interval() * 6);
vsync_timing_->set_last_vsync_time(future_vsync_time);
EXPECT_GT(vsync_timing_->last_vsync_time(), Now());
// Start the test.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(present_count, 0u);
SchedulePresent2Update(scheduler, mock_updater_, kSessionId,
future_vsync_time + vsync_timing_->vsync_interval());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Wait for one vsync period.
RunLoopUntil(zx::time(future_vsync_time + vsync_timing_->vsync_interval()));
// Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, 0u);
// End the pending frame.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
EXPECT_EQ(present_count, 1u);
// Wait for a very long time.
RunLoopFor(zx::sec(10));
// No further render calls should have been made.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, 1u);
}
TEST_F(FrameSchedulerTest, PresentsForTheSameFrame_ShouldGetSingleRenderCall) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId1 = 1;
constexpr SessionId kSessionId2 = 2;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId1);
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId2);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule two updates for now.
zx::time now = Now();
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId1, now);
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId2, now);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Both Presents should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
// End the pending frame.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
// Wait for a very long time.
RunLoopFor(zx::sec(10));
// No further render calls should have been made.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
}
TEST_F(FrameSchedulerTest, Present2sForTheSameFrame_ShouldGetSingleRenderCall) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId1 = 1;
constexpr SessionId kSessionId2 = 2;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId1);
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId2);
scheduler->SetOnFramePresentedCallbackForSession(kSessionId1, [](auto) {});
scheduler->SetOnFramePresentedCallbackForSession(kSessionId2, [](auto) {});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule two updates for now.
zx::time now = Now();
SchedulePresent2Update(scheduler, mock_updater_, kSessionId1, now);
SchedulePresent2Update(scheduler, mock_updater_, kSessionId2, now);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Both Presents should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
// End the pending frame.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
// Wait for a very long time.
RunLoopFor(zx::sec(10));
// No further render calls should have been made.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
}
TEST_F(FrameSchedulerTest, PresentsForDifferentFrames_ShouldGetSeparateRenderCalls) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
EXPECT_EQ(Now(), vsync_timing_->last_vsync_time());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule an update for now.
zx::time now = Now();
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, now);
// Schedule an update for in between the next two vsyncs.
const auto vsync_interval = vsync_timing_->vsync_interval();
zx::time time_after_vsync =
vsync_timing_->last_vsync_time() + vsync_interval + vsync_interval / 2;
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, time_after_vsync);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// First Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// Wait for one more vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Second Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
}
TEST_F(FrameSchedulerTest, Present2sForDifferentFrames_ShouldGetSeparateRenderCalls) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
scheduler->SetOnFramePresentedCallbackForSession(kSessionId, [](auto) {});
EXPECT_EQ(Now(), vsync_timing_->last_vsync_time());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule an update for now.
zx::time now = Now();
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, now);
// Schedule an update for in between the next two vsyncs.
const auto vsync_interval = vsync_timing_->vsync_interval();
zx::time time_after_vsync =
vsync_timing_->last_vsync_time() + vsync_interval + vsync_interval / 2;
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, time_after_vsync);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// First Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// Wait for one more vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Second Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
}
TEST_F(FrameSchedulerTest, SecondPresentDuringRender_ShouldApplyUpdatesAndReschedule) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
// Schedule an update for now.
zx::time now = Now();
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, now);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
// Schedule another update for now.
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, now);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Updates should be applied, but not rendered.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
// End previous frame.
mock_renderer_->EndFrame(/* frame number */ 0, Now());
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 1u);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Second render should have occurred.
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
mock_renderer_->EndFrame(/* frame number */ 1, Now());
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 2u);
}
TEST_F(FrameSchedulerTest, SecondPresent2DuringRender_ShouldApplyUpdatesAndReschedule) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
uint64_t present_count = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId, [&present_count](fuchsia::scenic::scheduling::FramePresentedInfo info) {
present_count += info.presentation_infos.size();
});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(present_count, 0u);
// Schedule an update for now.
zx::time now = Now();
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, now);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
EXPECT_EQ(present_count, 0u);
// Schedule another update for now.
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, now);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Updates should be applied, but not rendered.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, 0u);
// End previous frame.
mock_renderer_->EndFrame(/* frame number */ 0, Now());
EXPECT_EQ(present_count, 1u);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Second render should have occurred.
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
mock_renderer_->EndFrame(/* frame number */ 1, Now());
EXPECT_EQ(present_count, 2u);
}
TEST_F(FrameSchedulerTest, RenderCalls_ShouldNotExceed_MaxOutstandingFrames) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
auto maximum_allowed_render_calls = scheduler->kMaxOutstandingFrames;
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule more updates than the maximum, and signal them rendered but not
// presented.
zx::time now = Now();
for (size_t i = 0; i < maximum_allowed_render_calls + 1; ++i) {
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, now);
// Wait for a long time
zx::duration schedule_frame_wait(5 * vsync_timing_->vsync_interval().get());
RunLoopFor(schedule_frame_wait);
if (mock_renderer_->render_frame_call_count() <= i) {
break;
}
// Signal frame rendered.
mock_renderer_->SignalFrameCpuRendered(i, now + schedule_frame_wait);
mock_renderer_->SignalFrameRendered(i, now + schedule_frame_wait);
}
EXPECT_LE(mock_renderer_->render_frame_call_count(), maximum_allowed_render_calls);
}
TEST_F(FrameSchedulerTest, Present2RenderCalls_ShouldNotExceed_MaxOutstandingFrames) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
auto maximum_allowed_render_calls = scheduler->kMaxOutstandingFrames;
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule more updates than the maximum, and signal them rendered but not
// presented.
zx::time now = Now();
for (size_t i = 0; i < maximum_allowed_render_calls + 1; ++i) {
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, now);
// Wait for a long time
zx::duration schedule_frame_wait(5 * vsync_timing_->vsync_interval().get());
RunLoopFor(schedule_frame_wait);
if (mock_renderer_->render_frame_call_count() <= i) {
break;
}
// Signal frame rendered.
mock_renderer_->SignalFrameCpuRendered(i, now + schedule_frame_wait);
mock_renderer_->SignalFrameRendered(i, now + schedule_frame_wait);
}
EXPECT_LE(mock_renderer_->render_frame_call_count(), maximum_allowed_render_calls);
}
TEST_F(FrameSchedulerTest, SignalSuccessfulPresentCallbackOnlyWhenFramePresented) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
SessionId session_id = 1;
// Schedule an update for now.
zx::time now = Now();
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, now);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
// Schedule another update.
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, now);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Next render doesn't trigger until the previous render is finished.
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
// Drop frame #0. This should not trigger a frame presented signal.
mock_renderer_->SignalFrameDropped(/* frame number */ 0);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
// Frame #0 should still have rendered on the GPU; simulate this.
mock_renderer_->SignalFrameCpuRendered(/* frame number */ 0, Now());
mock_renderer_->SignalFrameRendered(/* frame number */ 0, Now());
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Presenting frame #1 should trigger frame presented signal.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->SignalFrameCpuRendered(/* frame number */ 1, Now());
mock_renderer_->SignalFrameRendered(/* frame number */ 1, Now());
mock_renderer_->SignalFramePresented(/* frame number */ 1, Now());
// Both callbacks are signaled (the failed frame #0, and the successful
// frame #1).
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
}
TEST_F(FrameSchedulerTest, SignalSuccessfulPresent2CallbackOnlyWhenFramePresented) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
uint64_t present_count = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId, [&present_count](fuchsia::scenic::scheduling::FramePresentedInfo info) {
present_count += info.presentation_infos.size();
});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(present_count, 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
SessionId session_id = 1;
// Schedule an update for now.
zx::time now = Now();
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, now);
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
// Schedule another update.
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, now);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Next render doesn't trigger until the previous render is finished.
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
// Drop frame #0. This should not trigger a frame presented signal.
mock_renderer_->SignalFrameDropped(/* frame number */ 0);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(present_count, 0u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
// Frame #0 should still have rendered on the GPU; simulate this.
mock_renderer_->SignalFrameCpuRendered(/* frame number */ 0, Now());
mock_renderer_->SignalFrameRendered(/* frame number */ 0, Now());
EXPECT_EQ(present_count, 0u);
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Presenting frame #1 should trigger frame presented signal.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->SignalFrameCpuRendered(/* frame number */ 1, Now());
mock_renderer_->SignalFrameRendered(/* frame number */ 1, Now());
mock_renderer_->SignalFramePresented(/* frame number */ 1, Now());
// Both callbacks are signaled (the failed frame #0, and the successful
// frame #1).
EXPECT_EQ(present_count, 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
}
TEST_F(FrameSchedulerTest, FailedUpdate_ShouldNotTriggerRenderCall) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
bool update_failed = false;
scheduler->SetOnUpdateFailedCallbackForSession(
kSessionId, [&update_failed, id = kSessionId, frame_scheduler = scheduler.get()]() {
update_failed = true;
// Clear callbacks set on the FrameScheduler when the update fails.
frame_scheduler->ClearCallbacksForSession(id);
});
mock_updater_->SetNextUpdateForSessionFails(kSessionId);
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, Now());
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_TRUE(update_failed);
}
TEST_F(FrameSchedulerTest, FailedUpdateWithRender_ShouldNotCrash) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId1 = 1;
bool update_failed1 = false;
scheduler->SetOnUpdateFailedCallbackForSession(
kSessionId1, [&update_failed1, id = kSessionId1, frame_scheduler = scheduler.get()]() {
update_failed1 = true;
// Clear callbacks set on the FrameScheduler when the update fails.
frame_scheduler->ClearCallbacksForSession(id);
});
mock_updater_->SetNextUpdateForSessionFails(kSessionId1);
constexpr SessionId kSessionId2 = 2;
bool update_failed2 = false;
scheduler->SetOnUpdateFailedCallbackForSession(kSessionId2,
[&update_failed2]() { update_failed2 = true; });
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId1, Now());
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId2, Now());
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, Now());
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_TRUE(update_failed1);
EXPECT_FALSE(update_failed2);
}
TEST_F(FrameSchedulerTest, FailedPresent2Update_ShouldNotTriggerRenderCall) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
bool update_failed = false;
scheduler->SetOnUpdateFailedCallbackForSession(
kSessionId, [&update_failed, id = kSessionId, frame_scheduler = scheduler.get()]() {
update_failed = true;
// Clear callbacks set on the FrameScheduler when the update fails.
frame_scheduler->ClearCallbacksForSession(id);
});
mock_updater_->SetNextUpdateForSessionFails(kSessionId);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, Now());
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_TRUE(update_failed);
}
TEST_F(FrameSchedulerTest, FailedPresent2UpdateWithRender_ShouldNotCrash) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId1 = 1;
bool update_failed1 = false;
scheduler->SetOnUpdateFailedCallbackForSession(
kSessionId1, [&update_failed1, id = kSessionId1, frame_scheduler = scheduler.get()]() {
update_failed1 = true;
// Clear callbacks set on the FrameScheduler when the update fails.
frame_scheduler->ClearCallbacksForSession(id);
});
mock_updater_->SetNextUpdateForSessionFails(kSessionId1);
constexpr SessionId kSessionId2 = 2;
bool update_failed2 = false;
scheduler->SetOnUpdateFailedCallbackForSession(kSessionId2,
[&update_failed2]() { update_failed2 = true; });
SchedulePresent2Update(scheduler, mock_updater_, kSessionId1, Now());
SchedulePresent2Update(scheduler, mock_updater_, kSessionId2, Now());
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, Now());
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_TRUE(update_failed1);
EXPECT_FALSE(update_failed2);
}
TEST_F(FrameSchedulerTest, NoOpUpdateWithSecondPendingUpdate_ShouldBeRescheduled) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId,
Now() + vsync_timing_->vsync_interval());
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId,
Now() + (vsync_timing_->vsync_interval() + zx::duration(1)));
mock_updater_->SuppressNeedsRendering(true);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_updater_->SuppressNeedsRendering(false);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
}
TEST_F(FrameSchedulerTest, NoOpPresent2UpdateWithSecondPendingUpdate_ShouldBeRescheduled) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
scheduler->SetOnFramePresentedCallbackForSession(kSessionId, [](auto) {});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
SchedulePresent2Update(scheduler, mock_updater_, kSessionId,
Now() + vsync_timing_->vsync_interval());
SchedulePresent2Update(scheduler, mock_updater_, kSessionId,
Now() + (vsync_timing_->vsync_interval() + zx::duration(1)));
mock_updater_->SuppressNeedsRendering(true);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_updater_->SuppressNeedsRendering(false);
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
}
TEST_F(FrameSchedulerTest, LowGpuRenderTime_ShouldNotMatter) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
// Guarantee the vsync interval here is what we expect.
zx::duration interval = zx::msec(100);
vsync_timing_->set_vsync_interval(interval);
EXPECT_EQ(0, Now().get());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
// Schedule a frame where the GPU render work finished before the CPU work.
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, Now());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Latch an early time here for the GPU rendering to finish at.
RunLoopFor(zx::msec(91));
auto gpu_render_time_finish = Now();
// Go to vsync.
RunLoopUntil(zx::time(vsync_timing_->last_vsync_time() + vsync_timing_->vsync_interval()));
vsync_timing_->set_last_vsync_time(Now());
// Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 0u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
// End the frame, at different render times.
mock_renderer_->SignalFrameCpuRendered(/* frame number */ 0, Now());
mock_renderer_->SignalFrameRendered(/* frame number */ 0, gpu_render_time_finish);
mock_renderer_->SignalFramePresented(/* frame number */ 0, Now());
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
EXPECT_EQ(mock_updater_->signal_successful_present_callback_count(), 1u);
// Now we assert that we predict reasonably, given that we had 0 GPU rendering time.
// Specifically, we should assume we will miss the upcoming frame and aim for the next
// one, because the large render duration pushes our prediction up.
RunLoopFor(zx::msec(91));
// Schedule the frame just a tad too late, given the CPU render duration.
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId, zx::time(0));
// Go to vsync.
RunLoopUntil(zx::time(vsync_timing_->last_vsync_time() + vsync_timing_->vsync_interval()));
vsync_timing_->set_last_vsync_time(Now());
// Nothing should have been scheduled yet.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
// Wait for one more vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 1, Now());
// Should have been scheduled and handled now.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
}
TEST_F(FrameSchedulerTest, LowPresent2GpuRenderTime_ShouldNotMatter) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
uint64_t present_count = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId, [&present_count](fuchsia::scenic::scheduling::FramePresentedInfo info) {
present_count += info.presentation_infos.size();
});
// Guarantee the vsync interval here is what we expect.
zx::duration interval = zx::msec(100);
vsync_timing_->set_vsync_interval(interval);
EXPECT_EQ(0, Now().get());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(present_count, 0u);
// Schedule a frame where the GPU render work finished before the CPU work.
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, Now());
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Latch an early time here for the GPU rendering to finish at.
RunLoopFor(zx::msec(91));
auto gpu_render_time_finish = Now();
// Go to vsync.
RunLoopUntil(zx::time(vsync_timing_->last_vsync_time() + vsync_timing_->vsync_interval()));
vsync_timing_->set_last_vsync_time(Now());
// Present should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_updater_->prepare_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, 0u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
// End the frame, at different render times.
mock_renderer_->SignalFrameCpuRendered(/* frame number */ 0, Now());
mock_renderer_->SignalFrameRendered(/* frame number */ 0, gpu_render_time_finish);
mock_renderer_->SignalFramePresented(/* frame number */ 0, Now());
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
EXPECT_EQ(present_count, 1u);
// Now we assert that we predict reasonably, given that we had 0 GPU rendering time.
// Specifically, we should assume we will miss the upcoming frame and aim for the next
// one, because the large render duration pushes our prediction up.
RunLoopFor(zx::msec(91));
// Schedule the frame just a tad too late, given the CPU render duration.
SchedulePresent2Update(scheduler, mock_updater_, kSessionId, zx::time(0));
// Go to vsync.
RunLoopUntil(zx::time(vsync_timing_->last_vsync_time() + vsync_timing_->vsync_interval()));
vsync_timing_->set_last_vsync_time(Now());
// Nothing should have been scheduled yet.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
// Wait for one more vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 1, Now());
// Should have been scheduled and handled now.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
}
TEST_F(FrameSchedulerTest, PresentAndPresent2Clients_CanCoexist) {
auto scheduler = CreateDefaultFrameScheduler();
// Present client.
constexpr SessionId kSessionId1 = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId1);
// Present2 client.
constexpr SessionId kSessionId2 = 2;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId2);
uint64_t present_count = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId2, [&present_count](fuchsia::scenic::scheduling::FramePresentedInfo info) {
present_count += info.presentation_infos.size();
});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule updates on both clients.
ScheduleUpdateAndCallback(scheduler, mock_updater_, kSessionId1, zx::time(0));
SchedulePresent2Update(scheduler, mock_updater_, kSessionId2, zx::time(0));
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// Both Present and Present2 should have been scheduled and handled.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, 1u);
}
TEST_F(FrameSchedulerTest, MultiplePresent2Clients) {
auto scheduler = CreateDefaultFrameScheduler();
// All three clients will call Present2 four times, one after the other.
constexpr uint64_t kNumPresents = 4;
constexpr uint64_t kNumClients = 3;
constexpr SessionId kSessionId1 = 0;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId1);
uint64_t present_count1 = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId1,
[&present_count1, kNumPresents](fuchsia::scenic::scheduling::FramePresentedInfo info) {
EXPECT_EQ(info.presentation_infos.size(), kNumPresents);
present_count1 += info.presentation_infos.size();
for (uint64_t i = 0; i < info.presentation_infos.size(); ++i)
EXPECT_EQ(info.presentation_infos[i].latched_time(), static_cast<zx_time_t>(kSessionId1));
});
constexpr SessionId kSessionId2 = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId2);
uint64_t present_count2 = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId2,
[&present_count2, kNumPresents](fuchsia::scenic::scheduling::FramePresentedInfo info) {
EXPECT_EQ(info.presentation_infos.size(), kNumPresents);
present_count2 += info.presentation_infos.size();
for (uint64_t i = 0; i < info.presentation_infos.size(); ++i)
EXPECT_EQ(info.presentation_infos[i].latched_time(), static_cast<zx_time_t>(kSessionId2));
});
constexpr SessionId kSessionId3 = 2;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId3);
uint64_t present_count3 = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId3,
[&present_count3, kNumPresents](fuchsia::scenic::scheduling::FramePresentedInfo info) {
EXPECT_EQ(info.presentation_infos.size(), kNumPresents);
present_count3 += info.presentation_infos.size();
for (uint64_t i = 0; i < info.presentation_infos.size(); ++i)
EXPECT_EQ(info.presentation_infos[i].latched_time(), static_cast<zx_time_t>(kSessionId3));
});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule interspersed updates on all clients, with the latched_time being the session_id, so we
// can differentiate between them in the OnFramePresented callbacks.
for (uint64_t i = 0; i < kNumPresents; ++i) {
for (SessionId session_id = 0; session_id < kNumClients; ++session_id) {
SchedulePresent2Update(scheduler, mock_updater_, session_id, zx::time(0), zx::time(0),
zx::time(session_id));
}
}
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// All Present2s should have been scheduled and handled in one go.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count1, kNumPresents);
EXPECT_EQ(present_count2, kNumPresents);
EXPECT_EQ(present_count3, kNumPresents);
}
TEST_F(FrameSchedulerTest, CoalescedPresent2s_CauseASingleOnFramePresentedEvent) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId2 = 1;
uint64_t present_count = 0;
constexpr uint64_t kNumPresents = 4;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId2);
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId2,
[&present_count, kNumPresents](fuchsia::scenic::scheduling::FramePresentedInfo info) {
EXPECT_EQ(info.presentation_infos.size(), kNumPresents);
present_count += info.presentation_infos.size();
});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule updates on both clients.
for (uint64_t i = 0; i < kNumPresents; ++i)
SchedulePresent2Update(scheduler, mock_updater_, kSessionId2, zx::time(0));
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// All Present2s should have been scheduled and handled in one go.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, kNumPresents);
}
TEST_F(FrameSchedulerTest, OnFramePresentedEvent_HasPresent2sInOrder) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId2 = 1;
uint64_t present_count = 0;
constexpr uint64_t kNumPresents = 4;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId2);
// Present in reverse order. This is to ensure that the Presents are ordered by submission, not
// necessarily latch point or present received values.
constexpr zx::time latched_time = zx::time(5);
constexpr zx::time original_present_received_time = zx::time(4);
constexpr zx::duration present_delta = zx::duration(-1);
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId2, [&present_count, kNumPresents, latched_time, original_present_received_time,
present_delta](fuchsia::scenic::scheduling::FramePresentedInfo info) {
EXPECT_EQ(info.presentation_infos.size(), kNumPresents);
present_count += info.presentation_infos.size();
zx::time present_received_time = original_present_received_time;
for (uint64_t i = 0; i < info.presentation_infos.size(); ++i) {
EXPECT_EQ(info.presentation_infos[i].present_received_time(),
present_received_time.get());
EXPECT_EQ(info.presentation_infos[i].latched_time(), latched_time.get());
present_received_time += present_delta;
}
});
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
// Schedule updates, changing the present received time for each one.
zx::time present_received_time = original_present_received_time;
for (uint64_t i = 0; i < kNumPresents; ++i) {
SchedulePresent2Update(scheduler, mock_updater_, kSessionId2, zx::time(0), zx::time(0),
latched_time, present_received_time);
present_received_time += present_delta;
}
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, Now());
// All Present2s should have been scheduled and handled in one go.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, kNumPresents);
}
TEST_F(FrameSchedulerTest, SinglePredictedPresentation_ShouldBeReasonable) {
auto scheduler = CreateDefaultFrameScheduler();
zx::time next_vsync = vsync_timing_->last_vsync_time() + vsync_timing_->vsync_interval();
// Ask for a prediction for one frame into the future.
std::vector<fuchsia::scenic::scheduling::PresentationInfo> predicted_presents;
scheduler->GetFuturePresentationInfos(zx::duration(0), [&](auto future_presents) {
predicted_presents = std::move(future_presents);
});
EXPECT_GE(predicted_presents.size(), 1u);
EXPECT_EQ(predicted_presents[0].presentation_time(), next_vsync.get());
for (size_t i = 0; i < predicted_presents.size(); i++) {
auto current = std::move(predicted_presents[i]);
EXPECT_LT(current.latch_point(), current.presentation_time());
EXPECT_GE(current.latch_point(), Now().get());
}
}
TEST_F(FrameSchedulerTest, ArbitraryPredictedPresentation_ShouldBeReasonable) {
// The main and only difference between this test and
// "SinglePredictedPresentation_ShouldBeReasonable" above is that we advance the clock before
// asking for a prediction, to ensure that GetPredictions() works in a more general sense.
auto scheduler = CreateDefaultFrameScheduler();
// Advance the clock to vsync1.
zx::time vsync0 = vsync_timing_->last_vsync_time();
zx::time vsync1 = vsync0 + vsync_timing_->vsync_interval();
zx::time vsync2 = vsync1 + vsync_timing_->vsync_interval();
EXPECT_GT(vsync_timing_->vsync_interval(), zx::duration(0));
EXPECT_EQ(vsync0, Now());
RunLoopUntil(vsync1);
// Ask for a prediction.
std::vector<fuchsia::scenic::scheduling::PresentationInfo> predicted_presents;
scheduler->GetFuturePresentationInfos(zx::duration(0), [&](auto future_presents) {
predicted_presents = std::move(future_presents);
});
EXPECT_GE(predicted_presents.size(), 1u);
EXPECT_EQ(predicted_presents[0].presentation_time(), vsync2.get());
for (size_t i = 0; i < predicted_presents.size(); i++) {
auto current = std::move(predicted_presents[i]);
EXPECT_LT(current.latch_point(), current.presentation_time());
EXPECT_GE(current.latch_point(), Now().get());
}
}
TEST_F(FrameSchedulerTest, MultiplePredictedPresentations_ShouldBeReasonable) {
auto scheduler = CreateDefaultFrameScheduler();
zx::time vsync0 = vsync_timing_->last_vsync_time();
zx::time vsync1 = vsync0 + vsync_timing_->vsync_interval();
zx::time vsync2 = vsync1 + vsync_timing_->vsync_interval();
zx::time vsync3 = vsync2 + vsync_timing_->vsync_interval();
zx::time vsync4 = vsync3 + vsync_timing_->vsync_interval();
// What we really want is a positive difference between each vsync.
EXPECT_GT(vsync_timing_->vsync_interval(), zx::duration(0));
// Ask for a prediction a few frames into the future.
std::vector<fuchsia::scenic::scheduling::PresentationInfo> predicted_presents;
scheduler->GetFuturePresentationInfos(
zx::duration((vsync4 - vsync0).get()),
[&](auto future_presents) { predicted_presents = std::move(future_presents); });
// Expect at least one frame of prediction.
EXPECT_GE(predicted_presents.size(), 1u);
auto past_prediction = std::move(predicted_presents[0]);
for (size_t i = 0; i < predicted_presents.size(); i++) {
auto current = std::move(predicted_presents[i]);
EXPECT_LT(current.latch_point(), current.presentation_time());
EXPECT_GE(current.latch_point(), Now().get());
if (i > 0)
EXPECT_LT(past_prediction.presentation_time(), current.presentation_time());
past_prediction = std::move(current);
}
}
TEST_F(FrameSchedulerTest, InfinitelyLargePredictionRequest_ShouldBeTruncated) {
auto scheduler = CreateDefaultFrameScheduler();
zx::time next_vsync = vsync_timing_->last_vsync_time() + vsync_timing_->vsync_interval();
// Ask for an extremely large prediction duration.
std::vector<fuchsia::scenic::scheduling::PresentationInfo> predicted_presents;
scheduler->GetFuturePresentationInfos(zx::duration(INTMAX_MAX), [&](auto future_presents) {
predicted_presents = std::move(future_presents);
});
constexpr static const uint64_t kOverlyLargeRequestCount = 100u;
EXPECT_LE(predicted_presents.size(), kOverlyLargeRequestCount);
EXPECT_EQ(predicted_presents[0].presentation_time(), next_vsync.get());
for (size_t i = 0; i < predicted_presents.size(); i++) {
auto current = std::move(predicted_presents[i]);
EXPECT_LT(current.latch_point(), current.presentation_time());
EXPECT_GE(current.latch_point(), Now().get());
}
}
// Without calling UpdateManager::RatchetPresentCallbacks(), updates can be applied but the present
// callbacks will never be invoked.
TEST(UpdateManagerTest, NoRatchetingMeansNoCallbacks) {
auto sum = std::make_unique<DefaultFrameScheduler::UpdateManager>();
MockSessionUpdater updater;
sum->AddSessionUpdater(updater.GetWeakPtr());
constexpr SessionId kSession1 = 1;
// Update is not expected to fail.
sum->SetOnUpdateFailedCallbackForSession(kSession1, [] { EXPECT_FALSE(true); });
auto status = ScheduleUpdateAndCallback(sum, &updater, kSession1, zx::time(1), zx::time(1));
fuchsia::images::PresentationInfo info;
info.presentation_interval = 1;
info.presentation_time = 1;
uint64_t frame_number = 1;
zx::time latched_time = zx::time(1);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_FALSE(reschedule);
EXPECT_TRUE(status->callback_passed);
EXPECT_FALSE(status->callback_invoked);
// Without calling RatchetPresentCallbacks(), the callbacks won't be invoked. NOTE: this wouldn't
// happen in practice; this is just testing/documenting the behavior.
sum->SignalPresentCallbacks(info);
EXPECT_FALSE(status->callback_invoked);
EXPECT_EQ(updater.signal_successful_present_callback_count(), 0U);
// Do it a few more times to prove that we're not just lucky when the callback is finally invoked.
sum->SignalPresentCallbacks(info);
EXPECT_FALSE(status->callback_invoked);
sum->SignalPresentCallbacks(info);
EXPECT_FALSE(status->callback_invoked);
sum->SignalPresentCallbacks(info);
EXPECT_FALSE(status->callback_invoked);
sum->SignalPresentCallbacks(info);
EXPECT_FALSE(status->callback_invoked);
EXPECT_EQ(updater.signal_successful_present_callback_count(), 0U);
// Finally, verify that calling RatchetPresentCallbacks() allows the signal to occur.
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_TRUE(status->callback_invoked);
EXPECT_EQ(updater.signal_successful_present_callback_count(), 1U);
// Verify that re-signaling doesn't result in callbacks being invoked again.
sum->SignalPresentCallbacks(info);
EXPECT_EQ(updater.signal_successful_present_callback_count(), 1U);
}
// A really slow fence can be repeatedly rescheduled until it is ready. It will block other updates
// from running, even if their fences are done.
TEST(UpdateManagerTest, ReallySlowFence) {
auto sum = std::make_unique<DefaultFrameScheduler::UpdateManager>();
MockSessionUpdater updater;
sum->AddSessionUpdater(updater.GetWeakPtr());
constexpr SessionId kSession1 = 1;
// Update is not expected to fail.
sum->SetOnUpdateFailedCallbackForSession(kSession1, [] { EXPECT_FALSE(true); });
auto status1 = ScheduleUpdateAndCallback(sum, &updater, kSession1, zx::time(1), zx::time(3));
auto status2 = ScheduleUpdateAndCallback(sum, &updater, kSession1, zx::time(2), zx::time(2));
auto status3 = ScheduleUpdateAndCallback(sum, &updater, kSession1, zx::time(3), zx::time(4));
fuchsia::images::PresentationInfo info;
info.presentation_interval = 1;
// Frame 1: Blocked on first update's fences.
info.presentation_time = 1;
uint64_t frame_number = 1;
zx::time latched_time = zx::time(1);
{
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_FALSE(render);
EXPECT_TRUE(reschedule);
}
EXPECT_FALSE(status1->callback_passed);
EXPECT_FALSE(status2->callback_passed);
EXPECT_FALSE(status3->callback_passed);
// Frame 2: Still blocked on first update's fences.
info.presentation_time = 2;
frame_number = 2;
latched_time = zx::time(2);
{
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_FALSE(render);
EXPECT_TRUE(reschedule);
}
EXPECT_FALSE(status1->callback_passed);
EXPECT_FALSE(status2->callback_passed);
EXPECT_FALSE(status3->callback_passed);
// Frame 3: First two updates are unblocked, but third is blocked on fences.
info.presentation_time = 3;
frame_number = 3;
latched_time = zx::time(3);
{
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_TRUE(reschedule);
}
EXPECT_FALSE(status3->callback_passed);
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_TRUE(status1->callback_invoked);
EXPECT_TRUE(status2->callback_invoked);
EXPECT_EQ(status1->presentation_info, info);
EXPECT_EQ(status2->presentation_info, info);
// Frame 4: The third update is unblocked, so no reschedule is required.
info.presentation_time = 4;
frame_number = 4;
latched_time = zx::time(4);
{
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_FALSE(reschedule);
}
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_TRUE(status3->callback_invoked);
EXPECT_EQ(status3->presentation_info, info);
}
// Verify that we properly observe all 4 possible responses from ApplyUpdates() in a
// multi-session/multi-updater scenario.
TEST(UpdateManagerTest, MultiUpdaterMultiSession) {
auto sum = std::make_unique<DefaultFrameScheduler::UpdateManager>();
// Pre-declare the Session IDs used in this test.
constexpr SessionId kSession1 = 1;
constexpr SessionId kSession2 = 2;
constexpr SessionId kSession3 = 3;
constexpr SessionId kSession4 = 4;
fuchsia::images::PresentationInfo info;
info.presentation_interval = 1;
MockSessionUpdater updater1;
MockSessionUpdater updater2;
sum->AddSessionUpdater(updater1.GetWeakPtr());
sum->AddSessionUpdater(updater2.GetWeakPtr());
updater1.BeRelaxedAboutUnexpectedSessionUpdates();
updater2.BeRelaxedAboutUnexpectedSessionUpdates();
// Update is not expected to fail.
sum->SetOnUpdateFailedCallbackForSession(kSession1, [] { EXPECT_FALSE(true); });
sum->SetOnUpdateFailedCallbackForSession(kSession2, [] { EXPECT_FALSE(true); });
sum->SetOnUpdateFailedCallbackForSession(kSession3, [] { EXPECT_FALSE(true); });
sum->SetOnUpdateFailedCallbackForSession(kSession4, [] { EXPECT_FALSE(true); });
// Frame 1: Too early for any to run.
auto status1_1 = ScheduleUpdateAndCallback(sum, &updater1, kSession1, zx::time(2), zx::time(3));
{
uint64_t frame_number = info.presentation_time = 1;
zx::time latched_time = zx::time(1);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_FALSE(render);
EXPECT_TRUE(reschedule);
}
// Frame 2: Blocked on first update's fences.
{
uint64_t frame_number = info.presentation_time = 2;
zx::time latched_time = zx::time(2);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_FALSE(render);
EXPECT_TRUE(reschedule);
}
// Frame 3: Sessions 1,2,3 unblocked, Session 4 still blocked on fences.
auto status2_1 = ScheduleUpdateAndCallback(sum, &updater1, kSession2, zx::time(3), zx::time(3));
auto status3_1 = ScheduleUpdateAndCallback(sum, &updater2, kSession3, zx::time(3), zx::time(3));
auto status4_1 = ScheduleUpdateAndCallback(sum, &updater2, kSession4, zx::time(3), zx::time(4));
{
uint64_t frame_number = info.presentation_time = 3;
zx::time latched_time = zx::time(3);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_TRUE(reschedule);
}
// Frame 4: Session 4 unblocked (both updates).
auto status4_2 = ScheduleUpdateAndCallback(sum, &updater2, kSession4, zx::time(4), zx::time(4));
{
uint64_t frame_number = info.presentation_time = 4;
zx::time latched_time = zx::time(4);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_FALSE(reschedule);
}
// Frame 5: Session 4 schedules update, then dies before update applied.
auto status4_3 = ScheduleUpdateAndCallback(sum, &updater2, kSession4, zx::time(5), zx::time(5));
updater2.KillSession(kSession4);
{
uint64_t frame_number = info.presentation_time = 5;
zx::time latched_time = zx::time(5);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_FALSE(render);
EXPECT_FALSE(reschedule);
}
}
// Verify that updaters can be dynamically updated and removed.
TEST(SessionUpdaterManagerTest, DynamicUpdaterAddRemove) {
auto sum = std::make_unique<DefaultFrameScheduler::UpdateManager>();
// Pre-declare the Session IDs used in this test.
constexpr SessionId kSession1 = 1;
constexpr SessionId kSession2 = 2;
constexpr SessionId kSession3 = 3;
constexpr SessionId kSession4 = 4;
constexpr SessionId kSession5 = 5;
// Updates are not expected to fail.
sum->SetOnUpdateFailedCallbackForSession(kSession1, [] { EXPECT_FALSE(true); });
sum->SetOnUpdateFailedCallbackForSession(kSession2, [] { EXPECT_FALSE(true); });
sum->SetOnUpdateFailedCallbackForSession(kSession3, [] { EXPECT_FALSE(true); });
sum->SetOnUpdateFailedCallbackForSession(kSession4, [] { EXPECT_FALSE(true); });
sum->SetOnUpdateFailedCallbackForSession(kSession5, [] { EXPECT_FALSE(true); });
fuchsia::images::PresentationInfo info;
info.presentation_interval = 1;
// Frame 1: Too early for any to run. Even though the updater is deleted, there is still a
// reschedule because it was too early to try to apply the updates so the manager's Unlike the
// "MultiUpdaterMultiSession" test above, there is no reschedule because the updater is deleted
// before updates are applied.
{
auto updater1 = std::make_unique<MockSessionUpdater>();
sum->AddSessionUpdater(updater1->GetWeakPtr());
auto status =
ScheduleUpdateAndCallback(sum, updater1.get(), kSession1, zx::time(2), zx::time(3));
updater1.reset();
uint64_t frame_number = info.presentation_time = 1;
zx::time latched_time = zx::time(1);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_FALSE(render);
EXPECT_TRUE(reschedule);
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_FALSE(status->callback_passed);
}
// Frame 2: Schedule another update, early enough to be applied this time. Thus, when we destroy
// the updater before applying updates, there is no reschedule nor render.
{
auto updater2 = std::make_unique<MockSessionUpdater>();
sum->AddSessionUpdater(updater2->GetWeakPtr());
auto status =
ScheduleUpdateAndCallback(sum, updater2.get(), kSession2, zx::time(2), zx::time(2));
updater2.reset();
uint64_t frame_number = info.presentation_time = 2;
zx::time latched_time = zx::time(2);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_FALSE(render);
EXPECT_FALSE(reschedule);
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_FALSE(status->callback_passed);
}
// Frame 3: Schedule another update, again early enough to be applied. This time we destroy it
// after updates but before signaling present callbacks; the callback should therefore be invoked
// (and, the scene should be rendered).
{
auto updater3 = std::make_unique<MockSessionUpdater>();
sum->AddSessionUpdater(updater3->GetWeakPtr());
auto status =
ScheduleUpdateAndCallback(sum, updater3.get(), kSession3, zx::time(3), zx::time(3));
uint64_t frame_number = info.presentation_time = 3;
zx::time latched_time = zx::time(3);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_FALSE(reschedule);
updater3.reset();
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_TRUE(status->callback_passed);
EXPECT_TRUE(status->callback_invoked);
EXPECT_TRUE(status->updater_disappeared);
}
// For the next few frames, we have multiple updaters at the same time.
auto updater4 = std::make_unique<MockSessionUpdater>();
auto updater5 = std::make_unique<MockSessionUpdater>();
sum->AddSessionUpdater(updater4->GetWeakPtr());
sum->AddSessionUpdater(updater5->GetWeakPtr());
updater4->BeRelaxedAboutUnexpectedSessionUpdates();
updater5->BeRelaxedAboutUnexpectedSessionUpdates();
auto status4 =
ScheduleUpdateAndCallback(sum, updater4.get(), kSession4, zx::time(4), zx::time(4));
auto status5 =
ScheduleUpdateAndCallback(sum, updater5.get(), kSession5, zx::time(4), zx::time(5));
// Frame 4: The update for |status4| will be applied, and |status5| will be blocked on its fence
// and rescheduled.
{
uint64_t frame_number = info.presentation_time = 4;
zx::time latched_time = zx::time(4);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_TRUE(reschedule);
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_TRUE(status4->callback_passed);
EXPECT_TRUE(status4->callback_invoked);
EXPECT_FALSE(status4->updater_disappeared);
EXPECT_FALSE(status5->callback_passed);
}
auto updater6 = std::make_unique<MockSessionUpdater>();
sum->AddSessionUpdater(updater6->GetWeakPtr());
updater6->BeRelaxedAboutUnexpectedSessionUpdates();
auto status6 =
ScheduleUpdateAndCallback(sum, updater6.get(), kSession5, zx::time(5), zx::time(5));
// Frame 5: The updates for both |status5| and |status6| will be applied, so there will be a
// render and no reschedule. Destroy |updater6| before the callbacks are signaled.
{
uint64_t frame_number = info.presentation_time = 5;
zx::time latched_time = zx::time(5);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_FALSE(reschedule);
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
// Unlike where we deleted |updater3| above, we reset after RatcherPresentCallbacks(). This one
// is the more common case, but UpdateManager doesn't care.
updater6.reset();
sum->SignalPresentCallbacks(info);
EXPECT_TRUE(status5->callback_passed);
EXPECT_TRUE(status5->callback_invoked);
EXPECT_TRUE(status6->callback_passed);
EXPECT_TRUE(status6->callback_invoked);
// As expected, we see that |updater6| was killed while |updater5| remains.
EXPECT_FALSE(status5->updater_disappeared);
EXPECT_TRUE(status6->updater_disappeared);
}
}
TEST(SessionUpdaterManagerTest, AddSessionUpdatersInSessionUpdater) {
auto sum = std::make_unique<DefaultFrameScheduler::UpdateManager>();
// Pre-declare the Session IDs used in this test.
constexpr SessionId kSession1 = 1;
// Updates are not expected to fail.
sum->SetOnUpdateFailedCallbackForSession(kSession1, [] { EXPECT_FALSE(true); });
fuchsia::images::PresentationInfo info;
info.presentation_interval = 1;
// Creates a mock SessionUpdater that creates 10 SessionUpdaters on every
// UpdateSessions call.
constexpr size_t kUpdatersToAddOnEveryUpdate = 10U;
std::vector<std::unique_ptr<MockSessionUpdater>> session_updaters_created;
auto updater1 = std::make_unique<MockSessionUpdaterWithFunctionOnUpdate>(
[sum = sum.get(), &session_updaters_created]() {
for (size_t i = 0; i < kUpdatersToAddOnEveryUpdate; i++) {
auto updater = std::make_unique<MockSessionUpdater>();
updater->BeRelaxedAboutUnexpectedSessionUpdates();
sum->AddSessionUpdater(updater->GetWeakPtr());
session_updaters_created.push_back(std::move(updater));
}
});
sum->AddSessionUpdater(updater1->GetWeakPtr());
// Frame 1: Updater1 creates 10 new SessionUpdaters this frame, but only
// updater1 will be called to update sessions.
{
auto status1 =
ScheduleUpdateAndCallback(sum, updater1.get(), kSession1, zx::time(1), zx::time(1));
EXPECT_EQ(updater1->update_sessions_call_count(), 0U);
uint64_t frame_number = info.presentation_time = 1;
zx::time latched_time = zx::time(1);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_FALSE(reschedule);
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_TRUE(status1->callback_passed && status1->callback_invoked);
EXPECT_FALSE(status1->updater_disappeared);
EXPECT_EQ(updater1->update_sessions_call_count(), 1U);
EXPECT_EQ(session_updaters_created.size(), kUpdatersToAddOnEveryUpdate);
EXPECT_TRUE(std::all_of(session_updaters_created.begin(), session_updaters_created.end(),
[](const auto& session_updater) {
return session_updater->update_sessions_call_count() == 0;
}));
}
// Frame 2: updater1 will create another 10 SessionUpdaters this frame, which
// will not be updated, while the SessionUpdaters created on previous
// frame will be updated now.
{
auto status2 =
ScheduleUpdateAndCallback(sum, updater1.get(), kSession1, zx::time(2), zx::time(2));
EXPECT_EQ(updater1->update_sessions_call_count(), 1U);
uint64_t frame_number = info.presentation_time = 2;
zx::time latched_time = zx::time(2);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_FALSE(reschedule);
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_TRUE(status2->callback_passed && status2->callback_invoked);
EXPECT_FALSE(status2->updater_disappeared);
EXPECT_EQ(updater1->update_sessions_call_count(), 2U);
EXPECT_EQ(session_updaters_created.size(), 2 * kUpdatersToAddOnEveryUpdate);
EXPECT_TRUE(std::count_if(session_updaters_created.begin(), session_updaters_created.end(),
[](const auto& session_updater) {
return session_updater->update_sessions_call_count() == 1;
}) == kUpdatersToAddOnEveryUpdate);
}
}
TEST(SessionUpdaterManagerTest, RemoveSessionUpdatersInSessionUpdater) {
auto sum = std::make_unique<DefaultFrameScheduler::UpdateManager>();
// Pre-declare the Session IDs used in this test.
constexpr SessionId kSession1 = 1;
// Updates are not expected to fail.
sum->SetOnUpdateFailedCallbackForSession(kSession1, [] { EXPECT_FALSE(true); });
fuchsia::images::PresentationInfo info;
info.presentation_interval = 1;
// Creates updaters to be removed later.
constexpr size_t kNumOfUpdatersToRemove = 10U;
std::vector<std::unique_ptr<MockSessionUpdater>> updaters_to_remove;
std::vector<fxl::WeakPtr<MockSessionUpdater>> updaters_to_remove_weak;
for (size_t i = 0; i < kNumOfUpdatersToRemove; i++) {
auto updater = std::make_unique<MockSessionUpdater>();
updater->BeRelaxedAboutUnexpectedSessionUpdates();
updaters_to_remove_weak.push_back(updater->GetWeakPtr());
updaters_to_remove.push_back(std::move(updater));
}
// Creates a mock SessionUpdater that removes a SessionUpdater on every
// UpdateSessions call.
auto updater1 = std::make_unique<MockSessionUpdaterWithFunctionOnUpdate>(
[updaters_to_remove = std::move(updaters_to_remove)]() mutable {
updaters_to_remove.pop_back();
});
updater1->BeRelaxedAboutUnexpectedSessionUpdates();
sum->AddSessionUpdater(updater1->GetWeakPtr());
for (const auto& updater : updaters_to_remove_weak) {
sum->AddSessionUpdater(updater->GetWeakPtr());
}
for (size_t frame_number = 1; frame_number <= kNumOfUpdatersToRemove; frame_number++) {
auto status1 = ScheduleUpdateAndCallback(sum, updater1.get(), kSession1, zx::time(frame_number),
zx::time(frame_number));
info.presentation_time = frame_number;
zx::time latched_time = zx::time(1);
auto [render, reschedule] =
sum->ApplyUpdates(zx::time(info.presentation_time), latched_time,
zx::duration(info.presentation_interval), frame_number);
EXPECT_TRUE(render);
EXPECT_FALSE(reschedule);
sum->RatchetPresentCallbacks(zx::time(info.presentation_time), frame_number);
sum->SignalPresentCallbacks(info);
EXPECT_TRUE(status1->callback_passed && status1->callback_invoked);
EXPECT_FALSE(status1->updater_disappeared);
EXPECT_EQ(updater1->update_sessions_call_count(), frame_number);
size_t remaining_updaters = 0;
for (const auto& updater : updaters_to_remove_weak) {
if (updater) {
EXPECT_TRUE(updater->update_sessions_call_count() == frame_number);
++remaining_updaters;
}
}
EXPECT_TRUE(remaining_updaters + frame_number == kNumOfUpdatersToRemove);
}
}
} // namespace test
} // namespace scheduling