iOS记录iOS

iOS优化-离屏渲染

2021-08-30  本文已影响0人  程序员进阶

离屏渲染定义:

如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的帧缓冲区(Frame Buffer),作为像素数据存储区域,而这也是GPU存储渲染结果的地方。

如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域(临时缓冲区),之后再写入frame buffer,那么这个过程被称之为离屏渲染。

离屏渲染的根本原因

视图包括多个图层, 在绘制过程中需要做混合图层的操作.

App在帧缓冲区之外开辟的一块临时缓冲区,用来进⾏额外的渲染和合并

最常用于他的优化:阴影+圆角+mask+cornerRadius+clipsToBounds+

直接使用CALayer的mask属性会导致离屏渲染

渲染性能的调优,其实始终是在做一件事:平衡CPUGPU的负载,让他们尽量做各自最擅长的工作。

触发离屏渲染的几种情况:

1、使用了mask的layer(layer.mask)

2、需要进行裁剪的layer(layer.maskToBounds/view.clipsToBounds)

3、设置了组透明度为YES,并且透明度不为1的layer(layer.allowsGroupOpacity/layer.opacity)

4、添加了投影的layer(layer.shodow*)

5、采用了光栅化的layer(layer.shouldRasterize)

6 、绘制了文字的layer(UILabel、CATextLayer、Core Text等)

如何优化:

1、使用AsyncDisplayKit(Texture);

2、图片:预处理-CoreGraphics/如果需要设置圆角,可以使用切好的圆角图片,或者自己使用贝塞尔曲线进行圆角绘制(最下面有代码)

3、视频圆角:图层盖住;

4、图片没有背景色->大胆使用cornerRadius

5、阴影shadowPath

6、凡是用mask遮罩,复用打开shouldRasterize,会进行复用

7、模糊效果(毛玻璃)不要用系统的,自定义


离屏渲染的利弊

优点:

(1)在我们项目中有一些特殊的效果(比如一些特殊动画效果),需要额外的缓冲区来保存中间状态,不得不使用离屏渲染。

(2)如果某一个效果会多次出现在屏幕上,那么可以提前渲染offscreen Buffer ,来达到复用的目的,这样CPU/GPU就不用做一些重复的计算。

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

缺点

(1)离屏渲染需要额外开辟离屏缓冲区的存储空间,加大了系统的负担,会造成性能上的损耗。而存储空间的大小的上限是2.5倍的屏幕像素大小,一旦超过,则无法使用离屏渲染。

(2)一旦因为离屏渲染导致最终存入帧缓存区的时候已经超过了16.67ms,则会出现掉帧的情况。

增大了性能的损耗。

容易掉帧。

离屏渲染的检测

可以通过在模拟器上,Debug-> Color Off-Screen Rendered

其中出现黄色背景的,则为触发了离屏渲染


肯定会触发的两种方式:毛玻璃效果,以及光栅化

// 触发方式1: 毛玻璃效果

    UIButton *btn0 = [UIButton buttonWithType:UIButtonTypeCustom];

    btn0.frame = CGRectMake(50, 30, 100, 100);

    [self.view addSubview:btn0];

    [btn0 setImage:[UIImage imageNamed:@"gdt_icon"] forState:UIControlStateNormal];

    UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];

    UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];

    //必须给effcetView的frame赋值,因为UIVisualEffectView是一个加到UIIamgeView上的子视图.

    effectView.frame = CGRectMake(20, 20, 50, 50);

    [btn0 addSubview:effectView];

    // 触发方式2: shouldRasterize

    UIButton *btn_s = [UIButton buttonWithType:UIButtonTypeCustom];

    btn_s.frame = CGRectMake(200, 30, 100, 100);

    btn_s.layer.shouldRasterize = YES;

    [self.view addSubview:btn_s];

    [btn_s setImage:[UIImage imageNamed:@"gdt_icon"] forState:UIControlStateNormal];

Button和ImageView的情况

Button和ImageView的情况

//1、Button存在背景图片

    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];

    btn.frame=CGRectMake(50,120,100,50);

    [btnsetImage:[UIImage imageNamed:@"offscreen.png"] forState:UIControlStateNormal];

    btn.backgroundColor = UIColor.blueColor;

    //btn.layer.cornerRadius = 20;//有离屏渲染

    btn.imageView.layer.cornerRadius = 20;//无离屏渲染

    btn.clipsToBounds = YES;

    [self.view addSubview:btn];

    //2、Button不存在背景图片//无离屏渲染

    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];

    btn1.frame=CGRectMake(200,120,100,50);

    btn1.backgroundColor = UIColor.blueColor;

    btn1.layer.cornerRadius = 20;

    btn1.clipsToBounds=YES;

    [self.viewaddSubview:btn1];

    //UIImageView 设置了图片+背景色; //有离屏渲染

    UIImageView *img1 = [[UIImageView alloc]init];

    img1.frame=CGRectMake(50,320,100,100);

    img1.backgroundColor = [UIColor blueColor];

    img1.layer.cornerRadius = 50;

    img1.layer.masksToBounds = YES;

    img1.image = [UIImage imageNamed:@"offscreen.png"];

    [self.viewaddSubview:img1];

    //UIImageView 只设置了图片,无背景色; //无离屏渲染

    UIImageView *img2 = [[UIImageView alloc]init];

    img2.frame=CGRectMake(200,320,100,100);

    img2.layer.cornerRadius = 50;

    img2.layer.masksToBounds = YES;

    img2.image = [UIImage imageNamed:@"offscreen.png"];

    [self.view addSubview:img2];

其他开发者的优化:

即刻大量应用AsyncDisplayKit(Texture)作为主要渲染框架,对于文字和图片的异步渲染操作交由框架来处理。关于这方面可以看我之前的一些介绍

对于图片的圆角,统一采用“precomposite”的策略,也就是不经由容器来做剪切,而是预先使用CoreGraphics为图片裁剪圆角

对于视频的圆角,由于实时剪切非常消耗性能,我们会创建四个白色弧形的layer盖住四个角,从视觉上制造圆角的效果

对于view的圆形边框,如果没有backgroundColor,可以放心使用cornerRadius来做

对于所有的阴影,使用shadowPath来规避离屏渲染

对于特殊形状的view,使用layer mask并打开shouldRasterize来对渲染结果进行缓存

对于模糊效果,不采用系统提供的UIVisualEffect,而是另外实现模糊效果(CIGaussianBlur),并手动管理渲染结果

UIBezierPath是UIKit中Core Graphics框架中的一个类,使用UIBezierPath可以绘制各种简单的图形。

在这里绘制贝塞尔曲线:- (void)drawRect:(CGRect)rect

重新绘制:调用- (void)drawRect:(CGRect)rect或者专门的方法[self setNeedsDisplay];

CAShapeLayer和drawRect比较:

CAShapeLayer:属于CoreAnimation框架,通过GPU来渲染图形,不耗费性能。

drawRect:属于Core Graphics框架爱,占用大量CPU,耗费性能。

怎么高效的实现控件的圆角效果?第八条

//直接对图片进行重绘 (使用Core Graphics),实际开发加异步处理,也可以给 SDWebImage 也做扩展;

- (UIImage *)imageWithCornerRadius:(CGFloat)radius {

   CGRect rect = (CGRect){0.f,0.f,self.size};

   UIGraphicsBeginImageContextWithOptions(self.size,NO, UIScreen.mainScreen.scale);

   CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath);

   CGContextClip(UIGraphicsGetCurrentContext());

   [selfdrawInRect:rect];

   UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

   UIGraphicsEndImageContext();

   returnimage;

}

// 利用CAShapeLayer圆角,替换原本的layer,达到圆角效果

UIBezierPath*maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:self.bounds.size];

CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];

maskLayer.frame =self.bounds;

maskLayer.path = maskPath.CGPath;

self.layer.mask = maskLayer;

参考:https://juejin.cn/post/6847902220017467406

上一篇下一篇

猜你喜欢

热点阅读