在平时的开发过程中,我们经常会听到离屏渲染这个词,在面试中也会经常被面试官问到,那么在iOS开发中到底什么是离屏渲染?离屏渲染有什么性能问题?离屏渲染是否应该完全禁止呢?

一、iOS渲染流程梳理

iOS开发中,将图像显示到屏幕上有两种方式:

1、正常渲染流程




iOS SceneKit 渲染全景图 ios渲染过程_圆角


2、离屏渲染流程


iOS SceneKit 渲染全景图 ios渲染过程_iOS SceneKit 渲染全景图_02


二、离屏渲染的性能问题

2.1 离屏渲染存在的性能问题

1、相比于正常的渲染流程,离屏渲染需要额外创建一个缓冲区,需要多耗费一些空间;

2、触发离屏渲染后,需要先从Frame Buffer切换到Off-Screen Buffer,渲染完毕后再切换回Frame Buffer,这一过程需是比较耗费性能的,因为要来回切换上下文;

3、数据由Off-Screen Buffer取出,再存入Frame Buffer也需要耗费时间,这样增加了掉帧的可能性;

4、离屏缓冲区存在空间限制,即屏幕像素的2.5倍,当大于这一值时便不会触发离屏渲染。

2.2 为何要使用离屏渲染

既然离屏渲染存在这么多的性能问题,为什么依然存在呢?主要有一下两点原因:

1、有些后续经常用到的图层数据,可以先缓存在离屏缓存,用到时直接复用

2、存在一些特殊效果,正常流程无法完成,必须使用离屏渲染,比如圆角、阴影和遮罩、高斯模糊、半透明图层混合等

正常的渲染流程采用油画算法由远及近的渲染图层,当一个图层显示到屏幕上后,帧缓冲区会立即删除这一图层的数据


iOS SceneKit 渲染全景图 ios渲染过程_图层_03


例如将这张图显示到屏幕上可以分为两步:

1、先绘制黄色背景图层,显示到屏幕上后,删除帧缓冲区中黄色图层的数据

2、再渲染蓝色图层,显示蓝色图层到屏幕后,删除帧缓冲区中蓝色图层数据

如果给图层设置了特殊效果则有可能需要触发离屏渲染,以圆角为例


iOS SceneKit 渲染全景图 ios渲染过程_iOS SceneKit 渲染全景图_04


我们想要是如右图所示的效果,设置圆角后包括子视图也进行圆角裁剪。 但是按照正常流程显示完黄色图层后,在渲染蓝色图层进行圆角设置时(超出时按圆角裁剪,未超出则不需要裁剪),已经找不到黄色图层的数据。因此,需要增加离屏缓冲区,将后续要用到的图层数据先缓存起来,在后续用到时进行渲染显示。

三、离屏渲染的触发及检测

3.1 离屏渲染检测

1、模拟器下检测:Simulator --> Debug --> Color Off-screen rendered,模拟器下只需要设置模拟器一次就可以


iOS SceneKit 渲染全景图 ios渲染过程_ios view 切上部分圆角_05


2、真机下检测:XCode --> Debug --> View Debugging -->Rendering --> Color Offscreen-Rendered Yellow,真机下每次运行后都要在XCode中设置一下生效


iOS SceneKit 渲染全景图 ios渲染过程_图层_06


检测结果如果覆盖有黄色图层,则表示产生了离屏渲染,否则没有产生离屏渲染


iOS SceneKit 渲染全景图 ios渲染过程_iOS SceneKit 渲染全景图_07


3.2 离屏渲染触发及建议

1、如上文所述,实现一些特殊效果例如圆角、阴影和遮罩、高斯模糊、半透明图层混合等

2、设置view.layer.shouldRasterize 为 true时,会触发离屏渲染

shouldRasterize 光栅化使用目的: 通过开辟离屏缓冲区缓存图像,以便将来使用,提升性能。但是如果缓存的图像会经常被更改,则开启离屏缓存区反而会降低性能。


iOS SceneKit 渲染全景图 ios渲染过程_ios view 切上部分圆角_08


因此对于是否开启shouldRasterize有以下建议:

  • 如果缓存的图像在之后用不到或很少用到(100ms内用不到),则不需要开启shouldRasterize
  • 如果缓存的图像会经常发生变动,比如本身处于动画中,或者像tabeleView的cell的上图片可能经常改变,则不要开启shouldRasterize
  • 缓存的图像过大,超过屏幕像素的2.5倍,不会触发离屏渲染,所以开启shouldRasterize也没有效果

四、iOS设置圆角触发离屏渲染原因

我们以UIButton和 UIImageView为例:

//1.按钮存在背景图片    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];    btn1.frame = CGRectMake(100, 30, 100, 100);    btn1.layer.cornerRadius = 50;    [self.view addSubview:btn1];//    [btn1 setTitle:@"超出边界了会离屏渲染" forState:UIControlStateNormal];//    [btn1 setTitleColor:[UIColor redColor] forState:UIControlStateNormal];    [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"];复制代码

这段代码的运行效果如下:


iOS SceneKit 渲染全景图 ios渲染过程_圆角_09


结果显示1和3触发了离屏渲染,2和4未触发离屏渲染。为什么会这样呢?我们先看一下cornerRadius和masksToBounds这几个属性。

cornerRadius用于设置圆角半径


iOS SceneKit 渲染全景图 ios渲染过程_iOS SceneKit 渲染全景图_10


masksToBounds设置超出部分裁剪,仅设置cornerRadius不会对内容进行圆角处理,只有设置 masksToBounds=YES才会对内容进行圆角处理


iOS SceneKit 渲染全景图 ios渲染过程_图层_11


从运行结果可以看出,虽然设置了cornerRadius和masksToBounds = YES, bt2并没有会触发离屏渲染,可见不是设置了masksToBounds = YES就一定会离屏渲染。

因为bt2只设置了一个背景颜色,只有一个背景图层,直接将这一层渲染到屏幕上就可以了,不需要开辟离屏缓冲区。而bt1设置了一个背景图片,会有一个背景图层和内容图层,所以需要离屏渲染(如果去掉图片,设置title,则title长度超出时,会离屏渲染,title未超出则不会触发)。

在 3 和 4中,img1设置了 图片 + 背景颜色,会有两个图层则产生离屏渲染,img2只设置背景,没有图片,则不会离屏渲染。

由此可见,设置圆角触发离屏渲染的条件是contents有子视图,并设置了masksToBounds = YES。这是一个与的关系,两者必须都满足。