什么是离屏渲染
离屏渲染顾名思义就是屏幕外的渲染,即渲染的结果不会立即呈现到显示屏上。
正常情况下,当GPU渲染完成后将数据放在屏幕的帧缓冲区,不需要额外的渲染空间,我们知道iPhone的屏幕刷新率是60Hz,也就是说刷新一帧的时间约为16.7ms,每隔这段时间视频控制器就会去读一次帧缓冲区的内容来显示。
离屏渲染的触发是提前将比较消耗性能的视图提前渲染好,这样能减少卡顿现象。
如何触发离屏渲染
当问到如何会触发离屏渲染时,经常会看到“设置了cornerRadius就会触发离屏渲染”的回答,其实这种说法是不完全正确的。
接下来,我们通过一个实例来看看,到底是什么情况才会出现离屏渲染。打开模拟器离屏渲染显示的,Simulator->Debug->Color Off-scrren Rendered。
//定义了一个button,将button的背景颜色设置成红色并给button设置圆角
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
btn1.frame = CGRectMake(0, 0, 100, 100);
[btn1 setCenter:CGPointMake(SCREENWIDTH/2,SCREENHEIGHT/2)];
btn1.backgroundColor = [UIColor redColor];
btn1.layer.cornerRadius = 50;
//YES:剪裁超出父视图范围的子视图部分。NO:不剪裁超出父视图范围的子视图
btn1.clipsToBounds = YES;
[self.view addSubview:btn1];
执行结果如下图:
image.png
从图片的效果看,这并没有触发离屏渲染,可是代码中有设置button的圆角啊。这是怎么回事呢?先来看官方文档对于cornerRadius的描述。
Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.
从文中描述可以看到,当设置cornerRadius大于0时,默认情况只会对layer的背景和边框设置圆角,而不会对layer的contents设置圆角,除非同时设置layer.masksToBunds为true。
根据官方文档的提示,我们修改上面的代码,增加layer.masksToBunds=true,再来看看效果。
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
btn1.frame = CGRectMake(0, 0, 100, 100);
[btn1 setCenter:CGPointMake(SCREENWIDTH/2,SCREENHEIGHT/2)];
btn1.backgroundColor = [UIColor redColor];
btn1.layer.cornerRadius = 50;
//YES:剪裁超出父视图范围的子视图部分。NO:不剪裁超出父视图范围的子视图
btn1.clipsToBounds = YES;
btn1.layer.masksToBounds = YES;
[self.view addSubview:btn1];
效果如下:
image.png
此时,仍然没有触发离屏渲染,这是为什么呢?会不会是因为我们button的layer上没图片导致呢?在上面的基础上,给button添加图片试试。
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
btn1.frame = CGRectMake(0, 0, 100, 100);
[btn1 setCenter:CGPointMake(SCREENWIDTH/2,SCREENHEIGHT/2)];
[btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
btn1.layer.cornerRadius = 50;
//YES:剪裁超出父视图范围的子视图部分。NO:不剪裁超出父视图范围的子视图
btn1.clipsToBounds = YES;
btn1.layer.masksToBounds = YES;
[self.view addSubview:btn1];
效果如下:
image.png
从效果图可以看到,当设置了masksToBounds且同时设置图片,就会触发离屏渲染。
其实不光是图片,我们为当前视图添加有颜色、内容、边框等图像信息的子视图时也会触发离屏渲染。
触发离屏渲染的原因是什么?
首先,来说明一下CALayer,CALayer是由backgroundColor、contents、borderWidth&borderColor组成,如下图所示:
image.png
本来我们从后往前的绘制,绘制完一个图层之后就可以丢弃了,但是现在需要依次在offscreen buffer中保存,等待圆角、裁剪处理,即引发离屏渲染。
当没有contents时,绘制背景色、边框、背景色+边框,再加上圆角+裁剪,此时不管有没有设置masksToBounds。
当有contents时,需要先将背景色、边框、contents放在离屏缓冲区里面,再进行圆角、裁剪处理。此时就触发了离屏渲染。
图层的绘制是遵循“画家算法”,即先绘制场景中较远的物体,再绘制场景中较近的物体。
image.png
离屏渲染给我们带来方便的同时,也带来了严重的性能问题。由于离屏渲染中的离屏缓冲区是额外开辟的一个空间,当数据转存到Frame Buffer时,也是需要耗费时间的,所以转存的过程中也会存在掉帧的可能。另外,离屏缓冲区也不是无限大的,它的最大限制是屏幕的2.5倍。
既然知道离屏渲染会带来性能消耗,那为什么还要使用离屏渲染呢?
- 可以处理一些特殊的效果,这种效果并不是一次性能完成的,需要使用离屏缓冲区保存中间状态。这种情况的离屏渲染是系统自动触发的,如圆角、阴影、光栅化、高斯模糊等。
- 部分情况下可以提升渲染的效率,如果一个效果是多次实现,可以提前渲染保存在离屏缓冲区,以备复用,这种情况一般是开发者手动触发的。
iOS9系统之后,苹果对此进行优化。当没有设置backgroundColor且设置content时,是不会触发离屏渲染的。
UIImageView *img2 = [[UIImageView alloc]init];
img2.frame = CGRectMake(0, 0, 100, 100);
[img2 setCenter:CGPointMake(SCREENWIDTH/2, SCREENHEIGHT/2)];
img2.layer.cornerRadius = 50;
img2.layer.masksToBounds = YES;
img2.image = [UIImage imageNamed:@"btn.png"];
[self.view addSubview:img2];
效果如下:
image.png
如果只有单层layer需要进行圆角、裁剪处理,可以直接在缓冲区里面对数据进行处理,不需要额外使用离屏缓冲区。只有当多图层都需要进行圆角、裁剪处理时,才需要使用离屏缓冲区。
网友评论