vsync 信号处理

Android 系统每隔 16ms 发出 VSYNC 信号,触发对 UI 进行渲染,VSync 是 Vertical Synchronization(垂直同步)的缩写,是一种在PC上很早就广泛使用的技术,可以简单的把它认为是一种定时中断。而在 Android 4.1(JB) 中已经开始引入 VSync 机制,用来同步渲染,让AppUI 和 SurfaceFlinger 可以按硬件产生的 VSync 节奏进行工作。在整个 Android 视图绘制渲染流程中,VSync 信号都扮演着非常重要的作用,下面简单梳理一下 VSync 信号处理的流程。

一、基本概念

1,帧率(frame rate)
代表了GPU在一秒内绘制操作的帧数,即:GPU 生成帧的速率,单位:fps,如:60 fps。
2, 屏幕刷新率(refresh rate)
代表了屏幕在一秒内刷新屏幕的次数,即:设备刷新屏幕的频率,这取决于硬件的固定参数,单位:Hz,如:60Hz。
(注:对于一个特定的设备,帧率和刷新频率没有必然的大小关系。)

屏幕的刷新过程是每一行从左到右(行刷新,水平刷新,Horizontal Scanning),然后从上到下(屏幕刷新,垂直刷新,Vertical Scanning)。当整个屏幕刷新完毕,即一个垂直刷新周期完成,此时会有短暂的空白期,这时会发出 VSync 信号。

二,vsync 概述

Android 系统中有 2 种 VSync 信号:屏幕产生的硬件 VSync 信号、由 SurfaceFlinger 将其转成的软件 Vsync 信号。其中,硬件 VSync 是一个脉冲信号,起到开关或触发某种操作的作用。软件 VSync 信号经由 Binder 传递给 Choreographer。

1,单缓存

当没有引入 VSync 时,屏幕显示图像的工作流程如下:
在这里插入图片描述
CPU/GPU 向 Buffer 中生成图像,屏幕从 Buffer 中取图像、刷新后显示。这是一个典型的生产者——消费者模型。

此时,理想的情况是帧率和刷新频率相等,即:每绘制一帧,屏幕显示一帧。但实际上,二者之间没有必然的大小关系,如果没有锁来控制同步,很容易出现问题。例如:当帧率大于刷新频率,此时屏幕还没有刷新第 n-1 帧时,GPU 已经在生成第 n 帧了,从上往下开始覆盖第 n-1 帧的数据,当屏幕开始刷新第 n-1 帧的时候,Buffer 中的数据上半部分是第 n 帧数据,而下半部分是第 n-1 帧的数据,则显示出来的图像就会出现上半部分和下半部分有明显偏差的现象,我们称之为 “tearing(撕裂)”,如下图:
在这里插入图片描述

2、双重缓存(Double Buffer)

为了解决单缓存的“tearing(撕裂)”问题,双重缓存和 VSync 机制应运而生。双重缓存模型如下图:
在这里插入图片描述
两个缓存区分别为:Back Buffer 和 Front Buffer(或 Frame Buffer)。GPU 向 Back Buffer 中写数据,屏幕从 Frame Buffer 中读数据。VSync 信号负责调度从 Back Buffer 到 Frame Buffer 的复制操作,可认为该复制操作在瞬间完成(注:其实,该复制操作是等价后的效果,实际上双缓冲的实现方式是:交换 Back Buffer 和 Frame Buffer 内存地址名),所以可认为是瞬间完成。

双缓冲的模型下,工作流程如下:
在某个时间点,一个屏幕刷新周期完成,进入短暂的刷新空白期。此时,VSync 信号产生,先完成复制操作(即:交换),然后通知 CPU/GPU 绘制下一帧图像,同时将刚复制到 Frame Buffer 的数据显示到屏幕上,屏幕开始下一个刷新周期。
在这种模型下,只有当 VSync 信号产生时,CPU/GPU 才会开始绘制。这样,当帧率大于刷新频率时,帧率就会被迫跟刷新频率保持同步,从而避免了“tearing”现象。

注意:通常来说,帧率大于刷新频率只是一种理想的状况,在超过 60fps 的情况下,GPU所产生的帧数据会因为等待 VSYNC 的刷新信息而被Hold住,这样能够保持每次刷新都有实际的新的数据可以显示。但是我们遇到更多的情况是帧率小于刷新频率。

当 VSync 信号发出时,如果 GPU/CPU 正在生产帧数据,此时不会发生复制操作。屏幕进入下一个刷新周期时,从 Frame Buffer 中取出的是“老”数据,而非正在产生的帧数据,即:两个刷新周期显示的是同一帧数据,这就是发生了所谓的“掉帧”现象(Dropped Frame 或 Jank)。
在这里插入图片描述

3、三重缓存(Triple Buffer)

上述双重缓存的缺陷在于:当 CPU/GPU 绘制一帧的时间超过 16.67 ms (1/60s) 时,会产生 Jank。更要命的是,产生 Jank 的那一帧的显示期间,GPU/CPU 都是在闲置的。 于是就有了三重缓存:
在这里插入图片描述
三重缓存工作原理,与双重缓存类似,只是多了一个 Back Buffer。
注意:第三个缓存并不是总是存在的,只要当需要的时候才会创建。之所以这样,是因为三重缓存会显著增加用户输入到显示的延迟时间。

三、SurfaceFlinger

1、SurfaceFlinger 启动流程
SurfaceFlinger 自身拥有独立进程,由 init 进程启动,如下:

#frameworks/native/services/surfaceflinger/surfaceflinger.rc
 service surfaceflinger /system/bin/surfaceflinger
 	class core animation
 	user system
 	group graphics drmrpc readproc
 	onrestart restart zygote    // 挂掉后会重启 Zygote
 	writepid /dev/stune/foreground/tasks
 	...

然后执行 main_surfaceflinger 对应的 main() 函数,如下:

#frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp
int main(int, char**) {
...
	// 限制binder线程池中线程数最多不超过4,加上主线程一个5个
	ProcessState::self()->setThreadPoolMaxThreadCount(4);
	// 启动线程池
	sp<ProcessState> ps(ProcessState::self());
	ps->startThreadPool();
	// 创建SurfaceFlinger,执行SF::onFirstRef,最终调用MessageQueue.cpp::init()方法创建Handler、Looper
	sp<SurfaceFlinger> flinger = DisplayUtils::getInstance()->getSFInstance();
...
	// 客户端连接前的一系列初始化操作,包括:初始化DispSyncSource,以及EventThread(有两个:mEventThread对应app,mSFEventThread对应SurfaceFlinger),并初始化显示设备。
	flinger->init();
...
	// surfaceFlinger 执行主线程的run()方法,waitForEvent等消息,接受invalidate消息,并开始对应工作流。
	flinger->run();
	return 0;
}

到这里,从主线程上看,SurfaceFlinger进程就已经启动了。

2、SurfaceFlinger 处理 VSync 信号
VSync 信号的产生有两种来源,一种是硬件,一种是软件模拟,目前基本都是硬件产生的,硬件源就是 HWComposer,它属于HAL(硬件抽象层)的类,主要就是把硬件 VSync 信号传递到上层来。VSync 的分发流程如下:
在这里插入图片描述
VSync 信号的主要作用:同步 应用层的视图绘制任务 和 Native层的视图合成任务。整个过程如下:
在这里插入图片描述
(1)VSync产生:HWComposer 作为硬件 VSync 信号。
(2)VSync处理:周期性地唤醒 DipsSyncThread 产生虚拟化的 VSync 信号。
(3)VSync注册与回调:EventThread 负责注册 App、SurfaceFlinger 的 VSync 请求和分发 DipsSyncThread 产生的虚拟化 VSync 信号到 App、SurfaceFlinger。
总结:VSync 信号原则就是:App、SurfaceFlinger 按需请求,按请求分发。

四、App、SurfaceFlinger 的 VSync 信号的请求与接收过程

App 的注册和回调都是通过 Choreographer,它主要负责:input、animation、traversals。
1、App 端
(1)请求 vsync-app:
Choreographer postCallback 发起 vsync 请求,最终调用 FrameDisplayEventReceiver#scheduleVsync,然后进入 native 层,执行 DisplayEventReceiver::requestNextVsync(),最终通过一个 BpDisplayEventConnection 来发起 requestNextVsync 操作。
该过程简单理解为:为客户端和底层建立了一个 connection 连接,通过这个 connection,向 EventThread 注册一个 callback:在这里插入图片描述
(2)接收 vsync-app:
DipsSyncThread 周期性转发 vsync 给 EventThread 上注册的 callback,经过层层回调,App端最终通过 FrameDisplayEventReceiver#onVsync 来接收回调,但不是立即执行,而是通过 Handler.sendMessagerAtTime 加入消息池等待调度。调度到了执行其 run() 方法,走 doFrame 回调流程,如下:
2、SurfaceFlinger 端
(1)请求 vsync-sf:
触发请求 vsync 位置在 SurfaceFlinger.cpp 中的两个方法:

// 事务变化(如:增加window,window大小变化等)
void SurfaceFlinger::signalTransaction() {
	mEventQueue.invalidate();
}

// layer发生变化,主要发生在queueBuffer的时候
void SurfaceFlinger::signalLayerUpdate() {
	mEventQueue.invalidate();
}

另外还有一个 refresh 方法,表示需要刷新屏幕,一般为处理完事务后,发现需要重新绘制的时候触发该方法:

void surfaceFlinger::signalRefresh() {
	mEventQueue.refresh();
}

MessageQueue 执行 invalidate 最终还是建立 connection,向 EventThread 注册一个 callback:

void MessageQueue::invalidate() {
	mEvents->requestNextVsync();
}

总结:SurfaceFlinger 触发请求 vsync 请求的触发点比较分散,简言之,页面变化引起重绘的地方,就会触发 vsync 请求。

(2)接收 vsync-sf:
EventThread 传递 Invalidate 消息给 SurfaceFlinger 主线程 run() 方法 waitForEvent 来执行:
在这里插入图片描述
附录:
(1)VSync 机制的详细说明

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值