iOS优化-离屏渲染
离屏渲染定义:
如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的帧缓冲区(Frame Buffer),作为像素数据存储区域,而这也是GPU存储渲染结果的地方。
如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域(临时缓冲区),之后再写入frame buffer,那么这个过程被称之为离屏渲染。
离屏渲染的根本原因
视图包括多个图层, 在绘制过程中需要做混合图层的操作.
App在帧缓冲区之外开辟的一块临时缓冲区,用来进⾏额外的渲染和合并
最常用于他的优化:阴影+圆角+mask+cornerRadius+clipsToBounds+
直接使用CALayer的mask属性会导致离屏渲染
渲染性能的调优,其实始终是在做一件事:平衡CPU和GPU的负载,让他们尽量做各自最擅长的工作。
触发离屏渲染的几种情况:
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;