我们在分析离屏渲染之前,我们先来了解下图片的渲染流程。

一、图片的渲染流程

首先我们先来看张流程图:

ios 渲染skia ios 渲染流程 接口打印_离屏渲染

我们来解读下这张流程图:
1、我们点击屏幕触发事件,提交图片显示任务Commit Transaction 2、把图片提交到Render Server(渲染服务),进行图片解码Decode,然后等待下一次Runloop进行Draw Calls 3、把解码拿到的位图交给GPU进行渲染,GPU经过一系列操作把图片放到渲染缓冲区 4、然后帧缓存区渲染缓冲区拿到数据,接着显示控制器帧缓存区中读取显示到屏幕上

GPU的渲染过程

一样的我们先看张图:

ios 渲染skia ios 渲染流程 接口打印_离屏渲染_02


我们解析下流程:

我们从Commad buffer 中拿到位图 -> 然后通过Vertex Shader (顶点着色器)找到这张图片在屏幕上的显示的位置 -> 然后进行Tiling分割 —> 然后通过Pixel Shader(片元着色器)进行像素点着色 —> 然后把结果放在Render Buffer (渲染缓冲区)

了解了渲染过程,我们就比较容易分析离屏渲染产生的原因了。

离屏渲染

我们先看下普通渲染离屏渲染的区别:

  • 普通渲染

ios 渲染skia ios 渲染流程 接口打印_缓存_03

普通渲染是 通过从帧缓存区中读取进行显示

  • 离屏渲染

离屏渲染是从离屏缓存区中进行读取显示

那什么样的情况会开辟离屏渲染缓存区呢

答案就是:图层合并的时候。就会在GPU开辟离屏渲染缓存区,引发离屏渲染

一般什么情况会用到图层合并呢?

就是当显示的图片,不能一次绘制完成的时候,就需要,绘制多个图层,然后合并这些图层产出新的所需要的图片。当绘制多个图层的时候,就需要离屏渲染缓存区去保存这些临时绘制出来的图层。

我们以绘制 遮罩 为例:
我们先看下WWDC中的一张图

ios 渲染skia ios 渲染流程 接口打印_ios 渲染skia_04


我们想要得到Commpositing pass,不能通过一次绘制就能完成,必须经过两次图层的绘制,然后合并得到:

1、先计算好mask部分,然后放在离屏缓存区内
2、再计算layer部分,然后放在离屏缓存区内
3、对masklayer进行合并,然后剪裁计算,把结果放到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)

ios 渲染skia ios 渲染流程 接口打印_缓存_05

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倍屏幕像素大小的话,也会失效,且无法进行复用了;