1. 什么是离屏渲染
在正常情况下,经过CPU的计算以及GPU的渲染之后,会将结果存放到帧缓存区,随后视频控制器会读取帧缓存区的数据,经过数模转换,再逐行显示到屏幕上(如下图)。
并且在GPU渲染的过程中,一般情况下,会遵循‘画家算法’按次序由远及近的一层一层将结果放置到帧缓存区中,当当前帧缓存区的数据显示到屏幕上之后,就会将该帧丢弃,周而复始。(如下图)
但是,在某些特殊情况下(对一个多图层的view设置了圆角等),当我们一层层的渲染完图层,要应用一些操作,比如裁剪的时候,之前放置在帧缓存区的数据早已经被丢弃或者说是被覆盖了,这个时候也就不可能对所有图层做这些操作了。
因此,我们需要开辟一些离屏缓存区来存放一些中间状态的数据,等待全部的图层都渲染到离屏缓存区之后,分别从各个离屏缓存区取出数据,分别做相应的操作(裁剪等)之后,组合存入帧缓存区,再等待屏幕控制器的读取和屏幕刷新。大致流程如下图。
2.离屏渲染的利弊
劣势
离屏渲染其实是加大了系统的负担,确实会造成性能上的损耗。主要表现在以下几个方面。
离屏渲染需要额外的存储空间,存储空间大小的上限是2.5倍的屏幕像素大小,一旦超过,则无法使用离屏渲染
容易掉帧:一旦因为离屏渲染导致最终存入帧缓存区的时候,已经超过了16.67ms,则会出现掉帧的情况
优势
虽然离屏渲染会需要多开辟出新的临时缓存区来存储中间状态,但是对于多次出现在屏幕上的数据,可以提前渲染好,从而达到复用的目的,这样CPU/GPU就不用做一些重复的计算。
其实在很多iOS开发的需求背景之下,比如 一些特殊动画效果的开发,此时需要多图层以及离屏缓存区保存中间状态,这种情况下就不得不使用离屏渲染。
3.常见的几种出发离屏渲染的情况
1.使用了 mask 的 layer (layer.mask)
2.需要进行裁剪的 layer (layer.masksToBounds /view.clipsToBounds)
3.设置了组透明度为 YES,并且透明度不为 1 的layer (layer.allowsGroupOpacity/ layer.opacity)
4.添加了投影的 layer (layer.shadow*)
5.采用了光栅化的 layer (layer.shouldRasterize)
6.绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
4.切圆角所产生的离屏渲染
我们先打开模拟器的离屏渲染颜色标记:
代码如下:
此时,我们将看到
其中黄色的就代表发生了离屏渲染,也即1和3发生了离屏渲染。
当我们设置了cornerRadius以及masksToBounds(或者clipsToBounds)进行圆角+裁剪时,masksToBounds裁剪属性会应用到所有的图层上。
本来我们从后往前绘制,绘制完一个图层就可以丢弃了。但现在需要依次在 Offscreen Buffer中保存,等待圆角+裁剪处理,即引发了 离屏渲染 。
此时我们看一下cornerRadius的文档说明
cornerRadius的文档中明确说明对cornerRadius的设置只对 CALayer 的backgroundColor和borderWidth&borderColor起作用,不会对content进行圆角设置,除非同时设置了masksToBounds为YES(对应view中的clipsToBounds属性)此时两个属性相结合,产生离屏渲染。这也就说明了上面代码为什么1和3触发了离屏渲染,而2和4没有触发离屏渲染
几种解决办法:
方案1.
针对button上的imageView,改为
// 设置圆角
button.imageView.layer.cornerRadius = 100.0;
// 设置裁剪
button.imageView.clipsToBounds = YES;
方案2.
方案3.
方案4.
方案5.使用YYImage的对于圆角的处理代码