iOS离屏渲染

606 阅读4分钟

1.什么是离屏渲染

说到这个问题,就不得先了解一下图像渲染的框架和它的渲染流程.

iOS的渲染框架如下图:

iOS的渲染框架主要分为Application、CoreGraphicd/CoreAnnimation/CoreImage、OpenGL ES/Metal、GPU、Display几个部分组成。

需要经过一下几个阶段:

通常情况下,我们渲染一个视图的流程如下图:

详细的流程如下图:

那么这个时候如果我绘制一个图层,需要触发离屏渲染的流程将会是什么样的呢?如下图:
查看有没有出发离屏渲染:点击Xcode模拟器中的就能查看
详细的流程如下图:
。 通过上图我们可以看到。与普通情况下 GPU 直接将渲染好的内容放入 Framebuffer 中不同,需要先额外创建离屏渲染缓冲区 Offscreen Buffer,将提前渲染好的内容放入其中,等到合适的时机再将 Offscreen Buffer 中的内容进一步叠加、渲染,完成后将结果切换到 Framebuffer 中。 所以看出离屏渲染时由于 App 需要提前对部分内容进行额外的渲染并保存到 Offscreen Buffer,以及需要在必要时刻对 Offscreen Buffer 和 Framebuffer 进行内容切换,所以会需要更长的处理时间(实际上这两步关于 buffer 的切换代价都非常大)。 并且 Offscreen Buffer 本身就需要额外的空间,大量的离屏渲染可能早能内存的过大压力。与此同时,Offscreen Buffer 的总大小也有限,不能超过屏幕总像素的 2.5 倍。 可见离屏渲染的开销非常大,一旦需要离屏渲染的内容过多,很容易造成掉帧的问题。所以大部分情况下,我们都应该尽量避免离屏渲染。

2.为什么要使用离屏渲染

  • 一些特殊效果需要使用额外的 Offscreen Buffer来保存渲染的中间状态,所以不得不使用离屏渲染,比如毛玻璃、阴影等需要叠加合成实现完成效果的情况下。

  • 处于效率目的,可以将内容提前渲染保存在 Offscreen Buffer 中,达到复用的目的,也就是打开layer的shouldRasterize。开启光栅化后,会触发离屏渲染,Render Server 会强制将 CALayer 的渲染位图结果 bitmap 保存。这个时候bitmap会保存在CPU中。而保存的 bitmap 包含 layer 的 subLayer、圆角、阴影、组透明度 group opacity 等,所以如果 layer 的构成包含上述几种元素,结构复杂且需要反复利用,那么就可以考虑打开光栅化。

    使用shouldRasterize注意以下几点

    • 如果 layer 不能被复用,则没有必要打开光栅化
    • 如果 layer 不是静态,需要被频繁修改,比如处于动画之中,那么开启离屏渲染反而影响效率
    • 离屏渲染缓存内容有时间限制,缓存内容 100ms 内如果没有被使用,那么就会被丢弃,无法进行复用
    • 离屏渲染缓存空间有限,超过 2.5 倍屏幕像素大小的话也会失效,无法复用

4.触发离屏渲染的场景

  • 使用了 mask 的 layer (layer.mask)

  • 需要进行裁剪的 layer (layer.masksToBounds / view.clipsToBounds)

  • 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/layer.opacity)

  • 添加了投影的 layer (layer.shadow*)

  • 采用了光栅化的 layer (layer.shouldRasterize)

  • 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)

4.如何避免离屏渲染

  • 【换资源】直接使用带圆角的图片,或者替换背景色为带圆角的纯色背景图,从而避免使用圆角裁剪。不过这种方法需要依赖具体情况,并不通用
  • 【mask】再增加一个和背景色相同的遮罩 mask 覆盖在最上层,盖住四个角,营造出圆角的形状。但这种方式难以解决背景色为图片或渐变色的情况
  • 【UIBezierPath】用贝塞尔曲线绘制闭合带圆角的矩形,在上下文中设置只有内部可见,再将不带圆角的 layer 渲染成图片,添加到贝塞尔矩形中。这种方法效率更高,但是 layer 的布局一旦改变,贝塞尔曲线都需要手动地重新绘制,所以需要对 frame、color 等进行手动地监听并重绘
  • 【CoreGraphics】重写 drawRect:,用 CoreGraphics 相关方法,在需要应用圆角时进行手动绘制。不过 CoreGraphics 效率也很有限,如果需要多次调用也会有效率问题