Vsync 机制
在分析首帧渲染的过程中,可以发现Render Tree
的渲染逻辑(handleDrawFrame
方法)是直接执行的,但是后续每一帧的渲染都是Framework的主动调用导致的吗?实际上并非如此,也不能如此。试想一下,如果由Framework层控制每一帧的渲染,那么可能某一帧还没渲染完成,屏幕就开始刷新了,因为屏幕是按照自己的固有频率刷新的,而不会考虑具体的软件逻辑。此时,可能用于渲染的Buffer
中,一半是当前帧的数据,一半是上一帧的数据,这就是所谓的“撕裂”(Tearing),如图5-9所示。
为了避免撕裂,大部分UI框架都会引入Vsync机制,Vsync是垂直同步(Vertical Synchronization)的简称,其基本的思路是同步帧的渲染和显示器的刷新率。下面开始分析Flutter的Vsync机制。
Vsync 准备阶段
当UI需要更新一帧(通常是由于动画、手势或者直接调用setState
导致Element Tree
中出现脏节点)时,会调用ensureVisualUpdate
方法, 如果没有处于渲染状态,将调用scheduleFrame
方法。ensureVisualUpdate
代码如下:
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
scheduleFrame
代码如下:
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled) return;
ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}
其中ensureFrameCallbacksRegistered
方法代码如下:
// 代码清单5-25 flutter/packages/flutter/lib/src/scheduler/binding.dart
void ensureFrameCallbacksRegistered() {
window.onBeginFrame ??= _handleBeginFrame; // 由代码清单5-35调用
window.onDrawFrame ??= _handleDrawFrame; // 由代码清单5-35调用
}
void _handleBeginFrame(Duration rawTimeStamp) {
if (_warmUpFrame) {
// 首帧仍在渲染,见代码清单5-21
_rescheduleAfterWarmUpFrame = true;
return;
}
handleBeginFrame(rawTimeStamp); // 见代码清单5-36
}
void _handleDrawFrame() {
if (_rescheduleAfterWarmUpFrame) {
// 首帧预渲染导致的调用
_rescheduleAfterWarmUpFrame = false;
addPostFrameCallback((Duration timeStamp) {
_hasScheduledFrame = false;
scheduleFrame(); // 见代码清单5-20
});
return;
}
handleDrawFrame(); // 见代码清单5-37
}
window.onBeginFrame
和window.onDrawFrame
将在注册的Vsync信号到达后调用,这部分内容后面将详细分析,其对应的接口分别调用了handleBeginFrame
方法和handleDrawFrame
方法,后面将详细分析其逻辑。
需要注意的是,如果_warmUpFrame
字段为true
,即通过Vsync
驱动的渲染开始时发现首帧渲染仍在进行,则将_rescheduleAfterWarmUpFrame
标记为true
,并在_handleDrawFrame
中注册一个回调后退出,该回调将在首帧渲染后请求再次渲染一帧, 而这一帧将是通过Vsync
信号驱动的。
下面分析window.scheduleFrame
接口的逻辑,其对应的是一个Engine方法,如代码清单5-26所示。
// 代码清单5-26 engine/lib/ui/window/platform_configuration.cc
void ScheduleFrame(Dart_NativeArguments args) {
UIDartState::ThrowIfUIOperationsProhibited(); // 确保在UI线程中
UIDartState::Current()->platform_configuration()->client()->ScheduleFrame(); // 见代码清单5-27
} // RuntimeController是client的具体实现类
以上逻辑主要检查当前是否处于UI线程,然后调用RuntimeController
的ScheduleFrame
方法,最终会调用Animator
的RequestFrame
方法,如代码清单5-27所示。
// 代码清单5-27 engine/shell/common/animator.cc
void Animator::RequestFrame(bool regenerate_layer_tree) {
if (regenerate_layer_tree) {
// 仅有Platform View更新,复用上一帧的Layer Tree
regenerate_layer_tree_ = true; // 如果Animator已停止,则返回,但是如果屏幕配置发生改变(即第2个字段为true)
}
if (paused_ && !dimension_change_pending_) {
return; } // 仍会请求Vsync信号
if (!pending_frame_semaphore_.TryWait()) {
return; } // 已经有正在渲染的帧,返回
// 见代码清单5-34
task_runners_.GetUITaskRunner()->PostTask([ ...... ]() {
if (!self) {
return; }
self->AwaitVSync(); // 见代码清单5-28
});
frame_scheduled_ = true; // 注意,比上一句逻辑先执行
}
以上逻辑中,regenerate_layer_tree
用于表示是否重新生成Flutter的渲染数据,一般情况下为true
,仅当UI中存在Platform View
且只有该部分需要更新时才为false
。接着检查当前是否可以注册Vsync,并通过AwaitVSync
发起注册。由于是PostTask
方式,因此frame_scheduled
会在此之前标记为true
,表示当前正在计划渲染一帧。AwaitVSync
方法的逻辑如代码清单5-28所示。
// 代码清单5-28 engine/shell/common/animator.cc
void Animator::AwaitVSync() {
waiter_->AsyncWaitForVsync( // Vsync信号到达后将触发的逻辑
[self = weak_factory_.GetWeakPtr()](fml::TimePoint vsync_start_time, // Vsync信号到达的时间
fml::TimePoint frame_target_time) {
// 根据帧率计算的一帧绘制完成的最晚的时间点
if (self) {
// 见代码清单5-33
if (self->CanReuseLastLayerTree()) {
// 可以复用上一帧的Layer Tree
self->DrawLastLayerTree();
} else {
// 开始渲染新的一帧
self->BeginFrame(vsync_start_time, frame_target_time);
}
}
}); // 通知Dart VM:当前处于等待Vsync的空闲状态,非常适合进行GC等行为
delegate_.OnAnimatorNotifyIdle(dart_frame_deadline_);
}
以上逻辑中,OnAnimatorNotifyIdle
方法将通知 Dart VM 当前处于空闲状态,用于驱动 GC(Garbage Collection,垃圾回收)等逻辑的执行,因为当前将要注册Vsync并等待其信号,所以肯定不会更新UI,非常适合进行GC等行为。AsyncWaitForVsync
方法负责Vsync监听的注册,下面进行分析。
Vsync 注册阶段
代码清单5-28中,AsyncWaitForVsync
方法将继续Vsync
信号的注册,其逻辑如代码清单5-29所示。
// 代码清单5-29 engine/shell/common/vsync_waiter.cc
void VsyncWaiter::AsyncWaitForVsync(const Callback& callback) {
if (!callback) {
return; // 若没有设置回调,则监听没有意义,直接返回
}
TRACE_EVENT0("flutter", "AsyncWaitForVsync");
{
std::scoped_lock lock(callback_mutex_);
if (callback_) {
return; } // 说明有其他逻辑注册过,直接返回
callback_ = std::move(callback); // 赋值
if (secondary_callback_) {
return; } // 说明有其他逻辑注册过,无须再次注册,返回
}
AwaitVSync(); // 具体的实现由平台决定,Android平台的实现见代码清单5-30
}
以上逻辑中,callback
是必须携带的参数,因为没有回调的注册没有意义。接着,会检查callback_
字段是否已经被设置,如果有则说明其他逻辑已经注册过了,直接返回;如果为null
,则赋值为当前参数。注意,这里会接着检查secondary_callback_
是否有值,其一般由触摸事件触发,其赋值时会触发AwaitVSync
,所以此时callback_
只需要完成赋值即可,而无须重复注册Vsync
信号。以上两个回调会在后面Vsync
信号到达时一并处理。
完成以上逻辑后,将调用AwaitVSync
方法开始正式注册Vsync
信号,如代码清单5-30所示。
// 代码清单5-30 engine/shell/platform/android/vsync_waiter_android.cc
void VsyncWaiterAndroid::AwaitVSync() {
auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
jlong java_baton = reinterpret_cast<jlong>(weak_this); // 将当前实例变成long类型的id
task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {
// 切换线程
JNIEnv* env = fml::jni::AttachCurrentThread(); // 确保JNIEnv已准备完毕
env->CallStaticVoidMethod(g_vsync_waiter_class->obj(), // Embedder中的FlutterJNI实例
g_async_wait_for_vsync_method_, // 对应的Embedder方法
java_baton); // Vsync到达后通过该参数调用本对象
});
}
以上逻辑将在Java侧调用(在Platform
线程中),这是因为NDK中没有监听硬件信号Vsync
的API,而Android SDK中有。注意,java_baton
是当前对象的弱引用指针,将用于Vsync
信号到达时触发对应的回调。
该逻辑将调用Java侧的FlutterJNI
对象的asyncWaitForVsync
方法,最终将调用AsyncWaitForVsyncDelegate
的asyncWaitForVsync
方法。由代码清单4-3可知,该对象在启动时已经完成注册,具体逻辑如代码清单5-31所示。
// 代码清单5-31 engine/shell/platform/android/io/flutter/view/VsyncWaiter.java
private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate =
new FlutterJNI.AsyncWaitForVsyncDelegate() {
public void asyncWaitForVsync(long cookie) {
// cookie即代码清单5-30中的java_baton
Choreographer.getInstance().postFrameCallback( // 为下一个Vsync信号注册回调
new Choreographer.FrameCallback() {
public void doFrame(long frameTimeNanos) {
// Vsync到达时触发
float fps = windowManager.getDefaultDisplay().getRefreshRate(); // 设备帧率
long refreshPeriodNanos = (long) (1000000000.0 / fps); // 渲染一帧的最大耗时
FlutterJNI.nativeOnVsync( // 调用Engine的方法,见代码清单5-32
frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
}
}
);
}
};
以上逻辑主要是调用系统API,该API将在下一个Vsync
信号到达时调用doFrame
方法,其中,由frameTimeNanos
表示的Vsync
信号到达的时间会稍稍早于该回调发生的时间,因为从Vsync
信号到达到doFrame
被调用,中间必然有一些逻辑耗时。接着通过系统API获取当前设备的帧率,并计算绘制一帧所需要的时间,对fps
为60
的设备而言,绘制一帧需要16.6ms
。最后将通过FlutterJNI
调用Native
方法,开始进行Vsync
的响应,其中,第1个参数是Vsync
信号到达时间;第2个参数表示当前帧最晚完成绘制的时间,它将贯穿整个渲染流程;第3个参数表示Flutter Engine中响应该信号的对象的指针。
Vsync 响应阶段
下面开始分析nativeOnVsync
方法对应的Engine中的逻辑,如代码清单5-32所示。
// 代码清单5-32 engine/shell/platform/android/vsync_waiter_android.cc
void VsyncWaiterAndroid::OnNativeVsync( ...... ) {
TRACE_EVENT0("flutter", "VSYNC");
auto frame_time = fml::TimePoint::FromEpochDelta( // 时间格式转换
fml::TimeDelta::FromNanoseconds(frameTimeNanos));
auto target_time = fml::TimePoint::FromEpochDelta(
fml::TimeDelta::FromNanoseconds(frameTargetTimeNanos));
ConsumePendingCallback(java_baton, frame_time, target_time);
}
void VsyncWaiterAndroid::ConsumePendingCallback( ...... ) {
auto* weak_this = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(java_baton);
auto shared_this = weak_this->lock(); // 获取代码清单5-30中发起监听的VsyncWaiter实例
delete weak_this;
if (shared_this) {
// 触发回调,以上由具体平台实现,以下是VsyncWaiter通用逻辑
shared_this->FireCallback(frame_start_time, frame_target_time);
}
}
以上逻辑首先提取frame_time
和target_time
,其含义前面内容已解释过。其次调用ConsumePendingCallback
,将java_baton
还原成进行注册的实例,并调用其Callback
,具体逻辑如代码清单5-33所示。
// 代码清单5-33 engine/shell/common/vsync_waiter.cc
void VsyncWaiter::FireCallback( ...... ) {
Callback callback;
fml::closure secondary_callback;
{
std::scoped_lock lock(callback_mutex_);
callback = std::move(callback_); // callback_的赋值逻辑位于代码清单5-29
secondary_callback = std::move(secondary_callback_);
}
if (!callback && !secondary_callback) {
return; } // 没有回调,返回
if (callback) {
auto flow_identifier = fml::tracing::TraceNonce();
task_runners_.GetUITaskRunner()->PostTaskForTime([ ...... ]() {
callback(frame_start_time, frame_target_time); // 触发:见代码清单5-28
}, frame_start_time);
}
if (secondary_callback) {
task_runners_.GetUITaskRunner()->PostTaskForTime(
std::move(secondary_callback), frame_start_time);
}
}
以上逻辑提取callback_
和secondary_callback_
,如果都为null
则说明没有任何响应逻辑;如果有回调则在UI线程依次调用。对callback_
字段而言,其赋值在代码清单5-28中,其BeginFrame
方法如代码清单5-34所示。
// 代码清单5-34 engine/shell/common/animator.cc
void Animator::BeginFrame( ...... ) {
// SKIP 第1步,Trace & Timeline存储
frame_scheduled_ = false; // 当前不处于等待Vsync信号的状态
notify_idle_task_id_++; // idle(空闲状态)的计数id,每帧递增,作用见后面内容
regenerate_layer_tree_ = false; // 默认不重新生产layer_tree
pending_frame_semaphore_.Signal(); // 释放信号,允许接收新的Vsync信号请求,见代码清单5-27
if (!producer_continuation_) {
// 第2步,产生一个待渲染帧,详见5.2.5节
producer_continuation_ = layer_tree_pipeline_->Produce(); // 见代码清单5-40
if (!producer_continuation_) {
// 当前待渲染帧的队列已满
RequestFrame(); // 重新注册Vsync,在下一帧尝试加入队列,见代码清单5-27
return;
}
} // 第3步,重要属性的存储
last_frame_begin_time_ = fml::TimePoint::Now(); // 帧渲染实际开始时间
last_vsync_start_time_ = vsync_start_time; // Vsync信号通知的开始时间
last_frame_target_time_ = frame_target_time; // 当前帧完成渲染的最晚时间
dart_frame_deadline_ = FxlToDartOrEarlier(frame_target_time); // Dart VM的当前时间戳
{
// 第4步,开始渲染
delegate_.OnAnimatorBeginFrame(frame_target_time); // 见代码清单5-35
}
if (!frame_scheduled_) {
// 第5步,在UI线程注册任务,用于通知Dart VM当前空闲,可以进行GC等操作
task_runners_.GetUITaskRunner()->PostDelayedTask([ ...... ]() {
if (!self) {
return; }
if (notify_idle_task_id == self->notify_idle_task_id_ && // 没有正在渲染的帧
!self->frame_scheduled_) {
// 不等待Vsync信号(准备渲染)
self->delegate_.OnAnimatorNotifyIdle(Dart_TimelineGetMicros() + 100000);
} // 以上判断的核心是保证当前确实处于空闲状态
}, kNotifyIdleTaskWaitTime);
}
}
以上逻辑相对来说比较复杂,主要分为5步。第1步和第3步主要是一些重要属性的存储,代码中已有说明。第2步涉及一个复杂的设计,将在后面单独分析。第4步将触发Framework的渲染逻辑,这部分内容会在后面详细分析。第5步将在当前帧的渲染完成之后,在UI线程注册一个任务,同样是用于通知Dart VM当前处于空闲状态,可以进行GC等操作。这是非常有必要的,因为一帧绘制完成后Framework层会有大量对象的创建与销毁。该任务发出后能够执行的条件是当前没有新的帧待渲染(即保证渲染的优先级始终高于GC),具体表现为代码中的两个条件。
-
notify_idle_task_id == self->notify_idle_task_id_
:说明当前没有正在渲染的帧,否则后者会自增。 -
!self->frame_scheduled_
:说明没有正在等待Vsync信号的帧,否则该属性为true
。
下面开始分析渲染的逻辑。OnAnimatorBeginFrame
方法将经由Shell、Engine、Runtime-Controller
最终调用PlatformConfiguration
的BeginFrame
方法,如代码清单5-35所示。
// 代码清单5-35 engine/lib/ui/window/platform_configuration.cc
void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime) {
// 完成渲染的最晚时间
std::shared_ptr<tonic::DartState> dart_state = begin_frame_.dart_state().lock();
if (!dart_state) {
return; }
tonic::DartState::Scope scope(dart_state);
int64_t microseconds = (frameTime - fml::TimePoint()).ToMicroseconds();
tonic::LogIfError( tonic::DartInvoke(begin_frame_.Get(), // 见代码清单5-25
{
Dart_NewInteger(microseconds),})
);
UIDartState::Current()->FlushMicrotasksNow(); // 处理微任务
tonic::LogIfError(tonic::DartInvokeVoid(draw_frame_.Get())); // 见代码清单5-25
}
以上逻辑将调用Framework的window.onBeginFrame
和window.onDrawFrame
方法,并在中间调用FlushMicrotasksNow
方法以处理Dart VM的微任务。由此可以推断,帧渲染的逻辑将主要由Framework执行,下面开始详细分析。
Framework 响应阶段
由代码清单5-25可知,window.onBeginFrame
和window.onDrawFrame
接口所绑定的Dart 函数分别是handleBeginFrame
方法和handleDrawFrame
方法。前者逻辑如代码清单5-36所示。
// 代码清单5-36 flutter/packages/flutter/lib/src/scheduler/binding.dart
void handleBeginFrame(Duration? rawTimeStamp) {
Timeline.startSync('Frame'