// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter/shell/common/animator.h" #include "flutter/fml/trace_event.h" namespace flutter { namespace { // Wait 51 milliseconds (which is 1 more milliseconds than 3 frames at 60hz) // before notifying the engine that we are idle. See comments in |BeginFrame| // for further discussion on why this is necessary. constexpr fml::TimeDelta kNotifyIdleTaskWaitTime = fml::TimeDelta::FromMilliseconds(51); } // namespace Animator::Animator(Delegate& delegate, TaskRunners task_runners, std::unique_ptr waiter) : delegate_(delegate), task_runners_(std::move(task_runners)), waiter_(std::move(waiter)), last_begin_frame_time_(), dart_frame_deadline_(0), // TODO(dnfield): We should remove this logic and set the pipeline depth // back to 2 in this case. See https://github.com/flutter/engine/pull/9132 // for discussion. layer_tree_pipeline_(fml::MakeRefCounted( task_runners.GetPlatformTaskRunner() == task_runners.GetGPUTaskRunner() ? 1 : 2)), pending_frame_semaphore_(1), frame_number_(1), paused_(false), regenerate_layer_tree_(false), frame_scheduled_(false), notify_idle_task_id_(0), dimension_change_pending_(false), weak_factory_(this) {} Animator::~Animator() = default; float Animator::GetDisplayRefreshRate() const { return waiter_->GetDisplayRefreshRate(); } void Animator::Stop() { paused_ = true; } void Animator::Start() { if (!paused_) { return; } paused_ = false; RequestFrame(); } // Indicate that screen dimensions will be changing in order to force rendering // of an updated frame even if the animator is currently paused. void Animator::SetDimensionChangePending() { dimension_change_pending_ = true; } void Animator::EnqueueTraceFlowId(uint64_t trace_flow_id) { fml::TaskRunner::RunNowOrPostTask( task_runners_.GetUITaskRunner(), [self = weak_factory_.GetWeakPtr(), trace_flow_id] { if (!self) { return; } self->trace_flow_ids_.push_back(trace_flow_id); }); } // This Parity is used by the timeline component to correctly align // GPU Workloads events with their respective Framework Workload. const char* Animator::FrameParity() { return (frame_number_ % 2) ? "even" : "odd"; } static int64_t FxlToDartOrEarlier(fml::TimePoint time) { int64_t dart_now = 0; fml::TimePoint fxl_now = fml::TimePoint::Now(); return (time - fxl_now).ToMicroseconds() + dart_now; } void Animator::BeginFrame(fml::TimePoint frame_start_time, fml::TimePoint frame_target_time) { TRACE_EVENT_ASYNC_END0("flutter", "Frame Request Pending", frame_number_++); TRACE_EVENT0("flutter", "Animator::BeginFrame"); while (!trace_flow_ids_.empty()) { uint64_t trace_flow_id = trace_flow_ids_.front(); TRACE_FLOW_END("flutter", "PointerEvent", trace_flow_id); trace_flow_ids_.pop_front(); } frame_scheduled_ = false; notify_idle_task_id_++; regenerate_layer_tree_ = false; pending_frame_semaphore_.Signal(); if (!producer_continuation_) { // We may already have a valid pipeline continuation in case a previous // begin frame did not result in an Animation::Render. Simply reuse that // instead of asking the pipeline for a fresh continuation. producer_continuation_ = layer_tree_pipeline_->Produce(); if (!producer_continuation_) { // If we still don't have valid continuation, the pipeline is currently // full because the consumer is being too slow. Try again at the next // frame interval. RequestFrame(); return; } } // We have acquired a valid continuation from the pipeline and are ready // to service potential frame. FML_DCHECK(producer_continuation_); last_begin_frame_time_ = frame_start_time; dart_frame_deadline_ = FxlToDartOrEarlier(frame_target_time); { TRACE_EVENT2("flutter", "Framework Workload", "mode", "basic", "frame", FrameParity()); delegate_.OnAnimatorBeginFrame(last_begin_frame_time_); } if (!frame_scheduled_) { // Under certain workloads (such as our parent view resizing us, which is // communicated to us by repeat viewport metrics events), we won't // actually have a frame scheduled yet, despite the fact that we *will* be // producing a frame next vsync (it will be scheduled once we receive the // viewport event). Because of this, we hold off on calling // |OnAnimatorNotifyIdle| for a little bit, as that could cause garbage // collection to trigger at a highly undesirable time. task_runners_.GetUITaskRunner()->PostDelayedTask( [self = weak_factory_.GetWeakPtr(), notify_idle_task_id = notify_idle_task_id_]() { if (!self.get()) { return; } // If our (this task's) task id is the same as the current one // (meaning there were no follow up frames to the |BeginFrame| call // that posted this task) and no frame is currently scheduled, then // assume that we are idle, and notify the engine of this. if (notify_idle_task_id == self->notify_idle_task_id_ && !self->frame_scheduled_) { TRACE_EVENT0("flutter", "BeginFrame idle callback"); self->delegate_.OnAnimatorNotifyIdle(100000); } }, kNotifyIdleTaskWaitTime); } } void Animator::Render(std::unique_ptr layer_tree) { if (dimension_change_pending_ && layer_tree->frame_size() != last_layer_tree_size_) { dimension_change_pending_ = false; } last_layer_tree_size_ = layer_tree->frame_size(); if (layer_tree) { // Note the frame time for instrumentation. layer_tree->RecordBuildTime(last_begin_frame_time_); } // Commit the pending continuation. producer_continuation_.Complete(std::move(layer_tree)); delegate_.OnAnimatorDraw(layer_tree_pipeline_); } bool Animator::CanReuseLastLayerTree() { return !regenerate_layer_tree_; } void Animator::DrawLastLayerTree() { pending_frame_semaphore_.Signal(); delegate_.OnAnimatorDrawLastLayerTree(); } void Animator::RequestFrame(bool regenerate_layer_tree) { if (regenerate_layer_tree) { regenerate_layer_tree_ = true; } if (paused_ && !dimension_change_pending_) { return; } if (!pending_frame_semaphore_.TryWait()) { // Multiple calls to Animator::RequestFrame will still result in a // single request to the VsyncWaiter. return; } // The AwaitVSync is going to call us back at the next VSync. However, we want // to be reasonably certain that the UI thread is not in the middle of a // particularly expensive callout. We post the AwaitVSync to run right after // an idle. This does NOT provide a guarantee that the UI thread has not // started an expensive operation right after posting this message however. // To support that, we need edge triggered wakes on VSync. task_runners_.GetUITaskRunner()->PostTask([self = weak_factory_.GetWeakPtr(), frame_number = frame_number_]() { if (!self.get()) { return; } TRACE_EVENT_ASYNC_BEGIN0("flutter", "Frame Request Pending", frame_number); self->AwaitVSync(); }); frame_scheduled_ = true; } void Animator::AwaitVSync() { waiter_->AsyncWaitForVsync( [self = weak_factory_.GetWeakPtr()](fml::TimePoint frame_start_time, fml::TimePoint frame_target_time) { if (self) { if (self->CanReuseLastLayerTree()) { self->DrawLastLayerTree(); } else { self->BeginFrame(frame_start_time, frame_target_time); } } }); delegate_.OnAnimatorNotifyIdle(dart_frame_deadline_); } } // namespace flutter