blob: 9a7e314ca060279e1af10f2e984c042e6c956070 [file] [log] [blame]
// Copyright 2013 The Flutter 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 "flutter/shell/platform/fuchsia/flutter/gfx_session_connection.h"
#include <fuchsia/scenic/scheduling/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/async-testing/test_loop.h>
#include <lib/inspect/cpp/inspect.h>
#include <functional>
#include <string>
#include <vector>
#include "flutter/fml/logging.h"
#include "flutter/fml/time/time_delta.h"
#include "flutter/fml/time/time_point.h"
#include "gtest/gtest.h"
#include "fakes/scenic/fake_session.h"
using fuchsia::scenic::scheduling::FramePresentedInfo;
using fuchsia::scenic::scheduling::FuturePresentationTimes;
using fuchsia::scenic::scheduling::PresentReceivedInfo;
namespace flutter_runner::testing {
namespace {
std::string GetCurrentTestName() {
return ::testing::UnitTest::GetInstance()->current_test_info()->name();
}
fml::TimePoint TimePointFromInt(int64_t i) {
return fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(i));
}
fml::TimeDelta TimeDeltaFromInt(int64_t i) {
return fml::TimeDelta::FromNanoseconds(i);
}
int64_t TimePointToInt(fml::TimePoint time) {
return time.ToEpochDelta().ToNanoseconds();
}
fuchsia::scenic::scheduling::PresentationInfo CreatePresentationInfo(
zx_time_t latch_point,
zx_time_t presentation_time) {
fuchsia::scenic::scheduling::PresentationInfo info;
info.set_latch_point(latch_point);
info.set_presentation_time(presentation_time);
return info;
}
FramePresentedInfo MakeFramePresentedInfoForOnePresent(
int64_t latched_time,
int64_t frame_presented_time) {
std::vector<PresentReceivedInfo> present_infos;
present_infos.emplace_back();
present_infos.back().set_present_received_time(0);
present_infos.back().set_latched_time(0);
return FramePresentedInfo{
.actual_presentation_time = 0,
.presentation_infos = std::move(present_infos),
.num_presents_allowed = 1,
};
}
void AwaitVsyncChecked(GfxSessionConnection& session_connection,
bool& condition_variable,
fml::TimeDelta expected_frame_start,
fml::TimeDelta expected_frame_end) {
session_connection.AwaitVsync(
[&condition_variable,
expected_frame_start = std::move(expected_frame_start),
expected_frame_end = std::move(expected_frame_end)](
fml::TimePoint frame_start, fml::TimePoint frame_end) {
EXPECT_EQ(frame_start.ToEpochDelta(), expected_frame_start);
EXPECT_EQ(frame_end.ToEpochDelta(), expected_frame_end);
condition_variable = true;
});
}
}; // namespace
class GfxSessionConnectionTest : public ::testing::Test,
public fuchsia::ui::scenic::SessionListener {
protected:
GfxSessionConnectionTest()
: session_listener_(this), session_subloop_(loop_.StartNewLoop()) {
auto [session, session_listener] =
fake_session().Bind(session_subloop_->dispatcher());
session_ = std::move(session);
session_listener_.Bind(std::move(session_listener));
}
~GfxSessionConnectionTest() override = default;
async::TestLoop& loop() { return loop_; }
FakeSession& fake_session() { return fake_session_; }
inspect::Node GetInspectNode() {
return inspector_.GetRoot().CreateChild("GfxSessionConnectionTest");
}
fidl::InterfaceHandle<fuchsia::ui::scenic::Session> TakeSessionHandle() {
FML_CHECK(session_.is_valid());
return std::move(session_);
}
private:
// |fuchsia::ui::scenic::SessionListener|
void OnScenicError(std::string error) override { FAIL(); }
// |fuchsia::ui::scenic::SessionListener|
void OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events) override {
FAIL();
}
async::TestLoop loop_;
inspect::Inspector inspector_;
fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session_;
fidl::Binding<fuchsia::ui::scenic::SessionListener> session_listener_;
std::unique_ptr<async::LoopInterface> session_subloop_;
FakeSession fake_session_;
};
TEST_F(GfxSessionConnectionTest, Initialization) {
// Create the GfxSessionConnection but don't pump the loop. No FIDL calls are
// completed yet.
const std::string debug_name = GetCurrentTestName();
flutter_runner::GfxSessionConnection session_connection(
debug_name, GetInspectNode(), TakeSessionHandle(), []() { FAIL(); },
[](auto...) { FAIL(); }, 1, fml::TimeDelta::Zero());
EXPECT_EQ(fake_session().debug_name(), "");
EXPECT_TRUE(fake_session().command_queue().empty());
// Simulate an AwaitVsync that comes immediately, before
// `RequestPresentationTimes` returns.
bool await_vsync_fired = false;
AwaitVsyncChecked(session_connection, await_vsync_fired,
fml::TimeDelta::Zero(), kDefaultPresentationInterval);
EXPECT_TRUE(await_vsync_fired);
// Ensure the debug name is set.
loop().RunUntilIdle();
EXPECT_EQ(fake_session().debug_name(), debug_name);
EXPECT_TRUE(fake_session().command_queue().empty());
}
TEST_F(GfxSessionConnectionTest, SessionDisconnect) {
// Set up a callback which allows sensing of the session error state.
bool session_error_fired = false;
fml::closure on_session_error = [&session_error_fired]() {
session_error_fired = true;
};
// Create the GfxSessionConnection but don't pump the loop. No FIDL calls are
// completed yet.
flutter_runner::GfxSessionConnection session_connection(
GetCurrentTestName(), GetInspectNode(), TakeSessionHandle(),
std::move(on_session_error), [](auto...) { FAIL(); }, 1,
fml::TimeDelta::Zero());
EXPECT_FALSE(session_error_fired);
// Simulate a session disconnection, then Pump the loop. The session error
// callback will fire.
fake_session().DisconnectSession();
loop().RunUntilIdle();
EXPECT_TRUE(session_error_fired);
}
TEST_F(GfxSessionConnectionTest, BasicPresent) {
// Set up callbacks which allow sensing of how many presents
// (`RequestPresentationTimes` or `Present` calls) were handled.
size_t request_times_called = 0u;
size_t presents_called = 0u;
fake_session().SetRequestPresentationTimesHandler([&request_times_called](
auto...) -> auto {
request_times_called++;
return FuturePresentationTimes{
.future_presentations = {},
.remaining_presents_in_flight_allowed = 1,
};
});
fake_session().SetPresent2Handler([&presents_called](auto...) -> auto {
presents_called++;
return FuturePresentationTimes{
.future_presentations = {},
.remaining_presents_in_flight_allowed = 1,
};
});
// Set up a callback which allows sensing of how many vsync's
// (`OnFramePresented` events) were handled.
size_t vsyncs_handled = 0u;
on_frame_presented_event on_frame_presented = [&vsyncs_handled](auto...) {
vsyncs_handled++;
};
// Create the GfxSessionConnection but don't pump the loop. No FIDL calls are
// completed yet.
flutter_runner::GfxSessionConnection session_connection(
GetCurrentTestName(), GetInspectNode(), TakeSessionHandle(),
[]() { FAIL(); }, std::move(on_frame_presented), 1,
fml::TimeDelta::Zero());
EXPECT_TRUE(fake_session().command_queue().empty());
EXPECT_EQ(request_times_called, 0u);
EXPECT_EQ(presents_called, 0u);
EXPECT_EQ(vsyncs_handled, 0u);
// Pump the loop; `RequestPresentationTimes`, `Present`, and both of their
// callbacks are called.
loop().RunUntilIdle();
EXPECT_TRUE(fake_session().command_queue().empty());
EXPECT_EQ(request_times_called, 1u);
EXPECT_EQ(presents_called, 1u);
EXPECT_EQ(vsyncs_handled, 0u);
// Fire the `OnFramePresented` event associated with the first `Present`, then
// pump the loop. The `OnFramePresented` event is resolved.
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
EXPECT_TRUE(fake_session().command_queue().empty());
EXPECT_EQ(request_times_called, 1u);
EXPECT_EQ(presents_called, 1u);
EXPECT_EQ(vsyncs_handled, 1u);
// Simulate an AwaitVsync that comes after the first `OnFramePresented`
// event.
bool await_vsync_fired = false;
AwaitVsyncChecked(session_connection, await_vsync_fired,
fml::TimeDelta::Zero(), kDefaultPresentationInterval);
EXPECT_TRUE(await_vsync_fired);
// Call Present and Pump the loop; `Present` and its callback is called.
await_vsync_fired = false;
session_connection.Present();
loop().RunUntilIdle();
EXPECT_TRUE(fake_session().command_queue().empty());
EXPECT_FALSE(await_vsync_fired);
EXPECT_EQ(request_times_called, 1u);
EXPECT_EQ(presents_called, 2u);
EXPECT_EQ(vsyncs_handled, 1u);
// Fire the `OnFramePresented` event associated with the second `Present`,
// then pump the loop. The `OnFramePresented` event is resolved.
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
EXPECT_TRUE(fake_session().command_queue().empty());
EXPECT_FALSE(await_vsync_fired);
EXPECT_EQ(request_times_called, 1u);
EXPECT_EQ(presents_called, 2u);
EXPECT_EQ(vsyncs_handled, 2u);
// Simulate an AwaitVsync that comes after the second `OnFramePresented`
// event.
await_vsync_fired = false;
AwaitVsyncChecked(session_connection, await_vsync_fired,
kDefaultPresentationInterval,
kDefaultPresentationInterval * 2);
EXPECT_TRUE(await_vsync_fired);
}
TEST_F(GfxSessionConnectionTest, AwaitVsyncBackpressure) {
// Set up a callback which allows sensing of how many presents
// (`Present` calls) were handled.
size_t presents_called = 0u;
fake_session().SetPresent2Handler([&presents_called](auto...) -> auto {
presents_called++;
return FuturePresentationTimes{
.future_presentations = {},
.remaining_presents_in_flight_allowed = 1,
};
});
// Set up a callback which allows sensing of how many vsync's
// (`OnFramePresented` events) were handled.
size_t vsyncs_handled = 0u;
on_frame_presented_event on_frame_presented = [&vsyncs_handled](auto...) {
vsyncs_handled++;
};
// Create the GfxSessionConnection but don't pump the loop. No FIDL calls are
// completed yet.
flutter_runner::GfxSessionConnection session_connection(
GetCurrentTestName(), GetInspectNode(), TakeSessionHandle(),
[]() { FAIL(); }, std::move(on_frame_presented), 1,
fml::TimeDelta::Zero());
EXPECT_EQ(presents_called, 0u);
EXPECT_EQ(vsyncs_handled, 0u);
// Pump the loop; `RequestPresentationTimes`, `Present`, and both of their
// callbacks are called.
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 1u);
EXPECT_EQ(vsyncs_handled, 0u);
// Simulate an AwaitVsync that comes before the first `OnFramePresented`
// event.
bool await_vsync_fired = false;
AwaitVsyncChecked(session_connection, await_vsync_fired,
fml::TimeDelta::Zero(), kDefaultPresentationInterval);
EXPECT_FALSE(await_vsync_fired);
// Fire the `OnFramePresented` event associated with the first `Present`, then
// pump the loop. The `OnFramePresented` event is resolved. The AwaitVsync
// callback is resolved.
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
EXPECT_TRUE(await_vsync_fired);
EXPECT_EQ(presents_called, 1u);
EXPECT_EQ(vsyncs_handled, 1u);
// Simulate an AwaitVsync that comes before the second `Present`.
await_vsync_fired = false;
AwaitVsyncChecked(session_connection, await_vsync_fired,
kDefaultPresentationInterval,
kDefaultPresentationInterval * 2);
EXPECT_TRUE(await_vsync_fired);
// Call Present and Pump the loop; `Present` and its callback is called.
await_vsync_fired = false;
session_connection.Present();
loop().RunUntilIdle();
EXPECT_FALSE(await_vsync_fired);
EXPECT_EQ(presents_called, 2u);
EXPECT_EQ(vsyncs_handled, 1u);
// Simulate an AwaitVsync that comes before the second `OnFramePresented`
// event.
await_vsync_fired = false;
AwaitVsyncChecked(session_connection, await_vsync_fired,
kDefaultPresentationInterval * 2,
kDefaultPresentationInterval * 3);
EXPECT_FALSE(await_vsync_fired);
// Fire the `OnFramePresented` event associated with the second `Present`,
// then pump the loop. The `OnFramePresented` event is resolved.
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
EXPECT_TRUE(await_vsync_fired);
EXPECT_EQ(presents_called, 2u);
EXPECT_EQ(vsyncs_handled, 2u);
}
TEST_F(GfxSessionConnectionTest, PresentBackpressure) {
// Set up a callback which allows sensing of how many presents
// (`Present` calls) were handled.
size_t presents_called = 0u;
fake_session().SetPresent2Handler([&presents_called](auto...) -> auto {
presents_called++;
return FuturePresentationTimes{
.future_presentations = {},
.remaining_presents_in_flight_allowed = 1,
};
});
// Set up a callback which allows sensing of how many vsync's
// (`OnFramePresented` events) were handled.
size_t vsyncs_handled = 0u;
on_frame_presented_event on_frame_presented = [&vsyncs_handled](auto...) {
vsyncs_handled++;
};
// Create the GfxSessionConnection but don't pump the loop. No FIDL calls are
// completed yet.
flutter_runner::GfxSessionConnection session_connection(
GetCurrentTestName(), GetInspectNode(), TakeSessionHandle(),
[]() { FAIL(); }, std::move(on_frame_presented), 1,
fml::TimeDelta::Zero());
EXPECT_EQ(presents_called, 0u);
EXPECT_EQ(vsyncs_handled, 0u);
// Pump the loop; `RequestPresentationTimes`, `Present`, and both of their
// callbacks are called.
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 1u);
EXPECT_EQ(vsyncs_handled, 0u);
// Call Present and Pump the loop; `Present` is not called due to backpressue.
session_connection.Present();
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 1u);
EXPECT_EQ(vsyncs_handled, 0u);
// Call Present again and Pump the loop; `Present` is not called due to
// backpressue.
session_connection.Present();
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 1u);
EXPECT_EQ(vsyncs_handled, 0u);
// Fire the `OnFramePresented` event associated with the first `Present`, then
// pump the loop. The `OnFramePresented` event is resolved. The pending
// `Present` calls are resolved.
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 2u);
EXPECT_EQ(vsyncs_handled, 1u);
// Call Present and Pump the loop; `Present` is not called due to
// backpressue.
session_connection.Present();
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 2u);
EXPECT_EQ(vsyncs_handled, 1u);
// Call Present again and Pump the loop; `Present` is not called due to
// backpressue.
session_connection.Present();
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 2u);
EXPECT_EQ(vsyncs_handled, 1u);
// Fire the `OnFramePresented` event associated with the second `Present`,
// then pump the loop. The `OnFramePresented` event is resolved. The pending
// `Present` calls are resolved.
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 3u);
EXPECT_EQ(vsyncs_handled, 2u);
// Fire the `OnFramePresented` event associated with the third `Present`,
// then pump the loop. The `OnFramePresented` event is resolved. No pending
// `Present` calls exist, so none are resolved.
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 3u);
EXPECT_EQ(vsyncs_handled, 3u);
}
// The first set of tests has an empty |future_presentation_infos| passed in.
// Therefore these tests are to ensure that on startup and after not presenting
// for some time that we have correct, reasonable behavior.
TEST(CalculateNextLatchPointTest, PresentAsSoonAsPossible) {
fml::TimePoint present_requested_time = TimePointFromInt(0);
fml::TimePoint now = TimePointFromInt(0);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(0);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(0);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point), TimePointToInt(now));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + vsync_interval));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
TEST(CalculateNextLatchPointTest, LongFrameBuildTime) {
fml::TimePoint present_requested_time = TimePointFromInt(500);
fml::TimePoint now = TimePointFromInt(600);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(0);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(2500);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
EXPECT_GT(flutter_frame_build_time, vsync_interval);
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 2)));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 3)));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
TEST(CalculateNextLatchPointTest, DelayedPresentRequestWithLongFrameBuildTime) {
fml::TimePoint present_requested_time = TimePointFromInt(0);
fml::TimePoint now = TimePointFromInt(1500);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(0);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(2000);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
EXPECT_GT(flutter_frame_build_time, vsync_interval);
EXPECT_GT(now, present_requested_time + vsync_interval);
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point), TimePointToInt(now));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + vsync_interval));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
TEST(CalculateNextLatchPointTest, LastLastPointTargetedLate) {
fml::TimePoint present_requested_time = TimePointFromInt(2000);
fml::TimePoint now = TimePointFromInt(2000);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(2600);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(1000);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
EXPECT_GT(last_latch_point_targeted, present_requested_time);
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + vsync_interval));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 2)));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
// This set of tests provides (latch_point, vsync_time) pairs in
// |future_presentation_infos|. This tests steady state behavior where we're
// presenting frames virtually every vsync interval.
TEST(CalculateNextLatchPointTest, SteadyState_OnTimeFrames) {
fml::TimePoint present_requested_time = TimePointFromInt(5000);
fml::TimePoint now = TimePointFromInt(5000);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(4500);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(1000);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {
{TimePointFromInt(3500), TimePointFromInt(4000)},
{TimePointFromInt(4500), TimePointFromInt(5000)},
{TimePointFromInt(5500), TimePointFromInt(6000)},
{TimePointFromInt(6500), TimePointFromInt(7000)},
{TimePointFromInt(7500), TimePointFromInt(8000)},
};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + vsync_interval));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 2)));
EXPECT_EQ(TimePointToInt(calculated_latch_point), 6500);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
TEST(CalculateNextLatchPointTest, SteadyState_LongFrameBuildTimes) {
fml::TimePoint present_requested_time = TimePointFromInt(5000);
fml::TimePoint now = TimePointFromInt(5000);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(4500);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(2000);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {
{TimePointFromInt(3500), TimePointFromInt(4000)},
{TimePointFromInt(4500), TimePointFromInt(5000)},
{TimePointFromInt(5500), TimePointFromInt(6000)},
{TimePointFromInt(6500), TimePointFromInt(7000)},
{TimePointFromInt(7500), TimePointFromInt(8000)},
};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
EXPECT_GT(flutter_frame_build_time, vsync_interval);
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 2)));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 3)));
EXPECT_EQ(TimePointToInt(calculated_latch_point), 7500);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
TEST(CalculateNextLatchPointTest, SteadyState_LateLastLatchPointTargeted) {
fml::TimePoint present_requested_time = TimePointFromInt(5000);
fml::TimePoint now = TimePointFromInt(5000);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(6500);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(1000);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {
{TimePointFromInt(4500), TimePointFromInt(5000)},
{TimePointFromInt(5500), TimePointFromInt(6000)},
{TimePointFromInt(6500), TimePointFromInt(7000)},
{TimePointFromInt(7500), TimePointFromInt(8000)},
{TimePointFromInt(8500), TimePointFromInt(9000)},
};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
EXPECT_GT(last_latch_point_targeted, now + vsync_interval);
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + vsync_interval));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 2)));
EXPECT_EQ(TimePointToInt(calculated_latch_point), 6500);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
TEST(CalculateNextLatchPointTest,
SteadyState_DelayedPresentRequestWithLongFrameBuildTime) {
fml::TimePoint present_requested_time = TimePointFromInt(4000);
fml::TimePoint now = TimePointFromInt(5500);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(3500);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(2000);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {
{TimePointFromInt(4500), TimePointFromInt(5000)},
{TimePointFromInt(5500), TimePointFromInt(6000)},
{TimePointFromInt(6500), TimePointFromInt(7000)},
};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
EXPECT_GT(flutter_frame_build_time, vsync_interval);
EXPECT_GT(now, present_requested_time + vsync_interval);
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + vsync_interval));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 2)));
EXPECT_EQ(TimePointToInt(calculated_latch_point), 6500);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
TEST(CalculateNextLatchPointTest, SteadyState_FuzzyLatchPointsBeforeTarget) {
fml::TimePoint present_requested_time = TimePointFromInt(4000);
fml::TimePoint now = TimePointFromInt(4000);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(5490);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(1000);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {
{TimePointFromInt(4510), TimePointFromInt(5000)},
{TimePointFromInt(5557), TimePointFromInt(6000)},
{TimePointFromInt(6482), TimePointFromInt(7000)},
{TimePointFromInt(7356), TimePointFromInt(8000)},
};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + vsync_interval));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 2)));
EXPECT_EQ(TimePointToInt(calculated_latch_point), 5557);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
TEST(CalculateNextLatchPointTest, SteadyState_FuzzyLatchPointsAfterTarget) {
fml::TimePoint present_requested_time = TimePointFromInt(4000);
fml::TimePoint now = TimePointFromInt(4000);
fml::TimePoint last_latch_point_targeted = TimePointFromInt(5557);
fml::TimeDelta flutter_frame_build_time = TimeDeltaFromInt(1000);
fml::TimeDelta vsync_interval = TimeDeltaFromInt(1000);
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>
future_presentation_infos = {
{TimePointFromInt(4510), TimePointFromInt(5000)},
{TimePointFromInt(5490), TimePointFromInt(6000)},
{TimePointFromInt(6482), TimePointFromInt(7000)},
{TimePointFromInt(7356), TimePointFromInt(8000)},
};
// Assertions about given values.
EXPECT_GE(now, present_requested_time);
EXPECT_GE(flutter_frame_build_time, TimeDeltaFromInt(0));
EXPECT_GT(vsync_interval, TimeDeltaFromInt(0));
fml::TimePoint calculated_latch_point =
GfxSessionConnection::CalculateNextLatchPoint(
present_requested_time, now, last_latch_point_targeted,
flutter_frame_build_time, vsync_interval, future_presentation_infos);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 2)));
EXPECT_LE(TimePointToInt(calculated_latch_point),
TimePointToInt(now + (vsync_interval * 3)));
EXPECT_EQ(TimePointToInt(calculated_latch_point), 6482);
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(present_requested_time + flutter_frame_build_time));
EXPECT_GE(TimePointToInt(calculated_latch_point),
TimePointToInt(last_latch_point_targeted));
}
TEST(SnapToNextPhaseTest, SnapOverlapsWithNow) {
const auto now = fml::TimePoint::Now();
const auto last_presentation_time = now - fml::TimeDelta::FromNanoseconds(10);
const auto delta = fml::TimeDelta::FromNanoseconds(10);
const auto next_vsync = flutter_runner::GfxSessionConnection::SnapToNextPhase(
now, last_presentation_time, delta);
EXPECT_EQ(now + delta, next_vsync);
}
TEST(SnapToNextPhaseTest, SnapAfterNow) {
const auto now = fml::TimePoint::Now();
const auto last_presentation_time = now - fml::TimeDelta::FromNanoseconds(9);
const auto delta = fml::TimeDelta::FromNanoseconds(10);
const auto next_vsync = flutter_runner::GfxSessionConnection::SnapToNextPhase(
now, last_presentation_time, delta);
// math here: 10 - 9 = 1
EXPECT_EQ(now + fml::TimeDelta::FromNanoseconds(1), next_vsync);
}
TEST(SnapToNextPhaseTest, SnapAfterNowMultiJump) {
const auto now = fml::TimePoint::Now();
const auto last_presentation_time = now - fml::TimeDelta::FromNanoseconds(34);
const auto delta = fml::TimeDelta::FromNanoseconds(10);
const auto next_vsync = flutter_runner::GfxSessionConnection::SnapToNextPhase(
now, last_presentation_time, delta);
// zeroes: -34, -24, -14, -4, 6, ...
EXPECT_EQ(now + fml::TimeDelta::FromNanoseconds(6), next_vsync);
}
TEST(SnapToNextPhaseTest, SnapAfterNowMultiJumpAccountForCeils) {
const auto now = fml::TimePoint::Now();
const auto last_presentation_time = now - fml::TimeDelta::FromNanoseconds(20);
const auto delta = fml::TimeDelta::FromNanoseconds(16);
const auto next_vsync = flutter_runner::GfxSessionConnection::SnapToNextPhase(
now, last_presentation_time, delta);
// zeroes: -20, -4, 12, 28, ...
EXPECT_EQ(now + fml::TimeDelta::FromNanoseconds(12), next_vsync);
}
TEST(GetTargetTimesTest, ScheduleForNextVsync) {
const fml::TimeDelta vsync_offset = TimeDeltaFromInt(0);
const fml::TimeDelta vsync_interval = TimeDeltaFromInt(10);
const fml::TimePoint last_targeted_vsync = TimePointFromInt(10);
const fml::TimePoint now = TimePointFromInt(9);
const fml::TimePoint next_vsync = TimePointFromInt(10);
const auto target_times =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync, now, next_vsync);
EXPECT_EQ(TimePointToInt(target_times.frame_start), 10);
EXPECT_EQ(TimePointToInt(target_times.frame_target), 20);
}
TEST(GetTargetTimesTest, ScheduleForCurrentVsync_DueToOffset) {
const fml::TimeDelta vsync_offset = TimeDeltaFromInt(3);
const fml::TimeDelta vsync_interval = TimeDeltaFromInt(10);
const fml::TimePoint last_targeted_vsync = TimePointFromInt(0);
const fml::TimePoint now = TimePointFromInt(6);
const fml::TimePoint next_vsync = TimePointFromInt(10);
const auto target_times =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync, now, next_vsync);
EXPECT_EQ(TimePointToInt(target_times.frame_start), 7);
EXPECT_EQ(TimePointToInt(target_times.frame_target), 10);
}
TEST(GetTargetTimesTest, ScheduleForFollowingVsync_BecauseOfNow) {
const fml::TimeDelta vsync_offset = TimeDeltaFromInt(0);
const fml::TimeDelta vsync_interval = TimeDeltaFromInt(10);
const fml::TimePoint last_targeted_vsync = TimePointFromInt(10);
const fml::TimePoint now = TimePointFromInt(15);
const fml::TimePoint next_vsync = TimePointFromInt(10);
const auto target_times =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync, now, next_vsync);
EXPECT_EQ(TimePointToInt(target_times.frame_start), 20);
EXPECT_EQ(TimePointToInt(target_times.frame_target), 30);
}
TEST(GetTargetTimesTest, ScheduleForFollowingVsync_BecauseOfTargettedTime) {
const fml::TimeDelta vsync_offset = TimeDeltaFromInt(0);
const fml::TimeDelta vsync_interval = TimeDeltaFromInt(10);
const fml::TimePoint last_targeted_vsync = TimePointFromInt(20);
const fml::TimePoint now = TimePointFromInt(9);
const fml::TimePoint next_vsync = TimePointFromInt(10);
const auto target_times =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync, now, next_vsync);
EXPECT_EQ(TimePointToInt(target_times.frame_start), 20);
EXPECT_EQ(TimePointToInt(target_times.frame_target), 30);
}
TEST(GetTargetTimesTest, ScheduleForDistantVsync_BecauseOfTargettedTime) {
const fml::TimeDelta vsync_offset = TimeDeltaFromInt(0);
const fml::TimeDelta vsync_interval = TimeDeltaFromInt(10);
const fml::TimePoint last_targeted_vsync = TimePointFromInt(60);
const fml::TimePoint now = TimePointFromInt(9);
const fml::TimePoint next_vsync = TimePointFromInt(10);
const auto target_times =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync, now, next_vsync);
EXPECT_EQ(TimePointToInt(target_times.frame_start), 60);
EXPECT_EQ(TimePointToInt(target_times.frame_target), 70);
}
TEST(GetTargetTimesTest, ScheduleForFollowingVsync_WithSlightVsyncDrift) {
const fml::TimeDelta vsync_offset = TimeDeltaFromInt(0);
const fml::TimeDelta vsync_interval = TimeDeltaFromInt(10);
// Even though it appears as if the next vsync is at time 40, we should still
// present at time 50.
const fml::TimePoint last_targeted_vsync = TimePointFromInt(37);
const fml::TimePoint now = TimePointFromInt(9);
const fml::TimePoint next_vsync = TimePointFromInt(10);
const auto target_times =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync, now, next_vsync);
EXPECT_EQ(TimePointToInt(target_times.frame_start), 40);
EXPECT_EQ(TimePointToInt(target_times.frame_target), 50);
}
TEST(GetTargetTimesTest, ScheduleForAnOffsetFromVsync) {
const fml::TimeDelta vsync_offset = TimeDeltaFromInt(4);
const fml::TimeDelta vsync_interval = TimeDeltaFromInt(10);
const fml::TimePoint last_targeted_vsync = TimePointFromInt(10);
const fml::TimePoint now = TimePointFromInt(9);
const fml::TimePoint next_vsync = TimePointFromInt(10);
const auto target_times =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync, now, next_vsync);
EXPECT_EQ(TimePointToInt(target_times.frame_start), 16);
EXPECT_EQ(TimePointToInt(target_times.frame_target), 20);
}
TEST(GetTargetTimesTest, ScheduleMultipleTimes) {
const fml::TimeDelta vsync_offset = TimeDeltaFromInt(0);
const fml::TimeDelta vsync_interval = TimeDeltaFromInt(10);
fml::TimePoint last_targeted_vsync = TimePointFromInt(0);
fml::TimePoint now = TimePointFromInt(5);
fml::TimePoint next_vsync = TimePointFromInt(10);
for (int i = 0; i < 100; ++i) {
const auto target_times =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync, now, next_vsync);
EXPECT_EQ(TimePointToInt(target_times.frame_start), 10 * (i + 1));
EXPECT_EQ(TimePointToInt(target_times.frame_target), 10 * (i + 2));
// Simulate the passage of time.
now = now + vsync_interval;
next_vsync = next_vsync + vsync_interval;
last_targeted_vsync = target_times.frame_target;
}
}
TEST(GetTargetTimesTest, ScheduleMultipleTimes_WithDelayedWakeups) {
// It is often the case that Flutter does not wake up when it intends to due
// to CPU contention. This test has GfxSessionConnection wake up to
// schedule 0-4ns after when |now| should be - and we verify that the results
// should be the same as if there were no delay.
const fml::TimeDelta vsync_offset = TimeDeltaFromInt(0);
const fml::TimeDelta vsync_interval = TimeDeltaFromInt(10);
fml::TimePoint last_targeted_vsync = TimePointFromInt(0);
fml::TimePoint now = TimePointFromInt(5);
fml::TimePoint next_vsync = TimePointFromInt(10);
for (int i = 0; i < 100; ++i) {
const auto target_times =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync, now, next_vsync);
const auto target_times_delay =
flutter_runner::GfxSessionConnection::GetTargetTimes(
vsync_offset, vsync_interval, last_targeted_vsync,
now + TimeDeltaFromInt(i % 5), next_vsync);
EXPECT_EQ(TimePointToInt(target_times.frame_start),
TimePointToInt(target_times_delay.frame_start));
EXPECT_EQ(TimePointToInt(target_times.frame_target),
TimePointToInt(target_times_delay.frame_target));
// Simulate the passage of time.
now = now + vsync_interval;
next_vsync = next_vsync + vsync_interval;
last_targeted_vsync = target_times.frame_target;
}
}
TEST(UpdatePresentationInfoTest, SingleUpdate) {
std::vector<fuchsia::scenic::scheduling::PresentationInfo>
future_presentations = {};
// Update the |vsync_info|.
future_presentations.push_back(
CreatePresentationInfo(/*latch_point=*/5, /*presentation_time=*/10));
fuchsia::scenic::scheduling::FuturePresentationTimes future_info;
future_info.future_presentations = std::move(future_presentations);
future_info.remaining_presents_in_flight_allowed = 1;
fuchsia::scenic::scheduling::PresentationInfo presentation_info;
presentation_info.set_presentation_time(0);
fuchsia::scenic::scheduling::PresentationInfo new_presentation_info =
flutter_runner::GfxSessionConnection::UpdatePresentationInfo(
std::move(future_info), presentation_info);
EXPECT_EQ(new_presentation_info.presentation_time(), 10);
}
TEST(UpdatePresentationInfoTest, MultipleUpdates) {
std::vector<fuchsia::scenic::scheduling::PresentationInfo>
future_presentations = {};
// Update the |vsync_info|.
future_presentations.push_back(
CreatePresentationInfo(/*latch_point=*/15, /*presentation_time=*/20));
future_presentations.push_back(
CreatePresentationInfo(/*latch_point=*/25, /*presentation_time=*/30));
fuchsia::scenic::scheduling::FuturePresentationTimes future_info;
future_info.future_presentations = std::move(future_presentations);
future_info.remaining_presents_in_flight_allowed = 1;
fuchsia::scenic::scheduling::PresentationInfo presentation_info;
presentation_info.set_presentation_time(0);
fuchsia::scenic::scheduling::PresentationInfo new_presentation_info =
flutter_runner::GfxSessionConnection::UpdatePresentationInfo(
std::move(future_info), presentation_info);
EXPECT_EQ(new_presentation_info.presentation_time(), 20);
// Clear and re-try with more future times!
future_presentations.clear();
future_presentations.push_back(
CreatePresentationInfo(/*latch_point=*/15, /*presentation_time=*/20));
future_presentations.push_back(
CreatePresentationInfo(/*latch_point=*/25, /*presentation_time=*/30));
future_presentations.push_back(
CreatePresentationInfo(/*latch_point=*/35, /*presentation_time=*/40));
future_presentations.push_back(
CreatePresentationInfo(/*latch_point=*/45, /*presentation_time=*/50));
future_info.future_presentations = std::move(future_presentations);
future_info.remaining_presents_in_flight_allowed = 1;
new_presentation_info =
flutter_runner::GfxSessionConnection::UpdatePresentationInfo(
std::move(future_info), new_presentation_info);
EXPECT_EQ(new_presentation_info.presentation_time(), 30);
}
} // namespace flutter_runner::testing