3、离屏渲染原理
常见触发离屏渲染的几种情况:
- 使用了 mask 的 layer (layer.mask)
- 需要进行裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
- 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/ layer.opacity)
- 添加了投影的 layer (layer.shadow*)
- 采用了光栅化的 layer (layer.shouldRasterize)
- 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
正常渲染流程
app -> 帧缓冲区(Frame Buffer ) ->Display
APP中的数据经过CPU计算和GPU渲染后,将结果存放在帧缓冲区,利用视频控制器从帧缓冲区中取出,并显示到屏幕上。
- 在GPU的渲染流程中,显示到屏幕上的图像是遵循大画家算法按照由远及近的顺序,依次将结果存储到帧缓冲区
-
视屏控制器从帧缓冲区中读取一帧数据,将其显示到屏幕上后,会立即丢弃这帧数据,不会做任何保留,这样做的目的是可以节省空间,且在屏幕上是各自显示各自的,互相不影响。
正常数据渲染.png
离屏渲染流程
app ->离屏缓冲区(OffScreen Buffer)->(图层组合)->帧缓冲区(Frame Buffer )->Display
在图像显示过程中,图像一层一层从帧缓冲区读出,在完成显示后会将其从缓冲区中移除。但是图像往往不是做这么简单的显示,有时还需要对图像进行裁剪等操作。但这些已经从帧缓冲区中移除,再要对其进行操作肯定是无法实现的。所以,为了解决这一问题,添加了离屏缓冲区(offScreen Buffer),每一层图像先分别进行裁剪,存入离屏缓冲区中,然后再将处理好的结果进行合成放入帧缓冲区中。这一操作叫做离屏渲染。
当触发了离屏渲染之后,图像/图形的渲染流程变成了:app进⾏额外的渲染和合并-> offscreen Buffer(离屏缓冲区) 组合. -> FrameBuffer(帧缓冲区) -> 屏幕;特点:(离屏渲染-> 额外的存储空间/offscreen Buffer->FrameBuffer ) offscreenBuffer 空间大小-> 离屏缓冲区的空间最大为屏幕像素点2.5倍
由于离屏渲染中的离屏缓冲区,是额外开辟的一个存储空间,再进行数据转存,所以离屏渲染会影响性能问题
离屏渲染.png
但是一些特殊效果还是要使用离屏渲染
- 可以处理一些特殊的效果,这种效果并不能一次就完成,需要使用离屏缓冲区来保存中间状态,不得不使用离屏渲染,这种情况下的离屏渲染是系统自动触发的,例如经常使用的圆角、阴影、高斯模糊、光栅化等
- 可以提升渲染的效率,如果一个效果是多次实现的,可以提前渲染,保存到离屏缓冲区,以达到复用的目的。这种情况是需要开发者手动触发的(光栅化)。
圆角的离屏渲染
1.按钮设置背景图片 开启clipsToBounds或者layer.masksToBounds 会触发离屏渲染
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.png"] forState:UIControlStateNormal];
btn1.clipsToBounds = YES;
2.按钮不设置背景图片开启clipsToBounds或者layer.masksToBounds不会触发离屏渲染
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;
- 为UIButton设置一个图片,其实会添加一个UIImageView,所以按钮设置了图片进行圆角裁剪,会触发离屏渲染
3.UIImageView 设置了图片+背景色开启clipsToBounds或者layer.masksToBounds 会触发离屏渲染
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 只设置了图片,无背景色开启clipsToBounds或者layer.masksToBounds 不会触发离屏渲染
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"];
- 我们只设置contents或者UIImageView的image,并加上圆角+裁剪,是不会产生离屏渲染的。但如果加上了背景色、边框或其他有图像内容的图层,还是会产生离屏渲染。因为只有 单层 内容需要添加圆角和裁切,所以可以不需要用到离屏渲染技术。但如果加上了背景色、边框或其他有图像内容的图层,就会产生为 多层 添加圆角和裁切,所以还是会触发离屏渲染
这里不得不提一下CALayer,它是由backgroundColor、contents、borderWidth&borderColor构成的
E6EAD6AD-4176-4F7D-9CC3-F10032B8B531.png
当我们设置了cornerRadius以及masksToBounds进行圆角+裁剪时,masksToBounds裁剪属性会应用到所有的图层上。
正常渲染流程是依次绘制,绘制完一个图层就可以丢弃了。但现在需要依次在 Offscreen Buffer中保存,等待圆角+裁剪处理,即引发了 离屏渲染 。
总结
- 只有背景色、边框以及圆角的时候,设置layer.masksToBounds或者clipsToBounds为YES或NO都不会触发离屏渲染。
- 一旦我们 为contents设置了内容 ,无论是图片、绘制内容、有图像信息的子视图等,再加上圆角+裁剪,就会触发离屏渲染。
光栅化触发离屏渲染
当我们开启光栅化时,会将layer渲染成位图保存在缓存中,这样在下次使用时,就可以直接复用,提高效率。
光栅化使用建议:
- 如果layer不能被复用,则没有必要开启光栅化
- 如果layer不是静态,需要被频繁修改(例如动画过程中),此时开启光栅化反而影响效率
- 离屏渲染缓存内容有时间限制,如果100ms内没有被使用,那么就会丢弃,无法进行复用
- 离屏渲染的缓存空间有限,是屏幕的2.5倍,超过2.5倍屏幕像素大小的话也会失效,无法实现复用