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/fml/trace_event.h" |
8 | #include "third_party/dart/runtime/include/dart_tools_api.h" |
9 | |
10 | namespace flutter { |
11 | |
12 | namespace { |
13 | |
14 | // Wait 51 milliseconds (which is 1 more milliseconds than 3 frames at 60hz) |
15 | // before notifying the engine that we are idle. See comments in |BeginFrame| |
16 | // for further discussion on why this is necessary. |
17 | constexpr fml::TimeDelta kNotifyIdleTaskWaitTime = |
18 | fml::TimeDelta::FromMilliseconds(51); |
19 | |
20 | } // namespace |
21 | |
22 | Animator::Animator(Delegate& delegate, |
23 | TaskRunners task_runners, |
24 | std::unique_ptr<VsyncWaiter> waiter) |
25 | : delegate_(delegate), |
26 | task_runners_(std::move(task_runners)), |
27 | waiter_(std::move(waiter)), |
28 | last_frame_begin_time_(), |
29 | last_vsync_start_time_(), |
30 | last_frame_target_time_(), |
31 | dart_frame_deadline_(0), |
32 | #if FLUTTER_SHELL_ENABLE_METAL |
33 | layer_tree_pipeline_(fml::MakeRefCounted<LayerTreePipeline>(2)), |
34 | #else // FLUTTER_SHELL_ENABLE_METAL |
35 | // TODO(dnfield): We should remove this logic and set the pipeline depth |
36 | // back to 2 in this case. See |
37 | // https://github.com/flutter/engine/pull/9132 for discussion. |
38 | layer_tree_pipeline_(fml::MakeRefCounted<LayerTreePipeline>( |
39 | task_runners.GetPlatformTaskRunner() == |
40 | task_runners.GetRasterTaskRunner() |
41 | ? 1 |
42 | : 2)), |
43 | #endif // FLUTTER_SHELL_ENABLE_METAL |
44 | pending_frame_semaphore_(1), |
45 | frame_number_(1), |
46 | paused_(false), |
47 | regenerate_layer_tree_(false), |
48 | frame_scheduled_(false), |
49 | notify_idle_task_id_(0), |
50 | dimension_change_pending_(false), |
51 | weak_factory_(this) { |
52 | } |
53 | |
54 | Animator::~Animator() = default; |
55 | |
56 | float Animator::GetDisplayRefreshRate() const { |
57 | return waiter_->GetDisplayRefreshRate(); |
58 | } |
59 | |
60 | void Animator::Stop() { |
61 | paused_ = true; |
62 | } |
63 | |
64 | void Animator::Start() { |
65 | if (!paused_) { |
66 | return; |
67 | } |
68 | |
69 | paused_ = false; |
70 | RequestFrame(); |
71 | } |
72 | |
73 | // Indicate that screen dimensions will be changing in order to force rendering |
74 | // of an updated frame even if the animator is currently paused. |
75 | void Animator::SetDimensionChangePending() { |
76 | dimension_change_pending_ = true; |
77 | } |
78 | |
79 | void Animator::EnqueueTraceFlowId(uint64_t trace_flow_id) { |
80 | fml::TaskRunner::RunNowOrPostTask( |
81 | task_runners_.GetUITaskRunner(), |
82 | [self = weak_factory_.GetWeakPtr(), trace_flow_id] { |
83 | if (!self) { |
84 | return; |
85 | } |
86 | self->trace_flow_ids_.push_back(trace_flow_id); |
87 | }); |
88 | } |
89 | |
90 | // This Parity is used by the timeline component to correctly align |
91 | // GPU Workloads events with their respective Framework Workload. |
92 | const char* Animator::FrameParity() { |
93 | return (frame_number_ % 2) ? "even" : "odd" ; |
94 | } |
95 | |
96 | static int64_t FxlToDartOrEarlier(fml::TimePoint time) { |
97 | int64_t dart_now = Dart_TimelineGetMicros(); |
98 | fml::TimePoint fxl_now = fml::TimePoint::Now(); |
99 | return (time - fxl_now).ToMicroseconds() + dart_now; |
100 | } |
101 | |
102 | void Animator::BeginFrame(fml::TimePoint vsync_start_time, |
103 | fml::TimePoint frame_target_time) { |
104 | TRACE_EVENT_ASYNC_END0("flutter" , "Frame Request Pending" , frame_number_++); |
105 | |
106 | TRACE_EVENT0("flutter" , "Animator::BeginFrame" ); |
107 | while (!trace_flow_ids_.empty()) { |
108 | uint64_t trace_flow_id = trace_flow_ids_.front(); |
109 | TRACE_FLOW_END("flutter" , "PointerEvent" , trace_flow_id); |
110 | trace_flow_ids_.pop_front(); |
111 | } |
112 | |
113 | frame_scheduled_ = false; |
114 | notify_idle_task_id_++; |
115 | regenerate_layer_tree_ = false; |
116 | pending_frame_semaphore_.Signal(); |
117 | |
118 | if (!producer_continuation_) { |
119 | // We may already have a valid pipeline continuation in case a previous |
120 | // begin frame did not result in an Animation::Render. Simply reuse that |
121 | // instead of asking the pipeline for a fresh continuation. |
122 | producer_continuation_ = layer_tree_pipeline_->Produce(); |
123 | |
124 | if (!producer_continuation_) { |
125 | // If we still don't have valid continuation, the pipeline is currently |
126 | // full because the consumer is being too slow. Try again at the next |
127 | // frame interval. |
128 | RequestFrame(); |
129 | return; |
130 | } |
131 | } |
132 | |
133 | // We have acquired a valid continuation from the pipeline and are ready |
134 | // to service potential frame. |
135 | FML_DCHECK(producer_continuation_); |
136 | |
137 | last_frame_begin_time_ = fml::TimePoint::Now(); |
138 | last_vsync_start_time_ = vsync_start_time; |
139 | fml::tracing::TraceEventAsyncComplete("flutter" , "VsyncSchedulingOverhead" , |
140 | last_vsync_start_time_, |
141 | last_frame_begin_time_); |
142 | last_frame_target_time_ = frame_target_time; |
143 | dart_frame_deadline_ = FxlToDartOrEarlier(frame_target_time); |
144 | { |
145 | TRACE_EVENT2("flutter" , "Framework Workload" , "mode" , "basic" , "frame" , |
146 | FrameParity()); |
147 | delegate_.OnAnimatorBeginFrame(frame_target_time); |
148 | } |
149 | |
150 | if (!frame_scheduled_) { |
151 | // Under certain workloads (such as our parent view resizing us, which is |
152 | // communicated to us by repeat viewport metrics events), we won't |
153 | // actually have a frame scheduled yet, despite the fact that we *will* be |
154 | // producing a frame next vsync (it will be scheduled once we receive the |
155 | // viewport event). Because of this, we hold off on calling |
156 | // |OnAnimatorNotifyIdle| for a little bit, as that could cause garbage |
157 | // collection to trigger at a highly undesirable time. |
158 | task_runners_.GetUITaskRunner()->PostDelayedTask( |
159 | [self = weak_factory_.GetWeakPtr(), |
160 | notify_idle_task_id = notify_idle_task_id_]() { |
161 | if (!self.get()) { |
162 | return; |
163 | } |
164 | // If our (this task's) task id is the same as the current one |
165 | // (meaning there were no follow up frames to the |BeginFrame| call |
166 | // that posted this task) and no frame is currently scheduled, then |
167 | // assume that we are idle, and notify the engine of this. |
168 | if (notify_idle_task_id == self->notify_idle_task_id_ && |
169 | !self->frame_scheduled_) { |
170 | TRACE_EVENT0("flutter" , "BeginFrame idle callback" ); |
171 | self->delegate_.OnAnimatorNotifyIdle(Dart_TimelineGetMicros() + |
172 | 100000); |
173 | } |
174 | }, |
175 | kNotifyIdleTaskWaitTime); |
176 | } |
177 | } |
178 | |
179 | void Animator::Render(std::unique_ptr<flutter::LayerTree> layer_tree) { |
180 | if (dimension_change_pending_ && |
181 | layer_tree->frame_size() != last_layer_tree_size_) { |
182 | dimension_change_pending_ = false; |
183 | } |
184 | last_layer_tree_size_ = layer_tree->frame_size(); |
185 | |
186 | // Note the frame time for instrumentation. |
187 | layer_tree->RecordBuildTime(last_vsync_start_time_, last_frame_begin_time_, |
188 | last_frame_target_time_); |
189 | |
190 | // Commit the pending continuation. |
191 | bool result = producer_continuation_.Complete(std::move(layer_tree)); |
192 | if (!result) { |
193 | FML_DLOG(INFO) << "No pending continuation to commit" ; |
194 | } |
195 | |
196 | delegate_.OnAnimatorDraw(layer_tree_pipeline_, last_frame_target_time_); |
197 | } |
198 | |
199 | bool Animator::CanReuseLastLayerTree() { |
200 | return !regenerate_layer_tree_; |
201 | } |
202 | |
203 | void Animator::DrawLastLayerTree() { |
204 | pending_frame_semaphore_.Signal(); |
205 | delegate_.OnAnimatorDrawLastLayerTree(); |
206 | } |
207 | |
208 | void Animator::RequestFrame(bool regenerate_layer_tree) { |
209 | if (regenerate_layer_tree) { |
210 | regenerate_layer_tree_ = true; |
211 | } |
212 | if (paused_ && !dimension_change_pending_) { |
213 | return; |
214 | } |
215 | |
216 | if (!pending_frame_semaphore_.TryWait()) { |
217 | // Multiple calls to Animator::RequestFrame will still result in a |
218 | // single request to the VsyncWaiter. |
219 | return; |
220 | } |
221 | |
222 | // The AwaitVSync is going to call us back at the next VSync. However, we want |
223 | // to be reasonably certain that the UI thread is not in the middle of a |
224 | // particularly expensive callout. We post the AwaitVSync to run right after |
225 | // an idle. This does NOT provide a guarantee that the UI thread has not |
226 | // started an expensive operation right after posting this message however. |
227 | // To support that, we need edge triggered wakes on VSync. |
228 | |
229 | task_runners_.GetUITaskRunner()->PostTask([self = weak_factory_.GetWeakPtr(), |
230 | frame_number = frame_number_]() { |
231 | if (!self.get()) { |
232 | return; |
233 | } |
234 | TRACE_EVENT_ASYNC_BEGIN0("flutter" , "Frame Request Pending" , frame_number); |
235 | self->AwaitVSync(); |
236 | }); |
237 | frame_scheduled_ = true; |
238 | } |
239 | |
240 | void Animator::AwaitVSync() { |
241 | waiter_->AsyncWaitForVsync( |
242 | [self = weak_factory_.GetWeakPtr()](fml::TimePoint vsync_start_time, |
243 | fml::TimePoint frame_target_time) { |
244 | if (self) { |
245 | if (self->CanReuseLastLayerTree()) { |
246 | self->DrawLastLayerTree(); |
247 | } else { |
248 | self->BeginFrame(vsync_start_time, frame_target_time); |
249 | } |
250 | } |
251 | }); |
252 | |
253 | delegate_.OnAnimatorNotifyIdle(dart_frame_deadline_); |
254 | } |
255 | |
256 | void Animator::ScheduleSecondaryVsyncCallback(const fml::closure& callback) { |
257 | waiter_->ScheduleSecondaryCallback(callback); |
258 | } |
259 | |
260 | } // namespace flutter |
261 | |