离屏渲染原理及案例解析

1,626 阅读4分钟

一、模拟器开启离屏渲染检测

二、离屏渲染原理

APP -> FrameBuffer -> Display

APP -> offscreenBuffer -> FrameBuffer -> Display

mask layer 渲染完毕,不能单独立即显示,要等待 contents layer 渲染完毕进行混合,等待过程中,先存在 离屏渲染缓冲区。
等待内容都渲染完毕,传递到 帧缓冲区,等待显示。

app 进行额外的渲染和合并 -> offscreenBuffer 组合 -> FrameBuffer -> Display

离屏渲染缺点:

  1. 额外的存储空间;
  2. offscreenBuffer 传递到 FrameBuffer 需要时间 最终可能导致 掉帧

离屏渲染空间限制:屏幕的 2.5 倍

使用离屏渲染的原因

  1. 特殊效果
    需要使用额外的 offscreenBuffer 保存中间状态,不得不使用。
    系统自动触发:圆角、阴影、高斯模糊等
  2. 效率优势
    效果会多次渲染 -> 提前渲染保存在 offscreenBuffer -> 可以实现复用

一、高斯模糊 触发离屏渲染

1、获取图像 -> Render Content
2、缩放处理 -> Capture Content
3、水平毛玻璃 -> Horizontal Blur Content
4、垂直毛玻璃 -> Vertical Blur Content
5、合成 -> Compositing Content
6、输出到 FrameBuffer

由于两个 帧缓冲区 空间有限,所以不能存在 帧缓冲区。
处理过程中的 content 存储在 offscreenBuffer。

二、开启光栅化(shouldRasterize) 触发离屏渲染

开启光栅化,layer 渲染为位图后, 会保存在缓冲区,可以复用在其他内容。

不建议开启的场景:

  1. layer 不能被复用
  2. layer 不是静态的,需要被频繁修改。比如处于动画之中,开启会影响效率。
  3. 离屏渲染缓存有时间限制,100ms 没被复用,就会被丢弃。
  4. 离屏渲染缓存空间有限,超过 2.5 倍屏幕像素大小的话,也会失效。

layer.cornerRadius 属性与离屏渲染之间的解读

layer 渲染的时候分为三层

Apple 文档

文档显示:

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 触发离屏渲染的常见案例

  1. 使用了 mask 的 layer (layer.mask)
  2. 需要进行裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
  3. 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/ layer.opacity)
  4. 添加了投影的 layer (layer.shadow*)
  5. 采用了光栅化的 layer (layer.shouldRasterize)
  6. 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)