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
10namespace flutter {
11
12namespace {
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.
17constexpr fml::TimeDelta kNotifyIdleTaskWaitTime =
18 fml::TimeDelta::FromMilliseconds(51);
19
20} // namespace
21
22Animator::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
54Animator::~Animator() = default;
55
56float Animator::GetDisplayRefreshRate() const {
57 return waiter_->GetDisplayRefreshRate();
58}
59
60void Animator::Stop() {
61 paused_ = true;
62}
63
64void 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.
75void Animator::SetDimensionChangePending() {
76 dimension_change_pending_ = true;
77}
78
79void 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.
92const char* Animator::FrameParity() {
93 return (frame_number_ % 2) ? "even" : "odd";
94}
95
96static 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
102void 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
179void 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
199bool Animator::CanReuseLastLayerTree() {
200 return !regenerate_layer_tree_;
201}
202
203void Animator::DrawLastLayerTree() {
204 pending_frame_semaphore_.Signal();
205 delegate_.OnAnimatorDrawLastLayerTree();
206}
207
208void 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
240void 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
256void Animator::ScheduleSecondaryVsyncCallback(const fml::closure& callback) {
257 waiter_->ScheduleSecondaryCallback(callback);
258}
259
260} // namespace flutter
261