项目中我们会用到头像或是在视图展示时,设置“友好”“不尖锐”的圆角,进行美化,当视图的渲染帧率低于 40 帧每秒,普通用户就会察觉明显的不流畅了。视图和圆角的大小对帧率的影响微乎其微,真正影响帧率的是圆角图片的数量,数量越多,对于性能的影响是非常大的。
通常我们设置头像圆角的方法:
yourImageView.layer.cornerRadius = aImageView.frame.size.width/2.0;
yourImageView.layer.masksToBounds = YES;
使用这个方法会造成 Off-Screen Rendering(离屏渲染)。频繁发生离屏渲染是非常耗时的,所以会造成帧率会下降,造成卡顿的情况,对性能上造成影响。
Off-Screen Rendering
离屏渲染,指的是 GPU (图形处理器)在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。道离屏渲染耗时是发生在离屏这个动作上面,而不是渲染。为什么离屏这么耗时?原因主要有创建缓冲区和上下文切换。创建新的缓冲区代价都不算大,付出最大代价的是上下文切换。
上下文切换
上下文切换,不管是在GPU (图形处理器)渲染过程中,还是一直所熟悉的进程切换,上下文切换在哪里都是一个相当耗时的操作。首先我要保存当前屏幕渲染环境,然后切换到一个新的绘制环境,申请绘制资源,初始化环境,然后开始一个绘制,绘制完毕后销毁这个绘制环境,如需要切换到On-Screen Rendering或者再开始一个新的离屏渲染重复之前的操作。 下图描述了一次mask的渲染操作。
批注: 离屏渲染上下文,个人理解,视图切换,会发生离屏渲染,造成耗费时间,影响性能。
如果 shouldRasterize 被设置成 YES,在触发离屏绘制的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用。这将在很大程度上提升渲染性能。
一次mask发生了两次离屏渲染和一次主屏渲染。即使忽略昂贵的上下文切换,一次mask需要渲染三次才能在屏幕上显示,这已经是普通视图显示3陪耗时,若再加上下文环境切换,一次mask就是普通渲染的30倍以上耗时操作。问我这个30倍以上这个数据怎么的出来的?当我在cell的UIImageView的实例增加到150个,并去掉圆角的时候,帧数才跌至28帧每秒。虽然不是甚准确,但至少反映mask这个耗时是无mask操作的耗时的数十倍的。
解决方案:
//设置圆角视图样式
-(void)setUpSelfView{
//阴影 Shadow
self.layer.shadowColor = [UIColor blackColor].CGColor; //黑
self.layer.shadowOpacity = 0.33;//阴影的不透明度
self.layer.shadowOffset = CGSizeMake(0, 1.5);//阴影的偏移
self.layer.shadowRadius = 4.0;//阴影半径
self.layer.shouldRasterize = YES; //圆角缓存
self.layer.rasterizationScale = [UIScreen mainScreen].scale;//栅格化图层 提高流畅度
//圆角
self.layer.cornerRadius = 10.0f;
}
这样大部分情况下可以马上挽救你的帧数在55帧每秒以上。shouldRasterize = YES会使视图渲染内容被缓存起来,下次绘制的时候可以直接显示缓存,当然要在视图内容不改变的情况下。
除了上面非要作死的人外,大家还是采取预先生成圆角图片,并缓存起来这个方法才是比较好的手段。预处理圆角图片可以在后台处理,处理完毕后缓存起来,再在主线程显示,这就避免了不必要的离屏渲染了。
另外也有在图片上面覆盖一个镂空圆形图片的方法可以实现圆形头像效果,这个也是极为高效的方法。缺点就是对视图的背景有要求,单色背景效果就最为理想。
使用贝塞尔曲线绘制, 解决离屏渲染问题,推荐
- (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());
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
总结
实现圆角cornerRadius要比mask高效很多。Rasterize在大部分情况下极大减少GPU工作。在有空间的情况下,大部分情况下缓存总能帮到你,不是吗?后台预处理图片也能很简单帮上你很大的忙。
其中shouldRasterize(光栅化)是比较特别的一种:
光栅化概念:将图转化为一个个栅格组成的图象。光栅化特点:每个元素对应帧缓冲区中的一像素。shouldRasterize = YES
在其他属性触发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用。shouldRasterize
= YES,这将隐式的创建一个位图,各种阴影遮罩等效果也会保存到位图中并缓存起来,从而减少渲染的频度(不是矢量图)。 相当于光栅化是把GPU的操作转到CPU上了,生成位图缓存,直接读取复用。 当你使用光栅化时,你可以开启“Color Hits Green
and Misses Red”来检查该场景下光栅化操作是否是一个好的选择。绿色表示缓存被复用,红色表示缓存在被重复创建。
如果光栅化的层变红得太频繁那么光栅化对优化可能没有多少用处。位图缓存从内存中删除又重新创建得太过频繁,红色表明缓存重建得太迟。可以针对性的选择某个较小而较深的层结构进行光栅化,来尝试减少渲染时间。
注意:对于经常变动的内容,这个时候不要开启,否则会造成性能的浪费 例如我们日程经常打交道的TableViewCell,因为TableViewCell的重绘是很频繁的(因为Cell的复用),如果Cell的内容不断变化,则Cell需要不断重绘,如果此时设置了cell.layer可光栅化。则会造成大量的离屏渲染,降低图形性能。
所以当使用离屏渲染的时候会很容易造成性能消耗,因为在OPENGL里离屏渲染会单独在内存中创建一个屏幕外缓冲区并进行渲染,而屏幕外缓冲区跟当前屏幕缓冲区上下文切换是很耗性能的。
为什么会使用离屏渲染
当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制,所以就需要屏幕外渲染被唤起。
屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。
iOS版本上的优化
iOS 9.0 之前UIimageView跟UIButton设置圆角都会触发离屏渲染
iOS 9.0 之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片>设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。
这可能是苹果也意识到离屏渲染会产生性能问题,所以能不产生离屏渲染的地方苹果也就不用离屏渲染了。
感谢:
http://www.cocoachina.com/ios/20150803/12873.html
http://blog.csdn.net/qxuewei/article/details/50953950
http://foggry.com/blog/2015/05/06/chi-ping-xuan-ran-xue-xi-bi-ji/?utm_source=tuicool&utm_medium=referral
https://zsisme.gitbooks.io/ios-/content/chapter15/offscreen-rendering.html