Dive Into Flutter

首先从过去的CRT显示器原理说起。CRT的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertial synchronization),简称VSync。显示器通常以固定频率进行刷新,这个刷新率就是VSync信号产生的频率。尽管现在的设备大都是液晶显示屏了,但原理仍然没有变。

原理

屏幕显示图片的原理

dive_into_flutter_screen_scan

首先从过去的CRT显示器原理说起。CRT的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertial synchronization),简称VSync。显示器通常以固定频率进行刷新,这个刷新率就是VSync信号产生的频率。尽管现在的设备大都是液晶显示屏了,但原理仍然没有变。

dive_into_flutter_screen_display

通常来说,计算机系统中CPU,GPU,显示器是以上面这种方式协同工作的。CPU计算好显示内容提交到GPU,GPU渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照VSync信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。CPU和GPU的任务是各有偏重的,CPU主要用于基本数学和逻辑计算,而GPU主要执行和图形处理相关的复杂的数学,如矩阵变化和几何计算,GPU的主要作用就是确定最终输送给显示器的各个像素点的色值。

在最简单的情况下,帧缓存区只有一个,这时帧缓存区的读取和刷新都会有比较大的效率问题。为了解决效率问题,显示系统通常会引入两个缓冲区,让视频控制器读取,当下一帧渲染好后,GPU会直接把视频控制器的指针指向第二个缓冲器。如此一来效率会有很大的提高。

双缓冲虽然能解决效率问题,但会引入一个新的问题。当视频控制器还未读取完成时,即屏幕内容刚显示一半时,GPU将新的一帧内容提交到帧缓存区并把两个缓冲区进行交换后,视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象,如下图:

![dive_into_flutter_vsync_off](/images/dive_into_flutter_vsync_off.jpg

为了解决这个问题,GPU通常有一个机制叫做垂直同步(简写也是V-Sync),当开启垂直同步后,GPU会等待显示器的VSync信号发出后,才进行新的一帧渲染和缓存区更新。这样能解决画面撕裂现象,也增加了画面流畅度,但需要消费更多的计算资源,也会带来部分延迟。

那么目前主流的移动设备是什么情况?从网上查到的资料可以知道,iOS设备会始终使用双缓存,并开启垂直同步。而安卓设备到4.1版本,Google才开始引入这种机制,目前安卓系统是三缓存+垂直同步。

dive_into_flutter_frame_drop

当VSync信号到来后,系统图形服务会通过CADisplayLink(iOS),ViewTreeObserver(Android)等机制通知App,App主线程开始在CPU中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后CPU会将计算好的内容提交到GPU去,由GPU进行变化、合成、渲染。随后GPU会把渲染结果提交到帧缓冲区去,等待下一次VSync信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个VSync时间内,CPU或者GPU没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。

卡顿产生的原因和解决方案

参考:
iOS保持界面流畅的技巧

作者

8MilesRD

发布于

2019-08-09

更新于

2020-09-17

许可协议

评论