一、什么是离屏渲染
离屏渲染:意为在屏幕之外的渲染流程,渲染的流程结果并不会直接显示在屏幕上,而是将渲染结果添加进一个离屏渲染缓冲区里,在合适的时机将缓冲区的数据加载在屏幕上。
正常情况下GPU渲染完一帧图像后,会将图像数据存到帧缓冲区里去,然后在进行第二次渲染放进另一个缓冲区里。(详细内容见上篇),但是在某些情况下,苹果无法在一帧内渲染出想要的结果,就需要用到这个离屏渲染的机制来得到我们想要的结果。
二、离屏渲染的触发
我们在学习过程中,会听到这样一个知识点:对一个控件进行添加一个圆角后,就会触发离屏渲染。 那么到底会不会触发呢? 这里来进行一下测试。
创建四种情况:
- 有背景图,无背景色,圆角,view切边
- 无背景图,有背景色,圆角,view切边
- 有背景图,有背景色,圆角,layer切边
- 有背景图,无背景色,圆角,layer切边
//1. 有背景图,无背景色,圆角,view切边
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.jpg"] forState:UIControlStateNormal];
btn1.clipsToBounds = YES;
//2. 无背景图,有背景色,圆角,view切边
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. 有背景图,有背景色,圆角,layer切边
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.jpg"];
//4. 有背景图,无背景色,圆角,layer切边
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.jpg"];
运行后,选中模拟器 在 Debug -> Color Off-screen Rendered 打开选项,得到的界面:
黄色标记的视图就是触发了离屏渲染,所以得到结论:并不是所有的圆角都会触发离屏渲染。
2.1、真正离屏渲染的触发
前文都讲到纹理是如何渲染到屏幕上的,简要一下流程如下:
app会把需要渲染的数据提交到帧缓冲区(Frame Buffer),然后屏幕不断的在帧缓冲区去显示数据。但是触发了离屏渲染之后,app并不会把渲染的数据提交到帧缓冲区(Frame Buffer),而是先把数据提交一个额外的缓冲区,即离屏渲染缓冲区(offscreen Buffer)里,把几个需要渲染的数据进行叠加之后再通过屏幕显示出来。
详细的流程如下:
- openGL -> 顶点着色器 -> 片元着色器 -> 将①数据存到离屏渲染缓冲区
- openGL -> 顶点着色器 -> 片元着色器 -> 将②数据存到离屏渲染缓冲区
- openGL -> 顶点着色器 -> 片元着色器 -> 将①②数据合并起来放到在帧缓冲区进行显示
为什么说要优化离屏渲染:
- 离屏渲染需要额外的存储空间(离屏渲染缓冲区)
- 将离屏渲染缓冲区转存到帧缓冲区是需要时间的
- 大量的离屏渲染将会为内存造成较大的压力
- 离屏渲染缓冲区的空间是有限的,为屏幕的2.5倍
离屏渲染的意义:
- 为了达到某种特殊效果,需要使用额外的缓冲区来保存中间状态,不得不使用离屏渲染缓冲区(系统自动触发)
- 效率将会有优势:多次出现在屏幕上的效果可以提前渲染存储到离屏渲染缓冲区,达到复用目的(需要手动触发)
离屏渲染的简要实现流程: 要对图片添加某种特殊效果,就会使用到离屏渲染:
总结:app渲染的时候需要对其进行额外的渲染和合并,才会开启离屏渲染利用离屏渲染缓冲区来存放合并多个需要渲染的数据,再放进帧缓冲区里,进行显示。
2.2、离屏渲染的第二种原因:shouldRasterize光栅化
官方文档有对 shouldRasterize 一段说明:
When the value of this property is YES, the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content.
shouldRasterize光栅化使用建议:
- 如果layer不能被复用,则没有必要打开光栅化
- 如果layer不是静态的,需要被频繁修改,比如处于动画之中,那么开启离屏渲染反而影响效率
- 离屏渲染缓存内容有时间限制,缓存内容100ms内没有被使用,那么它就会丢弃,无法进行复用
- 离屏渲染存储空间有限,超过2.5倍屏幕像素大小的话,也会失效,并且无法复用
三、圆角触发离屏渲染的条件
一个图片控件包含了背景色、图片内容、边框。当显示这样的图片的时候,它是由三层来进行分别处理累加的。
此时对它进行设置一个属性(为layer添加圆角):
view.layer.cornerRadius = 2;
先来看一下官方对cornerRadius的说明:
设置layer.cornerRadius只会设置backgroundColor和border的圆角。不会设置content的圆角;除非同时设置了layer。masksToBounds为true(对应view中的clipsToBounds属性)
所以view.layer.masksToBounds = ture进行切边是会触发离屏渲染的
四、离屏渲染的逻辑
在图片中由三个图片构成,来自著名的算法 -- 画家算法。
它指的是当多个图层进行叠加的时候,优先绘制较远的图层,依次这样进行绘制。
4.1、不开启离屏渲染的绘制逻辑
在屏幕上进行三次渲染来显示出图案,此时如果需要对齐进行添加圆角操作,因为已经丢弃掉数据,是无法对其进行操作的。
4.2、开启离屏渲染的绘制逻辑
在屏幕上绘制出三个layer后,会将数据全部存放进离屏渲染缓冲区。如过对齐进行圆角+裁剪后触发离屏渲染:
它可以在离屏渲染缓冲区中读取设置后再次合并起来进行显示
五、iOS上圆角处理手段参考
- _imageView.clipsTobounds = YES; _imageView.layer.correnrRadius = 4.0
- 使用贝塞尔曲线来进行圆角处理
- 使用圆角UI图
其他:可以参考YYImage的处理方式
六、常见的出发离屏渲染的情况
- 使用了mask的layer(layer.mask)
- 需要进行裁剪的layer(layer.masksToBounds/view.clipsToBounds)
- 设置了组透明度为YES,且透明度不为1的layer(layer.allowsGroupOpacity/layer.opacity)
- 添加了投影的layer(layer.shadowOffset)
- 采用了光栅化的layer(layer.shouldRasterize)
- 绘制了文字的layer(UILabel,CATextLayer,Core Text等)