虽然当下的设备越来越强大,处理速度也越来越快,显示效果越来越好。但是如果处理不好,再强大的设备还是会有卡顿的可能性,所以了解卡顿产生的原因是很有必要的。

帧率和刷新率的区别

我们先来看一下显示器是如何显示图像的,CPU 将计算好的内容传递给 GPU,GPU 进行图像处理(GPU 对浮点计算做了优化),GPU 执行渲染之后将内容放入帧缓冲区,显示器需要显示图像时就会经过一系列操作获取帧缓冲区的内容,显示到屏幕上,展示给用户。如下图所示:

CPU GPU

接下来开始说帧率和刷新率,帧率指的是 GPU 绘制图片的速度,帧率 60FPS 指的是在 1 秒内绘制 60 张图片。刷新率指的是屏幕 1 秒内显示图片的次数, 刷新率 60Hz 指的是 1 秒内需要显示 60 张图片。我们举几个例子来更好的说明:

  1. 如果 GPU 的帧率是 2FPS,显示器的刷新率是 1Hz,这个就会造成 GPU 1 秒内绘制的两张图片只会有一张被显示,因为当显示器第二次从帧缓存区中取图片数据的时候,帧缓存区中已经是 GPU 绘制的第三张图片了,GPU 绘制好的第二张图片被丢弃了。
  2. 如果 GPU 的帧率是 1FPS,当显示器的刷新率是 2Hz,GPU 1 秒绘制一张图片,显示器 1 秒要显示两张图片,GPU 1 秒将绘制好的图片放入帧缓存区中,显示器第一次从帧缓存区中取出图片数据显示到屏幕上,当显示器第二次去帧缓存区中取图片时 GPU 才绘制好新一帧的一半,这就造成后面要说的画面撕裂。

屏幕撕裂

屏幕撕裂也是画面撕裂,屏幕撕裂 (Screen Tearing) 是指显示器把两个或更多的帧显示在同一画面上。

显示器的更新频率是固定的,通常是 60Hz。现在显卡效能大幅提高,游戏时输出的帧率可以非常高,如果显卡的输出高于 60FPS,上面我们提到过一个例子 GPU 的绘制速度恰好显示器的刷新速度的两倍,虽然 GPU 和显示器的频率不相同但是也不至于造成屏幕撕裂。但是事实上 GPU 和显示器的组合有很多种可能,如果 GPU 的帧率是 75FPS,显示器的刷新率是 60Hz,也会造成屏幕撕裂。

CPU GPU

如何解决屏幕撕裂

多个帧缓冲区

上面提到的屏幕撕裂的例子都是以单个帧缓冲区为前提条件,因为只有一个帧缓冲区,GPU 只能在这个帧缓冲区中绘制图像,就很容易造成上面所说的屏幕撕裂。

引入多个帧缓冲区可以有效地缓解屏幕撕裂,多个帧缓冲区分为帧缓冲区和多个后备缓冲区,GPU 在一个缓冲区绘制完之后,就到下一个缓冲区中绘制,这样 GPU 新绘制的图像帧就不会影响前面的图像帧。显示器每次会从帧缓冲区中取出图像显示,当帧缓冲区的图像显示之后,会将后备缓冲区的图像覆盖到帧缓冲区。

但多个帧缓冲区并不能真正的解决屏幕撕裂的问题,上面说到多个帧缓冲区只能缓解屏幕撕裂。以使用双缓冲区的设备为例,如果后备缓冲区绘制完成,就会开始将后备缓冲区的图像内容覆盖到帧缓冲区,此时帧缓冲区的图像还没有被显示器取出显示,在覆盖期间就会导致帧缓冲区的图像有 GPU 绘制的两帧的内容,如果这个时候显示器从帧缓冲区中取出图像同样会造成屏幕撕裂。

这里当然可以再增加帧缓冲区来解决问题,但是没有真正的解决根本问题,就是 GPU 和显示器的同步问题。为了解决屏幕撕裂的问题,引入了垂直同步(V-Sync),有了这个机制之后就能彻底解决屏幕撕裂。

垂直同步(V-Sync)

上面我们提到的 GPU 的绘制与显示器的显示是完全独立的,中间只通过帧缓冲区来交互。垂直同步就是保证 GPU 的绘制和显示器的显示是同步的,GPU 绘制好的每一帧图像显示器都会显示,不会出现一帧没有显示就被后面的帧覆盖的情况。

垂直同步的工作原理大概是:显示器在处理完当前图像帧,会向 GPU 发送 VSync 信号,当 GPU 接收到 VSync 信号之后,就会进行帧缓冲区的更新和新图像帧的绘制。这样就能保证 GPU 绘制的图像帧不会出现被覆盖的情况,也就解决了屏幕撕裂。

垂直同步理想的工作状态如下图所示:

VSync 理想情况

如上图所示,CPU 和 GPU 能在两个 VSync 信号之间将显示器要显示的图像帧准备完成,就能保证显示器流畅的刷新,我们就不会感受到卡顿的情况。

虽然垂直同步解决了 GPU 和显示器的同步问题,但是垂直同步也是有一定缺点的,显卡的性能再好也得看显示器的 VSync 信号的频率,所以垂直同步会限制 GPU 的性能;并且垂直同步也会消耗更多的计算资源,也会带来部分延迟。现在的设备通常是使用多缓冲区和垂直同步两种方式相结合,目前 iOS 设备使用的双缓冲区加垂直同步机制,Android 设备有使用双缓冲区或者三缓冲区加垂直同步机制。

目前有更好的显示技术可以解决 GPU 和显示器的同步问题:Nvidia 提出的专利技术 G-Sync 以及 AMD 提出开放标准 FreeSync。关于这两个技术可以看:显示器刷新率和显卡fps一定要很匹配吗?

卡顿的原因

上面我们提到理想状态下,显示器的刷新是流畅的。那在不理想的情况下就会造成要说的卡顿现象,专业的说法就是掉帧 🤨。

VSync 卡顿情况

从上图可以看出,CPU 和 GPU 在两个 VSync 信号之间不能将显示器要显示的图像帧准备好,就会造成掉帧的情况。如果 CPU 和 GPU 没有将图像帧准备好,这一帧就会被丢弃,等待下一次 VSync 信号的到来。显示屏就会保持当前的显示图像不变,如果这时界面是在不断变化的,我们就会感受到卡顿。

解决卡顿

卡顿产生的原因是因为 CPU 和 GPU 在两个 VSync 信号之间,没有完成显示图像的提交。要解决卡顿就需要了解 CPU 和 GPU 在两个 VSync 信号做了什么事情。

CPU 相关的操作

  1. 对象创建 - 对象的创建会分配内存、调整属性、甚至还有读取文件等操作,比较消耗 CPU 资源。

  2. 对象销毁 - 对象的销毁虽然消耗资源不多,但累积起来也是不容忽视的。通常当容器类持有大量对象时,其销毁时的资源消耗就非常明显。

  3. 布局计算 - 如果你的视图层级过于复杂,当视图呈现或者修改的时候,计算图层帧率就会消耗一部分时间。

  4. 解压图片 - PNG 或者 JPEG 压缩之后的图片文件会比同质量的位图小得多。但是在图片绘制到屏幕上之前,必须把它扩展成完整的未解压的尺寸(通常等同于图片宽 x 长 x 4个字节)。

  5. ....

GPU 相关的操作

  1. 纹理的渲染 - 所有的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。不论是提交到显存的过程,还是 GPU 调整和渲染 Texture 的过程,都要消耗不少 GPU 资源。

  2. 视图的混合 - 当多个视图重叠在一起显示时,GPU 会首先把他们混合到一起。如果视图结构过于复杂,混合的过程也会消耗很多 GPU 资源。

  3. 降低 GPU 性能的事情:

    • 太多的几何结构 - 这发生在需要太多的三角板来做变换,以应对处理器的栅格化的时候。

    • 重绘 - 主要由重叠的半透明图层引起。GPU 的填充比率(用颜色填充像素的比率)是有限的,所以需要避免重绘(每一帧用相同的像素填充多次)的发生。

    • 离屏绘制 - 这发生在当不能直接在屏幕上绘制,并且必须绘制到离屏图片的上下文中的时候。离屏绘制发生在基于 CPU 或者是 GPU 的渲染,或者是为离屏图片分配额外内存,以及切换绘制上下文,这些都会降低 GPU 性能。

    • 过大的图片 - 如果视图绘制超出 GPU 支持的 2048x2048 或者 4096x4096 尺寸的纹理,就必须要用 CPU 在图层每次显示之前对图片预处理,同样也会降低性能。

知道了 CPU 和 GPU 做的操作,就可以针对相应的操作来进行优化。例如:限制每一帧对象的创建、优化图片的处理等等。

Tip:GPU 和显卡的区别

GPU 即 Graphic Processing Unit,图像处理器,是整个显卡的核心。显卡是由 GPU、显存等等组成的。大部分情况下,我们所说 GPU 就等于指显卡,但是实际情况是 GPU 是显卡的一个核心组成部分。

相关链接