@@ -13,11 +13,10 @@ namespace flutter_runner {
1313
1414namespace {
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.
5148void 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.
8884void 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.
113106void 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.
121123void 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.
128130void 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.
164260void FlatlandConnection::EnqueueAcquireFence (zx::event fence) {
165261 acquire_fences_.push_back (std::move (fence));
0 commit comments