美文网首页
离屏渲染浅析

离屏渲染浅析

作者: 源本平凡 | 来源:发表于2020-07-08 00:19 被阅读0次

    1. 普通渲染

    • 正常情况下,渲染一张图片的流程:APP需要的渲染数据->CPU计算->GPU渲染->保存在帧缓存区(Frame Buffer)->视频控制器读取帧缓存区数据并显示在屏幕上。简要流程如下图所示:


      正常渲染流程.png
    • 在GPU的渲染过程中,显示到屏幕上的图像是遵循“画家算法”由远及近的顺序,依次将数据存储到帧缓存区。
    • 视频控制器从帧缓存区读取一帧数据,并将其显示到屏幕上之后,会立即抛弃这帧数据,不会做保留操作,这样做的目的是为了节约存储空间,也能保证每帧数据互不影响。

    2. 离屏渲染

    • 与普通渲染不同,离屏渲染的流程:APP需要渲染的数据->CPU计算->GPU渲染->离屏渲染缓冲区(Offscreen Buffer)->保存在帧缓存区(Frame Buffer)->视频控制器读取帧缓存区数据并显示在屏幕上。简要流程如下图所示:


      离屏渲染流程.png

    2.1 影响

    • 与非离屏渲染模式相比,多了进行额外的渲染合并操作,是对多个texture进行合并的过程。多了这么一步,所以说对性能要求更高一些,也更容易出现掉帧的情况。
    • 增加了额外的存储空间(离屏渲染缓冲区),其空间最大限制为屏幕像素的2.5倍

    2.2 使用原因

    情况1

    • 可以显示一些特殊效果,需要用到Offscreen buffer来保存中间状态。一般都是系统去触发,例如对layer层相关处理:包括圆角、阴影、mask等等。iOS系统扁平化后出现的高斯模糊也是利用离屏渲染方式。

    圆角处理:

    设置圆角为什么会触发离屏渲染呢?这个要从layer的结构说起。
    layer结构中包含3部分:
    backgroundColor
    contents
    boarder相关信息(borderWidth和borderColor)

    设置圆角代码,这个大家应该都知道:

    view.layer.cornerRadius = 1
    

    我们先看看官方文档中cornerRadius相关说明:


    cornerRadius.png

    Discussion中的说明:
    设置cornerRadius超过0.0,不会影响contents,但是会影响background color和border。如果设置了masksToBounds会对content进行裁剪。
    所以说触发离屏渲染的主要原因:

    masksToBounds=YES;
    

    这就是模拟器开启离屏渲染模式中说明的为什么要设置masksToBounds的原因。masksToBounds需要对layer上的所有内容进行裁剪,过程中需要对中间值进行保存。所以进行了离屏渲染操作。
    注意:
    如果说layer图层比较简单,也是不会触发离屏渲染的。例如:UIImageView设置cornerRadius和masksToBounds是不会触发离屏渲染的,如果再对UIImageView设置背景色,则会触发。

        self.view.backgroundColor = [UIColor grayColor];
        
        UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1"]];
        imageView.frame = CGRectMake(100, 100, 100, 100);
        imageView.layer.cornerRadius = 30;
        imageView.layer.masksToBounds = YES;
        [self.view addSubview:imageView];
        
        UIImageView *imageView2 = [[UIImageView alloc]  initWithImage:[UIImage imageNamed:@"1"]];
        imageView2.backgroundColor = UIColor.redColor;
        imageView2.frame = CGRectMake(100, 250, 100, 100);
        imageView2.layer.cornerRadius = 30;
        imageView2.layer.masksToBounds = YES;
        [self.view addSubview:imageView2];
        
        UIButton *btn = [UIButton   buttonWithType:UIButtonTypeCustom];
        [btn setBackgroundImage:[UIImage imageNamed:@"1"]   forState:UIControlStateNormal];
        btn.layer.cornerRadius = 30;
        btn.layer.masksToBounds = YES;
        btn.frame = CGRectMake(100, 400, 100, 100);
        [self.view addSubview:btn];
    

    代码中有三个控件,前两个是UIImageView,最后一个是UIButton。

    第一个UIImageView设置了图片没有设置背景色,没有触发离屏渲染。
    第二个UIImageView设置了图片和背景色,触发了离屏渲染。
    最后一个UIButton,设置图片没有设置背景色,触发了离屏渲染。原因是我们看到UIButton是由它的layer和UIImageView的layer混合起来的效果(UIButton有imageView),所以设置圆角的时候会触发离屏渲染。

    情况2

    • 如果texture会多次显示到屏幕上,可以使用offscreen buffer进行提前渲染,并且保存在其中,达到复用的效果。layer中有shouldRasterize属性。这是一种主动行为,是为了提高复用的效率。通常是设置layer的shouldRasterize属性来实现。
      shouldRasterize 光栅化
      shouldRasterize官方文档
      shouldRasterize.png
      开启后,会将layer作为位图保存下来,下次直接与其他内容进行混合。这个保存的位置就是OffscreenBuffer中。这样下次需要再次渲染的时候,就可以直接拿来使用了。

    shouldRasterize使用建议:

    layer不复用,没必要使用shouldRasterize;
    layer不是静态的,也就是说要频繁的进行修改,没必要使用shouldRasterize;
    时间方面:离屏渲染缓存有100ms时间限制,超过该时间的内容会被丢弃,进而不能达到复用的目的;
    空间方面:离屏渲染空间是屏幕像素的2.5倍,如果超过也无法复用。

    2.3 如何避免

    • 为了避免设置圆角时引起的离屏渲染操作,可以用一下方案代替直接设置圆角的操作:
    1. 直接更换资源,让UI提供带圆角的图片。
    2. 使用layer.mask属性,增加一个和背景色相同的遮罩覆盖上层,盖住四个角,营造出圆角的形状。但这种方式难以解决背景色为图片或渐变色的情况。
    3. 使用贝塞尔曲线绘制闭合圆角的矩形,在上下文中设置只有内部可见,再将不带圆角的 layer 渲染成图片,添加到贝塞尔矩形中。这种方法效率更高,但是 layer 的布局一旦改变,贝塞尔曲线都需要手动地重新绘制,所以需要对 frame、color 等进行手动地监听并重绘。
    4. CoreGraphics重写 drawRect:,用 CoreGraphics 相关方法,在需要应用圆角时进行手动绘制。不过 CoreGraphics 效率也很有限,如果需要多次调用也会有效率问题。

    2.4 产生原因总结

    • 设置layer.mashsToBounds/view.clipsToBounds
    • 设置layer.mask
    • 设置layer.shadow等相关属性
    • 设置layer.shouldRasterize光栅化
    • 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/layer.opacity)
    • 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)

    相关文章

      网友评论

          本文标题:离屏渲染浅析

          本文链接:https://www.haomeiwen.com/subject/yzftcktx.html