美文网首页
OpenGL-06-离屏渲染原理及触发条件

OpenGL-06-离屏渲染原理及触发条件

作者: 宇宙那么大丶 | 来源:发表于2020-07-17 17:13 被阅读0次

一、了解离屏渲染

1、正常渲染流程

APP -----> FrameBuffer(帧缓冲区) -----> Display

  • APP中的数据经过CPU和GPU计算渲染后,把结果放入帧缓冲区,再由视频控制器从甄嬛从去中读取并显示
  • GPU渲染过程中,显示到屏幕上的图像会遵循“画家算法”由远到近的顺序,依次将结果存储到帧缓冲区
  • 视频控制器从帧缓冲区读取一帧的数据将其显示后,就立刻丢弃了这一帧数据,然后进行下一帧的渲染显示。这样做的好处是节省了空间。


    image.png
2、离屏渲染流程及具体逻辑

APP -----> OffScreenBuffer(离屏缓冲区) -----> FrameBuffer(帧缓冲区) -----> Display

  • 当APP要进行额外的渲染和合并时(比如设置了圆角+裁剪),我们需要把不同的图层进行裁剪+合并的操作,这时就不能直接放入FrameBuffer了,我们要把渲染好的结果放入OffScreenBuffer,等待合适的机会将几个图层进行裁剪、合并叠加的操作,完成后把结果放入FrameBuffer中,由视频控制器读取显示
  • 离屏缓冲区相当于一个临时缓冲区,存放需要进行操作的数据,并不直接使用数据。因此,在方便我们的同时也有缺点,因为是额外开辟的空间,并且还需要转存数据到FrameBuffer中,所以大量的离屏渲染会影响性能,开销较大,也可能造成掉帧
  • OffScreenBuffer空间也是有限制的,是屏幕像素的2.5倍。如果缓存内容并100ms未被使用,会直接丢弃。


    image.png

二、离屏渲染触发的条件

我们通过代码调试来验证一下,通过打开模拟器的离屏选项来观察


image.png
1、高斯模糊 UIBlurEffectView(必定触发)
    //Button 背景色
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 50, 100, 100);
    btn1.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn1];
    
    
    //Button 背景色+高斯模糊
    UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn2.frame = CGRectMake(100, CGRectGetMaxY(btn1.frame)+50, 100, 100);
    btn2.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn2];
    
    UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
    UIVisualEffectView *effectVIew = [[UIVisualEffectView alloc]initWithEffect:effect];
    effectVIew.frame = btn2.bounds;
    [btn2 addSubview:effectVIew];
image.png

那么我们来看一下高斯模糊的离屏渲染逻辑


image.png
  • Content : 渲染内容
  • Capture Content : 捕获内容
  • Horizontal Blur : 水平模糊
  • Vertical Blur :垂直模糊
  • Compositing Pass : 合成过程
2、光栅化(必定触发)
    //Button 背景色
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 50, 100, 100);
    btn1.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn1];
    
    
    //Button 背景色+光栅化
    UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn2.frame = CGRectMake(100, CGRectGetMaxY(btn1.frame)+50, 100, 100);
    btn2.backgroundColor = [UIColor redColor];
    btn2.layer.shouldRasterize = YES;
    [self.view addSubview:btn2];
image.png

开启光栅化后,会触发离屏渲染,Render Server 会强制将 CALayer 的渲染位图结果 bitmap 保存下来,这样下次再需要渲染时就可以直接复用,从而提高效率。
而保存的 bitmap 包含 layer 的 subLayer、圆角、阴影、组透明度 group opacity 等,所以如果 layer 的构成包含上述几种元素,结构复杂且需要反复利用,那么就可以考虑打开光栅化。

使用光栅化shouldRasterize的一些建议:
1、如果layer不能被复用,没必要打开光栅化
2、如果layer是动态的,需要频繁修改,打开光栅化会造成很大的负荷,不建议打开
3、离屏缓冲区内容有时间限制,超过100ms没有被使用会被丢弃,无法复用
4、离屏缓冲区空间大小有限制,超过屏幕2.5倍就会失效,无法复用

3、阴影
    //Button 背景色
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 50, 100, 100);
    btn1.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn1];


    //Button 背景色+阴影
    UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn2.frame = CGRectMake(100, CGRectGetMaxY(btn1.frame)+50, 100, 100);
    btn2.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn2];
    
    btn2.layer.shadowColor = UIColor.blackColor.CGColor;
    btn2.layer.shadowOffset = CGSizeMake(2, 2);
    btn2.layer.shadowOpacity = 0.9;

image.png

不过,阴影存在优化方案,就是指定一下阴影路径,就能解决了

//在上述代码的基础上添加
btn2.layer.shadowPath = [UIBezierPath bezierPathWithRect:btn2.bounds].CGPath;
4、圆角

我们先以UIButton和UIImageView为例,看不同条件下,圆角是否触发离屏渲染

   //针对UIButton的圆角分情况测试
    for (int i = 0; i < 5; i++) {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        btn.frame = CGRectMake(50, 150*i + 50, 100, 100);
        btn.layer.cornerRadius = 50;
        btn.clipsToBounds = YES;
        [self.view addSubview:btn];
        
        if (i == 0) {
            
            //背景色+边框+图片
            btn.backgroundColor = [UIColor redColor];
            btn.layer.borderWidth = 2;
            [btn setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
            
        }else if (i == 1){
            
            //背景色+边框
            btn.backgroundColor = [UIColor redColor];
            btn.layer.borderWidth = 2;
            
        }else if (i == 2){
            
            //背景色+图片
            btn.backgroundColor = [UIColor redColor];
            [btn setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
            
        }else if (i == 3){
            
            //边框+图片
            btn.layer.borderWidth = 2;
            [btn setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
            
        }else if (i == 4){
            
            //图片
            [btn setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
            
        }
    }
    
    
    //针对UIImageView的圆角分情况测试
    for (int j = 0; j < 5; j++) {
        
        UIImageView *img = [[UIImageView alloc]init];
        img.frame = CGRectMake(200, 150*j + 50, 100, 100);
        img.layer.cornerRadius = 50;
        img.layer.masksToBounds = YES;
        [self.view addSubview:img];
         
        if (j == 0) {
            
            //背景色+边框+图片
            img.backgroundColor = [UIColor redColor];
            img.layer.borderWidth = 2;
            img.image = [UIImage imageNamed:@"btn.png"];
            
        }else if (j == 1){
            
            //背景色+边框
            img.layer.borderWidth = 2;
            img.backgroundColor = [UIColor redColor];
            
        }else if (j == 2){
            
            //背景色+图片
            img.backgroundColor = [UIColor redColor];
            img.image = [UIImage imageNamed:@"btn.png"];
            
        }else if (j == 3){
            
            //边框+图片
            img.layer.borderWidth = 2;
            img.image = [UIImage imageNamed:@"btn.png"];
            
        }else if (j == 4){
            
            //图片
            img.image = [UIImage imageNamed:@"btn.png"];
            
        } 
        
    }
image.png

通过上图,打开离屏渲染的选项之后,可以看出10种测试,我们都设置了圆角+ clipsToBounds/masksToBounds,为什么有的触发了离屏渲染,有的没有?

首先,我们来结合CALayer的层级关系和cornerRadius的官方介绍分析一下:


image.png
image.png
  • CALayer由backgroundColor(背景颜色层)、contents(内容层)、border(边框属性层)构成。
  • 而cornerRadius的文档中明确说明:设置了cornerRadius,只对 CALayer 的backgroundColor和borderWidth&borderColor起作用,如果contents有内容或者内容的背景不是透明的话,只有设置masksToBounds为 true 才能起作用,此时两个属性相结合,产生离屏渲染。
  • 那么我们看代码中:
    1、针对UIButton,只要是 图片+ clipsToBounds(即masksToBounds)的情况,都会触发离屏渲染
    2、针对UIImageView,只有 图片+背景色/边框+ masksToBounds,才会触发离屏渲染
    【这里我们要看一下iOS官方针对UIImageView做的一些优化:
    1、在iOS9之前,UIImageView和UIButton通过cornerRadius+masksToBounds/clipsToBounds设置圆角都会触发离屏渲染,
    2、在iOS9以后,针对UIImageView中的image设置圆角并不会触发离屏渲染,如果加上了背景色或者阴影等其他效果还是会触发离屏渲染的】

这样我们就解释的通了。

那么我们这里的contents仅仅指的是图片吗?

其实并不是,于是笔者尝试了以下代码,总结出,contents也可以是有色信息(颜色、图片)的子视图

for (int i = 0; i < 3; i++) {
        
        
       UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
       btn.frame = CGRectMake(50, 150*i + 50, 100, 100);
       btn.backgroundColor = [UIColor redColor];
       btn.layer.cornerRadius = 50;
       btn.clipsToBounds = YES;
       [self.view addSubview:btn];
       
       if (i == 0) {
           //无颜色
           UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
           btn1.frame = CGRectMake(0, 0 , 50, 50);
           btn1.backgroundColor = [UIColor clearColor];
           [btn addSubview:btn1];
           
       }else if (i == 1){
           //有颜色
           UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
           btn1.frame = CGRectMake(0, 0 , 50, 50);
           btn1.backgroundColor = [UIColor blackColor];
           [btn addSubview:btn1];
           
       }else if (i == 2){
           //有图片
           UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
           btn1.frame = CGRectMake(0, 0 , 50, 50);
           [btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
           [btn addSubview:btn1];
           
       }

image.png

所以,针对圆角如何避免触发离屏渲染,我们可以根据上述条件,根据自身项目需求进行特殊定制

5、layer.mask (遮罩/蒙版)

我们来看一下mask的渲染逻辑


image.png

如图:

  • 系统先计算好mask部分,然后保存到离屏缓冲区
  • 计算layer部分,计算好之后保存到离屏缓冲区
  • 对mask和layer进行合并剪裁计算,最后结果提交到FrameBuffer,展示到屏幕上

所以说:
mask是覆盖在所有layer及其子layer之上的,可能还带有一定的透明度。
mask也是需要等整个layer树绘制完成,再加上mask和组合后的lzyer进行组合,所以需要开辟一个独立于FrameBuffer的内存,用于将layer及其子layer画完,最后再和mask进行组合,存储到FrameBuffer,视频控制器从FrameBuffer中读取数据显示到屏幕上

优化方案:不使用mask,使用混合图层,在layer上方叠加相应mask形状的半透明layer

6、组透明度(layer.allowsGroupOpacity / layer.opacity)

1、groupOpacity中alpha并不是分别应用到每一层之上,需要整个layer树画完之后,在统一加上alpha,和底层其他layer的像素进行组合,此时显然无法通过一次遍历就得到结果
2、需要另外开启一个独立内存,先将layer及其子layer画好,最后给组合后的图层加上alpha进行渲染,将最终结果存储到帧缓冲区
3、GroupOpacity 开启离屏渲染的条件是:layer.opacity != 1.0并且有子 layer 或者背景图。

另外,两个半透明的view,通过addSubView方法叠加,也会产生离屏渲染。

优化方案:关闭allowsGroupOpacity属性,根据产品需求自己控制layer透明度

那么

总结一下,常见的触发情况
1、使用了 mask 的 layer (layer.mask)
2、需要进行裁剪的 layer (layer.masksToBounds / view.clipsToBounds) ,同时拥有多层layer需要处理的情况
3、设置了组透明度为 YES,并且透明度不为 1 的 layer(layer.allowsGroupOpacity/layer.opacity)
4、添加了阴影 (layer.shadow)
5、采用了光栅化 (layer.shouldRasterize)
6、绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
7、使用了高斯模糊
8、使用了抗锯齿(edge antialiasing)【allowsEdgeAntialiasing = YES】

三、离屏渲染与性能优化

1、离屏渲染的好处
  • 为了特殊效果,不得不使用。例如系统自动触发的情况:圆角、阴影、高斯模糊、光栅化
  • 提升效率。如果一个效果需要多次用到,我们可以提前渲染保存在offscreenbuffer中,免去重复计算的时间,达到复用的目的。这需要手动触发
2、 如何避免离屏渲染做到性能优化
  • 圆角:虽然并不是所有的圆角+裁剪都会触发,但是我们也要分情况使用,可以使用切好的圆角图片,或者自己使用贝塞尔曲线进行圆角绘制
  • 透明度:多层级的视图添加,不要设置透明度;不要设置组透明度
  • 光栅化:当不存在短时间内需要反复多次大量复用的layer时,shouldRasterize设置为NO
  • 阴影:增加阴影路径
  • mask:使用混合图层,在layer上方叠加相应mask形状的半透明layer
  • 抗锯齿:不开启 allowsEdgeAntialiasing 属性 (默认为NO)

相关文章

  • OpenGL-06-离屏渲染原理及触发条件

    一、了解离屏渲染 1、正常渲染流程 APP -----> FrameBuffer(帧缓冲区) -----> ...

  • 离屏渲染,OpenGL的三种渲染方式

    一. 离屏渲染触发的原理:APP在对图片进行渲染,合并的时候会触发离屏渲染,离屏渲染流程先把图层保存在帧缓冲区(o...

  • 四、iOS离屏渲染

    一、开启图层是否触发离屏渲染问题 注:离屏渲染的图层会标记为黄色 二、离屏渲染的渲染流程 三、离屏渲染触发的原因 ...

  • 离屏渲染触发原理及处理

    离屏渲染触发的原理 离屏渲染检测案例 先实现下面4个圆角Demo,分别为2个UIButton和2个UIImageV...

  • 离屏渲染的原理和分析

    1.常见触发离屏渲染的情况 在分析离屏渲染的原因之前先介绍几种常见的触发离屏渲染的情况 使⽤了 mask (遮罩...

  • iOS性能优化之图片圆角

    在Apple官方文档中多次提出开发时,避免触发离屏渲染效果.离屏渲染触发的情况有很多种,具体可参考iOS离屏渲染相...

  • 关于离屏渲染

    1.什么是离屏渲染 2.离屏渲染的触发方式 3.离屏渲染的意义 4.离屏渲染的不足 1.什么是离屏渲染 要了解离屏...

  • iOS 设置圆角会造成离屏渲染,你真的弄明白了吗?

    1. 如何设置圆角才会触发离屏渲染 我们经常看到,圆角会触发离屏渲染。但其实这个说法是不准确的,因为圆角触发离屏渲...

  • 离屏渲染触发原理简述

    数据的加载渲染流程有两种:1、正常渲染加载2、离屏渲染加载图1 可得:离屏渲染比正常渲染多一个离屏缓存区 一、正常...

  • 离屏渲染引发的反思

    经常看到说是离屏渲染会影响性能,我们要避免离屏渲染,然后阐述离屏渲染的触发情况有哪些? 既然离屏渲染那么不好,那为...

网友评论

      本文标题:OpenGL-06-离屏渲染原理及触发条件

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