[fuchsia] Restructure Flatland vsync loop (#45531)
This CL restructures Flatland vsync loop to fire for each vsync instead
of each OnNextFrameBegin. As shown in the traces attached to the bug,
the current implementation of firing callbacks on each OnNextFrameBegin
causes skips when Flutter has longer draw calls. By scheduling frames in
between, we are increasing the chance of sending one before the latch
point. OnNextFrameBegin is now used to keep track of present credits and
future presentation times as well as when to start frame, replacing the
need for max_frames_in_flight and vsync_offset fields.
Bug: b/296272449
(cherry picked from commit 633ba427c498d9a5d096ac4979ecaa83ace4deaa)
diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc
index b0612ac..3543c84 100644
--- a/shell/platform/fuchsia/flutter/engine.cc
+++ b/shell/platform/fuchsia/flutter/engine.cc
@@ -354,8 +354,6 @@
view_protocols = std::move(view_protocols),
request = parent_viewport_watcher.NewRequest(),
view_ref_pair = std::move(view_ref_pair),
- max_frames_in_flight = product_config.get_max_frames_in_flight(),
- vsync_offset = product_config.get_vsync_offset(),
software_rendering = product_config.software_rendering()]() mutable {
if (software_rendering) {
surface_producer_ = std::make_shared<SoftwareSurfaceProducer>();
@@ -365,8 +363,7 @@
flatland_connection_ = std::make_shared<FlatlandConnection>(
thread_label_, std::move(flatland),
- std::move(session_error_callback), [](auto) {},
- max_frames_in_flight, vsync_offset);
+ std::move(session_error_callback), [](auto) {});
fuchsia::ui::views::ViewIdentityOnCreation view_identity = {
.view_ref = std::move(view_ref_pair.second),
diff --git a/shell/platform/fuchsia/flutter/flatland_connection.cc b/shell/platform/fuchsia/flutter/flatland_connection.cc
index 5b3a029..cfc7ea0 100644
--- a/shell/platform/fuchsia/flutter/flatland_connection.cc
+++ b/shell/platform/fuchsia/flutter/flatland_connection.cc
@@ -13,11 +13,10 @@
namespace {
-fml::TimePoint GetNextPresentationTime(fml::TimePoint now,
- fml::TimePoint next_presentation_time) {
- return now > next_presentation_time
- ? now + flutter_runner::kDefaultFlatlandPresentationInterval
- : next_presentation_time;
+// Helper function for traces.
+double DeltaFromNowInNanoseconds(const fml::TimePoint& now,
+ const fml::TimePoint& time) {
+ return (time - now).ToNanoseconds();
}
} // namespace
@@ -26,9 +25,7 @@
std::string debug_label,
fuchsia::ui::composition::FlatlandHandle flatland,
fml::closure error_callback,
- on_frame_presented_event on_frame_presented_callback,
- uint64_t max_frames_in_flight,
- fml::TimeDelta vsync_offset)
+ on_frame_presented_event on_frame_presented_callback)
: flatland_(flatland.Bind()),
error_callback_(error_callback),
on_frame_presented_callback_(std::move(on_frame_presented_callback)) {
@@ -49,11 +46,9 @@
// This method is called from the raster thread.
void FlatlandConnection::Present() {
- if (!threadsafe_state_.first_present_called_) {
- std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
- threadsafe_state_.first_present_called_ = true;
- }
- if (present_credits_ > 0) {
+ TRACE_DURATION("flutter", "FlatlandConnection::Present");
+ std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
+ if (threadsafe_state_.present_credits_ > 0) {
DoPresent();
} else {
present_waiting_for_credit_ = true;
@@ -66,14 +61,15 @@
TRACE_FLOW_BEGIN("gfx", "Flatland::Present", next_present_trace_id_);
++next_present_trace_id_;
- FML_CHECK(present_credits_ > 0);
- --present_credits_;
+ FML_CHECK(threadsafe_state_.present_credits_ > 0);
+ --threadsafe_state_.present_credits_;
fuchsia::ui::composition::PresentArgs present_args;
present_args.set_requested_presentation_time(0);
present_args.set_acquire_fences(std::move(acquire_fences_));
present_args.set_release_fences(std::move(previous_present_release_fences_));
- present_args.set_unsquashable(false);
+ // Frame rate over latency.
+ present_args.set_unsquashable(true);
flatland_->Present(std::move(present_args));
// In Flatland, release fences apply to the content of the previous present.
@@ -86,38 +82,44 @@
// This method is called from the UI thread.
void FlatlandConnection::AwaitVsync(FireCallbackCallback callback) {
- std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
- const fml::TimePoint now = fml::TimePoint::Now();
+ TRACE_DURATION("flutter", "FlatlandConnection::AwaitVsync");
- // Immediately fire callbacks until the first Present. We might receive
- // multiple requests for AwaitVsync() until the first Present, which relies on
- // receiving size on PlatformView::OnGetLayout() at an uncertain time.
- if (!threadsafe_state_.first_present_called_) {
- callback(now, now + kDefaultFlatlandPresentationInterval);
+ std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
+ threadsafe_state_.pending_fire_callback_ = nullptr;
+ const auto now = fml::TimePoint::Now();
+
+ // Initial case.
+ if (MaybeRunInitialVsyncCallback(now, callback))
+ return;
+
+ // Throttle case.
+ if (threadsafe_state_.present_credits_ == 0) {
+ threadsafe_state_.pending_fire_callback_ = callback;
return;
}
- threadsafe_state_.fire_callback_ = callback;
-
- // Immediately fire callback if OnNextFrameBegin() is already called.
- if (threadsafe_state_.on_next_frame_pending_) {
- threadsafe_state_.fire_callback_(
- now, GetNextPresentationTime(
- now, threadsafe_state_.next_presentation_time_));
- threadsafe_state_.fire_callback_ = nullptr;
- threadsafe_state_.on_next_frame_pending_ = false;
- }
+ // Regular case.
+ RunVsyncCallback(now, callback);
}
// This method is called from the UI thread.
void FlatlandConnection::AwaitVsyncForSecondaryCallback(
FireCallbackCallback callback) {
- const fml::TimePoint now = fml::TimePoint::Now();
+ TRACE_DURATION("flutter",
+ "FlatlandConnection::AwaitVsyncForSecondaryCallback");
+
std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
- callback(now, GetNextPresentationTime(
- now, threadsafe_state_.next_presentation_time_));
+ const auto now = fml::TimePoint::Now();
+
+ // Initial case.
+ if (MaybeRunInitialVsyncCallback(now, callback))
+ return;
+
+ // Regular case.
+ RunVsyncCallback(now, callback);
}
+// This method is called from the raster thread.
void FlatlandConnection::OnError(
fuchsia::ui::composition::FlatlandError error) {
FML_LOG(ERROR) << "Flatland error: " << static_cast<int>(error);
@@ -127,30 +129,62 @@
// This method is called from the raster thread.
void FlatlandConnection::OnNextFrameBegin(
fuchsia::ui::composition::OnNextFrameBeginValues values) {
- present_credits_ += values.additional_present_credits();
+ // Collect now before locking because this is an important timing information
+ // from Scenic.
+ const auto now = fml::TimePoint::Now();
- if (present_waiting_for_credit_ && present_credits_ > 0) {
+ std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
+ threadsafe_state_.first_feedback_received_ = true;
+ threadsafe_state_.present_credits_ += values.additional_present_credits();
+ TRACE_DURATION("flutter", "FlatlandConnection::OnNextFrameBegin",
+ "present_credits", threadsafe_state_.present_credits_);
+
+ if (present_waiting_for_credit_ && threadsafe_state_.present_credits_ > 0) {
DoPresent();
present_waiting_for_credit_ = false;
}
- if (present_credits_ > 0) {
- FML_CHECK(values.has_future_presentation_infos() &&
- !values.future_presentation_infos().empty());
- const auto next_presentation_time =
- fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(
- values.future_presentation_infos().front().presentation_time()));
+ // Update vsync_interval_ by calculating the difference between the first two
+ // presentation times. Flatland always returns >1 presentation_infos, so this
+ // check is to guard against any changes to this assumption.
+ if (values.has_future_presentation_infos() &&
+ values.future_presentation_infos().size() > 1) {
+ threadsafe_state_.vsync_interval_ = fml::TimeDelta::FromNanoseconds(
+ values.future_presentation_infos().at(1).presentation_time() -
+ values.future_presentation_infos().at(0).presentation_time());
+ } else {
+ FML_LOG(WARNING)
+ << "Flatland didn't send enough future_presentation_infos to update "
+ "vsync interval.";
+ }
- std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
- if (threadsafe_state_.fire_callback_) {
- threadsafe_state_.fire_callback_(
- /*frame_start=*/fml::TimePoint::Now(),
- /*frame_target=*/next_presentation_time);
- threadsafe_state_.fire_callback_ = nullptr;
- } else {
- threadsafe_state_.on_next_frame_pending_ = true;
- }
- threadsafe_state_.next_presentation_time_ = next_presentation_time;
+ // Update next_presentation_times_.
+ std::queue<fml::TimePoint> new_times;
+ for (const auto& info : values.future_presentation_infos()) {
+ new_times.emplace(fml::TimePoint::FromEpochDelta(
+ fml::TimeDelta::FromNanoseconds(info.presentation_time())));
+ }
+ threadsafe_state_.next_presentation_times_.swap(new_times);
+
+ // Update vsync_offset_.
+ // We use modulo here because Flatland may point to the following vsync if
+ // OnNextFrameBegin() is called after the current frame's latch point.
+ auto vsync_offset =
+ (fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(
+ values.future_presentation_infos().front().presentation_time())) -
+ now) %
+ threadsafe_state_.vsync_interval_;
+ // Thread contention may result in OnNextFrameBegin() being called after the
+ // presentation time. Ignore these outliers.
+ if (vsync_offset > fml::TimeDelta::Zero()) {
+ threadsafe_state_.vsync_offset_ = vsync_offset;
+ }
+
+ // Throttle case.
+ if (threadsafe_state_.pending_fire_callback_ &&
+ threadsafe_state_.present_credits_ > 0) {
+ RunVsyncCallback(now, threadsafe_state_.pending_fire_callback_);
+ threadsafe_state_.pending_fire_callback_ = nullptr;
}
}
@@ -160,6 +194,68 @@
on_frame_presented_callback_(std::move(info));
}
+// Parses and updates next_presentation_times_.
+fml::TimePoint FlatlandConnection::GetNextPresentationTime(
+ const fml::TimePoint& now) {
+ const fml::TimePoint& cutoff =
+ now > threadsafe_state_.last_presentation_time_
+ ? now
+ : threadsafe_state_.last_presentation_time_;
+
+ // Remove presentation times that may have been passed. This may happen after
+ // a long draw call.
+ while (!threadsafe_state_.next_presentation_times_.empty() &&
+ threadsafe_state_.next_presentation_times_.front() <= cutoff) {
+ threadsafe_state_.next_presentation_times_.pop();
+ }
+
+ // Calculate a presentation time based on
+ // |threadsafe_state_.last_presentation_time_| that is later than cutoff using
+ // |vsync_interval| increments if we don't have any future presentation times
+ // left.
+ if (threadsafe_state_.next_presentation_times_.empty()) {
+ auto result = threadsafe_state_.last_presentation_time_;
+ while (result <= cutoff) {
+ result = result + threadsafe_state_.vsync_interval_;
+ }
+ return result;
+ }
+
+ // Return the next presentation time in the queue for the regular case.
+ const auto result = threadsafe_state_.next_presentation_times_.front();
+ threadsafe_state_.next_presentation_times_.pop();
+ return result;
+}
+
+// This method is called from the UI thread.
+bool FlatlandConnection::MaybeRunInitialVsyncCallback(
+ const fml::TimePoint& now,
+ FireCallbackCallback& callback) {
+ if (!threadsafe_state_.first_feedback_received_) {
+ TRACE_DURATION("flutter",
+ "FlatlandConnection::MaybeRunInitialVsyncCallback");
+ const auto frame_end = now + kInitialFlatlandVsyncOffset;
+ threadsafe_state_.last_presentation_time_ = frame_end;
+ callback(now, frame_end);
+ return true;
+ }
+ return false;
+}
+
+// This method may be called from the raster or UI thread, but it is safe
+// because VsyncWaiter posts the vsync callback on UI thread.
+void FlatlandConnection::RunVsyncCallback(const fml::TimePoint& now,
+ FireCallbackCallback& callback) {
+ const auto& frame_end = GetNextPresentationTime(now);
+ const auto& frame_start = frame_end - threadsafe_state_.vsync_offset_;
+ threadsafe_state_.last_presentation_time_ = frame_end;
+ TRACE_DURATION("flutter", "FlatlandConnection::RunVsyncCallback",
+ "frame_start_delta",
+ DeltaFromNowInNanoseconds(now, frame_start), "frame_end_delta",
+ DeltaFromNowInNanoseconds(now, frame_end));
+ callback(frame_start, frame_end);
+}
+
// This method is called from the raster thread.
void FlatlandConnection::EnqueueAcquireFence(zx::event fence) {
acquire_fences_.push_back(std::move(fence));
diff --git a/shell/platform/fuchsia/flutter/flatland_connection.h b/shell/platform/fuchsia/flutter/flatland_connection.h
index 2d894de..6b03f06 100644
--- a/shell/platform/fuchsia/flutter/flatland_connection.h
+++ b/shell/platform/fuchsia/flutter/flatland_connection.h
@@ -16,6 +16,7 @@
#include <cstdint>
#include <mutex>
+#include <queue>
#include <string>
namespace flutter_runner {
@@ -23,10 +24,10 @@
using on_frame_presented_event =
std::function<void(fuchsia::scenic::scheduling::FramePresentedInfo)>;
-// 2ms interval to target vsync is only used until Scenic sends presentation
-// feedback, or when we run out of present credits.
-static constexpr fml::TimeDelta kDefaultFlatlandPresentationInterval =
- fml::TimeDelta::FromMilliseconds(2);
+// 10ms interval to target vsync is only used until Scenic sends presentation
+// feedback.
+static constexpr fml::TimeDelta kInitialFlatlandVsyncOffset =
+ fml::TimeDelta::FromMilliseconds(10);
// The component residing on the raster thread that is responsible for
// maintaining the Flatland instance connection and presenting updates.
@@ -35,9 +36,7 @@
FlatlandConnection(std::string debug_label,
fuchsia::ui::composition::FlatlandHandle flatland,
fml::closure error_callback,
- on_frame_presented_event on_frame_presented_callback,
- uint64_t max_frames_in_flight,
- fml::TimeDelta vsync_offset);
+ on_frame_presented_event on_frame_presented_callback);
~FlatlandConnection();
@@ -70,6 +69,12 @@
void OnFramePresented(fuchsia::scenic::scheduling::FramePresentedInfo info);
void DoPresent();
+ fml::TimePoint GetNextPresentationTime(const fml::TimePoint& now);
+ bool MaybeRunInitialVsyncCallback(const fml::TimePoint& now,
+ FireCallbackCallback& callback);
+ void RunVsyncCallback(const fml::TimePoint& now,
+ FireCallbackCallback& callback);
+
fuchsia::ui::composition::FlatlandPtr flatland_;
fml::closure error_callback_;
@@ -78,7 +83,6 @@
uint64_t next_content_id_ = 0;
on_frame_presented_event on_frame_presented_callback_;
- uint32_t present_credits_ = 1;
bool present_waiting_for_credit_ = false;
// A flow event trace id for following |Flatland::Present| calls into Scenic.
@@ -89,10 +93,13 @@
// You should always lock mutex_ before touching anything in this struct
struct {
std::mutex mutex_;
- FireCallbackCallback fire_callback_;
- bool first_present_called_ = false;
- bool on_next_frame_pending_ = false;
- fml::TimePoint next_presentation_time_;
+ std::queue<fml::TimePoint> next_presentation_times_;
+ fml::TimeDelta vsync_interval_ = kInitialFlatlandVsyncOffset;
+ fml::TimeDelta vsync_offset_ = kInitialFlatlandVsyncOffset;
+ fml::TimePoint last_presentation_time_;
+ FireCallbackCallback pending_fire_callback_;
+ uint32_t present_credits_ = 1;
+ bool first_feedback_received_ = false;
} threadsafe_state_;
std::vector<zx::event> acquire_fences_;
diff --git a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc
index f0137c3..6a536d5 100644
--- a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc
+++ b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc
@@ -23,18 +23,6 @@
}
// Parse out all values we're expecting.
- if (document.HasMember("vsync_offset_in_us")) {
- auto& val = document["vsync_offset_in_us"];
- if (val.IsInt()) {
- vsync_offset_ = fml::TimeDelta::FromMicroseconds(val.GetInt());
- }
- }
- if (document.HasMember("max_frames_in_flight")) {
- auto& val = document["max_frames_in_flight"];
- if (val.IsInt()) {
- max_frames_in_flight_ = val.GetInt();
- }
- }
if (document.HasMember("intercept_all_input")) {
auto& val = document["intercept_all_input"];
if (val.IsBool()) {
diff --git a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h
index 3b6fceb..7fe73bc 100644
--- a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h
+++ b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h
@@ -7,8 +7,6 @@
#include <string>
-#include "flutter/fml/time/time_delta.h"
-
namespace flutter_runner {
class FlutterRunnerProductConfiguration {
@@ -16,8 +14,6 @@
FlutterRunnerProductConfiguration() {}
explicit FlutterRunnerProductConfiguration(std::string json_string);
- fml::TimeDelta get_vsync_offset() { return vsync_offset_; }
- uint64_t get_max_frames_in_flight() { return max_frames_in_flight_; }
bool get_intercept_all_input() { return intercept_all_input_; }
bool software_rendering() { return software_rendering_; }
bool enable_shader_warmup() { return enable_shader_warmup_; }
@@ -26,8 +22,6 @@
}
private:
- fml::TimeDelta vsync_offset_ = fml::TimeDelta::Zero();
- uint64_t max_frames_in_flight_ = 3;
bool intercept_all_input_ = false;
bool software_rendering_ = false;
bool enable_shader_warmup_ = false;
diff --git a/shell/platform/fuchsia/flutter/tests/external_view_embedder_unittests.cc b/shell/platform/fuchsia/flutter/tests/external_view_embedder_unittests.cc
index 69f0106..97581fa 100644
--- a/shell/platform/fuchsia/flutter/tests/external_view_embedder_unittests.cc
+++ b/shell/platform/fuchsia/flutter/tests/external_view_embedder_unittests.cc
@@ -414,12 +414,9 @@
const auto test_name =
::testing::UnitTest::GetInstance()->current_test_info()->name();
- const auto max_frames_in_flight = 1;
- const auto vsync_offset = fml::TimeDelta::Zero();
return std::make_shared<FlatlandConnection>(
std::move(test_name), std::move(flatland),
- /*error_callback=*/[] { FAIL(); }, /*ofpe_callback=*/[](auto...) {},
- max_frames_in_flight, vsync_offset);
+ /*error_callback=*/[] { FAIL(); }, /*ofpe_callback=*/[](auto...) {});
}
// Primary loop and subloop for the FakeFlatland instance to process its
diff --git a/shell/platform/fuchsia/flutter/tests/flatland_connection_unittests.cc b/shell/platform/fuchsia/flutter/tests/flatland_connection_unittests.cc
index 7aab547..f70afc9 100644
--- a/shell/platform/fuchsia/flutter/tests/flatland_connection_unittests.cc
+++ b/shell/platform/fuchsia/flutter/tests/flatland_connection_unittests.cc
@@ -52,11 +52,17 @@
}
std::vector<fuchsia::scenic::scheduling::PresentationInfo>
-CreateFuturePresentationInfos(int presentation_time) {
+CreateFuturePresentationInfos(const fml::TimePoint& presentation_time_1,
+ const fml::TimePoint& presentation_time_2) {
fuchsia::scenic::scheduling::PresentationInfo info_1;
- info_1.set_presentation_time(presentation_time);
+ info_1.set_presentation_time(
+ presentation_time_1.ToEpochDelta().ToNanoseconds());
std::vector<fuchsia::scenic::scheduling::PresentationInfo> infos;
infos.push_back(std::move(info_1));
+ fuchsia::scenic::scheduling::PresentationInfo info_2;
+ info_2.set_presentation_time(
+ presentation_time_2.ToEpochDelta().ToNanoseconds());
+ infos.push_back(std::move(info_2));
return infos;
}
@@ -81,15 +87,25 @@
}
// Syntactic sugar for OnNextFrameBegin
- void OnNextFrameBegin(int num_present_credits, int presentation_time = 345) {
+ void OnNextFrameBegin(int num_present_credits,
+ const fml::TimePoint& presentation_time_1,
+ const fml::TimePoint& presentation_time_2) {
fuchsia::ui::composition::OnNextFrameBeginValues on_next_frame_begin_values;
on_next_frame_begin_values.set_additional_present_credits(
num_present_credits);
on_next_frame_begin_values.set_future_presentation_infos(
- CreateFuturePresentationInfos(presentation_time));
+ CreateFuturePresentationInfos(presentation_time_1,
+ presentation_time_2));
fake_flatland().FireOnNextFrameBeginEvent(
std::move(on_next_frame_begin_values));
}
+ void OnNextFrameBegin(int num_present_credits) {
+ const auto now = fml::TimePoint::Now();
+ const auto kPresentationTime1 = now + fml::TimeDelta::FromSeconds(100);
+ const auto kPresentationTime2 = now + fml::TimeDelta::FromSeconds(200);
+ OnNextFrameBegin(num_present_credits, kPresentationTime1,
+ kPresentationTime2);
+ }
private:
async::TestLoop loop_;
@@ -106,13 +122,13 @@
const std::string debug_name = GetCurrentTestName();
flutter_runner::FlatlandConnection flatland_connection(
debug_name, TakeFlatlandHandle(), []() { FAIL(); },
- [](auto...) { FAIL(); }, 1, fml::TimeDelta::Zero());
+ [](auto...) { FAIL(); });
EXPECT_EQ(fake_flatland().debug_name(), "");
- // Simulate an AwaitVsync that comes immediately.
+ // Simulate an AwaitVsync that returns immediately.
bool await_vsync_fired = false;
AwaitVsyncChecked(flatland_connection, await_vsync_fired,
- kDefaultFlatlandPresentationInterval);
+ kInitialFlatlandVsyncOffset);
EXPECT_TRUE(await_vsync_fired);
// Ensure the debug name is set.
@@ -129,7 +145,7 @@
// completed yet.
flutter_runner::FlatlandConnection flatland_connection(
GetCurrentTestName(), TakeFlatlandHandle(), std::move(on_session_error),
- [](auto...) { FAIL(); }, 1, fml::TimeDelta::Zero());
+ [](auto...) { FAIL(); });
EXPECT_FALSE(error_fired);
// Simulate a flatland disconnection, then Pump the loop. The error callback
@@ -163,7 +179,7 @@
// completed yet.
flutter_runner::FlatlandConnection flatland_connection(
GetCurrentTestName(), TakeFlatlandHandle(), []() { FAIL(); },
- std::move(on_frame_presented), 1, fml::TimeDelta::Zero());
+ std::move(on_frame_presented));
EXPECT_EQ(presents_called, 0u);
EXPECT_EQ(vsyncs_handled, 0u);
@@ -175,7 +191,7 @@
// Simulate an AwaitVsync that comes after the first call.
bool await_vsync_fired = false;
AwaitVsyncChecked(flatland_connection, await_vsync_fired,
- kDefaultFlatlandPresentationInterval);
+ kInitialFlatlandVsyncOffset);
EXPECT_TRUE(await_vsync_fired);
// Call Present and Pump the loop; `Present` and its callback is called. No
@@ -193,17 +209,17 @@
EXPECT_FALSE(await_vsync_fired);
// Fire the `OnNextFrameBegin` event. AwaitVsync should be fired.
- const int kPresentationTime = 123;
- AwaitVsyncChecked(flatland_connection, await_vsync_fired,
- fml::TimePoint::FromEpochDelta(
- fml::TimeDelta::FromNanoseconds(kPresentationTime)));
+ const auto now = fml::TimePoint::Now();
+ const auto kPresentationTime1 = now + fml::TimeDelta::FromSeconds(100);
+ const auto kPresentationTime2 = now + fml::TimeDelta::FromSeconds(200);
fuchsia::ui::composition::OnNextFrameBeginValues on_next_frame_begin_values;
on_next_frame_begin_values.set_additional_present_credits(3);
on_next_frame_begin_values.set_future_presentation_infos(
- CreateFuturePresentationInfos(kPresentationTime));
+ CreateFuturePresentationInfos(kPresentationTime1, kPresentationTime2));
fake_flatland().FireOnNextFrameBeginEvent(
std::move(on_next_frame_begin_values));
loop().RunUntilIdle();
+ AwaitVsyncChecked(flatland_connection, await_vsync_fired, kPresentationTime1);
EXPECT_TRUE(await_vsync_fired);
// Fire the `OnFramePresented` event associated with the first `Present`,
@@ -219,9 +235,14 @@
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 2u);
EXPECT_EQ(release_fence_handle, first_release_fence_handle);
+
+ // AwaitVsync should be fired with the second present.
+ await_vsync_fired = false;
+ AwaitVsyncChecked(flatland_connection, await_vsync_fired, kPresentationTime2);
+ EXPECT_TRUE(await_vsync_fired);
}
-TEST_F(FlatlandConnectionTest, AwaitVsyncBeforePresent) {
+TEST_F(FlatlandConnectionTest, AwaitVsyncsBeforeOnNextFrameBegin) {
// Set up callbacks which allow sensing of how many presents were handled.
size_t presents_called = 0u;
fake_flatland().SetPresentHandler(
@@ -231,7 +252,7 @@
// completed yet.
flutter_runner::FlatlandConnection flatland_connection(
GetCurrentTestName(), TakeFlatlandHandle(), []() { FAIL(); },
- [](auto...) {}, 1, fml::TimeDelta::Zero());
+ [](auto...) {});
EXPECT_EQ(presents_called, 0u);
// Pump the loop. Nothing is called.
@@ -241,28 +262,20 @@
// Simulate an AwaitVsync that comes before the first Present.
bool await_vsync_callback_fired = false;
AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired,
- kDefaultFlatlandPresentationInterval);
+ kInitialFlatlandVsyncOffset);
EXPECT_TRUE(await_vsync_callback_fired);
- // Another AwaitVsync that comes before the first Present.
- await_vsync_callback_fired = false;
- AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired,
- kDefaultFlatlandPresentationInterval);
- EXPECT_TRUE(await_vsync_callback_fired);
-
- // Queue Present.
- flatland_connection.Present();
- loop().RunUntilIdle();
- EXPECT_EQ(presents_called, 1u);
-
- // Set the callback with AwaitVsync, callback should not be fired
- await_vsync_callback_fired = false;
- AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired,
- kDefaultFlatlandPresentationInterval);
- EXPECT_FALSE(await_vsync_callback_fired);
+ // AwaitVsync that comes before the first Present.
+ bool await_vsync_secondary_callback_fired = false;
+ flatland_connection.AwaitVsyncForSecondaryCallback(
+ [&await_vsync_secondary_callback_fired](fml::TimePoint frame_start,
+ fml::TimePoint frame_end) {
+ await_vsync_secondary_callback_fired = true;
+ });
+ EXPECT_TRUE(await_vsync_secondary_callback_fired);
}
-TEST_F(FlatlandConnectionTest, OutOfOrderAwait) {
+TEST_F(FlatlandConnectionTest, RunsOutOfFuturePresentationInfos) {
// Set up callbacks which allow sensing of how many presents were handled.
size_t presents_called = 0u;
fake_flatland().SetPresentHandler(
@@ -279,7 +292,7 @@
// completed yet.
flutter_runner::FlatlandConnection flatland_connection(
GetCurrentTestName(), TakeFlatlandHandle(), []() { FAIL(); },
- std::move(on_frame_presented), 1, fml::TimeDelta::Zero());
+ std::move(on_frame_presented));
EXPECT_EQ(presents_called, 0u);
EXPECT_EQ(vsyncs_handled, 0u);
@@ -291,7 +304,7 @@
// Simulate an AwaitVsync that comes before the first Present.
bool await_vsync_callback_fired = false;
AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired,
- kDefaultFlatlandPresentationInterval);
+ kInitialFlatlandVsyncOffset);
EXPECT_TRUE(await_vsync_callback_fired);
// Queue Present.
@@ -299,49 +312,30 @@
loop().RunUntilIdle();
EXPECT_EQ(presents_called, 1u);
- // Set the callback with AwaitVsync, callback should not be fired
- await_vsync_callback_fired = false;
- const int kPresentationTime1 = 567;
- AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired,
- fml::TimePoint::FromEpochDelta(
- fml::TimeDelta::FromNanoseconds(kPresentationTime1)));
- EXPECT_FALSE(await_vsync_callback_fired);
-
// Fire the `OnNextFrameBegin` event. AwaitVsync callback should be fired with
- // the given presentation time.
+ // the first presentation time.
await_vsync_callback_fired = false;
- OnNextFrameBegin(1, kPresentationTime1);
+ const auto kPresentationTime1 =
+ fml::TimePoint::Now() + fml::TimeDelta::FromSeconds(123);
+ const auto kVsyncInterval = fml::TimeDelta::FromSeconds(234);
+ const auto kPresentationTime2 = kPresentationTime1 + kVsyncInterval;
+ OnNextFrameBegin(1, kPresentationTime1, kPresentationTime2);
loop().RunUntilIdle();
+ AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired,
+ kPresentationTime1);
EXPECT_TRUE(await_vsync_callback_fired);
- // Second consecutive ONFB should not call the fire callback and should
- // instead set it to be pending to fire on next AwaitVsync
- await_vsync_callback_fired = false;
- const int kPresentationTime2 = 678;
- OnNextFrameBegin(1, kPresentationTime2);
- loop().RunUntilIdle();
- EXPECT_FALSE(await_vsync_callback_fired);
-
- // Now an AwaitVsync should immediately fire the pending callback with the
- // default presentation interval.
+ // Second consecutive AwaitVsync callback should be fired with
+ // the second presentation time.
await_vsync_callback_fired = false;
AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired,
- kDefaultFlatlandPresentationInterval);
+ kPresentationTime2);
EXPECT_TRUE(await_vsync_callback_fired);
- // With the pending callback fired, The new callback should be set for the
- // next OnNextFrameBegin to call
+ // Another AwaitVsync callback should be fired with vsync_interval.
await_vsync_callback_fired = false;
- const int kPresentationTime3 = 789;
AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired,
- fml::TimePoint::FromEpochDelta(
- fml::TimeDelta::FromNanoseconds(kPresentationTime3)));
- EXPECT_FALSE(await_vsync_callback_fired);
-
- // Now OnNextFrameBegin should fire the callback
- await_vsync_callback_fired = false;
- OnNextFrameBegin(1, kPresentationTime3);
- loop().RunUntilIdle();
+ kPresentationTime2 + kVsyncInterval);
EXPECT_TRUE(await_vsync_callback_fired);
}
@@ -371,7 +365,7 @@
on_frame_presented_event on_frame_presented = [](auto...) {};
flutter_runner::FlatlandConnection flatland_connection(
GetCurrentTestName(), TakeFlatlandHandle(), []() { FAIL(); },
- std::move(on_frame_presented), 1, fml::TimeDelta::Zero());
+ std::move(on_frame_presented));
EXPECT_EQ(num_presents_called, 0u);
// Pump the loop. Nothing is called.
diff --git a/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc b/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc
index 2e287db..51fa2a1 100644
--- a/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc
+++ b/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc
@@ -4,7 +4,6 @@
#include <gtest/gtest.h>
-#include "flutter/fml/time/time_delta.h"
#include "flutter/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h"
using namespace flutter_runner;
@@ -13,79 +12,24 @@
class FlutterRunnerProductConfigurationTest : public testing::Test {};
-TEST_F(FlutterRunnerProductConfigurationTest, ValidVsyncOffset) {
- const std::string json_string = "{ \"vsync_offset_in_us\" : 9000 } ";
- const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(9000);
-
- FlutterRunnerProductConfiguration product_config =
- FlutterRunnerProductConfiguration(json_string);
- EXPECT_EQ(product_config.get_vsync_offset(), expected_offset);
-}
-
TEST_F(FlutterRunnerProductConfigurationTest, InvalidJsonString) {
const std::string json_string = "{ \"invalid json string\" }}} ";
- const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(0);
+ const uint64_t expected_intercept_all_input = false;
FlutterRunnerProductConfiguration product_config =
FlutterRunnerProductConfiguration(json_string);
- EXPECT_EQ(product_config.get_vsync_offset(), expected_offset);
+ EXPECT_EQ(expected_intercept_all_input,
+ product_config.get_intercept_all_input());
}
TEST_F(FlutterRunnerProductConfigurationTest, EmptyJsonString) {
const std::string json_string = "";
- const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(0);
+ const uint64_t expected_intercept_all_input = false;
FlutterRunnerProductConfiguration product_config =
FlutterRunnerProductConfiguration(json_string);
- EXPECT_EQ(product_config.get_vsync_offset(), expected_offset);
-}
-
-TEST_F(FlutterRunnerProductConfigurationTest, EmptyVsyncOffset) {
- const std::string json_string = "{ \"vsync_offset_in_us\" : } ";
- const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(0);
-
- FlutterRunnerProductConfiguration product_config =
- FlutterRunnerProductConfiguration(json_string);
- EXPECT_EQ(product_config.get_vsync_offset(), expected_offset);
-}
-
-TEST_F(FlutterRunnerProductConfigurationTest, NegativeVsyncOffset) {
- const std::string json_string = "{ \"vsync_offset_in_us\" : -15410 } ";
- const fml::TimeDelta expected_offset =
- fml::TimeDelta::FromMicroseconds(-15410);
-
- FlutterRunnerProductConfiguration product_config =
- FlutterRunnerProductConfiguration(json_string);
- EXPECT_EQ(product_config.get_vsync_offset(), expected_offset);
-}
-
-TEST_F(FlutterRunnerProductConfigurationTest, NonIntegerVsyncOffset) {
- const std::string json_string = "{ \"vsync_offset_in_us\" : 3.14159 } ";
- const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(0);
-
- FlutterRunnerProductConfiguration product_config =
- FlutterRunnerProductConfiguration(json_string);
- EXPECT_EQ(product_config.get_vsync_offset(), expected_offset);
-}
-
-TEST_F(FlutterRunnerProductConfigurationTest, ValidMaxFramesInFlight) {
- const std::string json_string = "{ \"max_frames_in_flight\" : 5 } ";
- const uint64_t expected_max_frames_in_flight = 5;
-
- FlutterRunnerProductConfiguration product_config =
- FlutterRunnerProductConfiguration(json_string);
- EXPECT_EQ(product_config.get_max_frames_in_flight(),
- expected_max_frames_in_flight);
-}
-
-TEST_F(FlutterRunnerProductConfigurationTest, MissingMaxFramesInFlight) {
- const std::string json_string = "{ \"max_frames_in_flight\" : } ";
- const uint64_t minimum_reasonable_max_frames_in_flight = 1;
-
- FlutterRunnerProductConfiguration product_config =
- FlutterRunnerProductConfiguration(json_string);
- EXPECT_GE(product_config.get_max_frames_in_flight(),
- minimum_reasonable_max_frames_in_flight);
+ EXPECT_EQ(expected_intercept_all_input,
+ product_config.get_intercept_all_input());
}
TEST_F(FlutterRunnerProductConfigurationTest, ValidInterceptAllInput) {