离屏渲染产生的原因
在讨论离屏渲染之前我们先要搞清楚正常的渲染流程是怎样的
非离屏渲染流程:
非离屏渲染我们可以看到在非离屏渲染的场景下,需要渲染的数据是直接提交到GPU的帧缓冲区,等待屏幕的垂直同步信号来完成页面的显示,但是在离屏渲染的场景下又是怎样的呢?请看下图
离屏渲染
我们可以看到在离屏渲染的场景下,要完成页面的展示,中间多了一个过程,需要渲染的数据首先提交到了离屏缓冲区,然后再提交到帧缓冲区的,为什么要增加一个OffsceenBuffer呢?这个OffsceenBuffer又有什么作用呢?
为什么要增加OffsceenBuffer?
如果触发了离屏渲染,那么在显示这个页面的每一帧都会去做离屏渲染的处理,比如切圆角,显示阴影等,如果每一帧都要重新去处理,那么对于CPU以及GPU都带来很大的负担。所以引入了离屏缓冲区,将处理好的图层直接丢到离屏缓冲区,下次渲染的时候直接拿出来显示到屏幕上
离屏缓存区也是有限制的
- 时间限制:缓存的内容超过100ms没有被使用,内容将会被丢弃
- 空间限制:超过屏幕像素大小的2.5倍,将不能再存储新的数据,离屏渲染会失效
这样做虽然解决了多次处理数据的问题,但是还是会带来另外的性能问题,由于离屏缓冲区和帧缓冲区属于两个不同的缓冲区,如果一个页面触发了多个离屏渲染,那么GPU在显示每一帧的时候都要在离屏缓冲区和帧缓冲区之间切换,在快速滚动的场景下就会出现严重的掉帧。那么怎么解决这个问题呢?其实答案就是要减少离屏渲染,那些场景会触发离屏渲染呢?
触发离屏渲染的场景
离屏渲染的检测
在模拟器运行时我们在这里开启离屏渲染的检测:
离屏渲染检测开关
开启后,如果有离屏渲染,则会显示成黄色,如下图:
离屏渲染
1. cornerRadius + masksToBounds
这两个属性我们都不陌生,这是我们经常使用的切圆角的方式,将cornerRadius > 0同时masksToBounds = YES时,就可能会触发离屏渲染,注意,是可能,我们来看下图中的例子
例1
先不要着急,我们再来看另外一个例子:
例2
为什么同样都是设置cornerRadius + masksToBounds,例1和例2的结果却不相同呢?相信细心的同学已经发现了,例2多了一个绿色的view,例2中的图层层级是这样的:
例2图层
所以我们能够得出:
如果只有一个图层,就算同时设置 cornerRadius + masksToBounds 也并不会触发离屏渲染,只有多个图层发生叠加的时候才会触发离屏渲染
为什么多个图层叠加会触发离屏渲染呢 ?
画家算法: 当GPU在渲染图层的时候会根据图层的远近去由远及近完成渲染,这里说的远近就是叠加图层时由内到外,当一帧数据处理好后将被提交到帧缓冲区,但是如果当前图层是多个图层叠加后的结果,那么将不能被直接提交到帧缓冲区(如果提交了,下一个垂直同步信号到来的时候会被直接显示在界面上),需要存储起来,等待这个图层的所有子图层都被处理完通过叠加后,再提交到帧缓冲区用于显示
如果我们只设置cornerRadius,会不会触发离屏渲染呢?
cornerRadius
例3这个就要从CALayer的结构说起了
CALayer结构
CALayer大致分为三层
- backgroundColor : 背景颜色层
- content :内容显示层,这一层主要用于显示我们设置的内容
- border :当我们设置边框的时候,就是这一层在工作
cornerRadius只会设置backgroundColor和border的圆角,并不会对content设置圆角。除非同时设置了masksToBounds为YES(对应view中的clipsToBounds),所以如果只设置cornerRadius,在图层叠加时并不会触发离屏渲染
2.mask
Mask 效果与混合图层的效果非常相似,只是使用同一个遮罩图像时,mask 与混合图层的效果是相反的。Mask无法取消离屏渲染
例4
3. GroupOpacity
GroupOpacityGroupOpacity 是指 CALayer 的allowsGroupOpacity属性,UIView 的alpha属性等同于 CALayer opacity属性。开启 GroupOpacity 后,子 layer 在视觉上的透明度的上限是其父 layer 的opacity。
从 iOS 7 以后默认全局开启了这个功能,这样做是为了让子视图与其容器视图保持同样的透明度。
例5GroupOpacity 开启离屏渲染的条件是:layer.opacity != 1.0并且有子 layer 或者背景图。
4. shadow
阴影直接合成在视图的下面,视图结构里并没有多出一个视图。在没有指定阴影路径时,阴影是沿着视图的非透明部分扩展的,而且 CALayer 的三个视觉元素至少有一个存在时才会有阴影。
使用阴影必须保证 layer 的masksToBounds = false,因此阴影与系统圆角不兼容。但是注意,只是在视觉上看不到,对性能的影响依然。
例6
如果我们在设置阴影时,给阴影指定了路径,就不会触发离屏渲染了
例如,这样设置阴影:
self.view1.layer.shadowColor = [UIColor greenColor].CGColor;
self.view1.layer.shadowOffset = CGSizeMake(5, 5);
self.view1.layer.shadowOpacity = 0.5;
self.view1.layer.shadowRadius = 3;
UIBezierPath * path = [UIBezierPath bezierPathWithRect:self.view1.bounds];
self.view1.layer.shadowPath = path.CGPath;
例7
网友评论