1.离屏渲染的定义
正常情况下,在GPU渲染的过程中,会遵循‘画家算法’按次序由远及近的一层一层将结果放置到帧缓存区中,当需要时屏幕就会读取显示,然后将其从帧缓冲区中丢弃,周而复始。而某些时候,当我们要实现实现一些特殊效果,并且这些特殊效果不是一个图层就可以画出来,需要几次结果才能到,此时就需要使用一个东西来保存中间状态,这个东西就是离屏渲染缓冲区,这种机制也就叫做离屏渲染。
2.离屏渲染的工作流程
不使用离屏渲染,图像/图形的渲染流程为:app-> FrameBuffer(帧缓冲区) -> 屏幕。
渲染流程.png
当触发了离屏渲染之后,图像/图形的渲染流程变成了:app-> offscreen Buffer(离屏缓冲区) 组合 -> FrameBuffer(帧缓冲区) -> 屏幕
1732746a82832e5d.png
举个例子,我们现在绘制一个图形,他有三个subLayer,如果正常渲染,会按顺序直接从帧缓冲区中区中取出,显示到屏幕上。若此时当我们需要给他添加圆角,开启离屏渲染,首先就会在offscreen Buffer中先分别缓存这三个subLayer,在其中分别绘制圆角,再取出合并,传递到帧缓冲区,最后显示到屏幕上。
3.离屏渲染的优缺点
通过离屏渲染,可以帮助我们比较轻松的是实现一些特殊效果,阴影,毛玻璃等等。同时需要多次使用的效果,提前渲染存入离屏缓冲区,然后复用来提高效率。
可是离屏渲染也有不少的问题:
1.离屏渲染,需要开启额外的存储空间,存储空间需要消耗内存,所以大量的离屏渲染会导致内存暴涨,从而产生掉帧等性能问题。
2.offscreen Buffer的内存空间也有限制,只有当前屏幕像素的2.5倍。
3.offscreen Buffer中缓存时间也有限制,100ms,超过时间就会失效。
4.离屏渲染触发的原理探索
首先我们看一个问题,设置圆角一定会触发离谱渲染吗?可能很多人都会认为设置圆角就会触发离屏渲染,但其实这个说法是不准确的,因为圆角触发离屏渲染也是有条件的。我们看下面的例子。
//1.按钮存在背景图片
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
btn1.frame = CGRectMake(100, 30, 100, 100);
btn1.layer.cornerRadius = 50;
[self.view addSubview:btn1];
[btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
btn1.clipsToBounds = YES;
//2.按钮不存在背景图片
UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
btn2.frame = CGRectMake(100, 180, 100, 100);
btn2.layer.cornerRadius = 50;
btn2.backgroundColor = [UIColor blueColor];
[self.view addSubview:btn2];
btn2.clipsToBounds = YES;
//3.UIImageView 设置了图片+背景色;
UIImageView *img1 = [[UIImageView alloc]init];
img1.frame = CGRectMake(100, 320, 100, 100);
img1.backgroundColor = [UIColor blueColor];
[self.view addSubview:img1];
img1.layer.cornerRadius = 50;
img1.layer.masksToBounds = YES;
img1.image = [UIImage imageNamed:@"btn.png"];
//4.UIImageView 只设置了图片,无背景色;
UIImageView *img2 = [[UIImageView alloc]init];
img2.frame = CGRectMake(100, 480, 100, 100);
[self.view addSubview:img2];
img2.layer.cornerRadius = 50;
img2.layer.masksToBounds = YES;
img2.image = [UIImage imageNamed:@"btn.png"];
结果是,第1个和第三个触发了离屏渲染,2,4没有触发,但我们看到所有的控件都设置了圆角,但却不是全都出发了离屏渲染,为什么呢?
我们看下CALayer的图层结构和cornerRadius的文档:
CALayer.png
cornerRadius.png
可以看到,在cornerRadius的文档中明确说明对cornerRadius的设置只对 CALayer 的backgroundColor和borderWidth&borderColor起作用,如果contents有内容或者内容的背景不是透明的话,只有设置masksToBounds为 true 才能对content生效,此时也就会产生离屏渲染。这也就说明了上面代码为什么1和3触发了离屏渲染,而2和4没有触发离屏渲染。综上所述,我们也可以得到一个结论:离屏渲染只存在于有多个图层需要合成的情况,如果没有图层合成,渲染结果就直接提交到帧缓冲区。
5.离屏渲染触发的几种情况
- 使⽤了 mask 的 layer (layer.mask)
- 需要进⾏裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
- 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/
layer.opacity) - 添加了投影的 layer (layer.shadow*)
- 采⽤了光栅化的 layer (layer.shouldRasterize)
- 绘制了⽂字的 layer (UILabel, CATextLayer, Core Text 等)
6.总结
离屏渲染是系统触发,触发了之后才有离屏缓冲区,他在某些程度上可以有助于提高效率,但是大量的使用也会造成不少的性能问题。同时离屏渲染只有在当要进行的渲染操作需要对一个组合图层进行操作的时候,也就是说当你无法仅仅使用FrameBuffer来画出最终结果,那就只能另开一块内存空间offscreen Buffer来储存这些中间结果,以达到我们需要的渲染效果。









网友评论