Android VSync详解

本文详细阐述了帧率、屏幕刷新频率和VSync的概念,以及它们在Android系统中的交互。重点介绍了双缓冲和三缓冲技术如何解决屏幕显示卡顿问题,以及Choreographer在Android4.1系统中优化显示性能的角色。
摘要由CSDN通过智能技术生成

一、基本概念

  • 帧率(Frame Rate):与屏幕刷新率对应的,帧率是一个软件的概念,单位是FPS(Frame Per Second ),表示 CPU/GPU 在一秒内绘制合成产生的帧数,意思是每秒产生画面的个数,FPS 的值是由软件系统决定的。举例来说,60FPS 指的是每秒产生 60 个画面;90FPS 指的是每秒产生 90 个画面。
  • 屏幕刷新率(Screen Refresh Rate): 屏幕刷新率是一个硬件的概念,单位是Hz(赫兹),是说屏幕这个硬件刷新画面的频率:举例来说,60Hz 刷新率意思是:这个屏幕在 1 秒内,会刷新显示内容60 次;那么对应的,90Hz 是说在 1 秒内刷新显示内容 90 次。

如下图,屏幕的刷新过程是每一行从左到右(行刷新,水平刷新,Horizontal Scanning),从上到下(屏幕刷新,垂直刷新,Vertical Scanning)。
在这里插入图片描述
对于一个特定的设备,帧率和刷新频率没有必然的大小关系。

  • VSync(垂直同步,Vertical sync):安卓系统中有 2 种 VSync 信号,屏幕产生的硬件 VSync 和由 SurfaceFlinger 将其转成的软件 Vsync 信号。后者经由 Binder 传递给 Choreographer。硬件 VSync 是一个脉冲信号,起到开关或触发某种操作的作用。
    在这里插入图片描述

二、Vsync原理
在一个典型的显示系统中,一般包括CPU、GPU、Display三个部分:CPU负责计算帧数据,把计算好的数据交给GPU,GPU会对图形数据进行渲染,渲染好后放到buffer(图像缓冲区)里存起来,然后Display(屏幕或显示器)负责把Buffer里的数据呈现到屏幕上。屏幕上显示的内容,是从Buffer图像帧缓冲区中读取的,大致读取过程为:从Buffer的起始地址开始,从左往右,从上往下扫描整个Buffer,将内容映射到显示屏上。

1.单缓存
首先,我们来看下,没有引入 VSync 时,屏幕显示图像的工作流程
在这里插入图片描述
如上图,CPU/GPU 向 Buffer 中生成图像,屏幕从 Buffer 中取图像、刷新后显示。这是一个典型的生产者——消费者模型。 理想的情况是帧率和刷新频率相等,每绘制一帧,屏幕显示一帧。而实际情况是,二者之间没有必然的大小关系,如果没有锁来控制同步,很容易出现问题。

我们用以下两个假设来分析两者的关系:
1)屏幕刷新率比系统帧率快:
此时,在前缓冲区内容全部映射到屏幕上之后,后缓冲区尚未准备好下一帧,屏幕将无法读取下一帧,所以只能继续显示当前一帧的图形,造成一帧显示多次,也就是卡顿。
2)系统帧率比屏幕刷新率快:
当屏幕还没有刷新第 n-1 帧的时候,GPU 已经在生成第 n 帧了,从上往下开始覆盖第 n-1 帧的数据,当屏幕开始刷新第 n-1 帧的时候,Buffer 中的数据上半部分是第 n 帧数据,而下半部分是第 n-1 帧的数据,显示出来的图像就会出现上半部分和下半部分明显偏差的现象,我们称之为撕裂 “tearing”,如下图:
在这里插入图片描述

2.双缓存+Vsync

由于屏幕上显示的内容需要不断的更新,如果在同一个Buffer进行读取和写入操作,将会导致屏幕显示多帧内容而出现显示错乱。所以硬件层除了提供一个Buffer用于屏幕显示,还会提供了一个Buffer用于后台的CPU/GPU图形绘制与合成,也就是我们常说的双缓冲:让绘制和显示器拥有各自的Buffer:CPU/GPU 始终将完成的一帧图像数据写入到 后缓存区(Back Buffer),而显示器使用帧缓存区(Frame Buffer)或者前缓冲区 (Front Buffer),当屏幕刷新时,Frame Buffer 并不会发生变化,当Back Buffer准备就绪后,它们才进行交换。如下图所示:
在这里插入图片描述
两个缓存区分别为 Back 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”现象。

注意,当 VSync 信号发出时,如果 GPU/CPU 正在生产帧数据,此时不会发生复制操作。屏幕进入下一个刷新周期时,从 Frame Buffer 中取出的是“老”数据,而非正在产生的帧数据,即两个刷新周期显示的是同一帧数据。这是我们称发生了“掉帧”(Dropped Frame,Skipped Frame,Jank)现象。

通过上面的分析可以看出:屏幕的显示节奏是由屏幕刷新率的硬件参数决定且固定的,软件操作系统需要配合屏幕的显示,在固定的时间内准备好下一帧,以供屏幕进行显示,两者通过VSync信号来实现同步(VSync这个概念并不是Google首创的,它在早年的PC机领域就已经出现了)。

2.1 Drawing without Vsync
首先看看 Drawing without Vsync
请添加图片描述

上图中:
纵轴表示Buffer的使用者,由如下三个角色构成:

  • CPU : 代表利用CPU对界面View的Measure尺寸测量、Layout位置布局、Draw绘制并最终生成纹理的操作;
  • GPU:代表使用OpenGl库指令操作GPU硬件对CPU生成的纹理数据进行渲染和栅格化以及合成等操作;
  • Display:代表底层的显示屏幕;

横轴表示时间,每个长方形表示Buffer的使用,长方形的宽度代表使用时长,VSync代表垂直同步信号。

我们以时间为顺序来看看这种设计存在的潜在缺陷:

  1. Display上显示第0帧数据,此时CPU和GPU正在处理准备第1帧的画面,且在Display显示下一帧前完成;
  2. 因为CPU和GPU的处理及时,Display在第0帧显示完成后,也就是第1个VSync后,缓存进行交换,然后正常显示第1帧;
  3. 接着第2帧开始处理,但是CPU并没有立刻开始准备第2帧的数据,而是直到第2个VSync快来前才开始处理的,紧接着GPU处理延迟(第2个VSync来时数据还未处理完);
  4. 第2个VSync来时,由于GPU将第2帧数据还没有准备就绪,Back Buffer与Frame Buffer缓存没有交换,屏幕显示的还是第1帧画面,即产生了Jank丢帧现象(丢帧、掉帧表示这一帧延迟显示);
  5. 当GPU将第2帧数据准备完成后,它并不能立马被显示,而是要等到下一个VSync来后,进行前后缓存交换才能显示到屏幕上。

出现此掉帧卡顿问题的根本原因是:上层的CPU和GPU并不知道Vsync信号的到来,所以在底层屏幕的Vsync信号发出后并没有及时收到并开始下一帧画面的操作处理。根据前面的分析我们知道:双缓存的交换是在Vsyn信号到来时进行,交换后屏幕会读取Front Buffer内的新数据更新显示到屏幕上,而此时的Back Buffer 就可以供GPU准备下一帧数据了。如果 Vsyn到来时 CPU/GPU就开始操作的话,是有完整的Vsync周期时长来处理一帧数据,以避免卡顿的出现。那如何让 CPU/GPU的处理在 Vsyn信号到来时就开始进行呢?

由于双缓存的交换是在Vsyn到来时进行的,交换后屏幕会取Frame buffer内的新数据,此时后缓冲区就可以立即供CPU、GPU准备下一帧数据了。所以,如果Vsync一到来时CPU/GPU立马就开始操作的话,是有完整的16.6ms的,这样基本会避免jank的出现了(除非CPU/GPU计算超过了16.6ms)。

2.2 drawing with VSync
为了优化系统显示性能,Google在Android 4.1系统中对Android Display系统进行了重构,引入了Project Butter(黄油工程),其中很重要的一点修改就是实现了:在系统收到VSync信号后(16ms触发一次),CPU和GPU马上开始进行下一帧画面数据的处理,完成后及时将数据写入到Back Buffer中,Google称之为Drawing with Vsync。如下图所示:

请添加图片描述
有了VSync后,CPU总是在指定的地方开始。即CPU/GPU会根据VSYNC信号同步处理数据,这样就可以让CPU/GPU有完整的16ms时间来处理数据,减少了jank。

但是这样并不能完全避免jank问题,只能优化丢帧。比如界面比较复杂,CPU/GPU的处理时间较长,超过了16.6ms:

在这里插入图片描述

由于CPU/GPU处理耗时过长,超过了16.7ms,当第一个VSync信号到来时,缓冲区B中的数据还未准备完毕,Display未能完成交换只能继续显示缓冲区A中的内容。此时缓冲区A、B分别被Display和GPU占用了,CPU在第二个VSync时无法开始准备下一帧数据而只能空闲运行,当下一个VSync信号来临时,Display与B完成缓冲区交换,CPU才能继续处理下一帧数据,导致的结果就是相当于把屏幕的刷新率降低了。因为在第一个周期时原本应该显示第二帧的又多显示了第一帧。究其原因就是因为两个Buffer都被占用,CPU无法准备下一帧的数据。

分析一下整个过程:

  1. 在第一帧里,GPU墨迹了半天没有搞完,直到第二帧里GPU还在处理B帧,导致缓存没能交换,因此Display显示的还是第一帧的数据,即A帧被重复显示,出现了jank卡顿。
    并且会发现在第一个VSync信号过来后,CPU什么都没做,这是因为GPU占着后缓冲(那个绿色的长B块),当B帧完成后,又因为缺乏VSync pulse信号,CPU还是不能开始操作,只能等待下一个VSync信号到来时才能开始。于是在这一过程中,有一大段时间是被浪费的。
  2. 当下一个VSync到来时,缓存进行交换,显示屏显示B帧,CPU终于拿到了后缓冲的使用权,CPU马上开始执行计算A帧数据的操作,这时看起来是正常的,只不过由于执行时间仍然超过16ms,导致下一次应该执行的缓冲区交换又被推迟了……如此循环反复,便出现了越来越多的Jank。

在这个过程中,会发现有大量的时间是浪费的,比如第2个16ms内、第4个16ms内……这些时间的VSync到来时,CPU/GPU按道理是应该立即进行操作的,但实际却没有。那为什么CPU不能在第二个16ms处理绘制工作呢?这是因为只有两个buffer,Dispaly使用前缓冲,CPU和GPU一起使用后缓冲(这就意味着它们只能轮流使用后缓冲)。此时Back buffer正在被GPU用来处理B帧的数据,Frame buffer的内容用于Display的显示,这样两个buffer都被占用了,CPU就无法准备下一帧的数据。

为了解决这个问题,可以再提供一个buffer,CPU、GPU和Display都能使用各自的buffer工作,互不影响。因此出现了三缓存。

3.三缓存 + Vsync
三缓存就是在双缓冲机制基础上增加一个Graphic Buffer缓冲区,这样可以最大限度的利用空闲时间。缺点是多使用的一个Graphic Buffer所占用的内存。
在这里插入图片描述
在第一个VSync到来时,GPU还占用B缓冲区处理数据,所以Display只能显示A缓冲区的老数据。但此时C缓冲区是空闲的,CPU就占用C缓冲区开始计算数据;当第二个VSync到来时,B缓冲区数据处理完毕,Display与GPU缓冲区交换(相当于Display占用B,GPU占用A),因此Display切换到B缓冲区并显示,与此同时CPU处理完C的数据后会与GPU交换,GPU处理C缓冲区,CPU继续使用A缓冲区计算数据;当第三个VSync到来时,Display切换到C缓冲区(因为在第二个周期内GPU处理好了C缓冲),A缓冲被GPU占用,CPU使用B缓冲,如此循环,这样除了第一帧不可避免地产生Jank外,后续的帧显示效果都比较理想。

分析一下整个过程:

  1. 第一个Jank是不可避免的。但是在第二个16ms 时间段,CPU/GPU使用第三个Buffer完成C帧的计算,虽然还是会多显示一次A帧,但后续显示就比较顺畅了,有效避免Jank的进一步加剧。
  2. 注意在第3段中,A帧的计算已完成,但是在第4个VSync来的时候才显示,如果是双缓冲,那在第三个VSync就可以显示了。

通过引入三缓冲,当接到VSync脉冲时Display Buffer可以选择Frame Buffer和Graphic Buffer中已经准备好的数据进行交换。

三缓冲虽然不能完全避免卡顿问题,但可以大幅优化卡顿问题,尤其是避免连续卡顿。

注意:Buffer并不是越多越好,Buffer正常还是两个,当出现Jank后三个足以。

三缓冲有效利用了等待VSync的时间,减少了jank,但同时也带来了延迟、耗资源。 所以系统并非一直开启三缓冲,要想真正解决问题,还需要在CPU层对数据尽量优化,从而减小CPU和GPU的计算量,比如View尽量扁平化,少嵌套,少在UI线程做耗时操作等。

以上就是Android屏幕刷新的原理了。

三、Choreographer
上一节中讲到的,为了优化显示系统性能,Google在Android 4.1系统中对Android Display系统进行了重构,引入了Project Butter(黄油工程),其中很重要的一点修改就是实现了:在系统收到VSync信号后,上层CPU和GPU马上开始进行下一帧画面数据的处理,完成后及时将数据写入到Back Buffer中。为了实现这个效果,控制上层CPU和GPU在收到Vsync信号后马上开始一帧数据的处理,谷歌为此专门设计了一个名为Choreographer(中文翻译为“编舞者”)的类,来控制上层绘制的节奏。

Choreographer 的引入,主要是为了配合系统Vsync垂直同步机制,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机。Choreographer 扮演 Android 渲染链路中承上启下的角色:

  • 承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。
    比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等;
  • 启下:负责请求和接收 Vsync 信号。
    接收 Vsync 信号到来的事件后回调(通过 FrameDisplayEventReceiver.onVsync ),并请求 Vsync(FrameDisplayEventReceiver.scheduleVsync)

一般应用App有界面UI的变化时,最终都会调用走到ViewRootImpl#scheduleTraversals()方法中,该方法中会往Choreographer中放入一个CALLBACK_TRAVERSAL类型的绘制任务,如下代码所示:

/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
         ...
         // 通过Choreographer往主线程消息队列添加CALLBACK_TRAVERSAL绘制类型的待执行消息,用于触发后续UI线程真正实现绘制动作
         mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         ...
     }
}

Choreographer在收到的绘制任务后,其内部的工作流程如下图所示:
请添加图片描述
从以上流程图可以看出上层一般App应用UI中View的绘制流程(包含SurfaceView的游戏应用的绘制流程会有一些差异,篇幅有限此处不再展开分析):

  1. View#invalidate触发更新视图请求,此动作会调用ViewRootImpl#scheduleTraversals函数
  2. ViewRootImpl#scheduleTraversals中会向Choreographer中postCallback放入一个CALLBACK_TRAVERSAL类型绘制待执行任务;
  3. Choreographer通过DisplayEventReceiver向系统SurfaceFlinger注册下一个VSync信号;
  4. 当底层产生下一个VSync消息时,将该信号发送给DisplayEventReceiver,最后传递给Choreographer;
  5. Choreographer收到VSync信号之后,向主线程MessageQueue发送了一个异步消息;
  6. 最后,异步消息的执行者是跑在主线程中的ViewRootImpl#doTraversal,也就是真正开始绘制一帧的操作(包含measure、layout、draw三个过程);

至此,底层的VSync控制上层绘制的逻辑就解释完了。

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值