离屏渲染
屏幕显示图像的原理:
高中物理应该学过显示器是如何显示图像的:需要显示的图像经过电子枪以极快的速度进行一行一行的扫描,发射出来的电子撞击在屏幕的荧光剂上,荧光粉发光,就呈现了一帧画面,随后电子枪回到初始位置,因为人眼有视觉暂留效果,就形成了我们看到的图片或视频。
GPU屏幕渲染有两种方式:
(1)On-Screen Rendering (当前屏幕渲染)
指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行。GPU渲染过后,直接将结果放到帧缓存区,通过视频控制器读取后,显示到屏幕上。
(2)Off-Screen Rendering (离屏渲染)
指的是在GPU在当前屏幕缓冲区以外开辟一个缓冲区offscreenBuffer进行渲染操作。
相比于当前屏幕渲染,离屏渲染的代价是很高的
1.创建新缓冲区
要想进行离屏渲染,首先要创建一个新的缓冲区。存储空间大小的上限是屏幕像素大小的2.5倍。
2.上下文切换
离屏渲染的过程,需要多次切换上下文环境:先从当前屏幕切换到离屏,当离屏渲染结束后,需要将渲染结果显示到屏幕上,又需要将上下文环境切换到当前屏幕。
那么既然离屏渲染这么消耗性能,为什么又要使用离屏渲染呢?
1.不得不使用离屏渲染:比如使用了特殊的渲染效果,所以必须用离屏缓冲区(offscreenBuffer)来保存中间状态,然后进行合成、裁切等操作后,才显示到屏幕上。这种情况不得不使用离屏渲染。
2.效率优势:对于某些需要多次使用的效果,提前渲染到离屏缓冲区,使用的时候直接拿,通过复用来提升效率。
造成离屏渲染的常见情况:
- 为图层设置遮罩(layer.mask)
- 设置裁切:将图层的layer.masksToBounds / view.clipsToBounds属性设置为true
- 设置组透明:将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0
- 为图层设置阴影(layer.shadow *)。
- 为图层设置layer.shouldRasterize=true
- 具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的图层
- 绘制的文本的layer(任何种类,包括UILabel,CATextLayer,Core Text等)。
- 使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现。
关于离屏渲染的优化:
1.圆角的优化
a.系统的优化
iOS 9.0 之前UIimageView跟UIButton设置圆角都会触发离屏渲染。
iOS 9.0 之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。
b.使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100,100,100,100)];
imageView.image = [UIImage imageNamed:@"myImg"];
//开始对imageView进行画图
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size,NO,1.0);
//使用贝塞尔曲线画出一个圆形图
[[UIBezierPath bezierPathWithRoundedRect:imageView.boundscornerRadius:imageView.frame.size.width]addClip];
[imageView drawRect:imageView.bounds];
imageView.image=UIGraphicsGetImageFromCurrentImageContext();
//结束画图
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
c.使用CAShapeLayer和UIBezierPath设置圆角
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"myImg"];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
//设置大小
maskLayer.frame = imageView.bounds;
//设置图形样子
maskLayer.path = maskPath.CGPath;
imageView.layer.mask = maskLayer;
[self.view addSubview:imageView];
CAShapeLayer动画渲染直接提交到手机的GPU当中,相较于view的drawRect方法使用CPU渲染而言,其效率极高,能大大优化内存使用情况。推荐使用方案2
d.从图的角度:美工出圆角图,美工出圆角遮罩图、用户上传的图片由服务器切好圆角图
2.阴影优化shadow
imageView.layer.shadowColor=[UIColorgrayColor].CGColor;
imageView.layer.shadowOpacity=1.0;
imageView.layer.shadowRadius=2.0;
UIBezierPath *path=[UIBezierPathbezierPathWithRect:imageView.frame];
imageView.layer.shadowPath=path.CGPath;
其他的优化:
尽量使用不包含透明(alpha)通道的图片资源
尽量设置layer的大小值为整形值
使用异步进行layer渲染