美文网首页iOS记录iOS
iOS优化-离屏渲染

iOS优化-离屏渲染

作者: 程序员进阶 | 来源:发表于2021-08-30 10:35 被阅读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

    相关文章

      网友评论

        本文标题:iOS优化-离屏渲染

        本文链接:https://www.haomeiwen.com/subject/rkiniltx.html