离屏渲染了怎么办

前言

公司最近开发一个UI列表,其中一个cell的样式,我开发后效果是实现了,但总是感觉会产生离屏渲染,一旦有这种感觉,我心里就想查过明白,到底有没有离屏渲染,怎么会造成的呢,有什么好的解决方案呢

我们先看下设计图先。

20211210154826.jpg

一个白色背景,有圆角,上面是一张图片,图片的左手角和右上角也是圆角,无边框,无阴影。

你们会怎么写呢

CALayer和UIView的区别

  • CALayer:继承自 NSObject, 负责图像渲染,属于 QuartzCore 框架。
  • UIView:继承自 UIResponder, 主要负责事件响应,属于基于 UIKit 框架。

很明显双方是分工合作的,很多人会想,UIView也有background,frame,bound等,这些属性本质也是对CALayer的封装而已。

clipsToBounds和masksTobounds区别

  • clipsToBounds:是类View的属性,如果设置为yes,则不显示超出父View的部分。

  • masksToBounds:是类CALayer的属性,如果设置为yes,则不显示超出父View layer的部分。

可以说他们都是达到一样的效果,本质也是一样的。

离屏渲染

什么是离屏渲染

正常我们要再显示器上显示内容,我们就需要一个和屏幕像素数据量一样的 Framebuffer作为像素数据存储区域,GPU不停的将渲染完成后的内容放到Framebuffer针缓冲区内,而显示器就不断在Framebuffer上获取内容。

因为一些限制,GPU无法把渲染结果写入到Framebuffer上,需要在当前屏幕缓冲区以外新开辟一个缓冲区Offscreen Buffer进行渲染操作,把提前渲染好的内容放到Offscreen Buffer上,等需要用到再写入到Framebuffer上,这就是离屏渲染。

20211210163357.jpg

画家算法

image.png

画家算法是图层由远到近,一层一层绘制的。同样的道理也会应用到我们GPU渲染图层,也是一层一层往画布上输出,上层的sublayer会覆盖下层的sublayer,相当于按次序输出到framebuffer,然后按照次序绘制到屏幕,当绘制完一层,就会将该层从帧缓存区中移除,从而节约空间提交效率。

但是有些场景,GPU一层一层渲染,渲染完成后放到framebuffer,如果说你想改变在这一层的layer像素数据,这时候已经不能改变了,因为它在渲染中被永久覆盖了。那怎么办?

那只能令开辟一块内存作为临时中转区来完成复杂的修改/裁剪等操作,然后再切换到framebuffer上。

离屏渲染优缺点

优点

  • 处于效率目的,对于多次出现屏幕的视图,可以将内容提前渲染保存在 Offscreen Buffer 中,达到复用的目的。

缺点

  • 离屏渲染需要额外开辟一个新的缓存区,这会增加存储空间,大量的离屏渲染可能会造成内存过大的压力,空间大小为屏幕总像素的2.5倍。超出就无法使用。

  • 在渲染过程,我们需要进行2次上下文切换,先切换到屏幕外环境进行渲染,然后再切换到当前屏幕是很消耗性能的,而且如果最终存入到framebuffer上,超过16.67ms,会产生掉帧现象,造成卡顿。

离屏渲染场景

  1. 光栅化,shouldRasterize,一旦设置为true,就会把layer的渲染结果包括其子layer,以及圆角、阴影、group opacity等等)保存在一块内存中,这样一来在下一帧仍然可以被复用,而不会再次触发离屏渲染。主旨在于降低性能损失,但总是至少会触发一次离屏渲染。

  2. mask,遮罩作用。

  3. opacity,设置layer的透明度不为1。

  4. shadow,投影作用。

  5. 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)。

  6. UIBlurEffectView,高斯模糊,系统自动触发,用于保存中间状态。

  7. masksToBounds,裁剪的一些情况。

圆角 + 裁剪

看了那么多概念,那我们就来分析一下关于圆角+裁剪的情况。先给一个结论:并不是所有圆角+裁剪都会产生离屏渲染。

我们先打开debug -> Color Off-screen Rendered,如果有黄色区域显示,就证明是产生了离屏渲染。

我们先看一下官方给的圆角离屏渲染的图片。

image.png

也就是说圆角裁剪的时候,backgroundColor, contents, borderWidth, borderColor,如果都设置了这些属性就会产生离屏渲染。

圆角+裁剪+边框+背景颜色

- (void)drawView
{
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];

    // 设置背景色
    view1.backgroundColor = UIColor.systemPinkColor;

    // 设置边框宽度和颜色
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;

    // 设置圆角
    view1.layer.cornerRadius = 100.0;

    // 设置裁剪
    view1.clipsToBounds = YES;

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210173441.jpg

结果是没有离屏渲染。这是很明显的,因为没有需要裁剪处理的内容,所以masksToBounds设置为YES或者NO都没有影响。

圆角+裁剪+contents+边框或背景色

- (void)drawView2 {

    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];

    // 设置背景色
    view1.backgroundColor = UIColor.grayColor;

    // 设置边框宽度和颜色
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;

    //设置图片
    view1.layer.contents = (__bridge id)[UIImage imageNamed:@"destination_head_bg"].CGImage;

    // 设置圆角
    view1.layer.cornerRadius = 100.0;

    // 设置裁剪
    view1.clipsToBounds = YES;

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210174012.jpg

从效果图上看是离屏渲染了,全部都占有了,还不有问题对吧。

圆角+裁剪+contents

- (void)drawView3 {
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];

    //设置图片
    view1.layer.contents = (__bridge id)[UIImage imageNamed:@"destination_head_bg"].CGImage;

    // 设置圆角
    view1.layer.cornerRadius = 100.0;

    // 设置裁剪
    view1.layer.masksToBounds = YES;

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210174257.jpg

从效果图上看,是没有离屏渲染的,这里是重点,我们只设置了contents,没有设置背景颜色或者边框,就不会产生离屏渲染。因为我们就只有这一层,我们单层情况下,直接裁剪就行,不用放到Offscreen Buffer上进行渲染。

圆角+裁剪+有内容子视图+背景色或边框

- (void)drawView4 {
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];

    // 设置背景色
    view1.backgroundColor = UIColor.grayColor;

    // 设置圆角
    view1.layer.cornerRadius = 100;
    
    // 设置裁剪
    view1.layer.masksToBounds = YES;

    // 子视图
    UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 100.0, 100.0)];

    // 设置背景色
    view2.backgroundColor = [UIColor blueColor];

    [view1 addSubview:view2];

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210175446.jpg

从效果图上是有离屏渲染的,子视图不在裁剪的范围的,但设了背景色就产生了离屏渲染。如果去掉view1的背景色,那就不会离屏渲染了。

圆角+裁剪+子视图在裁剪区域

- (void)drawView5 {
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 375.0, 375.0)];

    // 设置圆角
    view1.layer.cornerRadius = 16;

    // 设置裁剪
    view1.layer.masksToBounds = YES;

    UIImageView * imageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"destination_head_bg"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
    imageView.frame = CGRectMake(0, 0, 375.0, 200.0);

    [view1 addSubview:imageView];

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210175939.jpg

从效果图上看还是离屏渲染了,本来以为对view1不设置背景颜色,能解决我在前言提出的问题,没想到还是离屏渲染了。

结论

  • 一旦设置圆角+裁剪,如果视图一定是有contents(图片、绘制内容、有图像信息的子视图),加上背景色或者边框,就会产生离屏渲染

  • 设置圆角+裁剪,加上子视图位于裁剪区域,也会离屏渲染

  • 仅有圆角+裁剪,和contents是不会离屏渲染的。经典例子就是【button setImage】的了,只需要对button.imageView.layer.cornerRadiusbutton.imageView.clipsToBounds进行就不会离屏渲染。

解决方案

  1. 找设计师?让设计师都设计好,例如圆角+阴影的卡片。

  2. UIBezierPath + CAShapeLayer?自己来画左上角和右上角?效果还是离屏渲染,性能可能会更好吧。

  3. 弄遮罩,模拟圆角?

大佬们,对于前言的问题,还是不能做出不离屏渲染的情况,在线救助给为大佬,谢谢!!!

猜你喜欢

转载自juejin.im/post/7040015747510173727
今日推荐