一. 离屏渲染介绍
正常图片的渲染流程基本是:CPU计算完图片布局等数据,发送给GPU,GPU进行渲染,渲染结果发送到帧缓冲区,显示器从帧缓冲区读取内容,进行显示。简单的示例如下图:
离屏渲染的流程相对于正常的渲染流程会需要一些额外的操作,所以当前的帧缓冲区不够了,需要申请一个新的渲染缓冲区,来对渲染中间状态的保存,下文会对这个进行详细讲解。离屏缓冲区大小有限制,最大为屏幕大小的2.5倍。
在开发过程中,我们可以通过模拟器,选择Color off-screen Rendered
来标记离屏渲染区域。
二. 经典场景解析
离屏渲染常见场景如下:
- 圆角,对layer进行裁剪 (layer.masksToBounds / view.clipsToBounds)
- 使用了mask的layer(layer.mask)
- 毛玻璃效果
- 视图子view之间设置了不同的透明度
- 增加投影
- 开启光栅化 (layer.shouldRasterize)
- layer添加文字
本文接下来会选择其中几种情况进行详细说明。
1. 圆角处理
以UIImageView
为例,我们想要圆角,一般实现如下:
UIImageView *imageView = [[UIImageView alloc]init];
imageView.layer.cornerRadius = 50;
imageView.layer.masksToBounds = YES;
不设置masksToBounds
, 不会发生离屏渲染,但是设置了,是不是就会一定产生离屏渲染?其实不一定。当我们仅显示图片,不设置背景颜色或者border
的宽度的时候,并不会触发离屏渲染。
当我们设置了borde
r的宽度为1的时候,就触发了离屏渲染:
当我们设置backgroudColor
的时候,也会触发离屏渲染:
这个是为什么勒?其实UIImageView
的图层其实可以分为三部分:background color
、contents
、border
。如下图所示:
设置cornerRadius
的时候,根据苹果文档的描述:
它仅作用于background color
和border
,只有设置了masksToBounds
(或者view的clipsToBounds
)才会对content
也进行圆角处理。
设置了masksToBounds
为YES后,当仅显示图片,没有设置background color
以及border
。那么圆角的操作,只需要对contents
进行处理,一次性就处理完了。不需要额外的缓冲区存储中间的内容,所以不会产生离屏渲染。
当设置了背景色或者border
后,那么就需要对两个图层:content
以及background Color(border)
进行圆角操作,我们就需要额外的一个缓冲区临时存储每个图层的圆角操作结果,然后等都处理完了,再合并在一起,显示到屏幕上。这就发生了离屏渲染。
2. mask操作
当理解了设置圆角产生离屏渲染的原理后,理解mask
,就会很简单了。
上图中1和2的结果都需要临时保存在离屏缓冲区中,然后合并生成最后的结果图。
3. shouldRasterize
开发过程中,layer.shouldRasterize
为YES,会把图层渲染为一个屏幕之外的为徒,然后将这个位图缓存起来,便于复用。设置了shouldRasterize
,需要注意也需要设置rasterizationScale
。使用shouldRasterize
的几个建议:
- 如果
layer
不能复用,则没必要打开光栅化 - 如果
layer
不是静态的,需要频繁被修改,例如处于动画中,则也不需要打开光栅化 - 离屏渲染缓存内容有时间限制,如果缓存内容100ms内没有被使用的话,就会被丢弃,那么就无法进行复用了
- 缓存的内容,如果超过屏幕大小的2.5倍,则光栅化也会失效
三. 圆角替代方案
如何不触发离屏渲染,实现圆角,我们可以使用贝塞尔曲线重新,对图片进行重新绘制,关键代码如下:
UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
[[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:5] addClip];
[image drawInRect:rect];
roundImage = UIGraphicsGetImageFromCurrentImageContext(); // 圆角图片
UIGraphicsEndImageContext();