离屏渲染

12/13/2022

# 什么是离屏渲染

正常情况下,我们在屏幕上显示都是GPU读取帧缓冲区(Frame Buffer)渲染好的数据,然后显示在屏幕上。(On-Screen Rendering)如果因为一些限制,无法把渲染结果直接写入frame buffer,而是暂存在另外一款内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。也就是GPU需要在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作。(Off-Screen Rendering)

# 离屏渲染是在哪一步发生的呢?

离屏渲染发生在某一层渲染完成后,写入到帧缓冲区前

# 离屏渲染产生的原因是什么?

某一层渲染完成后,无法回头来修改其中一部分,因为这一层之前的layer像素数据已经被永久覆盖了。这就意味对于每一层的layer要么能够通过单次变量就能完成渲染,要么就只能另外开辟一块内存作为临时中转区来完成复杂的修改/剪裁操作,再存储到帧缓冲区。

# 设置圆角一定回触发离屏渲染吗?

只是控件设置了圆角或(圆角+剪裁)并不会触发离屏渲染,同时需要满足父layer需要剪裁时,子layer也因为父layer设置了圆角也需要被剪裁(即视图contents有内容并发生了多图层被剪擦)时才会触发离屏渲染

# 触发离屏渲染的场景

  1. 为contents设置了内容,无论是图片、绘制内容、有图像信息等子视图等,再加上圆角+剪裁,就会触发离屏渲染
  2. 采用了光栅化的layer(layer.shouldRasterize)
  3. 使用了mask的layer(layer.mask)
  4. 需要进行剪裁的layer(layer.masksToBounds/view.clipsToBounds)
  5. 设置了组透明度为YES,并且透明度不为1的layer(layer.allowsGroupOpacity/layer.opacity)
  6. 使用了高斯模糊
  7. 添加了投影的layer(layer.shadow*)
  8. 绘制了文字的layer(UILabel,CATextLayer,Core Text等)

# 光栅化

shouldRasterize开启后,会将layer作为位图保存下来,下次直接与其他内容进行混合。这个保存的位置就是在OfferscreenBuffer中,这样下次需要再次渲染的时候,就可以直接拿来使用了。

使用建议:

  1. layer不复用,没必要打开shouldRasterize
  2. layer不是静态的,也就是说需要频繁的进行修改,没必要使用shouldRasterize
  3. 离屏渲染缓存内容有100ms限制,超过该时间的内容都会被丢弃,进而无法复用
  4. 离屏渲染空间是屏幕像素的2.5倍,如果超过也无法复用

# 离屏渲染既然会影响性能,我们为什么还要使用呢?优化方案又有哪些?

# 缺点

  1. 需要额外的存储空间
  2. 容易掉帧:一旦因为离屏渲染导致最终存入帧缓冲区的时候,已经超过了16.67ms,则会出现掉帧的情况,造成卡顿。

# 优点

  1. 对于多次出现在屏幕上的数据,可以提前渲染好,从而进行复用,这样CPU/GPU就不用做一些重复的计算
  2. 为实现一些特殊效果,需要多图层以及离屏缓冲区保存中间态,这种情况下就不得不使用离屏渲染。比如产品需要实现高斯模糊,无论自定义高斯模糊还是调用系统API都会触发离屏渲染。

# 优化方案

  1. 文字和图片的异步渲染可以使用AsyncDisplayKit(Texture)框架渲染
  2. 图片圆角,不经有容器来做剪裁,而是预先使用CoreGraphics为图片剪裁
  3. 对于视频圆角,由于实时剪切非常消耗性能,可以创建四个白色弧形的layer,覆盖四个角,从视觉上制造圆角效果
  4. 对于view的圆形边框,如果没有background,可以放心使用corenerRadius来做
  5. 对于所有的阴影,使用shadowPath来规避离屏渲染
  6. 对于特殊形状的view,使用layer mask并打开shouldRasterize来对渲染结果进行缓存
  7. 对于模糊效果,不采用系统提供的UIVisualEffect,而是另外实现模糊效果(CoreImage的CIGaussianBlur),并手动管理渲染结果
Last Updated: 10/25/2024, 6:55:06 AM