一、CPU VS GPU
1. 对于计算机硬件,绘图和动画有哪两种处理方式?为什么处理图像尽量使用 GPU?
- CPU:中央处理器
- GPU:图形处理器
- 总体来说,我们可以用软件(CPU)做任何事情,但是对于图像处理,通常用硬件(GPU)会更快,
因为 GPU 使用图像对高度和浮点运算做了优化。
2. 就 CPU 和 GPU 而言,我们所说的动画性能优化,其实是让这两者怎么配合?
- 大多数动画性能优化都是关于
合理利用 GPU 和 CPU,使得它们都不会超出负荷。 - 于是我们首先需要知道 Core Animation 是如何在这两个处理器之间分配工作的。
3. Core Animation 处在 iOS 的核心地位:应用内和应用间都会用到它,那对于 iOS 它处于什么层级呢?
- 一个简单的动画可能同步显示多个 app 的内容,例如当在 iPad 上多个程序之间使用手势切换,会使得多个程序同时显示在屏幕上。
- 在一个特定的应用程序中用代码实现它是没有意义的,因为在 iOS 中不可能实现这种效果(APP 都是被沙箱管理,不能访问别的视图)。
- 动画和屏幕上组合的图层实际上被一个单独的进程管理,而不是你的应用程序。这个进制就是所谓的
渲染服务。 - 在 iOS5 和之前的版本这个
渲染服务叫做SpringBoard 进程(同时管理着 iOS 的主屏)。在 iOS6 之后的版本叫做BackBoard。
4. 一个动画的展示过程?
在应用程序内,当运行一段动画时候,这个过程会被分离成如下四个阶段
- ①
布局- 这是准备你的视图/图层的层级关系,以及设置图层属性(位置,背景色,边框等等)的阶段。 - ②
显示- 这个图层的寄宿图片被绘制的阶段。绘制可能涉及你的-drawRect:和-drawLayer:inContext:方法的调用路径。 - ③
准备- 这是Core Animation准备发送动画数据到渲染服务的阶段。这同时也是 Core Animation将要执行一些别的事务例如解码动画过程。 - ④
提交- 这是最后的阶段,Core Animation 打包所有图层和动画属性,然后通过 IPC(进程间通信)发送到渲染服务进行显示。
但是上面四个阶段仅仅发生在你的应用程序之内,在动画在屏幕上显示之前仍然有更多的工作。一旦打包的图层和动画到达
渲染服务进程,它们会被反序列化来形成另一个叫做渲染树的图层树。使用这个树状结构,渲染服务对动画的每一帧做出如下工作:
- ⑤ 对所有的图层属性计算中间值,设置 OpenGL 几何形状(纹理化的三角形)来执行渲染
- ⑥ 在屏幕上渲染可见的三角形
所以一共有六个阶段;最后两个阶段在动画过程中不停地重复。
5. 上面六个阶段,哪些在 CPU?哪些在 GPU?哪些阶段开发者能介入?
- 前五个阶段都在软件层面处理(通过 CPU),只有最后一个被 GPU 执行。
- 而且,你真正只能控制前面两个阶段:布局和显示。Core Animation 框架在内部处理剩下的事务,你也控制不了它。
- 这个并不是个问题,因为在布局和显示阶段,你可以决定哪些由 CPU 执行,哪些交给 GPU 去做。那么该如何判断呢?
6. GPU 主要干些什么事情?
- GPU 为一个具体的任务做了优化:它用来采集图片和形状(三角形),运行变换,应用纹理和混合然后把它们输送到屏幕上。
7. 有一些事情会降低(基于 GPU)图层绘制(至少说两点)?
- 重绘 - 主要由重叠的半透明图层引起。GPU 的
填充比例(用颜色填充像素的比率)是有限的,所以需要避免重绘(每一帧用相同的像素填充多次)的发生。 - 离屏绘制 - 这发生在当不能直接在屏幕上绘制,并且必须回到离屏图片的上下文的时候。
- 过大的图片 - 如果视图超过 GPU 支持的2048x2048或者4096x4096尺寸的纹理,就必须要用CPU在图层每次显示之前对图片预处理,
8. 哪些 CPU 的操作会延迟动画的开始时间,从而造成卡顿(至少说两点)?
- 布局计算 - 如果你的视图层级过于复杂,当视图呈现或者修改的时候,计算图层帧率就会消耗一部分时间。
- 大量视图懒加载 - iOS只会当视图控制器的视图显示到屏幕上时才会加载它,但是同一瞬间太多视图需要加载了。
- 解压图片 - PNG或者JPEG压缩之后的图片文件会比同质量的位图小得多。但是在图片绘制到屏幕上之前,必须把它扩展成完整的未解压的尺寸(通常等同于图片宽 x 长 x 4个字节)。
二、离屏渲染
1. 如何通过iOS模拟器判断一个视图,是否发生了离屏渲染?
- 开启 iOS 模拟器的下面这个选项,如果视图呈换色,就表明发生了离屏渲染

image.png
2. 为什么有些操作会发生离屏渲染?
- 图层的叠加绘制大概遵循
“画家算法” -
画家算法:先绘制场景中离观察者较远的物体,再绘制较近的物体。
先绘制红色部分,再绘制⻩色部分,最后再绘制灰⾊部分,即可解决隐藏面消除的问题。即将场景按照物理距离和观察者的距离远近排序,由远及近的绘制即可。

image.png
当我们设置了cornerRadius以及masksToBounds进行圆角+裁剪时,masksToBounds裁剪属性会应用到所有的图层上。

image.png
本来我们从后往前绘制,绘制完一个图层就可以丢弃了。但现在需要依次在 Offscreen Buffer中保存,等待圆角+裁剪处理,即引发了 离屏渲染 。
3. 为什么离屏渲染会降低效率?
- 要创建离屏缓冲区
- 要进行缓冲区上下文的切换
- 需要离屏渲染的计算通常比较复杂(不符合画家算法)

渲染结果先经过了离屏buffer,再到frame buffer
4. 同时设置 cornerRadius 和 masksToBounds,也就是圆角 + 裁剪,一定会触发离屏渲染吗?
- 不一定
- 如果仅仅裁剪了
一个背景颜色或者一个图片,不会触发离屏渲染(其实就是符合画家算法,不需要另起离屏缓冲区)
5. 小技巧:对于只有一张图片的 UIButton,如何设置圆角而不触发离屏渲染?
- 下面这种做法
不会触发离屏渲染
// 下面这种做法不会触发离屏渲染
btn.imageView.clipsToBounds = YES;
btn.imageView.layer.cornerRadius = 30;
- 下面这种做法
会触发离屏渲染
// 下面这种做法会触发离屏渲染
btn.clipsToBounds = YES;
btn.layer.cornerRadius = 30;