zoukankan      html  css  js  c++  java
  • 关于iOS离屏渲染的深入研究

    1.离屏渲染是什么

    首先我们要知道图像渲染的基本原理:由CPU计算好显示内容,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 HSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。

    如果在当前用于显示的屏幕缓冲区中进行渲染操作,那就是当前屏幕渲染,如果是在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作,那就是离屏渲染

    2.如何触发离屏渲染

    我们经常看到,圆角会触发离屏渲染。以最近做的IM项目举例,在聊天列表页,每个cell的头像都得切圆角,如果使用以下方式:

    1 imageView.backgroundColor = [UIColor whiteColor];
    2 imageView.layer.cornerRadius = 25;
    3 imageView.layer.masksToBounds = YES;

    当数据量较大的时候,快速滑动必然会触发卡顿,我们可以在xcode中打开模拟器的 debug -> 选取 color Offscreen-Rendered,进行离屏渲染检测:

    这里我们通过一个demo来进行测试:

    1 UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    2 [self.view addSubview:view1];
    3 // 背景色
    4 view1.backgroundColor = UIColor.redColor;
    5 // 边框
    6 view1.layer.borderWidth = 2;
    7 view1.layer.borderColor = UIColor.blackColor.CGColor;
    8 // 圆角
    9 view1.layer.cornerRadius = 50;

     我们可以看到,这里虽然设置了cornerRadius,但是并没有触发离屏渲染。当我们开启layer.masksToBounds或者clipsToBounds时,依然不会触发。

    但是如果我们在这个红色的view1上,再添加一个view2,并且将view1开启maskToBounds:

     1    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100.0, 100.0)];
     2     [self.view addSubview:view1];
     3     // 背景色
     4     view1.backgroundColor = UIColor.redColor;
     5     // 边框
     6     view1.layer.borderWidth = 2;
     7     view1.layer.borderColor = UIColor.blackColor.CGColor;
     8     // 圆角
     9     view1.layer.cornerRadius = 50;
    10     view1.layer.masksToBounds = YES;
    11     
    12     UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 20, 20)];
    13     [view1 addSubview:view2];
    14     // 背景色
    15     view2.backgroundColor = UIColor.greenColor;

    离屏渲染出现了:

     3.离屏渲染出现的原因

    • CALayer产生GPU离屏渲染

    首先来看一下CALayer的层次结构,CALayer由背景色backgroundColor、内容contents、边缘borderWidth&borderColor构成,如下图所示:

    计算机图形学里讲过一个词“油画家算法”,即先绘制场景中的离观察者较远的物体,再绘制较近的物体。而图层的绘制基本遵循这一原则。

    当我们设置了cornerRadius以及masksToBounds进行圆角+裁剪时,masksToBounds裁剪属性会应用到所有的图层,本来我们的绘制过程,应该是绘制一个图层丢弃一个图层,但现在contents中有了内容,那就需要在离屏缓冲区中保存,等待圆角+裁剪处理,即引发了 离屏渲染 。

    除了cornerRadius+masksToBounds以外,还有以下几种情况也会触发离屏渲染:

    • 开启shadows(阴影)
    • 毛玻璃效果
    • mask(遮罩)
    • allowsGroupOpacity(组不透明)
    当 CALayer 使用圆角,阴影,遮罩等属性的的时候,图层属性的混合体被指定为在未预合成之前不能直接在屏幕中渲染,则过程中需要进行离屏渲染。
    实际项目中主要由Core Graphics API(核心绘图)的使用导致。
     
     4.离屏渲染的优化方案
    实际开发中,当视图内容是静态不变时,可以通过设置 shouldRasterize(光栅化)为YES,当我们开启光栅化后,会在首次产生一个位图缓存,当再次使用时候就会复用这个缓存。但是如果图层发生改变的时候就会重新产生位图缓存,所以这个功能一般不能用于UITableViewCell中。
    最彻底的解决办法,就是把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。
    • 例如使用CAShapeLayer和UIBezierPath设置圆角
     1 UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
     2 
     3 imageView.image = [UIImage imageNamed:@"myImg"];
     4 
     5 UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
     6 
     7 CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
     8 
     9 //设置大小
    10 
    11 maskLayer.frame = imageView.bounds;
    12 
    13 //设置图形样子
    14 
    15 maskLayer.path = maskPath.CGPath;
    16 
    17 imageView.layer.mask = maskLayer;
    18 
    19 [self.view addSubview:imageView];

    感兴趣的同学可以阅读一下Google开源的AsyncDisplayKit(现在迁移到Texture),一个用于保持界面流畅的库,以后有时间我再详细的介绍和分析一下这个库。

  • 相关阅读:
    LightOJ 1094
    hdu 2586
    hdu 5234
    hdu 2955
    LightOJ 1030 数学期望
    poj 1273
    CodeIgniter学习笔记(十五)——CI中的Session
    CodeIgniter学习笔记(十四)——CI中的文件上传
    CodeIgniter学习笔记(十三)——CI中的分页
    CodeIgniter学习笔记(十二)——CI中的路由
  • 原文地址:https://www.cnblogs.com/hadyt/p/13266755.html
Copyright © 2011-2022 走看看