基础知识
CPU、GPU
- CPU:中央处理器,主要负责计算数据,在Android中主要用于三大绘制流程中Surface的计算过程。
- GPU:图像处理器,主要负责对图形数据进行渲染,在Android中主要用于将CPU计算好的Surface数据合成后放到buffer中,让显示器进行读取呈现到屏幕上。
逐行扫描
屏幕在刷新buffer的时候,并不是一次性扫描完成,而是从左到右,从上到下的一个读取过程,顺序显示一屏的每个像素点,按60HZ的屏幕刷新率来算,这个过程只有16.66666...ms。
- 从初始位置(第一行左上角)开始扫描,从左到右,进行水平扫描(Horizontal Scanning)
- 每一行扫描完成,扫描线会切换到下一行起点,这个切换过程叫做水平消隐,简称 hblank(horizontal blank interval),并发送水平同步信号(horizontal synchronization,又称行同步)
- 依次类推,整个屏幕(一个垂直周期)扫描完成后,显示器就可以呈现一帧的画面
- 屏幕最后一行(一个垂直周期)扫描完成后,需要重返左上角初始位置,这个过程叫垂直消隐,简称 vblank(vertical blank interval)
- 扫描线回到初始位置之后,准备扫描下一帧,同时发出垂直同步信号(vertical synchronization,又称场同步)。
显卡帧率
表示GPU在1s中内可以渲染多少帧到buffer中,单位是fps,这里要理解的是帧率是一个动态的,比如我们平时说的60fps,只是1s内最多可以渲染60帧,假如我们屏幕是静止的,则GPU此时就没有任何操作,帧率就为0.
屏幕刷新频率
屏幕刷新频率:屏幕在1s内去buffer中取数据的次数,单位为HZ,常见屏幕刷新率为60HZ。屏幕刷新率是一个固定值和硬件参数有关。也就是以这个频率发出 垂直同步信号,告诉 GPU 可以往 buffer 里写数据了,即渲染下一帧。
屏幕刷新机制演变过程
单buffer
GPU和显示器共用一块buffer
screen tearing 屏幕撕裂、画面撕裂
当只有一个buffer时,GPU 向 buffer 中写入数据,屏幕从 buffer 中取图像数据、刷新后显示,理想的情况是显卡帧率和屏幕刷新频率相等,每绘制一帧,屏幕显示一帧。而实际情况是,二者之间没有必然的大小关系,如果没有同步机制,很容易出现问题。
当显卡帧率大于屏幕刷新频率,屏幕准备刷新第2帧的时候,GPU 已经在生成第3帧了,就会覆盖第2帧的部分数据。
当屏幕开始刷新第2帧的时候,缓冲区中的数据一部分是第3帧数据,一部分是第2帧的数据,显示出来的图像就会出现明显的偏差,称为屏幕撕裂,其本质是显卡帧率和屏幕刷新频率不一致所导致。
双buffer
安卓4.1之前
基本原理就是采用两块buffer。
GPU写入的缓存为:Back Buffer
屏幕刷新使用的缓存为:Frame Buffer
因为使用双buffer,屏幕刷新时,frame buffer不会发生变化,通过交换buffer来实现帧数据切换。
什么时候就行buffer交换呢,当设备屏幕刷新完毕后到下一帧刷新前,因为没有屏幕刷新,所以这段时间就是缓存交换的最佳时间。
此时硬件屏幕会发出一个脉冲信号,告知GPU和CPU可以交换了,这个就是Vsync信号,垂直同步信号。
不可否认,双缓冲可以在很大程度上降低screen tearing错误,但是呢,还是会出现一些其他问题。
Jank 掉帧
如果在Vsync到来时back buffer并没有准备好,就不会进行缓存的交换,屏幕显示的还是前一帧画面,即两个刷新周期显示的是同一帧数据,称为Jank掉帧。
image.png发生jank的原因是:在第2帧CPU处理数据的时候太晚了,GPU没有及时将数据写入到buffer中,导致jank的发生。
CPU处理数据和GPU写入buffer的时机比较随意。
Project Butter 黄油工程
安卓4.1
系统在收到VSync信号之后,马上进行CPU的绘制以及GPU的buffer写入。最大限度的减少jank的发生。
如果显卡帧率大于屏幕刷新频率,也就是屏幕在刷新一帧的时间内,CPU和GPU可以充分利用刷新一帧的时间处理完数据并写入buffer中,那么这个方案是完美的,显示效果将很好。
image.png由于主线程做了一些相对复杂耗时逻辑,导致CPU和GPU的处理时间超过屏幕刷新一帧的时间,由于此时back buffer写入的是B帧数据,在交换buffer前不能被覆盖,而frame buffer被Display用来做刷新用,所以在B帧写入back buffer完成到下一个VSync信号到来之前两个buffer都被占用了,CPU无法继续绘制,这段时间就会被空着,于是又出现了三缓存。
三buffer
image.png最大程度避免CPU空闲的情况。
Choreographer
系统在收到VSync信号之后,会马上进行CPU的绘制以及GPU的buffer写入。在安卓系统中由Choreographer实现。
- 在Choreographer的构造函数中会创建一个FrameDisplayEventReceiver类对象,这个对象实现了onVSync方法,用于VSync信号回调。
- FrameDisplayEventReceiver这个对象的父类构造方法中会调用nativeInit方法将当前FrameDisplayEventReceiver对象传递给native层,native层返回一个地址mReceiverPtr给上层。
- 主线程在scheduleVsync方法中调用nativeScheduleVsync,并传入2中返回的mReceiverPtr,这样就在native层就正式注册了一个FrameDisplayEventReceiver对象。
- native层在GPU的驱使下会定时回调FrameDisplayEventReceiver的onVSync方法,从而实现了:在VSync信号到来时,立即执行doFrame方法。
- doFrame方法中会执行输入事件,动画事件,layout/measure/draw流程并提交数据给GPU。
参考文献:
https://juejin.cn/post/7163858831309537294
https://blog.csdn.net/litefish/article/details/53939882
https://www.jianshu.com/p/996bca12eb1d
网友评论