1. 普通渲染
-
正常情况下,渲染一张图片的流程:APP需要的渲染数据->CPU计算->GPU渲染->保存在帧缓存区(Frame Buffer)->视频控制器读取帧缓存区数据并显示在屏幕上。简要流程如下图所示:
正常渲染流程.png - 在GPU的渲染过程中,显示到屏幕上的图像是遵循“画家算法”由远及近的顺序,依次将数据存储到帧缓存区。
- 视频控制器从帧缓存区读取一帧数据,并将其显示到屏幕上之后,会立即抛弃这帧数据,不会做保留操作,这样做的目的是为了节约存储空间,也能保证每帧数据互不影响。
2. 离屏渲染
-
与普通渲染不同,离屏渲染的流程:APP需要渲染的数据->CPU计算->GPU渲染->离屏渲染缓冲区(Offscreen Buffer)->保存在帧缓存区(Frame Buffer)->视频控制器读取帧缓存区数据并显示在屏幕上。简要流程如下图所示:
离屏渲染流程.png
2.1 影响
- 与非离屏渲染模式相比,多了进行额外的渲染合并操作,是对多个texture进行合并的过程。多了这么一步,所以说对性能要求更高一些,也更容易出现掉帧的情况。
- 增加了额外的存储空间(离屏渲染缓冲区),其空间最大限制为屏幕像素的2.5倍。
2.2 使用原因
情况1
- 可以显示一些特殊效果,需要用到Offscreen buffer来保存中间状态。一般都是系统去触发,例如对layer层相关处理:包括圆角、阴影、mask等等。iOS系统扁平化后出现的高斯模糊也是利用离屏渲染方式。
圆角处理:
设置圆角为什么会触发离屏渲染呢?这个要从layer的结构说起。
layer结构中包含3部分:
backgroundColor
contents
boarder相关信息(borderWidth和borderColor)
设置圆角代码,这个大家应该都知道:
view.layer.cornerRadius = 1
我们先看看官方文档中cornerRadius相关说明:
cornerRadius.png
Discussion中的说明:
设置cornerRadius超过0.0,不会影响contents,但是会影响background color和border。如果设置了masksToBounds会对content进行裁剪。
所以说触发离屏渲染的主要原因:
masksToBounds=YES;
这就是模拟器开启离屏渲染模式中说明的为什么要设置masksToBounds的原因。masksToBounds需要对layer上的所有内容进行裁剪,过程中需要对中间值进行保存。所以进行了离屏渲染操作。
注意:
如果说layer图层比较简单,也是不会触发离屏渲染的。例如:UIImageView设置cornerRadius和masksToBounds是不会触发离屏渲染的,如果再对UIImageView设置背景色,则会触发。
self.view.backgroundColor = [UIColor grayColor];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1"]];
imageView.frame = CGRectMake(100, 100, 100, 100);
imageView.layer.cornerRadius = 30;
imageView.layer.masksToBounds = YES;
[self.view addSubview:imageView];
UIImageView *imageView2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1"]];
imageView2.backgroundColor = UIColor.redColor;
imageView2.frame = CGRectMake(100, 250, 100, 100);
imageView2.layer.cornerRadius = 30;
imageView2.layer.masksToBounds = YES;
[self.view addSubview:imageView2];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setBackgroundImage:[UIImage imageNamed:@"1"] forState:UIControlStateNormal];
btn.layer.cornerRadius = 30;
btn.layer.masksToBounds = YES;
btn.frame = CGRectMake(100, 400, 100, 100);
[self.view addSubview:btn];
代码中有三个控件,前两个是UIImageView,最后一个是UIButton。
第一个UIImageView设置了图片没有设置背景色,没有触发离屏渲染。
第二个UIImageView设置了图片和背景色,触发了离屏渲染。
最后一个UIButton,设置图片没有设置背景色,触发了离屏渲染。原因是我们看到UIButton是由它的layer和UIImageView的layer混合起来的效果(UIButton有imageView),所以设置圆角的时候会触发离屏渲染。
情况2
- 如果texture会多次显示到屏幕上,可以使用offscreen buffer进行提前渲染,并且保存在其中,达到复用的效果。layer中有shouldRasterize属性。这是一种主动行为,是为了提高复用的效率。通常是设置layer的shouldRasterize属性来实现。
shouldRasterize 光栅化
shouldRasterize官方文档
shouldRasterize.png
开启后,会将layer作为位图保存下来,下次直接与其他内容进行混合。这个保存的位置就是OffscreenBuffer中。这样下次需要再次渲染的时候,就可以直接拿来使用了。
shouldRasterize使用建议:
layer不复用,没必要使用shouldRasterize;
layer不是静态的,也就是说要频繁的进行修改,没必要使用shouldRasterize;
时间方面:离屏渲染缓存有100ms时间限制,超过该时间的内容会被丢弃,进而不能达到复用的目的;
空间方面:离屏渲染空间是屏幕像素的2.5倍,如果超过也无法复用。
2.3 如何避免
- 为了避免设置圆角时引起的离屏渲染操作,可以用一下方案代替直接设置圆角的操作:
- 直接更换资源,让UI提供带圆角的图片。
- 使用layer.mask属性,增加一个和背景色相同的遮罩覆盖上层,盖住四个角,营造出圆角的形状。但这种方式难以解决背景色为图片或渐变色的情况。
- 使用贝塞尔曲线绘制闭合圆角的矩形,在上下文中设置只有内部可见,再将不带圆角的 layer 渲染成图片,添加到贝塞尔矩形中。这种方法效率更高,但是 layer 的布局一旦改变,贝塞尔曲线都需要手动地重新绘制,所以需要对 frame、color 等进行手动地监听并重绘。
- CoreGraphics重写 drawRect:,用 CoreGraphics 相关方法,在需要应用圆角时进行手动绘制。不过 CoreGraphics 效率也很有限,如果需要多次调用也会有效率问题。
2.4 产生原因总结
- 设置layer.mashsToBounds/view.clipsToBounds
- 设置layer.mask
- 设置layer.shadow等相关属性
- 设置layer.shouldRasterize光栅化
- 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/layer.opacity)
- 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
网友评论