一、模拟器开启离屏渲染检测
二、离屏渲染原理
APP -> FrameBuffer -> Display
APP -> offscreenBuffer -> FrameBuffer -> Display
mask layer 渲染完毕,不能单独立即显示,要等待 contents layer 渲染完毕进行混合,等待过程中,先存在 离屏渲染缓冲区。
等待内容都渲染完毕,传递到 帧缓冲区,等待显示。
app 进行额外的渲染和合并 -> offscreenBuffer 组合 -> FrameBuffer -> Display
离屏渲染缺点:
- 额外的存储空间;
- offscreenBuffer 传递到 FrameBuffer 需要时间 最终可能导致 掉帧
离屏渲染空间限制:屏幕的 2.5 倍
使用离屏渲染的原因
- 特殊效果
需要使用额外的 offscreenBuffer 保存中间状态,不得不使用。
系统自动触发:圆角、阴影、高斯模糊等 - 效率优势
效果会多次渲染 -> 提前渲染保存在 offscreenBuffer -> 可以实现复用
一、高斯模糊 触发离屏渲染
1、获取图像 -> Render Content
2、缩放处理 -> Capture Content
3、水平毛玻璃 -> Horizontal Blur Content
4、垂直毛玻璃 -> Vertical Blur Content
5、合成 -> Compositing Content
6、输出到 FrameBuffer
由于两个 帧缓冲区 空间有限,所以不能存在 帧缓冲区。
处理过程中的 content 存储在 offscreenBuffer。
二、开启光栅化(shouldRasterize) 触发离屏渲染
开启光栅化,layer 渲染为位图后, 会保存在缓冲区,可以复用在其他内容。
不建议开启的场景:
- layer 不能被复用
- layer 不是静态的,需要被频繁修改。比如处于动画之中,开启会影响效率。
- 离屏渲染缓存有时间限制,100ms 没被复用,就会被丢弃。
- 离屏渲染缓存空间有限,超过 2.5 倍屏幕像素大小的话,也会失效。
layer.cornerRadius 属性与离屏渲染之间的解读
layer 渲染的时候分为三层
文档显示:
layer.cornerRadius 只会设置 backgroundColor 和 border 的圆角。不会设置 contents 的圆角。
除非同时设置了 layer.masksToBounds 为 YES(对应 view.clipsToBounds)。
简单理解:
layer.cornerRadius 会修改 backgroundColor 和 border 两个图层。
layer.masksToBounds 会让上述切圆角扩张到修改 contents 图层,子视图的图层也在这里。 此时,绘制完 backgroundColor 层后,需要暂存到 offscreenBuffer,等待 contents 层渲染完毕,再进行 裁剪操作,然后输出到 FrameBuffer。这就是就是离屏渲染。
三、触发离屏渲染的案例
- 案例一
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
btn1.frame = CGRectMake(100, 30, 100, 100);
btn1.layer.cornerRadius = 50;
[self.view addSubview:btn1];
btn1.clipsToBounds = YES;
[btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
解析
设置 image 后,触发离屏渲染。
如果不设置 image, contents = nil,系统进行了优化, 不用切圆角,因此直接对 backgroundColor 层操作后,输出到 FrameBuffer 即可。
- 案例二
// UIImageView 设置了切圆角,设置了背景色,设置了图片
UIImageView *img1 = [[UIImageView alloc]init];
img1.frame = CGRectMake(100, 320, 100, 100);
[self.view addSubview:img1];
img1.layer.cornerRadius = 50;
img1.layer.masksToBounds = YES;
img1.backgroundColor = [UIColor blueColor];
img1.image = [UIImage imageNamed:@"btn.png"];
解析
如果不设置 backgroundColor,不会触发离屏渲染。
因为 backgroundColor 图层为空,不要渲染,因此,只需要对 contents 层操作后,输出到 FrameBuffer 即可。
- 案例三
// 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;
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
subView.backgroundColor = [UIColor redColor];
[img2 addSubview:subView];
解析
案例二中,如果没有背景色,img2.layer.contents 直接是位图,可以直接裁剪,输出到 FrameBuffer。
但是案例三中,img2.layer.contents 其实是子视图的渲染出来的位图,如果要给 contents 加圆角,则必须等待子视图渲染完成,存到 offscreenBuffer,然后再对缓冲区中的渲染结果进行裁剪,因此产生了离屏渲染。
‼️️ 注意
案例二中,UIImageView 不设置背景色,只设置 image,就不会触发背景色。
那为什么在案例一中,UIButton 只是设置了 image,没有设置背景色,却触发了离屏渲染呢?
其实是因为 UIButton 设置 image 的时候,是把 image 添加到了 UIButton 的一个 UIImageView 子视图上,就变成了案例三的这种情况,会触发离屏渲染。
四、离屏渲染原理总结
离屏渲染本质:要等待对多个非空图层的渲染结果进行操作,过程中的图层需要储存起来,这个存储位置就是 offscreenBuffer(离屏渲染缓冲区),整个过程就叫做离屏渲染。
五、iOS 不触发离屏渲染设置圆角
方案一
// UIImageView 设置了切圆角,不设置背景色
imageView.backgroundColor = [UIColor clearColor];
imageView.layer.cornerRadius = 50;
imageView.layer.masksToBounds = YES;
方案二
UI 切图带圆角
方案三
使用 贝塞尔曲线,裁剪 UIImage,绘制一个带圆角的 UIImage。
方案五
增加一个带圆角,中间镂空的切图,盖在需要圆角的视图上面。
六、iOS 触发离屏渲染的常见案例
- 使用了 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 等)