几乎做iOS开发的人都知道,设置圆角会触发离屏渲染,那么什么情况下设置圆角不会触发离屏渲染呢,为什么会触发离屏渲染。
油画算法
计算机图层的叠加绘制大概遵循油画算法,在这种算法下会按层绘制,首先绘制距离较远的场景,然后用绘制距离较近的场景覆盖较远的部分,如下图。
这样就不会导致远的物体挡住近的物体,但是有个局限,就是无法在后面一层渲染完成后,再回去修改前面图层,因为前面的图层已经被覆盖了
离屏渲染
对于有前后依赖的图层(如全局剪切,阴影等),油画算法无法满足我们的需求,对于有前后依赖的图层,我们可以再另开辟一个空间,用于临时渲染,渲染完成后再渲染到当前的缓冲区上,这个临时渲染,就是离屏渲染,由于需要开辟一个新的内存空间,并且共享同一个上下文,所以还需要做上下文切换(状态切换),并且渲染完成后还要进行拷贝操作
开辟临时缓存空间上下文切换,上下文对象比较大,切换操作会带来一定的性能消耗内存拷贝额外的渲染(没有进一步考证)
上面4项带来的开销会很大,并且每一帧渲染都需要执行,如果屏幕上触发离屏渲染的操作过多,会导致GPU渲染时间过长造成卡顿,应该避免触发离屏渲染
iOS圆角问题
官方文档关于layer.cornerRadius的描述
layer.cornerRadius只作用backgroundColor和border,不会作用于content,支持动画
离屏渲染是GPU无法按油画算法一次性渲染完我们的视图才会触发,我们先来看几个iOS的例子,模拟器打开Color Off-screen Rendered
1 | // 1. UIImageView |
如果设置了cornerRadius+masksToBounds(裁切),并且用于渲染的图层大于1,就会触发离屏渲染,其中如果设置backgroundColor,背景颜色相当于一个单独一个图层,subviews的图层也算,UILabel如果text为空(subviews为空,backgroundColor为空),则不会生成渲染图层
所以设置了cornerRadius+masksToBounds的
UIImageView设置图片不会触发离屏渲染UIView设置了背景颜色,但不添加subview,不会触发离屏渲染UILabel设置了文字,并且设置了backgroundColor,会触发离屏渲染UIButton只设置文字和背景,会触发离屏渲染
优化圆角问题
基于上面的问题,我们可以有几个优化方向
- 避免使用
裁切(masksToBounds)操作,如果我们能确保View里面的内容不会溢出,就可以不用masksToBounds - 即使要用到裁切的操作,尽量放到子view里面,不要在上层view使用masksToBounds,因为裁切需要对所有的layer和subview所有图层都进行裁切,这样离屏渲染会需要更大的空间,裁切更多的图层,应该只对必要的view/layer进行裁切
- 提前切好需要的圆角,避免渲染的时候再切
其他触发离屏渲染的情况
- 使用了遮罩的 layer (
layer.mask) - 需要进行裁剪的 layer (
layer.masksToBounds/view.clipsToBounds) - 设置了组透明度为 YES,并且透明度不为 1 的layer (
layer.allowsGroupOpacity/layer.opacity) - 添加了投影的 layer (
layer.shadow),但如果设置了shadowPath,则系统已经知道如何绘制阴影了,不会触发离屏渲染 - 采用了光栅化的 layer (
layer.shouldRasterize),光栅化也可以优化离屏渲染问题 - 绘制了文字的 layer (
UILabel,CATextLayer,CoreText等)
毛玻璃
在iOS系统中,毛玻璃效果应用的非常广泛,从上面分析也可以知道,这个肯定会触发离屏渲染的,图层之间存在依赖,下面是UIBlurEffect的处理过程
在GPU的渲染过程如下图
GPU在渲染完Content之后,会另外开辟一个Off-screen buffer,执行下面步骤,最后再做合并处理,最后再拷贝回On-screen buffer上