离屏渲染
要理解离屏渲染,需要先了解两个名词,OffScreen Buffer
(离屏缓冲区),Frame Buffer
(帧缓冲区)。
- 一般情况下,系统把需要显示位图的直接放到
Frame Buffer
,然后显示出来。
一般情况下显示流程 - 当出现离屏渲染之后,会出现一个一个新的缓冲区
OffScreen Buffer
,在OffScreen Buffer
完成在Frame Buffer
完成不了的操作,然后提交到帧缓冲区,最后显示出来
当出现离屏渲染的时候的流程
为什么离屏渲染会产生性能问题?
- 产生了离屏渲染,会开额外的内存空间
OffScreen Buffer
- 在
OffScreen Buffer
进行渲染然后提交到Frame Buffer
需要时间
-
OffScreen Buffer
空间是有大小限制的,屏幕像素的2.5倍
产生的原因
可能大家都知道设置圆角就有可能会产生离屏渲染,可是为什么设置圆角就有可能会产生离屏渲染呢?我们结合下面两张图来理解:
我们在显示一个图层的时候其实包括了三部分内容,
backgroundColor,contents,borderWith/borderColor
。
Apple对cornerRadius的解释
我们可以看到,我们通过
cornerRadius
设置圆角的时候,其实只是对background
起作用,如果contents
也需要设置圆角,需要设置maskToBounds
属性。接下来在代码中来看一下这个问题
两张ImageView设置圆角,一个出现了离屏渲染,一个没有
看下代码
// 设置了backgroundColor
UIImageView * imageOne = [[UIImageView alloc] init];
imageOne.frame = CGRectMake(100, 180, 100, 100);
imageOne.image = [UIImage imageNamed:@"photo"];
imageOne.backgroundColor = [UIColor greenColor];
imageOne.layer.cornerRadius = 50.f;
imageOne.layer.masksToBounds = YES;
[self.view addSubview:imageOne];
//没有设置backgroundColor
UIImageView * imageTwo = [[UIImageView alloc] init];
imageTwo.frame = CGRectMake(100, 320, 100, 100);
imageTwo.image = [UIImage imageNamed:@"photo"];
imageTwo.layer.cornerRadius = 50.f;
imageTwo.layer.masksToBounds = YES;
[self.view addSubview:imageTwo];
当我们只设置了cornerRadius
和maskToBounds
并没有发生离屏渲染,但当我们同时设置了background
这时就会产生离屏渲染。
根本原因就是我们在没有设置
backgroundColor
的时候,默认是不需要对background
层进行处理,在imageView
里面我们只需要渲染出content,然后进行切maskToBounds
,放入Frame Buffer
能然后就能直接显示出来了。一旦我们设置了background
,我们需要对background
进行渲染,对contents
进行渲染,然后进行混合运算后做圆角处理,再提交到Frame Buffer
,进行显示。因为在同一layer
下出现了多个图层,圆角的剪切是需要进行混合运算再剪切,通过上面的图例我们知道ImageView
其实是有background
,contents
。maskToBounds
其实是基于cornerRadius
来做出运算后对contents切圆角,当我们没有设置backgroundColor
也就没有必要去渲染background
,对contents
基于cornerRadius
运算后得出圆角提交帧缓冲区(Frame Buffer
)就行了。这也默认是对内存的一种优化,只有当遇到同一Layer
下多层图层,直接提交Screen Buffer
处理不了的融合显示,才会产生离屏渲染,开辟OffScreen Buffer
,在OffScreen Buffer
进行相应的各种算法。
哪些情况会触发离屏渲染
- 对
layer
使用了mask
(遮罩)。 - 对
layer
使用了masksToBounds
,clipsToBounds
。 - 对
layer
添加了投影。 - 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)。
- 设置了不为1的组透明度。
因为透明度不为1,像素点颜色的混合运算,需要等这几个不同透明度的图层都渲染好了,提交到Screen Buffer
再进行运算,最后提交Frame Buffer
进行显示。 - 采用了光栅化
layer.shouldRasterize
shouldRasterize 的使用建议:
- 如果layer不能被复用,则没必要开启光栅化。
- 如果layer不是静态的,需要被频繁修改,比如处于动画之中,开启离屏渲染,会影响效率。
- 离屏渲染缓存内容有时间限制,如果缓存内容
100ms
没有被使用,那么就会被抛弃,无法进行复用。 - 离屏渲染空间有限,屏幕像素的
2.5
倍,超过了这个大小,无法被缓存
如何解决
官方对离屏渲染产生性能问题也进行了优化:
iOS 9.0 之前UIimageView跟UIButton设置圆角都会触发离屏渲染。
iOS 9.0 之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。
不对layer
使用maskToBounds/clipsToBounds
,例如imageView
imageView.clipsToBounds = YES;
imageView.layer.cornerRadius = 50.f;
使用贝塞尔曲线(UIBezierPath
)绘制圆图
[[UIBezierPath bezierPathWithRoundedRect:imageView.boundscornerRadius:imageView.frame.size.width]addClip];
[imageView drawRect:imageView.bounds];
imageView.image=UIGraphicsGetImageFromCurrentImageContext();
//结束画图
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
使用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
是CALayer
的子类,可以使用CALayer
的所有属性,但是只有配合UIBezierPath
才会起作用,使用CAShapeLayer
(属于CoreAnimation
)与贝塞尔曲线可以实现不在view
的drawRect
(继承于CoreGraphics
走的是CPU
,消耗的性能较大)方法中画出一些想要的图形,CAShapeLayer
动画渲染直接提交到手机的GPU
当中,相较于view
的drawRect
方法使用CPU
渲染而言,其效率极高,能大大优化内存使用情况。
对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优化性能,能大幅提高性能。
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;
YYImage开源库,对imageView的圆角也可以研究一下。
离屏渲染真的一无是处么?
离屏渲染
离屏渲染并不是一无是处的,虽然会造成很多额外的开销,但也是为了充分利用设备的资源来保证界面的流畅。发生离屏渲染时,是为了引起开发者对性能的关注,减少不必要的透明视图层级。
怎么查看当前显示是否有发生离屏渲染
Simulator
菜单Debug模式中打开Color Off-Screen Rendered
网友评论