Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 49998b9

Browse files
committed
[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 633ba42)
1 parent 749e67a commit 49998b9

8 files changed

Lines changed: 241 additions & 224 deletions

shell/platform/fuchsia/flutter/engine.cc

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,6 @@ void Engine::Initialize(
354354
view_protocols = std::move(view_protocols),
355355
request = parent_viewport_watcher.NewRequest(),
356356
view_ref_pair = std::move(view_ref_pair),
357-
max_frames_in_flight = product_config.get_max_frames_in_flight(),
358-
vsync_offset = product_config.get_vsync_offset(),
359357
software_rendering = product_config.software_rendering()]() mutable {
360358
if (software_rendering) {
361359
surface_producer_ = std::make_shared<SoftwareSurfaceProducer>();
@@ -365,8 +363,7 @@ void Engine::Initialize(
365363

366364
flatland_connection_ = std::make_shared<FlatlandConnection>(
367365
thread_label_, std::move(flatland),
368-
std::move(session_error_callback), [](auto) {},
369-
max_frames_in_flight, vsync_offset);
366+
std::move(session_error_callback), [](auto) {});
370367

371368
fuchsia::ui::views::ViewIdentityOnCreation view_identity = {
372369
.view_ref = std::move(view_ref_pair.second),

shell/platform/fuchsia/flutter/flatland_connection.cc

Lines changed: 150 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ namespace flutter_runner {
1313

1414
namespace {
1515

16-
fml::TimePoint GetNextPresentationTime(fml::TimePoint now,
17-
fml::TimePoint next_presentation_time) {
18-
return now > next_presentation_time
19-
? now + flutter_runner::kDefaultFlatlandPresentationInterval
20-
: next_presentation_time;
16+
// Helper function for traces.
17+
double DeltaFromNowInNanoseconds(const fml::TimePoint& now,
18+
const fml::TimePoint& time) {
19+
return (time - now).ToNanoseconds();
2120
}
2221

2322
} // namespace
@@ -26,9 +25,7 @@ FlatlandConnection::FlatlandConnection(
2625
std::string debug_label,
2726
fuchsia::ui::composition::FlatlandHandle flatland,
2827
fml::closure error_callback,
29-
on_frame_presented_event on_frame_presented_callback,
30-
uint64_t max_frames_in_flight,
31-
fml::TimeDelta vsync_offset)
28+
on_frame_presented_event on_frame_presented_callback)
3229
: flatland_(flatland.Bind()),
3330
error_callback_(error_callback),
3431
on_frame_presented_callback_(std::move(on_frame_presented_callback)) {
@@ -49,11 +46,9 @@ FlatlandConnection::~FlatlandConnection() = default;
4946

5047
// This method is called from the raster thread.
5148
void FlatlandConnection::Present() {
52-
if (!threadsafe_state_.first_present_called_) {
53-
std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
54-
threadsafe_state_.first_present_called_ = true;
55-
}
56-
if (present_credits_ > 0) {
49+
TRACE_DURATION("flutter", "FlatlandConnection::Present");
50+
std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
51+
if (threadsafe_state_.present_credits_ > 0) {
5752
DoPresent();
5853
} else {
5954
present_waiting_for_credit_ = true;
@@ -66,14 +61,15 @@ void FlatlandConnection::DoPresent() {
6661
TRACE_FLOW_BEGIN("gfx", "Flatland::Present", next_present_trace_id_);
6762
++next_present_trace_id_;
6863

69-
FML_CHECK(present_credits_ > 0);
70-
--present_credits_;
64+
FML_CHECK(threadsafe_state_.present_credits_ > 0);
65+
--threadsafe_state_.present_credits_;
7166

7267
fuchsia::ui::composition::PresentArgs present_args;
7368
present_args.set_requested_presentation_time(0);
7469
present_args.set_acquire_fences(std::move(acquire_fences_));
7570
present_args.set_release_fences(std::move(previous_present_release_fences_));
76-
present_args.set_unsquashable(false);
71+
// Frame rate over latency.
72+
present_args.set_unsquashable(true);
7773
flatland_->Present(std::move(present_args));
7874

7975
// In Flatland, release fences apply to the content of the previous present.
@@ -86,38 +82,44 @@ void FlatlandConnection::DoPresent() {
8682

8783
// This method is called from the UI thread.
8884
void FlatlandConnection::AwaitVsync(FireCallbackCallback callback) {
85+
TRACE_DURATION("flutter", "FlatlandConnection::AwaitVsync");
86+
8987
std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
90-
const fml::TimePoint now = fml::TimePoint::Now();
88+
threadsafe_state_.pending_fire_callback_ = nullptr;
89+
const auto now = fml::TimePoint::Now();
9190

92-
// Immediately fire callbacks until the first Present. We might receive
93-
// multiple requests for AwaitVsync() until the first Present, which relies on
94-
// receiving size on PlatformView::OnGetLayout() at an uncertain time.
95-
if (!threadsafe_state_.first_present_called_) {
96-
callback(now, now + kDefaultFlatlandPresentationInterval);
91+
// Initial case.
92+
if (MaybeRunInitialVsyncCallback(now, callback))
9793
return;
98-
}
99-
100-
threadsafe_state_.fire_callback_ = callback;
10194

102-
// Immediately fire callback if OnNextFrameBegin() is already called.
103-
if (threadsafe_state_.on_next_frame_pending_) {
104-
threadsafe_state_.fire_callback_(
105-
now, GetNextPresentationTime(
106-
now, threadsafe_state_.next_presentation_time_));
107-
threadsafe_state_.fire_callback_ = nullptr;
108-
threadsafe_state_.on_next_frame_pending_ = false;
95+
// Throttle case.
96+
if (threadsafe_state_.present_credits_ == 0) {
97+
threadsafe_state_.pending_fire_callback_ = callback;
98+
return;
10999
}
100+
101+
// Regular case.
102+
RunVsyncCallback(now, callback);
110103
}
111104

112105
// This method is called from the UI thread.
113106
void FlatlandConnection::AwaitVsyncForSecondaryCallback(
114107
FireCallbackCallback callback) {
115-
const fml::TimePoint now = fml::TimePoint::Now();
108+
TRACE_DURATION("flutter",
109+
"FlatlandConnection::AwaitVsyncForSecondaryCallback");
110+
116111
std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
117-
callback(now, GetNextPresentationTime(
118-
now, threadsafe_state_.next_presentation_time_));
112+
const auto now = fml::TimePoint::Now();
113+
114+
// Initial case.
115+
if (MaybeRunInitialVsyncCallback(now, callback))
116+
return;
117+
118+
// Regular case.
119+
RunVsyncCallback(now, callback);
119120
}
120121

122+
// This method is called from the raster thread.
121123
void FlatlandConnection::OnError(
122124
fuchsia::ui::composition::FlatlandError error) {
123125
FML_LOG(ERROR) << "Flatland error: " << static_cast<int>(error);
@@ -127,30 +129,62 @@ void FlatlandConnection::OnError(
127129
// This method is called from the raster thread.
128130
void FlatlandConnection::OnNextFrameBegin(
129131
fuchsia::ui::composition::OnNextFrameBeginValues values) {
130-
present_credits_ += values.additional_present_credits();
132+
// Collect now before locking because this is an important timing information
133+
// from Scenic.
134+
const auto now = fml::TimePoint::Now();
135+
136+
std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
137+
threadsafe_state_.first_feedback_received_ = true;
138+
threadsafe_state_.present_credits_ += values.additional_present_credits();
139+
TRACE_DURATION("flutter", "FlatlandConnection::OnNextFrameBegin",
140+
"present_credits", threadsafe_state_.present_credits_);
131141

132-
if (present_waiting_for_credit_ && present_credits_ > 0) {
142+
if (present_waiting_for_credit_ && threadsafe_state_.present_credits_ > 0) {
133143
DoPresent();
134144
present_waiting_for_credit_ = false;
135145
}
136146

137-
if (present_credits_ > 0) {
138-
FML_CHECK(values.has_future_presentation_infos() &&
139-
!values.future_presentation_infos().empty());
140-
const auto next_presentation_time =
141-
fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(
142-
values.future_presentation_infos().front().presentation_time()));
143-
144-
std::scoped_lock<std::mutex> lock(threadsafe_state_.mutex_);
145-
if (threadsafe_state_.fire_callback_) {
146-
threadsafe_state_.fire_callback_(
147-
/*frame_start=*/fml::TimePoint::Now(),
148-
/*frame_target=*/next_presentation_time);
149-
threadsafe_state_.fire_callback_ = nullptr;
150-
} else {
151-
threadsafe_state_.on_next_frame_pending_ = true;
152-
}
153-
threadsafe_state_.next_presentation_time_ = next_presentation_time;
147+
// Update vsync_interval_ by calculating the difference between the first two
148+
// presentation times. Flatland always returns >1 presentation_infos, so this
149+
// check is to guard against any changes to this assumption.
150+
if (values.has_future_presentation_infos() &&
151+
values.future_presentation_infos().size() > 1) {
152+
threadsafe_state_.vsync_interval_ = fml::TimeDelta::FromNanoseconds(
153+
values.future_presentation_infos().at(1).presentation_time() -
154+
values.future_presentation_infos().at(0).presentation_time());
155+
} else {
156+
FML_LOG(WARNING)
157+
<< "Flatland didn't send enough future_presentation_infos to update "
158+
"vsync interval.";
159+
}
160+
161+
// Update next_presentation_times_.
162+
std::queue<fml::TimePoint> new_times;
163+
for (const auto& info : values.future_presentation_infos()) {
164+
new_times.emplace(fml::TimePoint::FromEpochDelta(
165+
fml::TimeDelta::FromNanoseconds(info.presentation_time())));
166+
}
167+
threadsafe_state_.next_presentation_times_.swap(new_times);
168+
169+
// Update vsync_offset_.
170+
// We use modulo here because Flatland may point to the following vsync if
171+
// OnNextFrameBegin() is called after the current frame's latch point.
172+
auto vsync_offset =
173+
(fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(
174+
values.future_presentation_infos().front().presentation_time())) -
175+
now) %
176+
threadsafe_state_.vsync_interval_;
177+
// Thread contention may result in OnNextFrameBegin() being called after the
178+
// presentation time. Ignore these outliers.
179+
if (vsync_offset > fml::TimeDelta::Zero()) {
180+
threadsafe_state_.vsync_offset_ = vsync_offset;
181+
}
182+
183+
// Throttle case.
184+
if (threadsafe_state_.pending_fire_callback_ &&
185+
threadsafe_state_.present_credits_ > 0) {
186+
RunVsyncCallback(now, threadsafe_state_.pending_fire_callback_);
187+
threadsafe_state_.pending_fire_callback_ = nullptr;
154188
}
155189
}
156190

@@ -160,6 +194,68 @@ void FlatlandConnection::OnFramePresented(
160194
on_frame_presented_callback_(std::move(info));
161195
}
162196

197+
// Parses and updates next_presentation_times_.
198+
fml::TimePoint FlatlandConnection::GetNextPresentationTime(
199+
const fml::TimePoint& now) {
200+
const fml::TimePoint& cutoff =
201+
now > threadsafe_state_.last_presentation_time_
202+
? now
203+
: threadsafe_state_.last_presentation_time_;
204+
205+
// Remove presentation times that may have been passed. This may happen after
206+
// a long draw call.
207+
while (!threadsafe_state_.next_presentation_times_.empty() &&
208+
threadsafe_state_.next_presentation_times_.front() <= cutoff) {
209+
threadsafe_state_.next_presentation_times_.pop();
210+
}
211+
212+
// Calculate a presentation time based on
213+
// |threadsafe_state_.last_presentation_time_| that is later than cutoff using
214+
// |vsync_interval| increments if we don't have any future presentation times
215+
// left.
216+
if (threadsafe_state_.next_presentation_times_.empty()) {
217+
auto result = threadsafe_state_.last_presentation_time_;
218+
while (result <= cutoff) {
219+
result = result + threadsafe_state_.vsync_interval_;
220+
}
221+
return result;
222+
}
223+
224+
// Return the next presentation time in the queue for the regular case.
225+
const auto result = threadsafe_state_.next_presentation_times_.front();
226+
threadsafe_state_.next_presentation_times_.pop();
227+
return result;
228+
}
229+
230+
// This method is called from the UI thread.
231+
bool FlatlandConnection::MaybeRunInitialVsyncCallback(
232+
const fml::TimePoint& now,
233+
FireCallbackCallback& callback) {
234+
if (!threadsafe_state_.first_feedback_received_) {
235+
TRACE_DURATION("flutter",
236+
"FlatlandConnection::MaybeRunInitialVsyncCallback");
237+
const auto frame_end = now + kInitialFlatlandVsyncOffset;
238+
threadsafe_state_.last_presentation_time_ = frame_end;
239+
callback(now, frame_end);
240+
return true;
241+
}
242+
return false;
243+
}
244+
245+
// This method may be called from the raster or UI thread, but it is safe
246+
// because VsyncWaiter posts the vsync callback on UI thread.
247+
void FlatlandConnection::RunVsyncCallback(const fml::TimePoint& now,
248+
FireCallbackCallback& callback) {
249+
const auto& frame_end = GetNextPresentationTime(now);
250+
const auto& frame_start = frame_end - threadsafe_state_.vsync_offset_;
251+
threadsafe_state_.last_presentation_time_ = frame_end;
252+
TRACE_DURATION("flutter", "FlatlandConnection::RunVsyncCallback",
253+
"frame_start_delta",
254+
DeltaFromNowInNanoseconds(now, frame_start), "frame_end_delta",
255+
DeltaFromNowInNanoseconds(now, frame_end));
256+
callback(frame_start, frame_end);
257+
}
258+
163259
// This method is called from the raster thread.
164260
void FlatlandConnection::EnqueueAcquireFence(zx::event fence) {
165261
acquire_fences_.push_back(std::move(fence));

shell/platform/fuchsia/flutter/flatland_connection.h

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@
1616

1717
#include <cstdint>
1818
#include <mutex>
19+
#include <queue>
1920
#include <string>
2021

2122
namespace flutter_runner {
2223

2324
using on_frame_presented_event =
2425
std::function<void(fuchsia::scenic::scheduling::FramePresentedInfo)>;
2526

26-
// 2ms interval to target vsync is only used until Scenic sends presentation
27-
// feedback, or when we run out of present credits.
28-
static constexpr fml::TimeDelta kDefaultFlatlandPresentationInterval =
29-
fml::TimeDelta::FromMilliseconds(2);
27+
// 10ms interval to target vsync is only used until Scenic sends presentation
28+
// feedback.
29+
static constexpr fml::TimeDelta kInitialFlatlandVsyncOffset =
30+
fml::TimeDelta::FromMilliseconds(10);
3031

3132
// The component residing on the raster thread that is responsible for
3233
// maintaining the Flatland instance connection and presenting updates.
@@ -35,9 +36,7 @@ class FlatlandConnection final {
3536
FlatlandConnection(std::string debug_label,
3637
fuchsia::ui::composition::FlatlandHandle flatland,
3738
fml::closure error_callback,
38-
on_frame_presented_event on_frame_presented_callback,
39-
uint64_t max_frames_in_flight,
40-
fml::TimeDelta vsync_offset);
39+
on_frame_presented_event on_frame_presented_callback);
4140

4241
~FlatlandConnection();
4342

@@ -70,6 +69,12 @@ class FlatlandConnection final {
7069
void OnFramePresented(fuchsia::scenic::scheduling::FramePresentedInfo info);
7170
void DoPresent();
7271

72+
fml::TimePoint GetNextPresentationTime(const fml::TimePoint& now);
73+
bool MaybeRunInitialVsyncCallback(const fml::TimePoint& now,
74+
FireCallbackCallback& callback);
75+
void RunVsyncCallback(const fml::TimePoint& now,
76+
FireCallbackCallback& callback);
77+
7378
fuchsia::ui::composition::FlatlandPtr flatland_;
7479

7580
fml::closure error_callback_;
@@ -78,7 +83,6 @@ class FlatlandConnection final {
7883
uint64_t next_content_id_ = 0;
7984

8085
on_frame_presented_event on_frame_presented_callback_;
81-
uint32_t present_credits_ = 1;
8286
bool present_waiting_for_credit_ = false;
8387

8488
// A flow event trace id for following |Flatland::Present| calls into Scenic.
@@ -89,10 +93,13 @@ class FlatlandConnection final {
8993
// You should always lock mutex_ before touching anything in this struct
9094
struct {
9195
std::mutex mutex_;
92-
FireCallbackCallback fire_callback_;
93-
bool first_present_called_ = false;
94-
bool on_next_frame_pending_ = false;
95-
fml::TimePoint next_presentation_time_;
96+
std::queue<fml::TimePoint> next_presentation_times_;
97+
fml::TimeDelta vsync_interval_ = kInitialFlatlandVsyncOffset;
98+
fml::TimeDelta vsync_offset_ = kInitialFlatlandVsyncOffset;
99+
fml::TimePoint last_presentation_time_;
100+
FireCallbackCallback pending_fire_callback_;
101+
uint32_t present_credits_ = 1;
102+
bool first_feedback_received_ = false;
96103
} threadsafe_state_;
97104

98105
std::vector<zx::event> acquire_fences_;

shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,6 @@ FlutterRunnerProductConfiguration::FlutterRunnerProductConfiguration(
2323
}
2424

2525
// Parse out all values we're expecting.
26-
if (document.HasMember("vsync_offset_in_us")) {
27-
auto& val = document["vsync_offset_in_us"];
28-
if (val.IsInt()) {
29-
vsync_offset_ = fml::TimeDelta::FromMicroseconds(val.GetInt());
30-
}
31-
}
32-
if (document.HasMember("max_frames_in_flight")) {
33-
auto& val = document["max_frames_in_flight"];
34-
if (val.IsInt()) {
35-
max_frames_in_flight_ = val.GetInt();
36-
}
37-
}
3826
if (document.HasMember("intercept_all_input")) {
3927
auto& val = document["intercept_all_input"];
4028
if (val.IsBool()) {

0 commit comments

Comments
 (0)