| 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 | |