1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #include "flutter/shell/common/animator.h" |
6 | |
7 | #include "flutter/flow/frame_timings.h" |
8 | #include "flutter/fml/time/time_point.h" |
9 | #include "flutter/fml/trace_event.h" |
10 | #include "third_party/dart/runtime/include/dart_tools_api.h" |
11 | |
12 | namespace flutter { |
13 | |
14 | namespace { |
15 | |
16 | // Wait 51 milliseconds (which is 1 more milliseconds than 3 frames at 60hz) |
17 | // before notifying the engine that we are idle. See comments in |BeginFrame| |
18 | // for further discussion on why this is necessary. |
19 | constexpr fml::TimeDelta kNotifyIdleTaskWaitTime = |
20 | fml::TimeDelta::FromMilliseconds(millis: 51); |
21 | |
22 | } // namespace |
23 | |
24 | Animator::Animator(Delegate& delegate, |
25 | const TaskRunners& task_runners, |
26 | std::unique_ptr<VsyncWaiter> waiter) |
27 | : delegate_(delegate), |
28 | task_runners_(task_runners), |
29 | waiter_(std::move(waiter)), |
30 | #if SHELL_ENABLE_METAL |
31 | layer_tree_pipeline_(std::make_shared<LayerTreePipeline>(2)), |
32 | #else // SHELL_ENABLE_METAL |
33 | // TODO(dnfield): We should remove this logic and set the pipeline depth |
34 | // back to 2 in this case. See |
35 | // https://github.com/flutter/engine/pull/9132 for discussion. |
36 | layer_tree_pipeline_(std::make_shared<LayerTreePipeline>( |
37 | args: task_runners.GetPlatformTaskRunner() == |
38 | task_runners.GetRasterTaskRunner() |
39 | ? 1 |
40 | : 2)), |
41 | #endif // SHELL_ENABLE_METAL |
42 | pending_frame_semaphore_(1), |
43 | weak_factory_(this) { |
44 | } |
45 | |
46 | Animator::~Animator() = default; |
47 | |
48 | void Animator::EnqueueTraceFlowId(uint64_t trace_flow_id) { |
49 | fml::TaskRunner::RunNowOrPostTask( |
50 | runner: task_runners_.GetUITaskRunner(), |
51 | task: [self = weak_factory_.GetWeakPtr(), trace_flow_id] { |
52 | if (!self) { |
53 | return; |
54 | } |
55 | self->trace_flow_ids_.push_back(v: trace_flow_id); |
56 | self->ScheduleMaybeClearTraceFlowIds(); |
57 | }); |
58 | } |
59 | |
60 | void Animator::BeginFrame( |
61 | std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) { |
62 | TRACE_EVENT_ASYNC_END0("flutter" , "Frame Request Pending" , |
63 | frame_request_number_); |
64 | frame_request_number_++; |
65 | |
66 | frame_timings_recorder_ = std::move(frame_timings_recorder); |
67 | frame_timings_recorder_->RecordBuildStart(build_start: fml::TimePoint::Now()); |
68 | |
69 | size_t flow_id_count = trace_flow_ids_.size(); |
70 | std::unique_ptr<uint64_t[]> flow_ids = |
71 | std::make_unique<uint64_t[]>(n: flow_id_count); |
72 | for (size_t i = 0; i < flow_id_count; ++i) { |
73 | flow_ids.get()[i] = trace_flow_ids_.at(i: i); |
74 | } |
75 | |
76 | TRACE_EVENT_WITH_FRAME_NUMBER(frame_timings_recorder_, "flutter" , |
77 | "Animator::BeginFrame" , flow_id_count, |
78 | flow_ids.get()); |
79 | |
80 | while (!trace_flow_ids_.empty()) { |
81 | uint64_t trace_flow_id = trace_flow_ids_.front(); |
82 | TRACE_FLOW_END("flutter" , "PointerEvent" , trace_flow_id); |
83 | trace_flow_ids_.pop_front(); |
84 | } |
85 | |
86 | frame_scheduled_ = false; |
87 | regenerate_layer_tree_ = false; |
88 | pending_frame_semaphore_.Signal(); |
89 | |
90 | if (!producer_continuation_) { |
91 | // We may already have a valid pipeline continuation in case a previous |
92 | // begin frame did not result in an Animator::Render. Simply reuse that |
93 | // instead of asking the pipeline for a fresh continuation. |
94 | producer_continuation_ = layer_tree_pipeline_->Produce(); |
95 | |
96 | if (!producer_continuation_) { |
97 | // If we still don't have valid continuation, the pipeline is currently |
98 | // full because the consumer is being too slow. Try again at the next |
99 | // frame interval. |
100 | TRACE_EVENT0("flutter" , "PipelineFull" ); |
101 | RequestFrame(); |
102 | return; |
103 | } |
104 | } |
105 | |
106 | // We have acquired a valid continuation from the pipeline and are ready |
107 | // to service potential frame. |
108 | FML_DCHECK(producer_continuation_); |
109 | const fml::TimePoint frame_target_time = |
110 | frame_timings_recorder_->GetVsyncTargetTime(); |
111 | dart_frame_deadline_ = frame_target_time.ToEpochDelta(); |
112 | uint64_t frame_number = frame_timings_recorder_->GetFrameNumber(); |
113 | delegate_.OnAnimatorBeginFrame(frame_target_time, frame_number); |
114 | |
115 | if (!frame_scheduled_ && has_rendered_) { |
116 | // Wait a tad more than 3 60hz frames before reporting a big idle period. |
117 | // This is a heuristic that is meant to avoid giving false positives to the |
118 | // VM when we are about to schedule a frame in the next vsync, the idea |
119 | // being that if there have been three vsyncs with no frames it's a good |
120 | // time to start doing GC work. |
121 | task_runners_.GetUITaskRunner()->PostDelayedTask( |
122 | task: [self = weak_factory_.GetWeakPtr()]() { |
123 | if (!self) { |
124 | return; |
125 | } |
126 | auto now = fml::TimeDelta::FromMicroseconds(micros: Dart_TimelineGetMicros()); |
127 | // If there's a frame scheduled, bail. |
128 | // If there's no frame scheduled, but we're not yet past the last |
129 | // vsync deadline, bail. |
130 | if (!self->frame_scheduled_ && now > self->dart_frame_deadline_) { |
131 | TRACE_EVENT0("flutter" , "BeginFrame idle callback" ); |
132 | self->delegate_.OnAnimatorNotifyIdle( |
133 | deadline: now + fml::TimeDelta::FromMilliseconds(millis: 100)); |
134 | } |
135 | }, |
136 | delay: kNotifyIdleTaskWaitTime); |
137 | } |
138 | } |
139 | |
140 | void Animator::Render(std::unique_ptr<flutter::LayerTree> layer_tree, |
141 | float device_pixel_ratio) { |
142 | has_rendered_ = true; |
143 | last_layer_tree_size_ = layer_tree->frame_size(); |
144 | |
145 | if (!frame_timings_recorder_) { |
146 | // Framework can directly call render with a built scene. |
147 | frame_timings_recorder_ = std::make_unique<FrameTimingsRecorder>(); |
148 | const fml::TimePoint placeholder_time = fml::TimePoint::Now(); |
149 | frame_timings_recorder_->RecordVsync(vsync_start: placeholder_time, vsync_target: placeholder_time); |
150 | frame_timings_recorder_->RecordBuildStart(build_start: placeholder_time); |
151 | } |
152 | |
153 | TRACE_EVENT_WITH_FRAME_NUMBER(frame_timings_recorder_, "flutter" , |
154 | "Animator::Render" , /*flow_id_count=*/0, |
155 | /*flow_ids=*/nullptr); |
156 | frame_timings_recorder_->RecordBuildEnd(build_end: fml::TimePoint::Now()); |
157 | |
158 | delegate_.OnAnimatorUpdateLatestFrameTargetTime( |
159 | frame_target_time: frame_timings_recorder_->GetVsyncTargetTime()); |
160 | |
161 | auto layer_tree_item = std::make_unique<LayerTreeItem>( |
162 | args: std::move(layer_tree), args: std::move(frame_timings_recorder_), |
163 | args&: device_pixel_ratio); |
164 | // Commit the pending continuation. |
165 | PipelineProduceResult result = |
166 | producer_continuation_.Complete(resource: std::move(layer_tree_item)); |
167 | |
168 | if (!result.success) { |
169 | FML_DLOG(INFO) << "No pending continuation to commit" ; |
170 | return; |
171 | } |
172 | |
173 | if (!result.is_first_item) { |
174 | // It has been successfully pushed to the pipeline but not as the first |
175 | // item. Eventually the 'Rasterizer' will consume it, so we don't need to |
176 | // notify the delegate. |
177 | return; |
178 | } |
179 | |
180 | delegate_.OnAnimatorDraw(pipeline: layer_tree_pipeline_); |
181 | } |
182 | |
183 | const std::weak_ptr<VsyncWaiter> Animator::GetVsyncWaiter() const { |
184 | std::weak_ptr<VsyncWaiter> weak = waiter_; |
185 | return weak; |
186 | } |
187 | |
188 | bool Animator::CanReuseLastLayerTree() { |
189 | return !regenerate_layer_tree_; |
190 | } |
191 | |
192 | void Animator::DrawLastLayerTree( |
193 | std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) { |
194 | // This method is very cheap, but this makes it explicitly clear in trace |
195 | // files. |
196 | TRACE_EVENT0("flutter" , "Animator::DrawLastLayerTree" ); |
197 | |
198 | pending_frame_semaphore_.Signal(); |
199 | // In this case BeginFrame doesn't get called, we need to |
200 | // adjust frame timings to update build start and end times, |
201 | // given that the frame doesn't get built in this case, we |
202 | // will use Now() for both start and end times as an indication. |
203 | const auto now = fml::TimePoint::Now(); |
204 | frame_timings_recorder->RecordBuildStart(build_start: now); |
205 | frame_timings_recorder->RecordBuildEnd(build_end: now); |
206 | delegate_.OnAnimatorDrawLastLayerTree(frame_timings_recorder: std::move(frame_timings_recorder)); |
207 | } |
208 | |
209 | void Animator::RequestFrame(bool regenerate_layer_tree) { |
210 | if (regenerate_layer_tree) { |
211 | // This event will be closed by BeginFrame. BeginFrame will only be called |
212 | // if regenerating the layer tree. If a frame has been requested to update |
213 | // an external texture, this will be false and no BeginFrame call will |
214 | // happen. |
215 | TRACE_EVENT_ASYNC_BEGIN0("flutter" , "Frame Request Pending" , |
216 | frame_request_number_); |
217 | regenerate_layer_tree_ = true; |
218 | } |
219 | |
220 | if (!pending_frame_semaphore_.TryWait()) { |
221 | // Multiple calls to Animator::RequestFrame will still result in a |
222 | // single request to the VsyncWaiter. |
223 | return; |
224 | } |
225 | |
226 | // The AwaitVSync is going to call us back at the next VSync. However, we want |
227 | // to be reasonably certain that the UI thread is not in the middle of a |
228 | // particularly expensive callout. We post the AwaitVSync to run right after |
229 | // an idle. This does NOT provide a guarantee that the UI thread has not |
230 | // started an expensive operation right after posting this message however. |
231 | // To support that, we need edge triggered wakes on VSync. |
232 | |
233 | task_runners_.GetUITaskRunner()->PostTask( |
234 | task: [self = weak_factory_.GetWeakPtr()]() { |
235 | if (!self) { |
236 | return; |
237 | } |
238 | self->AwaitVSync(); |
239 | }); |
240 | frame_scheduled_ = true; |
241 | } |
242 | |
243 | void Animator::AwaitVSync() { |
244 | waiter_->AsyncWaitForVsync( |
245 | callback: [self = weak_factory_.GetWeakPtr()]( |
246 | std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) { |
247 | if (self) { |
248 | if (self->CanReuseLastLayerTree()) { |
249 | self->DrawLastLayerTree(frame_timings_recorder: std::move(frame_timings_recorder)); |
250 | } else { |
251 | self->BeginFrame(frame_timings_recorder: std::move(frame_timings_recorder)); |
252 | } |
253 | } |
254 | }); |
255 | if (has_rendered_) { |
256 | delegate_.OnAnimatorNotifyIdle(deadline: dart_frame_deadline_); |
257 | } |
258 | } |
259 | |
260 | void Animator::ScheduleSecondaryVsyncCallback(uintptr_t id, |
261 | const fml::closure& callback) { |
262 | waiter_->ScheduleSecondaryCallback(id, callback); |
263 | } |
264 | |
265 | void Animator::ScheduleMaybeClearTraceFlowIds() { |
266 | waiter_->ScheduleSecondaryCallback( |
267 | id: reinterpret_cast<uintptr_t>(this), callback: [self = weak_factory_.GetWeakPtr()] { |
268 | if (!self) { |
269 | return; |
270 | } |
271 | if (!self->frame_scheduled_ && !self->trace_flow_ids_.empty()) { |
272 | size_t flow_id_count = self->trace_flow_ids_.size(); |
273 | std::unique_ptr<uint64_t[]> flow_ids = |
274 | std::make_unique<uint64_t[]>(n: flow_id_count); |
275 | for (size_t i = 0; i < flow_id_count; ++i) { |
276 | flow_ids.get()[i] = self->trace_flow_ids_.at(i: i); |
277 | } |
278 | |
279 | TRACE_EVENT0_WITH_FLOW_IDS( |
280 | "flutter" , "Animator::ScheduleMaybeClearTraceFlowIds - callback" , |
281 | flow_id_count, flow_ids.get()); |
282 | |
283 | while (!self->trace_flow_ids_.empty()) { |
284 | auto flow_id = self->trace_flow_ids_.front(); |
285 | TRACE_FLOW_END("flutter" , "PointerEvent" , flow_id); |
286 | self->trace_flow_ids_.pop_front(); |
287 | } |
288 | } |
289 | }); |
290 | } |
291 | |
292 | } // namespace flutter |
293 | |