blob: 3e5ab542304ff60a19209a07bde22dbcdd37f115 [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 <gmock/gmock.h>
#include "src/ui/scenic/lib/gfx/tests/mocks/util.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_map<SessionId, PresentId>& sessions_to_update,
uint64_t trace_id) override {
if (function_) {
function_();
}
return MockSessionUpdater::UpdateSessions(std::move(sessions_to_update), 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, SessionId session_id,
zx::time presentation_time, OnPresentedCallback callback = [](auto...) {},
std::vector<zx::event> release_fences = {}) {
scheduling::PresentId present_id =
scheduler->RegisterPresent(session_id, std::move(callback), std::move(release_fences));
scheduler->ScheduleUpdateForSession(presentation_time,
{.session_id = session_id, .present_id = present_id});
}
// 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,
SessionId session_id, zx::time presentation_time,
zx::time present_received_time = zx::time(0),
std::vector<zx::event> release_fences = {}) {
std::variant<OnPresentedCallback, Present2Info> variant;
auto& info = variant.emplace<Present2Info>(session_id);
info.SetPresentReceivedTime(present_received_time);
scheduling::PresentId present_id =
scheduler->RegisterPresent(session_id, std::move(variant), std::move(release_fences));
scheduler->ScheduleUpdateForSession(presentation_time,
{.session_id = session_id, .present_id = present_id});
}
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);
});
}
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, kSessionId, /* presentation */ zx::time(0));
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ 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, kSessionId, /* presentation */ zx::time(0));
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ 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, 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, /*time_done*/ 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, 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, /*time_done*/ 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_renderer_->render_frame_call_count(), 0u);
ScheduleUpdateAndCallback(scheduler, 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_renderer_->render_frame_call_count(), 1u);
// End the pending frame.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ 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, 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_renderer_->render_frame_call_count(), 0u);
SchedulePresent2Update(scheduler, 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_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, /*time_done*/ 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_renderer_->render_frame_call_count(), 0u);
ScheduleUpdateAndCallback(scheduler, 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_renderer_->render_frame_call_count(), 1u);
// End the pending frame.
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ 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, 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_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(present_count, 0u);
SchedulePresent2Update(scheduler, 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_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, /*time_done*/ 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, SinglePresent2_ShouldGetPresentCallbackWhenNoContentToRender) {
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());
mock_renderer_->SetRenderFrameReturnValue(scheduling::RenderFrameResult::kNoContentToRender);
// Start the test.
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(present_count, 0u);
SchedulePresent2Update(scheduler, 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_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->pending_frames(), 0u);
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, kSessionId1, now);
ScheduleUpdateAndCallback(scheduler, 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, /*time_done*/ 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, kSessionId1, now);
SchedulePresent2Update(scheduler, 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, /*time_done*/ 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, 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, 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, /*time_done*/ 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, 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 = now + vsync_interval + vsync_interval / 2;
SchedulePresent2Update(scheduler, 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, /*time_done*/ 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_renderer_->render_frame_call_count(), 0u);
// Schedule an update for now.
zx::time now = Now();
ScheduleUpdateAndCallback(scheduler, 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_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(mock_renderer_->pending_frames(), 1u);
// Schedule another update for now.
ScheduleUpdateAndCallback(scheduler, 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_renderer_->render_frame_call_count(), 1u);
// End previous frame.
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Second render should have occurred.
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
mock_renderer_->EndFrame(/* frame number */ 1, Now());
}
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_renderer_->render_frame_call_count(), 0u);
EXPECT_EQ(present_count, 0u);
// Schedule an update for now.
zx::time now = Now();
SchedulePresent2Update(scheduler, 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_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, 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_renderer_->render_frame_call_count(), 1u);
EXPECT_EQ(present_count, 0u);
// End previous frame.
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
EXPECT_EQ(present_count, 1u);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
// Second render should have occurred.
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, 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, 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_renderer_->render_frame_call_count(), 0u);
SessionId session_id = 1;
// Schedule an update for now.
zx::time now = Now();
ScheduleUpdateAndCallback(scheduler, 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, 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_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_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_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, 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, 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, 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;
// Remove session from FrameScheduler when the update fails.
frame_scheduler->RemoveSession(id);
});
mock_updater_->SetUpdateSessionsReturnValue({.sessions_with_failed_updates = {kSessionId1}});
constexpr SessionId kSessionId2 = 2;
bool update_failed2 = false;
scheduler->SetOnUpdateFailedCallbackForSession(kSessionId2,
[&update_failed2]() { update_failed2 = true; });
ScheduleUpdateAndCallback(scheduler, kSessionId1, Now());
ScheduleUpdateAndCallback(scheduler, kSessionId2, Now());
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ 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, 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;
// Remove session from FrameScheduler when the update fails.
frame_scheduler->RemoveSession(id);
});
mock_updater_->SetUpdateSessionsReturnValue({.sessions_with_failed_updates = {kSessionId1}});
constexpr SessionId kSessionId2 = 2;
bool update_failed2 = false;
scheduler->SetOnUpdateFailedCallbackForSession(kSessionId2,
[&update_failed2]() { update_failed2 = true; });
scheduler->SetOnFramePresentedCallbackForSession(kSessionId2, [](auto...) {});
SchedulePresent2Update(scheduler, kSessionId1, Now());
SchedulePresent2Update(scheduler, kSessionId2, Now());
EXPECT_NO_FATAL_FAILURE(RunLoopFor(zx::duration(vsync_timing_->vsync_interval())));
EXPECT_TRUE(update_failed1);
EXPECT_FALSE(update_failed2);
EXPECT_NO_FATAL_FAILURE(mock_renderer_->EndFrame(/* frame number */ 0, Now()));
EXPECT_NO_FATAL_FAILURE(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, kSessionId, Now() + vsync_timing_->vsync_interval());
ScheduleUpdateAndCallback(scheduler, kSessionId,
Now() + (vsync_timing_->vsync_interval() + zx::duration(1)));
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
}
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, kSessionId, Now() + vsync_timing_->vsync_interval());
SchedulePresent2Update(scheduler, kSessionId,
Now() + (vsync_timing_->vsync_interval() + zx::duration(1)));
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
}
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_renderer_->render_frame_call_count(), 0u);
// Schedule a frame where the GPU render work finished before the CPU work.
ScheduleUpdateAndCallback(scheduler, 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_renderer_->render_frame_call_count(), 1u);
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);
// 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, 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_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, 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_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, 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, kSessionId1, zx::time(0));
SchedulePresent2Update(scheduler, kSessionId2, zx::time(0));
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ 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].present_received_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].present_received_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].present_received_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, session_id, /*presentation_time=*/zx::time(0),
/*present_received_time=*/zx::time(session_id));
}
}
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ 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, kSessionId2, zx::time(0));
// Wait for one vsync period.
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ 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 original_present_received_time = zx::time(4);
constexpr zx::duration present_delta = zx::duration(-1);
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId2, [&present_count, kNumPresents, 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());
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, kSessionId2, /*presentation_time=*/zx::time(0),
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, /*time_done*/ 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());
}
}
// Verify that we properly observe 4 updates for all session updaters.
TEST_F(FrameSchedulerTest, MultiUpdaterMultiSession) {
auto scheduler = CreateDefaultFrameScheduler();
// 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;
MockSessionUpdater updater1;
MockSessionUpdater updater2;
scheduler->AddSessionUpdater(updater1.GetWeakPtr());
scheduler->AddSessionUpdater(updater2.GetWeakPtr());
// Update is not expected to fail.
scheduler->SetOnUpdateFailedCallbackForSession(kSession1, [] { EXPECT_FALSE(true); });
scheduler->SetOnUpdateFailedCallbackForSession(kSession2, [] { EXPECT_FALSE(true); });
scheduler->SetOnUpdateFailedCallbackForSession(kSession3, [] { EXPECT_FALSE(true); });
scheduler->SetOnUpdateFailedCallbackForSession(kSession4, [] { EXPECT_FALSE(true); });
ScheduleUpdateAndCallback(scheduler, kSession1, zx::time(2));
ScheduleUpdateAndCallback(scheduler, kSession2, zx::time(3));
ScheduleUpdateAndCallback(scheduler, kSession3, zx::time(4));
ScheduleUpdateAndCallback(scheduler, kSession4, zx::time(5));
// Should still only get one combined update for each session.
ScheduleUpdateAndCallback(scheduler, kSession4, zx::time(6));
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(updater1.last_sessions_to_update().size(), 4u);
EXPECT_EQ(updater2.last_sessions_to_update().size(), 4u);
EXPECT_TRUE(updater1.last_sessions_to_update().find(kSession1) !=
updater1.last_sessions_to_update().end());
EXPECT_TRUE(updater1.last_sessions_to_update().find(kSession2) !=
updater1.last_sessions_to_update().end());
EXPECT_TRUE(updater1.last_sessions_to_update().find(kSession3) !=
updater1.last_sessions_to_update().end());
EXPECT_TRUE(updater1.last_sessions_to_update().find(kSession4) !=
updater1.last_sessions_to_update().end());
EXPECT_TRUE(updater2.last_sessions_to_update().find(kSession1) !=
updater2.last_sessions_to_update().end());
EXPECT_TRUE(updater2.last_sessions_to_update().find(kSession2) !=
updater2.last_sessions_to_update().end());
EXPECT_TRUE(updater2.last_sessions_to_update().find(kSession3) !=
updater2.last_sessions_to_update().end());
EXPECT_TRUE(updater2.last_sessions_to_update().find(kSession4) !=
updater2.last_sessions_to_update().end());
}
TEST_F(FrameSchedulerTest, AddSessionUpdatersInSessionUpdater) {
auto scheduler = CreateDefaultFrameScheduler();
// Pre-declare the Session IDs used in this test.
constexpr SessionId kSession1 = 1;
// Updates are not expected to fail.
SetSessionUpdateFailedNotExpected(scheduler.get(), kSession1);
// 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>(
[scheduler = scheduler.get(), &session_updaters_created]() {
for (size_t i = 0; i < kUpdatersToAddOnEveryUpdate; i++) {
auto updater = std::make_unique<MockSessionUpdater>();
scheduler->AddSessionUpdater(updater->GetWeakPtr());
session_updaters_created.push_back(std::move(updater));
}
});
scheduler->AddSessionUpdater(updater1->GetWeakPtr());
// Frame 1: Updater1 creates 10 new SessionUpdaters this frame, but only
// updater1 will be called to update sessions.
bool callback_called1 = false;
{
ScheduleUpdateAndCallback(scheduler, kSession1, zx::time(0),
[&callback_called1](auto...) { callback_called1 = true; });
EXPECT_EQ(updater1->update_sessions_call_count(), 0U);
RunLoopFor(zx::sec(2));
mock_renderer_->EndFrame(/*frame number=*/0, /*time_done=*/Now() + zx::sec(1));
RunLoopFor(zx::sec(2));
EXPECT_EQ(updater1->update_sessions_call_count(), 1U);
EXPECT_TRUE(callback_called1);
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.
bool callback_called2 = false;
{
ScheduleUpdateAndCallback(scheduler, kSession1, zx::time(0),
[&callback_called2](auto...) { callback_called2 = true; });
EXPECT_EQ(updater1->update_sessions_call_count(), 1U);
RunLoopFor(zx::sec(2));
mock_renderer_->EndFrame(/*frame number=*/1, /*time_done=*/Now());
RunLoopFor(zx::sec(2));
RunLoopUntilIdle();
EXPECT_TRUE(callback_called2);
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);
}
}
// Checks that SessionUpdater being deleted by another SessionUpdater doesn't crash the frame
// scheduler.
// NOTE: This test relies on the frame scheduler at least initially maintaining insertion order of
// SessionUpdaters. If this changes the test needs to be reworked.
TEST_F(FrameSchedulerTest, KillingFollowingSessionUpdaterInPreviousSessionUpdater_ShouldNotCrash) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSession1 = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSession1);
auto updater1 = std::make_unique<MockSessionUpdaterWithFunctionOnUpdate>(
[]() { EXPECT_FALSE(true) << "Should never be called."; });
auto updater2 = std::make_unique<MockSessionUpdaterWithFunctionOnUpdate>([&updater1]() {
// No call should have been made to UpdateSessions of |updater1|. If this ever fails then the
// SessionUpdaters are probably out of expected order in the frame scheduler and the test needs
// to be reworked.
EXPECT_EQ(updater1->update_sessions_call_count(), 0U);
updater1.reset();
});
// Add updaters in opposite order to ensure updater1 will be called after updater2.
scheduler->AddSessionUpdater(updater2->GetWeakPtr());
scheduler->AddSessionUpdater(updater1->GetWeakPtr());
// Schedule an update.
ScheduleUpdateAndCallback(scheduler, kSession1, zx::time(0), [](auto...) {});
// We should now only be calling the update on |updater2|. If the deletion wasn't handled
// properly, we should see a crash in RunLoop.
EXPECT_NO_FATAL_FAILURE(RunLoopFor(zx::sec(2)));
EXPECT_FALSE(updater1);
EXPECT_EQ(updater2->update_sessions_call_count(), 1U);
}
TEST_F(FrameSchedulerTest, SquashedPresents_ShouldSignalAllCallbacksInOrder) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
std::vector<int64_t> callback_order;
// Schedule three callbacks, which should be squashed.
ScheduleUpdateAndCallback(scheduler, kSessionId, zx::time(0),
[&callback_order](auto...) { callback_order.push_back(1); });
ScheduleUpdateAndCallback(scheduler, kSessionId, zx::time(0),
[&callback_order](auto...) { callback_order.push_back(2); });
ScheduleUpdateAndCallback(scheduler, kSessionId, zx::time(0),
[&callback_order](auto...) { callback_order.push_back(3); });
// Schedule a callback for later, which should not be squashed.
ScheduleUpdateAndCallback(scheduler, kSessionId, Now() + zx::sec(1),
[&callback_order](auto...) { callback_order.push_back(4); });
RunLoopFor(zx::sec(2));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
RunLoopUntilIdle();
EXPECT_THAT(callback_order, ::testing::ElementsAreArray({1, 2, 3}));
}
TEST_F(FrameSchedulerTest, SquashedPresent2s_ShouldHaveCorrectNumberOfHandledPresentsInCallback) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
uint64_t present_count = 0;
uint64_t num_callbacks = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId,
[&present_count, &num_callbacks](fuchsia::scenic::scheduling::FramePresentedInfo info) {
present_count += info.presentation_infos.size();
++num_callbacks;
});
// Schedule three callbacks, which should be squashed.
SchedulePresent2Update(scheduler, kSessionId, zx::time(0));
SchedulePresent2Update(scheduler, kSessionId, zx::time(0));
SchedulePresent2Update(scheduler, kSessionId, zx::time(0));
// Schedule a callback for later, which should not be squashed.
SchedulePresent2Update(scheduler, kSessionId, Now() + zx::sec(1));
RunLoopFor(zx::sec(2));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
RunLoopUntilIdle();
EXPECT_EQ(present_count, 3u);
EXPECT_EQ(num_callbacks, 1u);
}
TEST_F(FrameSchedulerTest, SkippedPresent_ShouldSignalAllCallbacksInOrder) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
std::vector<int64_t> callback_order;
// These will never get scheduled.
scheduler->RegisterPresent(kSessionId,
[&callback_order](auto...) { callback_order.push_back(1); },
/*release_fences=*/{});
scheduler->RegisterPresent(kSessionId,
[&callback_order](auto...) { callback_order.push_back(2); },
/*release_fences=*/{});
// Next one should be scheduled and presented.
ScheduleUpdateAndCallback(scheduler, kSessionId, zx::time(0),
[&callback_order](auto...) { callback_order.push_back(3); });
// This should never get scheduled, and it's callback should never be triggered.
scheduler->RegisterPresent(kSessionId,
[&callback_order](auto...) { callback_order.push_back(4); },
/*release_fences=*/{});
RunLoopFor(zx::sec(1));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
RunLoopUntilIdle();
EXPECT_THAT(callback_order, ::testing::ElementsAreArray({1, 2, 3}));
}
// Verify that updaters can be removed after updates have been queued without crashing.
TEST_F(FrameSchedulerTest, CanRemoveUpdaterWithQueuedUpdates) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSession1 = 1;
scheduler->SetOnUpdateFailedCallbackForSession(kSession1, [] { EXPECT_FALSE(true); });
auto updater = std::make_unique<MockSessionUpdater>();
scheduler->AddSessionUpdater(updater->GetWeakPtr());
ScheduleUpdateAndCallback(scheduler, kSession1, zx::time(0));
updater.reset();
EXPECT_NO_FATAL_FAILURE(RunLoopFor(zx::duration(vsync_timing_->vsync_interval())));
}
// Verify that updaters added after updates have been queued still get all updates.
TEST_F(FrameSchedulerTest, CanAddUpdaterWithQueuedUpdates) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSession1 = 1;
scheduler->SetOnUpdateFailedCallbackForSession(kSession1, [] { EXPECT_FALSE(true); });
ScheduleUpdateAndCallback(scheduler, kSession1, zx::time(0));
auto updater = std::make_unique<MockSessionUpdater>();
scheduler->AddSessionUpdater(updater->GetWeakPtr());
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
EXPECT_EQ(updater->update_sessions_call_count(), 1u);
}
// Tests creating a session and calling Present several times with release fences. Fences should
// fire as the subsequent Present call is presented to the display.
TEST_F(FrameSchedulerTest, ReleaseFences_ShouldBeFiredAfterSubsequentFramePresented) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSession = 1;
// Create release fences
std::vector<zx::event> release_fences1 = scenic_impl::gfx::test::CreateEventArray(2);
zx::event release_fence1 = scenic_impl::gfx::test::CopyEvent(release_fences1.at(0));
zx::event release_fence2 = scenic_impl::gfx::test::CopyEvent(release_fences1.at(1));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence1, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence2, ZX_EVENT_SIGNALED));
std::vector<zx::event> release_fences2 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence3 = scenic_impl::gfx::test::CopyEvent(release_fences2.at(0));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence3, ZX_EVENT_SIGNALED));
bool callback1_fired = false;
ScheduleUpdateAndCallback(
scheduler, kSession, zx::time(0), [&callback1_fired](auto...) { callback1_fired = true; },
std::move(release_fences1));
EXPECT_FALSE(callback1_fired);
RunLoopFor(zx::duration(vsync_timing_->vsync_interval()));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
EXPECT_TRUE(callback1_fired);
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence1, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence2, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence3, ZX_EVENT_SIGNALED));
bool callback2_fired = false;
ScheduleUpdateAndCallback(
scheduler, kSession, Now() + (vsync_timing_->vsync_interval() + zx::duration(1)),
[&callback2_fired](auto...) { callback2_fired = true; }, std::move(release_fences2));
EXPECT_FALSE(callback2_fired);
RunLoopFor(zx::sec(1));
mock_renderer_->EndFrame(/* frame number */ 1, Now());
EXPECT_TRUE(callback2_fired);
EXPECT_TRUE(scenic_impl::gfx::test::IsEventSignalled(release_fence1, ZX_EVENT_SIGNALED));
EXPECT_TRUE(scenic_impl::gfx::test::IsEventSignalled(release_fence2, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence3, ZX_EVENT_SIGNALED));
}
// Tests creating a session and calling Present2 several times with release fences. Fences should
// fire as the subsequent Present call is presented to the display.
TEST_F(FrameSchedulerTest, ReleaseFences_WithPresent2_ShouldBeFiredAfterSubsequentFramePresented) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
scheduler->SetOnFramePresentedCallbackForSession(kSessionId, [](auto) {});
// Create release fences
std::vector<zx::event> release_fences1 = scenic_impl::gfx::test::CreateEventArray(2);
zx::event release_fence1 = scenic_impl::gfx::test::CopyEvent(release_fences1.at(0));
zx::event release_fence2 = scenic_impl::gfx::test::CopyEvent(release_fences1.at(1));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence1, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence2, ZX_EVENT_SIGNALED));
std::vector<zx::event> release_fences2 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence3 = scenic_impl::gfx::test::CopyEvent(release_fences2.at(0));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence3, ZX_EVENT_SIGNALED));
bool callback1_fired = false;
SchedulePresent2Update(scheduler, kSessionId, zx::time(0), zx::time(1),
std::move(release_fences1));
EXPECT_FALSE(callback1_fired);
RunLoopFor(zx::sec(3));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence1, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence2, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence3, ZX_EVENT_SIGNALED));
SchedulePresent2Update(scheduler, kSessionId,
Now() + (vsync_timing_->vsync_interval() + zx::duration(1)), zx::time(1),
std::move(release_fences2));
RunLoopFor(zx::sec(1));
mock_renderer_->EndFrame(/* frame number */ 1, Now());
EXPECT_TRUE(scenic_impl::gfx::test::IsEventSignalled(release_fence1, ZX_EVENT_SIGNALED));
EXPECT_TRUE(scenic_impl::gfx::test::IsEventSignalled(release_fence2, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence3, ZX_EVENT_SIGNALED));
}
TEST_F(FrameSchedulerTest, SquashedPresents_ShouldHaveAllPreviousFencesSignaled) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
// Create release fences
std::vector<zx::event> release_fences1 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence1 = scenic_impl::gfx::test::CopyEvent(release_fences1.at(0));
std::vector<zx::event> release_fences2 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence2 = scenic_impl::gfx::test::CopyEvent(release_fences2.at(0));
std::vector<zx::event> release_fences3 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence3 = scenic_impl::gfx::test::CopyEvent(release_fences3.at(0));
int64_t present_count = 0;
scheduler->SetOnFramePresentedCallbackForSession(
kSessionId, [&present_count](fuchsia::scenic::scheduling::FramePresentedInfo info) {
present_count += info.presentation_infos.size();
});
// Schedule three presents, which should be squashed. First fence should be signaled.
SchedulePresent2Update(scheduler, kSessionId, zx::time(0), zx::time(0),
std::move(release_fences1));
SchedulePresent2Update(scheduler, kSessionId, zx::time(0), zx::time(0),
std::move(release_fences2));
// Schedule a present for later, which should not be part of the squashed presents.
SchedulePresent2Update(scheduler, kSessionId, Now() + zx::sec(1), zx::time(0),
std::move(release_fences3));
RunLoopFor(zx::sec(2));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
RunLoopUntilIdle();
EXPECT_EQ(present_count, 2);
EXPECT_TRUE(scenic_impl::gfx::test::IsEventSignalled(release_fence1, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence2, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence3, ZX_EVENT_SIGNALED));
}
TEST_F(FrameSchedulerTest, SkippedPresents_ShouldHaveAllPreviousFencesSignaled) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
// Create release fences
std::vector<zx::event> release_fences1 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence1 = scenic_impl::gfx::test::CopyEvent(release_fences1.at(0));
std::vector<zx::event> release_fences2 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence2 = scenic_impl::gfx::test::CopyEvent(release_fences2.at(0));
std::vector<zx::event> release_fences3 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence3 = scenic_impl::gfx::test::CopyEvent(release_fences3.at(0));
std::vector<zx::event> release_fences4 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence4 = scenic_impl::gfx::test::CopyEvent(release_fences4.at(0));
int64_t callback_count = 0;
// These will never get scheduled, but will be skipped and fences should be signaled.
scheduler->RegisterPresent(
kSessionId, [&callback_count](auto...) { ++callback_count; }, std::move(release_fences1));
scheduler->RegisterPresent(
kSessionId, [&callback_count](auto...) { ++callback_count; }, std::move(release_fences2));
// Next one should be scheduled and presented. Fences should not be signaled.
ScheduleUpdateAndCallback(
scheduler, kSessionId, zx::time(0), [&callback_count](auto...) { ++callback_count; },
std::move(release_fences3));
// This should never get scheduled, and it's callback should never be triggered.
scheduler->RegisterPresent(
kSessionId, [&callback_count](auto...) { ++callback_count; }, std::move(release_fences3));
RunLoopFor(zx::sec(1));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
RunLoopUntilIdle();
EXPECT_EQ(callback_count, 3);
EXPECT_TRUE(scenic_impl::gfx::test::IsEventSignalled(release_fence1, ZX_EVENT_SIGNALED));
EXPECT_TRUE(scenic_impl::gfx::test::IsEventSignalled(release_fence2, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence3, ZX_EVENT_SIGNALED));
EXPECT_FALSE(scenic_impl::gfx::test::IsEventSignalled(release_fence4, ZX_EVENT_SIGNALED));
}
TEST_F(FrameSchedulerTest, ReleaseFences_ShouldFireInOrder) {
auto scheduler = CreateDefaultFrameScheduler();
constexpr SessionId kSessionId = 1;
SetSessionUpdateFailedNotExpected(scheduler.get(), kSessionId);
std::vector<int> fence_order;
// Create release fences
std::vector<zx::event> release_fences1 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence1 = scenic_impl::gfx::test::CopyEvent(release_fences1.at(0));
async::Wait waiter1(release_fence1.get(), ZX_EVENT_SIGNALED, 0,
[&fence_order](auto...) { fence_order.push_back(1); });
waiter1.Begin(dispatcher());
std::vector<zx::event> release_fences2 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence2 = scenic_impl::gfx::test::CopyEvent(release_fences2.at(0));
async::Wait waiter2(release_fence2.get(), ZX_EVENT_SIGNALED, 0,
[&fence_order](auto...) { fence_order.push_back(2); });
waiter2.Begin(dispatcher());
std::vector<zx::event> release_fences3 = scenic_impl::gfx::test::CreateEventArray(1);
zx::event release_fence3 = scenic_impl::gfx::test::CopyEvent(release_fences3.at(0));
async::Wait waiter3(release_fence3.get(), ZX_EVENT_SIGNALED, 0,
[&fence_order](auto...) { fence_order.push_back(3); });
waiter3.Begin(dispatcher());
// These will never get scheduled, but will be skipped and fences should be signaled.
scheduler->RegisterPresent(
kSessionId, /*callback=*/[](auto...) {}, std::move(release_fences1));
scheduler->RegisterPresent(
kSessionId, /*callback=*/[](auto...) {}, std::move(release_fences2));
scheduler->RegisterPresent(
kSessionId, /*callback=*/[](auto...) {}, std::move(release_fences3));
// Next one should be scheduled and presented, triggering signalling of previous fences.
ScheduleUpdateAndCallback(scheduler, kSessionId, zx::time(0), /*callback=*/[](auto...) {},
/*release_fence=*/{});
RunLoopFor(zx::sec(1));
mock_renderer_->EndFrame(/* frame number */ 0, /*time_done*/ Now());
EXPECT_TRUE(fence_order.empty());
RunLoopUntilIdle();
EXPECT_THAT(fence_order, ::testing::ElementsAreArray({1, 2, 3}));
}
} // namespace test
} // namespace scheduling