我们在分析离屏渲染之前,我们先来了解下图片的渲染流程。
一、图片的渲染流程
首先我们先来看张流程图:
我们来解读下这张流程图:
1、我们点击屏幕触发事件,提交图片显示任务Commit Transaction
2、把图片提交到Render Server(渲染服务)
,进行图片解码Decode
,然后等待下一次Runloop
进行Draw Calls
3、把解码拿到的位图交给GPU
进行渲染,GPU经过一系列操作把图片放到渲染缓冲区
4、然后帧缓存区
从渲染缓冲区
拿到数据,接着显示控制器
从帧缓存区
中读取显示到屏幕上
GPU的渲染过程
一样的我们先看张图:
我们解析下流程:
我们从
Commad buffer
中拿到位图 -> 然后通过Vertex Shader (顶点着色器)
找到这张图片在屏幕上的显示的位置 -> 然后进行Tiling
分割 —> 然后通过Pixel Shader(片元着色器)
进行像素点着色 —> 然后把结果放在Render Buffer (渲染缓冲区)
中
了解了渲染过程,我们就比较容易分析离屏渲染产生的原因了。
离屏渲染
我们先看下普通渲染
和离屏渲染
的区别:
- 普通渲染
普通渲染是 通过从
帧缓存区
中读取进行显示
- 离屏渲染
离屏渲染是从
离屏缓存区
中进行读取显示
那什么样的情况会开辟离屏渲染缓存区呢
?
答案就是:图层合并的时候。就会在
GPU
外开辟离屏渲染缓存区
,引发离屏渲染
一般什么情况会用到图层合并呢?
就是当显示的图片,不能一次绘制完成的时候,就需要,绘制多个图层,然后合并这些图层产出新的所需要的图片。当绘制多个图层的时候,就需要
离屏渲染缓存区
去保存这些临时绘制出来的图层。
我们以绘制 遮罩
为例:
我们先看下WWDC中的一张图
我们想要得到Commpositing pass
,不能通过一次绘制就能完成,必须经过两次图层的绘制,然后合并得到:
1、先计算好
mask
部分,然后放在离屏缓存区内
2、再计算layer
部分,然后放在离屏缓存区内
3、对mask
和layer
进行合并,然后剪裁计算,把结果放到Frame buffer
中,然后显示在屏幕上。
此过程进行多次绘制,然后合并显示,就会触发离屏渲染。
- 为什么我们绘制圆角的时候,有时候会触发离屏渲染,有时候不会呢?
// 触发离屏渲染 背景图层(1) + 图片图层(2)
let button1 = UIButton()
button1.frame = CGRect(x: 50, y: 100, width: 50, height: 50)
button1.setImage(UIImage.init(named: "icon"), for: .normal)
button1.layer.masksToBounds = true
button1.layer.cornerRadius = 25
self.view.addSubview(button1)
// 不触发离屏渲染 只有 背景图层(1)
let button2 = UIButton()
button2.frame = CGRect(x: 120, y: 100, width: 50, height: 50)
button2.layer.masksToBounds = true
button2.layer.cornerRadius = 25
button2.backgroundColor = .blue
self.view.addSubview(button2)
button1
产生了离屏渲染
,button2
没有,因为button1
是因为有两个图层合并后,剪裁圆角形成的,所以走的是离屏渲染
button2
,只需要剪切一个图层的圆角,所以走的是普通的渲染
。
注: 所以说设置了圆角
和maskToBounds
并不一定会触发离屏渲染。关键还是看是否有图层合并
。
总结
- 离屏渲染的缺点:
- 开辟缓存区
- 多个缓存区,结果合并形成新的动作,比较耗性能
- 触发离屏渲染的多种情况:
- 毛玻璃效果
- 添加了阴影效果
- 采用了shouldRasterize 光栅化
- cornerRadius+maskToBounds(必须是叠加图层,例如UIImageView同时设置背景颜色+图片,才会触发)
- 使用了mask的layer(layer.mask)
- 设置了组透明度为YES,并且透明度不为1的layer(透明度混合View)
- 绘制文字的layer(UILabel,CATextLayer,Core Text等)
- 避免离屏渲染的建议:
- 设置圆角的时候,最好使用切好的圆角图片,或者使用贝塞尔曲线进行绘制
- 尽量少用透明度,把alpha = 1
- 当不存在短时间多次复用layer的时候,尽量不要开启光栅化
- 合理的运用cornerRadius+masksToBounds。
- 补充:
shouldResterize(光栅化)
使用建议:
- 如果layer不能被复用,则没有必要打开光栅化;
-如果layer 不是静态的,需要被频繁修改,比如处于动画之中.那么
开启离屏渲染反而影响效率了;
-离屏渲染缓存内容有时间限制,缓存内容10Oms内容如果没有被
使用,那么它就会丢弃;无法进行复用了;- 离屏渲染缓存空间有限,超过2.5倍屏幕像素大小的话,也会失效,且无法进行复用了;