OpenGL & Metal

关于离屏渲染的深入理解

2020-07-08  本文已影响0人  黑眼豆豆_

前言

        离屏渲染,这应该是一个老生常谈的话题了。许多人对于离屏渲染都可以说出一二,而且在面试中,离屏渲染也是一个面试官很爱问的问题,比如说为何会出现离屏渲染,如何防止离屏渲染,可能大家都会说减少圆角,减少边框,减少阴影等等。但是,这是这只是冰山一角。
        那么,我们今天就一起来探讨一下离屏渲染

原理

首先我们看一个例子。

    //1.按钮存在背景图片
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 30, 100, 100);
    btn1.layer.cornerRadius = 50;
    [self.view addSubview:btn1];
    btn1.layer.shouldRasterize = YES;
    [btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
    btn1.layer.masksToBounds = 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"];

这是我们代码中常常会出现的情况。那么,这几种会造成离屏渲染吗?接下来我们通过调试。运行代码。

Simulator -> Debug -> Color Off-screen Rendered

image.png

接下来可以看到结果。


image.png

通过结果可以看到,第一个和第三个图像出现了离屏渲染,别急,待会儿和大家一一解答。

渲染流程

渲染流程对比图.png

通过图片可以看到,和正常的渲染流程不同的是,离屏渲染会把数据放入一个离屏缓冲区(Offscreen Buffer)中,待所有图层的结果进行混合计算完成后才会在屏幕进行展示。


mask.jpg

举一个例子,如上图所示,在一个相机的图标上添加一个遮罩(Mask),总共会经过这么几步:

总结,通过以上的流程我们可以看到,Pass 1和Pass 2两次步骤的数据因为要进行合并和渲染,所以在执行完成后并不能进行丢弃,而必须存入离屏缓冲区这个中间变量,所以就要开辟一个空间,而造成离屏渲染
用一句通俗的话讲,离屏渲染出现的原因就是App进行额外的渲染和合并,会将中间过程产生的数据存入离屏缓冲区(Offscreen Buffer),从而造成离屏渲染。

离屏渲染的危害

离屏渲染触发方式

主动触发 -- 开启光栅化(ShouldRasterize)

When the value of this property is true, the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content.

        用一句话概括就是当shouldRasterize设成true时,layer被渲染成一个bitmap,并缓存起来,等下次使用时不会再重新去渲染了。
        
由此可以得知,光栅化开启时,会造成离屏渲染,但是光栅化会对layer进行复用,所以这就是一个很矛盾的点了,离屏渲染会造成性能损耗,但是光栅化又会节约内存,所以到底怎么用光栅化呢?在这里给大家几个建议:

被动触发

设置圆角触发的离屏渲染的离屏渲染。

cornerRadius.jpg

将半径设置为大于0.0的值会使该图层开始在其背景上绘制圆角。默认情况下,拐角半径不适用于图层的contents属性中的图像;它仅适用于图层的背景颜色和边框。但是,将masksToBounds属性设置为true会导致内容被裁剪到圆角。

此属性的默认值为0.0。

通过官方文档,我们可以知道设置cornerRadius仅适用于图层的背景颜色(backgroundColor)边框(border),而对其中的内容(content)无效,而如果要对内容设置圆角,则需要加上masksToBounds

clipsToBounds(UIView)是指视图上的子视图,如果超出父视图的部分就截取掉
masksToBounds(CALayer)却是指视图的图层上的子图层,如果超出父图层的部分就截取掉

我们来看一个例子。

    UIImageView *img = [[UIImageView alloc]init];
    img.frame = CGRectMake(150, 300, 100, 100);
    img.backgroundColor = [UIColor blueColor];
    [self.view addSubview:img];
    img.layer.cornerRadius = 50;
    img.image = [UIImage imageNamed:@"btn.png"];

看到结果,

非圆角.jpg
在上面的例子中我们可以看到,虽然设置了img.layer.cornerRadius = 50,但是仍然没有出现圆角:我们对img对象设置了圆角,但是cornerRadius对图片内容无效,所以就导致image里的内容仍然是正方形,盖在img上面后,出现了非圆角的情况。
接下里,我们加上
  img.layer.masksToBounds = YES;

可以看到效果


圆角.jpg

但是,这个时候会触发离屏渲染。因为我们本身背景色是一个图层,而内容又是一个图层,当两个图层叠加而且需要进行组合处理(画圆角)就会触发离屏渲染。

还记得上面那个例子吗?

    //1.按钮存在背景图片
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 30, 100, 100);
    btn1.layer.cornerRadius = 50;
    [self.view addSubview:btn1];
    btn1.layer.shouldRasterize = YES;
    [btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
    btn1.layer.masksToBounds = 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"];

防止

圆角

-(void)pd_setRadius:(float)radius{
    
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
    
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.frame = bounds;
    maskLayer.path = maskPath.CGPath;
    
    [self.layer setMask: maskLayer];
}

阴影

设置阴影后,设置CALayer的 shadowPath。

let shadowView = UIView()
shadowView.frame = CGRect(x: 50, y: 100, width: 200, height: 200)
shadowView.layer.backgroundColor = UIColor.green.cgColor
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOpacity = 0.5
shadowView.layer.shadowRadius = 10
shadowView.layer.shadowOffset = CGSize(width: 10, height: 10)
shadowView.layer.shadowPath = UIBezierPath(rect: shadowView.bounds).cgPath
view.addSubview(shadowView)
上一篇 下一篇

猜你喜欢

热点阅读