iOS-- 离屏渲染的解析与合理使用

什么是离屏渲染

APP的渲染流程是CPU将图片解码 -> frame buffer(帧缓冲区) -> 视频控制器读取显示,然后把图片丢掉。如下图:
在这里插入图片描述
但是在某些情况下无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域Offscreen Buffer,之后再写入frame buffer,那么这个过程被称之为离屏渲染。如下图:
在这里插入图片描述

iOS中渲染的底层是由OpenGL/Metal来完成的,在OpenGL/Metal中有一种渲染方法是油画渲染,就是在渲染过程中根据深度值由大到小进行渲染,也就是说先渲染远处的图像,由远到近依次渲染,可以根据此图来感受一下:先绘制红⾊部分,再绘制⻩⾊部分,最后再绘制灰⾊部分。
在这里插入图片描述
但是如果我们需要将整个图像处理一下,比如:圆角、加一层遮罩、加阴影或者我们需要多次用到整个图像,那我们需要将渲染完的整个图像存放到一个缓冲区(Offscreen Buffer)中,然后在帧缓冲区中进行整个图像的处理(圆角等操作),最后显示到屏幕上。

如何才会触发离屏渲染

1.圆角

开启圆角都是这两行代码button.clipsToBounds = YES;button.layer.cornerRadius = 4;
通常情况下设置圆角就会触发离屏渲染,但是设置圆角并不是100%的触发离屏渲染,比如一个button 我只是设置了背景色和字体,开启圆角设置,在这种情况下会触发离屏渲染么?
带着这个疑问让我们来探究一下代码:

 //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"];

在模拟器中运行是这样的
在这里插入图片描述
然后我们在debug中打开color Offscree-Renderd
在这里插入图片描述
在这里插入图片描述
打开之后我们就会发现模拟器中有些图片被标记了黄色,那这些标记黄色的图片就开启了离屏渲染,1和3开启了离屏渲染。在这四段代码中都开启了圆角设置,那为什么只有1、3是离屏渲染呢?

在iOS中CALayer由backgroundColor、contents、borderWidth&borderColor构成。
在这里插入图片描述cornerRadius只对backgroundColor和borderWidth&borderColor生效,如果contents不为空,只有设置masksToBounds为 Yes,contents才会变成圆角,此时是圆角操作可以看作两个部分渲染,并且同时需要显示到屏幕上,就会触发离屏渲染,所以代码1、3触发了离屏渲染

圆角如何才能避免离屏渲染呢
  • 使用带有圆角图片;
  • 预先使用CoreGraphics为图片裁剪圆角;
  • 使用不透明的带有四个圆角图层

2. 阴影(shadow)

其原因在于,虽然layer本身是一块矩形区域,但是阴影默认是作用在其中”非透明区域“的,而且需要显示在所有layer内容的下方,因此根据画家算法必须被渲染在先。但矛盾在于此时阴影的本体(layer和其子layer)都还没有被组合到一起,怎么可能在第一步就画出只有完成最后一步之后才能知道的形状呢?这样一来又只能另外申请一块内存,把本体内容都先画好,再根据渲染结果的形状,添加阴影到frame buffer,最后把内容画上去。
在这里插入图片描述

阴影如何避免离屏渲染

通过CoreAnimation的shadowPath属性,记录阴影的几何形状,那么阴影就可以先被独立渲染出来,不需要依赖layer本体,也就不再需要离屏渲染了。

 view.layer.shadowPath=[UIBezierPath pathWithCGRect:view.bounds].CGPath;

3. 组透明度(group opacit)

GroupOpacity 是指 CALayer 的allowsGroupOpacity属性,UIView 的alpha属性等同于 CALayer opacity属性。开启 GroupOpacity 后,子 layer 在视觉上的透明度的上限是其父 layer 的opacity。

从 iOS 7 以后默认全局开启了这个功能,这样做是为了让子视图与其容器视图保持同样的透明度。

GroupOpacity 开启离屏渲染的条件是:layer.opacity != 1.0并且有子 layer 或者背景图。

这个触发条件并不需要subLayer.opacity != 1.0,非常容易满足。然而在 TableView 这样的视图里设置 cell 或 cell.contentView 的alpha属性小于1并不能检测离屏渲染的黄色特征,性能上也没有明显差别。经过摸索发现:只有设置 tableView 的alpha小于1时才会触发离屏渲染,对性能无明显影响;设置 cell 的alpha属性并不会对整体的透明度产生影响,只有设置 cell.contentView 才有效。

在一般的 UIViewController 的视图下可以很容易地观察到 GroupOpacity 触发的离屏渲染,这里只能猜测 TableView 更改了这些行为。

4.遮罩(Masking)

mask是应用在layer和其所有子layer的组合之上的,而且可能带有透明度,那么其实和group opacity的原理类似,不得不在离屏渲染中完成。

5.光栅化(shouldRasterize)

光栅化是手动启动离屏渲染。
shouldRasterize = false时,离屏渲染的黄色特征仅限于自动触发离屏渲染的效果的部分;shouldRasterize = true后该部分和开启了该属性的 layer 整体都有黄色特征。

光栅化的使用建议
  • 如果layer不需要被复用,则不需要打开;
  • 如果layer不是静态的,需要被频繁修改,比如出于动画之中,则开启光栅华反而影响性能
  • 离屏渲染缓存有时间限制,当超过100ms,内容没有被使用就会被丢弃,无法复用
  • 离屏渲染缓存有空间限制,超过屏幕像素的2.5倍则失效,并无法使用

离屏渲染的利弊

优点:

  • 提高渲染效率。比如说某种效果多次出现在屏幕上,利用离屏渲染机制进行复用。

缺点

  • 增大了性能的损耗。
  • 容易掉帧。

离屏渲染本来是个优化设计,但是在优化同时也牺牲了性能,具体的是否启用离屏渲染,还是需要具体的需求来判断。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值