iOS 离屏渲染
1、离屏渲染
1.1什么是离屏渲染
在正常情况下,经过CPU的计算以及GPU的渲染之后,会将结果存放到帧缓存区,随后视频控制器会读取帧缓存区的数据,经过数模转换,再逐行显示到屏幕上。在GPU渲染的过程中,一般情况下,会遵循‘画家算法’按次序由远及近的一层一层将结果放置到帧缓存区中,当当前帧缓存区的数据显示到屏幕上之后,就会将该帧丢弃,周而复始。(如下图)
画家算法--由远及近
但是某些特殊情况下(例:一个多图层的view设置圆角),当我们一层一层的渲染完图层后,要应用一些操作(例:裁剪),但是之前放置在帧缓存区的已渲染完的数据早已经被丢弃,这时是不可能对已经丢弃的图层进行操作了。
因此,我们需要开辟些离屏缓存区来存放一些中间状态的数据,等待全部的图层都渲染到离屏缓存区之后,再分别从各离屏缓存区取出数据,分别做相应的操作(裁剪)后,组合存入帧缓存区,再等待屏幕控制器的读取和屏幕刷新。就是离屏渲染。
1.2离屏渲染触发原理
app进行额外的渲染和合并-->需额外开辟offsecrren buffer空间 --> frame buffer --> 屏幕(如下图)
离屏渲染流程.png
例1、2离屏渲染图/正常渲染图
正常渲染
当sublayer绘制到屏幕上之后,就会将sublayer从帧缓存区移除,从而节省空间
离屏渲染图
1.3离屏渲染的优劣
-
优势
多次出现在屏幕上的数据,可提前渲染,达到复用的目的,CPU/GPU避免一些重复的计算。
在很多iOS开发的需求背景之下,一些特殊动画效果的开发,需要多图层以及离屏缓存区保存中间状态,这种情况下就不得不使用离屏渲染。 -
劣势
性能问题:离屏渲染需开辟额外空间其实是加大了系统的负担,会造成性能上的损耗。
离屏渲染需要额外的存储空间,存储空间大小的上限是2.5倍的屏幕像素大小,超过则无法使用离屏渲染。
容易掉帧:因为离屏渲染导致最终存入帧缓存区的时候,已经超过了16.67ms,则会出现掉帧的情况
1.4 常见的几种触发离屏渲染的情况
- 使用了 mask 的 layer (layer.mask)
- 需要进行裁剪的 layer (layer.masksToBounds/view.clipsToBounds)
- 设置了组透明度为 YES,并且透明度不为 1 的layer(layer.allowsGroupOpacity/ layer.opacity)
- 添加了投影的 layer (layer.shadow*)
- 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
- 高斯模糊
- 采用了光栅化的 layer (layer.shouldRasterize)
光栅化--离屏渲染
光栅化是将一个layer预先渲染成位图(bitmap),然后加入缓存中。对于阴影效果比消耗资源对静态内容进行缓存,可提升一定幅度的性能。
使用时注意事项:
- 离屏渲染的空间有限,超过屏幕像素点2.5倍,离屏渲染也会失效,无法复用
- 离屏渲染的缓存有时间限制,100ms内如果缓存的内容没有被复用,则会被丢弃,而无法复用
- layer不能被重用,没必要使用光栅化;
- layer非静态的,需要被频繁修改(例:如处于动画之中),开启离屏渲染反而影响效率
1.4 iOS常见的圆角导致的离屏渲染的处理方法
方案1
_imageView.clipsToBounds=YES;
_imageView.layer.cornerRadius=4.0;
方案2
方案3
方案3
方案4
方案4
2、圆角离屏渲染实例
圆角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的backgroundColor和border设置圆角;而不会对layer的contents设置圆角,除非同时设置了layer.masksToBounds为true(对应UIView的clipsToBounds属性)。
验证官方解释
2.1 只设置cornerRadius>0,不设置ClipsBounds和layer.masksToBounds 结果:如图1
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
view1.backgroundColor = [UIColor cyanColor];
view1.layer.cornerRadius = 20;
view1.layer.borderColor = [UIColor blackColor].CGColor;
view1.layer.borderWidth = 1.0;
[self.view addSubview:view1];
图1
结果图显示backgroundcolor和border都显示圆角,并且无触发离屏渲染。
设置clipsToBounds或layer.masksToBounds 为true。同样无触发离屏渲染。 结果图:图1。
2.2 为view1添加子view2结果(如图2)或者view1的contents添加内容结果(如图3)。只设置cornerRadius。根据结果图显示子view2和contents内容都未显示成圆角。未触发离屏渲染。
//**********view1的contents添加内容
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
view1.backgroundColor = [UIColor cyanColor];
view1.layer.cornerRadius = 20;
view1.layer.borderColor = [UIColor blackColor].CGColor;
view1.layer.borderWidth = 1.0;
view1.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"123"].CGImage);
//view1.clipsToBounds = YES;
[self.view addSubview:view1];
//**********view1添加子view2
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
view1.backgroundColor = [UIColor cyanColor];
view1.layer.cornerRadius = 20;
view1.layer.borderColor = [UIColor blackColor].CGColor;
view1.layer.borderWidth = 1.0;
[self.view addSubview:view1];
UIView *view2 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
view2.backgroundColor = [UIColor purpleColor];
[view1 addSubview:view2];
设置clipsToBounds或layer.masksToBounds为true。添加子view结果(结果图4),contents结果(图5)。结果图显示设置裁剪后无论添加的子view2或者contents内容都显示成圆角了。并且触发离屏渲染。
从上面例子可以看出设置圆角、裁剪时触发离屏渲染的例子。单独设置背景或一个图层的圆角时。子view或着contents是没有圆角的,也没有触发离屏渲染。所以获取每一图层单独渲染即可。
同时设置裁剪时,只有单一图层或着背景时,同样也没有触发离屏渲染。获取相应的图层直接渲染即可。当存在其他子view或着contents时。需要其中的子view或contents都显示成圆角(子view/contents 因为背景view的裁剪属性需组合)。触发了离屏渲染。
因此圆角触发离屏渲染只需多个图层都需要显示成圆角并且组合之后显示才行。
第二个例子中在view1中添加子view2。但是不设置裁剪。分别设置view1和view2的cornerRadius值相同。也可达到我们想要的圆角效果。但是结果却是未触发离屏渲染。这是因为view1和view2的两个图层不需要进行组合。
2.3圆角离屏渲染触发
图层的叠加绘制大概遵循“画家算法/油画算法”(由远及近,即先绘制场景中距离观察者较远的物体,再绘制较近的物体)。
例:图7, 先绘制红色图形,在绘制黄色图形,在绘制灰色图形。
当我们设置了cornerRadius以及masksToBounds进行圆角+裁剪时,masksToBounds裁剪属性会应用到所有的图层上。
裁剪图层
需各图层在offsecreen(离屏缓存区)保存,等待圆角、裁剪处理完成后在片元着色器组合 ->进入帧缓存区->渲染显示(1.2中离屏渲染流程图)。