Files
knowledge-kit/Chapter1 - iOS/1.101.md
LiuBinPeng 2864a120ff feat: APM
2022-03-30 16:58:48 +08:00

107 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 离屏渲染
## 什么是离屏渲染
如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的 Frame Buffer帧缓冲区作为像素数据存储区域然后由显示控制器把帧缓冲区的数据显示到屏幕上。如果因为面临一些限制比如阴影、光栅、遮罩等CPU 无法把渲染结果直接写入 Frame Buffer而是先暂时把中间的临时状态保存在额外的内存区域之后再写入 Frame Buffer那么这个过程被称为离屏渲染。
系统如果没有直接把渲染结果直接写入到 GPU FrameBuffer 中则认为发生了一次离屏渲染。离屏渲染指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
## 哪些 case 会触发离屏渲染
- 光栅化shouldRasterize
属于系统的优化机制,当开启光栅化的时候,系统会将该图片以 BitMap 的形式缓存起来缓存时间为100ms当下次需要显示这张图片的时候系统会从缓存中获取出这张图片传递给 GPU不需要 GPU 再次渲染这部分图层,达到减少 GPU 运算量的目的。
应用场景非常小因为时间仅为100ms且会触发离屏渲染。
```
self.imageView.layer.shouldRasterize = YES
```
- 遮罩mask
遮罩 Mask 相当于增加了 GPU 绘制复杂度,无法一次计算完成,需要增加一块新的帧缓冲区计算。
```
CALayer *layer = [CALayer layer];
layer.frame = // ...;
self.imageView.layer.mask = layer;
```
- 阴影(shadow)
因为阴影位于视图下层,绘制完阴影,界面还没有绘制完主要内容,所以需要一块额外的帧缓冲区中转。
```
self.imageView.layer.shadowColor = [UIColor redColor].CGColor;
self.imageView.layer.shadowOpacity = 0.2;
```
如果给阴影设置 shadowPath 则不会触发离屏幕渲染。因为 shadowPath 预先告诉 CoreAnimation 框架阴影的几何形状,因此不需要依赖 layer 本体,可以独立渲染。
- 抗锯齿(竖直图片旋转后会出现锯齿)
可能会触发离屏渲染,假如 UIImageView 控件的尺寸和图片素材的大小不一致,比如设置旋转,则会触发离屏渲染,否则不会。
抗锯齿的计算量很大,因此需要额外的帧缓冲区保存计算结果,则触发离屏渲染。
```
CGFloate angle = M_PI/60.0;
self.iamgeView.layer setTransform3DRotate(self.imageView.layer.transform, angle, 0, 0, 1);
self.iamgeView.layer.allowsEdgeAntialiasing = YES;
```
- 不透明
当 alpha 为1的时候不会触发离屏渲染。
当 alpha 不为1的时候且设置了父视图的 allowsGroupOpacity 为 YES则会触发离屏渲染。因为父视图的 allowsGroupOpacity 为 YES则代表子视图的透明度是否和父视图一样一样则需要额外的帧缓冲区计算。
```
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,0, 10, 20)];
view.backgroundColor = [UIColor redColor];
[self.imageView addSubview: view];
self.iamgeView.alpha = 1;
self.iamgeView.layer.allowsGroupOpacity = YES;
```
- 圆角
当给视图设置了背景颜色且设置了圆角,则会触发离屏渲染。
UILabel 比较特殊,给 UILabel 设置 backgroundColor 其实就是给 UILabel 的 contents 设置背景颜色contents 层级比 layer 高,所以 UILabel 整体显示为红色。
下面显示结果为:红绿
```
self.label.backgroundColor = [UIColor redColor];
self.label.layer.backgroundColor = [UIColor greenColor].CGColor;
self.label.layer.cornerRadius = YES;
self.iamgeView.backgroundColor = [UIColor redColor];
self.iamgeView.layer.backgroundColor = [UIColor greenColor].CGColor;
self.imageView.layer.cornerRadius = YES;
```
## 离屏渲染的影响?
离屏渲染,指的是 GPU 在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。由上面的一个结论视图和圆角的大小对帧率并没有什么影响,数量的多少才显著影响性能。可以知道离屏渲染耗时是发生在离屏这个动作上面,而不是渲染。为什么离屏这么耗时?原因主要有创建缓冲区和上下文切换。创建新的缓冲区代价都不算大,付出最大代价的是上下文切换。
上下文切换不管是在GPU渲染过程中还是广为人知的进程切换上下文切换都是一个相当耗时的操作。首先要保存当前屏幕渲染环境然后切换到一个新的绘制环境申请绘制资源初始化环境然后开始一个绘制绘制完毕后销毁这个绘制环境如需要切换到On-Screen Rendering 或者再开始一个新的离屏渲染重复之前的操作。
一次mask发生了两次离屏渲染和一次主屏渲染。即使忽略昂贵的上下文切换一次mask需要渲染三次才能在屏幕上显示这已经是普通视图显示3陪耗时若再加上下文环境切换一次mask就是普通渲染的nn>3倍以上耗时操作
正常流程App Source Code -> CPU -> Frame Buffer -> Dispaly
离屏渲染流程App Source Code -> CPU -> Off Screen Frame Buffer -> Frame Buffer -> Dispaly
## 如何优化?
- 针对 shadow 可以增加 shadowPath
- 针对圆角可以增加贝塞尔曲线或者一张图片实现(类似遮罩)。
## 特殊的离屏渲染
- drawRect 是一种特殊情况,因为是依赖 Core Graphics 将绘制结果保存在 backing store 中,是 CPU 层面的操作,离屏渲染是 GPU 层面的。
1. 重写 drawRect 方法的时候系统会为该 View 创建一块内存区域,渲染结果先保存到该内存区域,最后将该内存区域的结果写入到 FrameBuffer 中,但是该现象不属于严格意义的离屏渲染。
## 画家算法
画家算法,也叫做优先填充算法,是计算机三维图形学中处理可见性问题的一种解法(三维场景投影到二维平面上)。画家算法先将场景中的多边形按照深度进行排序,然后按照由远及近的顺序进行描述,这样可以将不可见的部分覆盖,解决“可见性”问题(也就是一层层画布进行绘制,最后叠加)。
GPU 利用片元将整个图片分为一个个像素,并且并行计算了每一个像素的颜色。在同一个栅格内可能存在多个视图,根据距离眼睛的远近,存在多个不同的物体。显而易见,我们应该将最近物体的颜色作为该栅栏的颜色,后面物体的颜色应该被遮挡(如果后面物体的颜色被传递给片元着色器,这时候就是一个显示错误,比如我们打游戏的时候可以看到墙后的人)
画家算法带来2个问题。第一个问题是相互交错的物体类似于死循环无法pick出谁应该最先被渲染按照画家算法这样的情况GPU 会无从下手。所以早期的时候,设计师总是避免这样相互交错的设计。
第二个问题是过度绘制,因为画家算法总是一层层绘制,所以存在重合叠加的情况,层级较低的物体总是会被过度绘制,浪费资源。
因为 GPU 的设计是并发、无序的,所以我们期望的画家算法是不希望浪费、等待,同时为了绘制速度,所以在此基础上引入了 Depth Buffer 和 Early-Z 和深度缓冲。
画家算法有个缺点,就是当后面的图层开始渲染时,是无法回过头去处理之前的图层,这就对于一些前后依赖的图层时,无法实现,因此需要申请一块额外的帧缓冲区来完成,比如阴影、圆角。
明白了画家算法的工作原理,也就明白了为什么会发生离屏渲染。
- 离屏渲染需要创建额外的帧缓冲区
- 渲染相关的上下文对象、帧缓冲区都比较大,切换会带来性能损耗
- 内存拷贝,需要将临时帧缓冲区的内容拷贝到真正的帧缓冲区。单帧渲染都会比较耗费性能了,如果屏幕上多个视图渲染都存在离屏渲染,整个界面会发生卡顿。
## 如何检测离屏渲染
Xcode 就提供了检测功能,打开路径为: Xcode -> Debug -> View Debugging -> Rendering -> Color Offscreen-Renderer Yellow
## 引申阅读
- [实时渲染管线中的光源渲染问题](https://zhuanlan.zhihu.com/p/392748735)
- [蒙特卡罗方法](https://wiki.mbalib.com/wiki/蒙特卡罗方法)
- [抗锯齿方法](https://zhuanlan.zhihu.com/p/56385707)