这个版本我们设计师采用了大量的圆角, 阴影,缩放等效果, 导致刚打开app就加载到了首页,iPhone6以下的机型普遍可以感觉到卡顿的现象.为了解决这一问题我就想到了应该是离屏渲染导致的结果.
GPU屏幕渲染有两种方式:
(1)On-Screen Rendering (当前屏幕渲染)
指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行。
(2)Off-Screen Rendering (离屏渲染)
指的是在GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作。
当前屏幕渲染不需要额外创建新的缓存,也不需要开启新的上下文,相对于离屏渲染性能更好。但是受当前屏幕渲染的局限因素限制(只有自身上下文、屏幕缓存有限等),当前屏幕渲染有些情况下的渲染解决不了的,就使用到离屏渲染。
相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在两个方面:
(1)创建新缓冲区
要想进行离屏渲染,首先要创建一个新的缓冲区。
(2)上下文切换
离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen),等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上有需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。
由于垂直同步的机制,如果在一个 HSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
既然离屏渲染这么耗性能,为什么有这套机制呢?
有些效果被认为不能直接呈现于屏幕,而需要在别的地方做额外的处理预合成。图层属性的混合体没有预合成之前不能直接在屏幕中绘制,所以就需要屏幕外渲染。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。
下面的情况或操作会引发离屏渲染:
- 为图层设置遮罩(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的图层
- 文本(任何种类,包括UILabel,CATextLayer,Core Text等)。
- 使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现。
优化方案
官方对离屏渲染产生性能问题也进行了优化:
iOS 9.0 之前UIimageView跟UIButton设置圆角都会触发离屏渲染。
iOS 9.0 之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。
1、圆角优化
我们设置圆角一般通过如下方式:
imageView.layer.cornerRadius = CGFloat(10);
imageView.layer.masksToBounds = YES;
这样处理的渲染机制是GPU在当前屏幕缓冲区外新开辟一个渲染缓冲区进行工作,也就是离屏渲染,这会给我们带来额外的性能损耗,如果这样的圆角操作达到一定数量,会触发缓冲区的频繁合并和上下文的的频繁切换,性能的代价会宏观地表现在用户体验上——掉帧。
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继承于CALayer,可以使用CALayer的所有属性值;
CAShapeLayer需要贝塞尔曲线配合使用才有意义
使用CAShapeLayer(属于CoreAnimation)与贝塞尔曲线可以实现不在view的drawRect(继承于CoreGraphics走的是CPU,消耗的性能较大)方法中画出一些想要的图形
CAShapeLayer动画渲染直接提交到手机的GPU当中,相较于view的drawRect方法使用CPU渲染而言,其效率极高,能大大优化内存使用情况。
总的来说就是用CAShapeLayer的内存消耗少,渲染速度快
2、shadow优化
let shadowView =UIView.init(frame:self.frame)
shadowView.backgroundColor=UIColor.white
shadowView.layer.shadowColor=UIColor.red.cgColor
shadowView.layer.shadowOpacity=0.6
shadowView.layer.shadowOffset=CGSize.init(width:0, height:4)
shadowView.layer.shadowRadius= 4
添加这一行可以解决离屏渲染卡顿问题
缓存离屏渲染的轨迹,以便重用
shadowView.layer.shadowPath=UIBezierPath.init(rect:self.bounds).cgPath
如果不保存渲染的轨迹的话, 每次执行到这些需要离屏渲染的视图时都需要重新计算渲染的内容, 保存了以后GPU下次直接拿来上次的使用.
经过改良后的渲染, 再使用instruments 检测, 发现黄色的离屏渲染已经没有了,当然由此引起的卡顿想现象也消失了
网友评论