一、什么是离屏渲染
在正常情况下,经过CPU的计算以及GPU的渲染之后,会将结果存放到帧缓存区,随后视频控制器会读取帧缓存区的数据,经过数模转换,再逐行显示到屏幕上
一般情况下,会遵循‘画家算法’(油画算法)按次序由远及近的一层一层将结果放置到帧缓存区中,当前帧缓存区的数据显示到屏幕上之后,就会将该帧丢弃,周而复始
但是,在某些特殊情况下(多图层的view设置了圆角等),当我们一层层的渲染完图层,要应用一些操作,比如裁剪的时候,之前放置在帧缓存区的数据早已经被丢弃或者说是被覆盖了,这个时候也就不可能对所有图层做这些操作了。
因此,我们需要开辟一些离屏缓存区来存放一些中间状态的数据,等待全部的图层都渲染到离屏缓存区之后,分别从各个离屏缓存区取出数据,分别做相应的操作(裁剪等)之后,组合存入帧缓存区,再等待屏幕控制器的读取和屏幕刷新。
二、离屏渲染的优劣
劣势
离屏渲染其实是加大了系统的负担,确实会造成性能上的损耗。
- 离屏渲染需要额外的存储空间,上限是2.5倍的屏幕像素大小,一旦超过,则无法使用离屏渲染
容易掉帧:一旦因为离屏渲染导致最终存入帧缓存区的时候,已经超过了16.67ms,则会出现掉帧的情况
优势
虽然离屏渲染会需要多开辟出新的临时缓存区来存储中间状态,但是对于多次出现在屏幕上的数据,可以提前渲染好,从而达到复用的目的,这样CPU/GPU就不用做一些重复的计算。
其实在很多iOS开发的需求背景之下,比如 一些特殊动画效果的开发,此时需要多图层以及离屏缓存区保存中间状态,这种情况下就不得不使用离屏渲染。三、离屏渲染的检测
可以通过在模拟器上,
Debug-> Color Off-Screen Rendered- 其中出现黄色背景的,则为触发了离屏渲染
四、离屏渲染和常规渲染流程的区别
常规渲染
常规渲染:App -> FrameBuffer -> Display当系统绘制好需要展示的图形之后,直接提交到帧缓冲区,然后显示在屏幕上就可以,每次帧缓冲区绘制完成后,显示到屏幕时,原来的帧缓冲区中内容立即抛弃,进行下一次渲染

离屏渲染
离屏渲染:App -> OffScreenBuffer(进行计算合并等操作) -> FrameBuffer -> Display当需要显示的图片由多个图层组合,一次绘制完成不了的时候,那么就需要在每个图层绘制好之后,保存到OffScreenBuffer中去,最后当所有图层的内容全部绘制完成,再组合计算,把最后需要显示的内容提交到FrameBuffer中,然后显示到屏幕

下图是mask+layer发生离屏渲染的简易流程图
① 计算好mask部分,储存到离屏缓冲区。
② 计算好layer部分,储存到离屏缓冲区。
③ 在离屏渲染缓冲区里,对mask和layer进行合并剪裁计算,最后结果提交到FrameBuffer,展示到屏幕上
下图是层级较多的离屏渲染(实现一个毛玻璃效果)
- 渲染内容 Render Content
- 捕获内容 capture Content
- 水平模糊 Horizontal Blur
- 垂直模糊 Vertical Blue
- 合成过程,然后提交 Compositing Pass
五、哪些情况会出现离屏渲染
1.使用了 masks
masklayer 作为遮罩,显示在其所在的大layer以及大layer的所有子sublayer之上。masklayer可能也会带有透明度、形状(例如,显示指定区域内的的内容)等。
面对上面的一种情况,我们必须在离屏渲染缓冲区内完成Image和Mask的裁切合并处理,才能将最终的Masked Image -> 帧缓冲区显示。
2.毛玻璃模糊效果(高斯模糊)
3.阴影效果(shadow)
shadow是一个矩形,是一个背景色,是layer的背景,所以是在layer的下面。shadow是根据layer而来,所以要先知道layer才能直到shadow的大小位置。
如果没有离屏渲染,和前文一样,按照画家算法,必须先将shadow放入帧缓存区,先显示。但是layer没有,不可能先渲染出shadow,只能利用离屏渲染缓冲区,等待shadow、layer等渲染并合并完成后,再送入帧缓存区等待显示。
4.组透明度(group opacity)
有很多sublayer,当我们对大的layer设置alpha时,会首先在离屏渲染缓冲区等待整个layer里面的sublayer全部完成后,再根据组透明度opacity计算新的颜色,再和下面的layer颜色整合,才会给帧缓冲区等待显示。 所以并不是每渲染一层sublayer就立马给显示。如果opacity为1则不需要调整透明度,正常画家算法显示。
5.光栅化(shouldRasterize)
/* When true, the layer is rendered as a bitmap in its local coordinate* space ("rasterized"), then the bitmap is composited into the* destination (with the minificationFilter and magnificationFilter* properties of the layer applied if the bitmap needs scaling).* Rasterization occurs after the layer's filters and shadow effects* are applied, but before the opacity modulation. As an implementation* detail the rendering engine may attempt to cache and reuse the* bitmap from one frame to the next. (Whether it does or not will have* no affect on the rendered output.)** When false the layer is composited directly into the destination* whenever possible (however, certain features of the compositing* model may force rasterization, e.g. adding filters).** Defaults to NO. Animatable. */@property BOOL shouldRasterize;
如果开启,会将layer最后渲染,包括阴影、裁切等的最终效果变成位图放入离屏缓冲区,等待复用。但是,离屏缓冲区的大小不能超过屏幕的2.5倍,否则被释放;其次,layer如果不是静态的,比如imageview的image需要改变,label的text会发生改变等会发生频繁改变的,开启shouldRasterize离屏渲染会影响效率;离屏缓冲区是有时间限制的,超过100ms如果没有被使用,也会被释放。
所以,我们要善用shouldRasterize:
- 如果layer不是静态的,不建议开启
- 如果layer不能被复用,也不建议开启
- 超过100ms没有被复用,也不建议开启
- 超出屏幕像素的2.5倍,也不建议开启
6.圆角裁切(不一定会发生离屏渲染)
苹果官方文档对于cornerRadius的描述:
Setting the radius to a value greater than
0.0causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’scontentsproperty; it applies only to the background color and border of the layer. However, setting themasksToBoundsproperty totruecauses the content to be clipped to the rounded corners.
设置cornerRadius大于0时,只为layer的backgroundColor和border设置圆角;而不会对layer的contents设置圆角,除非同时设置了layer.masksToBounds为true(对应UIView的clipsToBounds)
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];// 设置背景色view1.backgroundColor = UIColor.redColor;// 设置边框宽度和颜色view1.layer.borderWidth = 2.0;view1.layer.borderColor = UIColor.blackColor.CGColor;// 设置圆角view1.layer.cornerRadius = 100.0;view1.center = self.view.center;[self.view addSubview:view1];
以上代码设置了 背景色 边框 圆角,并没有发生离屏渲染
我们再设置layer.masksToBounds或者clipsToBounds为true,同样的没有触发离屏渲染。这是因为我们还没有设置图片,不光是图片,我们为视图添加一个有颜色、内容或边框等有图像信息的子视图也会触发离屏渲染。
本来我们从后往前绘制,绘制完一个图层就可以丢弃了。但现在有背景色,有图片内容,有边框,需要依次在Offscreen Buffer中保存,等待圆角+裁剪处理,就引发了离屏渲染 。
7.绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
8.抗锯齿(allowsEdgeAntialiasing)
设置 allowsEdgeAntialiasing 属性为YES(默认为NO)
