文章

图形系统基础

图形系统基础

图形系统基础

术语

tearing(screen tearing,画面撕裂,屏幕显示 2 帧)

tearing adj. 撕裂的

tearing 现象

一个屏幕内的数据同一时刻来自 2 个不同的帧,画面会出现撕裂感:

image.png

tearing 出现的原因

屏幕刷新频是固定的,比如每 16.6ms 从 buffer 取数据显示完一帧,理想情况下帧率和刷新频率保持一致,即每绘制完成一帧,显示器显示一帧。但是 CPU/GPU 写数据是不可控的,所以会出现 buffer 里有些数据根本没显示出来就被重写了,即 buffer 里的数据可能是来自不同的帧的,当屏幕刷新时,此时它并不知道 buffer 的状态,因此从 buffer 抓取的帧并不是完整的一帧画面,即出现画面撕裂。

简单说就是 Display 在显示的过程中,buffer 内数据被 CPU/GPU 修改,导致画面撕裂。

display 从显示第一行到显示最后一行,我们称为一个显示周期,在一个显示周期内,如果 cpu/gpu 改写了后边的 back buffer 的内容,就会出现 tearing。

举个例子,假设显示周期为 0.01 秒。0-0.01 秒的时候,CPU/GPU 写了一帧数据,那 display 就正确的显示;0.01-0.02 秒的时候,CPU/GPU 写了 2 帧数据(第二帧,第三帧),那 display 就会上半部分显示第二帧,下半部分显示第三帧,出现撕裂情况。

只有 CPU/GPU 的频率是 display 刷新频率的整数倍或者 1/N 时才不会产生 tearing。

有人说 CPU/GPU 的频率高于 display 才会有 tearing,这也是错误的。CPU/GPU 的频率低于 display 也可能产生 tearing 的,比如 display 为 100Hz,CPU/GPU 为 80Hz,那么在 0.01 时候显示 buffer 里的第一屏数据,底部会有留白,因为 buffer 没填满。而第二秒的时候,此时 CPU/GPU,处理了 1.6 个 buffer,所以此时 buffer 内的前 60% 是第二帧,后 40% 是第一帧,那也是会 tearing 的。
所以,只有 CPU/GPU 的频率是 display 刷新频率的整数倍或者 1/N 时才不会产生 tearing。

比如 60Hz 的刷新频率,那 CPU/GPU 的频率得是 60, 120, 30 才可以.
但是但是,实际上 display 的刷新频率是固定的,但是 CPU/GPU 写 buffer 的时间是不定的,所以 tearing 的产生几乎是必然的。

tearing 解决

画面撕裂的原因就是单缓存,在显示系统显示的时候,CPU/GPU 又更新了缓存,导致同一时刻显示了两帧画面
使用双缓存可以解决 tearing

jank (一帧显示 2 次)

jank adj. 质量极差的

jank 现象

一帧数据在屏幕上连续出现 2 次

jank 出现的原因

使用了双缓存,CPU/GPU 在一个 vsync 周期内没有处理数据,导致下一个 vsync 信号到来时,没有新的数据显示,导致显示了还是上一帧的数据

jank 解决

Android 中引入了三缓存,Triple Buffer

lag (画面延迟)

lag n. 落后;迟延
从用户体验来说,就是点击下去到呈现效果之间存在延迟
Android 中引入了三缓存解决

屏幕刷新频率 HZ

一秒内屏幕刷新多少次(一秒内显示了多少帧的图像),单位 Hz(赫兹),如常见的 60Hz。刷新频率取决于硬件的固定参数(不会变的)。

逐行扫描: 显示器并不是一次性将画面显示到屏幕上,而是从左到右边,从上到下逐行扫描,顺序显示整屏的一个个像素点,不过这一过程快到人眼无法察觉到变化。以 60 Hz 刷新率的屏幕为例,这一过程即 1000 / 60 ≈ 16.67ms。

帧率(Frame Rate)FPS

表示 GPU 在一秒内绘制操作的帧数,单位 FPS。

例如在电影界采用 24 帧的速度足够使画面运行的非常流畅。而 Android 系统则采用更加流程的 60 FPS,即每秒钟 GPU 最多绘制 60 帧画面。帧率是动态变化的,例如当画面静止时,GPU 是没有绘制操作的,屏幕刷新的还是 buffer 中的数据,即 GPU 最后操作的帧数据。

Buffer

单缓冲区

在早期 Android(4.1 以前),UI 显示利用的是单缓冲区,在单缓冲区情况下,CPU 和 GPU 绘图过程和屏幕刷新所用的 buffer 是同一块,假设此时屏幕的刷新频率和 CPU/GPU 的绘制速度不一致时,侧可能出现屏幕 “ 割裂(screen tearing)” 的现象,即屏幕上同时显示两个不同帧中的部分画面。

Double Buffer 双缓冲

tearing 发生的原因是 display 读 buffer 同时,buffer 被修改,那么多一个 buffer 是不是能解决问题,是的,事实上目前所有的显示系统都是双缓存的,单缓存存在于 30 年前。

什么是双缓冲

双缓冲技术,基本原理就是采用两块 buffer。一块 back buffer 用于 CPU/GPU 后台绘制,另一块 frame buffer 则用于显示,当 back buffer 准备就绪后,它们才进行交换。double buffering 可以在很大程度上降低 screen tearing 错误,但也有例外。
!!image.png

什么时候进行两个缓冲区的交换呢?VBI

假如是 back buffer 准备完成一帧数据以后就进行,那么如果此时屏幕还没有完整显示上一帧内容的话,肯定是会出问题的。看来只能是等到屏幕处理完一帧数据后,才可以执行这一操作了。

大家应该能想到了,vsync 这个时间点就是我们进行缓冲区交换的最佳时间。因为此时屏幕没有在刷新,也就避免了交换过程中出现 screen tearing 的状况。

Triple Buffer 三缓冲

三缓冲区,Android4.1 Project Butter 引入,用于减少 jank 引入

VSync(垂直同步信号)的生成、请求和接收

见 [[Android屏幕刷新机制及VSync]] 章节

显示系统组成

一个典型的显示系统中,一般包括 CPU、GPU、display 三个部分;很多时候,我们可以把 CPU、GPU 放在一起说

CPU

CPU 负责计算数据,把计算好数据交给 GPU
CPU : Central Processing Unit , 中央处理器,是计算机设备核心器件,用于执行程序代码。

GPU

GPU : Graphic Processing Unit , 图形处理器,主要用于处理图形运算,通常所说 “ 显卡 “ 的核心部件就是 GPU。

GPU 会对图形数据进行渲染,渲染好后放到 buffer 里存起来

GPU 中有一块帧缓冲区叫做 Frame Buffer,用以交给手机屏幕进行绘制;
还有一块缓冲区 Back Buffer 这个用以交给应用的,让 CPU 往里面填充数据。

GPU 会定期交换 Back Buffer 和 Frame Buffer ,也就是对 Back Buffer 中的数据进行栅格化后将其转到 Frame Buffer 然后交给屏幕进行显示绘制,同时让原先的 Frame Buffer 变成 Back Buffer 让程序处理。

CPU 和 GPU 的结构对比图:

image.png

  • 黄色的 Control 为控制器,用于协调控制整个 CPU 的运行,包括取出指令、控制其他模块的运行等;
  • 绿色的 ALU(Arithmetic Logic Unit)是算术逻辑单元,用于进行数学、逻辑运算;
  • 橙色的 Cache 和 DRAM 分别为缓存和 RAM,用于存储信息。
  1. 从结构图可以看出,CPU 的控制器较为复杂,而 ALU 数量较少。因此 CPU 擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算。
  2. CPU 是串行结构。以计算 100 个数字为例,对于 CPU 的一个核,每次只能计算两个数的和,结果逐步累加。
  3. 和 CPU 不同的是,GPU 就是为实现大量数学运算设计的。从结构图中可以看到,GPU 的控制器比较简单,但包含了大量 ALU。GPU 中的 ALU 使用了并行设计,且具有较多浮点运算单元。
  4. 硬件加速的主要原理,就是通过底层软件代码,将 CPU 不擅长的图形计算转换成 GPU 专用指令,由 GPU 完成。

Display

display(也叫屏幕或者显示器)负责把 buffer 里的数据呈现到屏幕上

Ref

本文由作者按照 CC BY 4.0 进行授权