美文网首页
关于 iOS 离屏渲染的分析与处理

关于 iOS 离屏渲染的分析与处理

作者: 马子边 | 来源:发表于2020-07-14 22:40 被阅读0次

    一、OpenGL中,GPU屏幕渲染有以下两种方式

    • On-Screen Rendering 当前屏幕渲染: 是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。


      没有离屏渲染时的步骤
    • Off-Screen Rendering 离屏渲染: GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。


      发生离屏渲染后的渲染步骤

    二、离屏渲染定义:

    通常情况下,我们在屏幕上显示都是读取帧缓冲区(Frame Buffer)渲染好的的数据,然后显示在屏幕上;而离屏渲染,则是将渲染好的图像放入离屏缓冲区(OffScreen Buffer)等多个图层的数据渲染完,再组合在一起,放入帧缓冲区,然后在屏幕上显示。


    画家算法(把每一层依次输入到画布)

    如上图,要在屏幕上显示图的ImageView,通常GPU的Render Server会遵循 “画家算法” 按秩序先渲染图1的那一层,然后渲染图2的那一层,最后渲染图3,渲染好后的每一层都会存入帧缓存区,然后按照次序绘制到屏幕,当绘制完一层,就会将该层从帧缓存区中移除(以节省空间)。

    当对图3显示需要进行圆角和裁剪:imageView.clipsToBounds = YES,imageView.layer.cornerRadius=4.0时,渲染完图1,图2,图3,绘制到屏幕上后,还要进行裁减,无法在某一层渲染完成之后,再回过头来擦除/改变其中的某个部分,所以不能按照正常的流程,因此苹果会先渲染好每一层,存入一个缓冲区中,即离屏缓冲区,然后经过层叠加和处理后,再存储到帧缓存去中,然后绘制到屏幕上,这种处理方式叫做离屏渲染。

    三、如何检测项目中哪些图层触发了离屏渲染问题:

    • 首先我们先开启离屏渲染的检测,在模拟器打开Color Off-screen Rendered,开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。
      //1.按钮存在背景图片
        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.按钮不存在背景图片
        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;
        
        //3.UIImageView 设置了图片+背景色;
        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 只设置了图片,无背景色;
        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"];
    
    运行效果

    这里就明显看出1和3变成了黄色,标记为触发了离屏渲染。

    四、CALayer的层次结构及cornerRadius

    • CALayer由背景色backgroundColor、内容contents、边缘borderWidth&borderColor构成。


      CALayer的层次结构
    • cornerRadius


      cornerRadius:文档
    • 绘制图层背景圆角时使用的半径.可设置动画.

    • 设置layer.cornerRaclius只会设置backgroundColor的圆角.不会设置content的圆角,除非设置了layer.masksToBounds 为Ture(view中的clipsToBounds)

    cornerRadius并不是对content(image)有效,根据苹果官方解释:cornerRadius 的文档中明确说明对 cornerRadius 的设置只对 CALayer 的 backgroundColor 和 borderWidth、borderColor起作用。
    cornerRadius+masksToBounds 只有在设置了content且背景不是透明时,才会出现离屏渲染。
    如果一定要使用cornerRadius+masksToBounds的方式裁切图片,不要设置backgroundColor。

    五、离屏渲染有哪些问题

    离屏渲染的代价很高,想要进行离屏渲染,首选要创建一个新的缓冲区,屏幕渲染会有一个上下文环境的一个概念,离屏渲染的整个过程需要切换上下文环境,先从当前屏幕切换到离屏,等结束后,又要将上下文环境切换回来。这也是为什么会消耗性能的原因了

    • 内存开支:开辟离屏缓冲区(大小不超过2.5倍屏幕像素大小)
    • 时间和性能开支:从离屏缓冲区拷贝数据到帧缓冲区,上下文切换耗性能

    六、为什么要要使用离屏渲染

    • 用户需要特殊的渲染效果:使用额外的离屏缓冲区(offscreen butter)保存中间状态,最后叠加、处理后绘制在屏幕上,这样就不得不使用离屏渲染
    • 效率优势:需要多次使用的效果,提前渲染存入离屏缓冲区,然后复用来提高效率

    七、解决标题三出现的问题

    • YY_image处理圆角的方法 - (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius corners:(UIRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor borderLineJoin:(CGLineJoin)borderLineJoin
    为UIImage类扩展一个实例函数,仿YYImage做法
    - (UIImage *)imageWithCornerRadius:(CGFloat)radius ofSize:(CGSize)size{
        /* 当前UIImage的可见绘制区域 */
        CGRect rect = (CGRect){0.f,0.f,size};
        /* 创建基于位图的上下文 */
        UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
        /* 在当前位图上下文添加圆角绘制路径 */
        CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath);
        /* 当前绘制路径和原绘制路径相交得到最终裁剪绘制路径 */
        CGContextClip(UIGraphicsGetCurrentContext());
        /* 绘制 */
        [self drawInRect:rect];
        /* 取得裁剪后的image */
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        /* 关闭当前位图上下文 */
        UIGraphicsEndImageContext();
        return image;
    }
    
    • Core Graphics方式 用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角
    • CAShapeLayer 方式

    八、其他情况常见的触发离屏渲染以及解决办法:

    1. mask(遮罩)------>使用混合图层,在layer上方叠加相应mask形状的半透明layer

    2.edge antialiasing(抗锯齿)----->不设置 allowsEdgeAntialiasing 属性为YES(默认为NO)

    1. allowsGroupOpacity(组不透明,开启CALayer的allowsGroupOpacity属性后,子 layer 在视觉上的透明度的上限是其父 layer 的opacity(对应UIView的alpha),并且从 iOS 7 以后默认全局开启了这个功能,这样做是为了让子视图与其容器视图保持同样的透明度。)------->关闭 allowsGroupOpacity 属性,按产品需求自己控制layer透明度

    4.shadows(阴影)------>设置阴影后,设置CALayer的 shadowPath,view.layer.shadowPath=[UIBezierPath pathWithCGRect:view.bounds].CGPath;

    CALayer离屏渲染终极解决方案:当视图内容是静态不变时,设置 shouldRasterize(光栅化)为YES(缓存离屏渲染的数据,当下次用到的时候直接拿,不需要开辟新的离屏缓冲区),此方案最为实用方便。view.layer.shouldRasterize = true;view.layer.rasterizationScale = view.layer.contentsScale;

    shouldRasterize (光栅化使用建议):

    1.如果layer不需要服用,则没有必要打开

    2.如果layer不是静态的,需要被频繁修改,比如出于动画之中,则开启光栅化反而影响性能

    3.离屏渲染缓存有时间限制,当超过100ms,内容没有被使用就会被丢弃,无法复用

    4.离屏渲染缓存有空间限制,超过屏幕像素的2.5倍则失效,并无法使用

    面试小结

    在UITableVIewCell触发了离屏渲染,会导致在滑动的时候高频率的开辟离屏缓冲区,这样就会造成tableView滑动卡顿,如果视图内容是静态不变时,设置 shouldRasterize(光栅化)为YES,此方案最为实用方便,但是当视图内容是动态变化(如后台下载图片完毕后切换到主线程设置)时,使用此方案反而为增加系统负荷。

    相关文章

      网友评论

          本文标题:关于 iOS 离屏渲染的分析与处理

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