1.离屏渲染流程
通常的渲染流程是这样的:
App 通过 CPU和 GPU 的合作,不停地将内容渲染完成放入 Framebuffer 帧缓冲器中,而显示屏幕不断地从 Framebuffer中获取内容,显示实时的内容。
APP需要额外创建离屏渲染缓冲区 Offscreen Buffer,将提前渲染好的内容放入其中,等到合适的时机再将
Offscreen Buffer 中的内容进一步叠加、渲染,完成后将结果切换到Framebuffer中。
2.离屏渲染性能问题
- 首先离屏渲染会需要开辟额外的内存空间。大量的离屏渲染会给内存造成过大的压力。离屏渲染的缓存空间大小有限,为屏幕像素大小的2.5倍。
- 再者将渲染内容从
Offscreen Buffer转存到Framebuffer时,也需要一定的时间开销。 所以,我们要尽量避免离屏缓存。
3.为什么使用离屏渲染
3.1 系统自动触发
一些特殊效果需要使用额外的Offscreen Buffer 来保存渲染的中间状态,所以不得不使用离屏渲染。一般都是系统自动触发的,比如阴影、圆角等等。
实现图片的模糊效果,同样也会触发离屏渲染。模糊的渲染过程,需要经过渲染内筒->缩放->垂直模糊->水平模糊->最终合成几个阶段。这就需要Offscreen Buffer将几种状态存储起来,最终合成。
3.2 光栅化 shouldRasterize
开启光栅化后,会触发离屏渲染,Render Server 会强制将CALayer 的渲染位图结果bitmap 保存下来,这样下次再需要渲染时就可以直接复用,从而提高效率。
光栅化 shouldRasterize使用建议:
- 如果 layer 不能被复用,则没有必要打开光栅化;
- 如果 layer 不是静态,需要被频繁修改,比如处于动画之中,那么开启离屏渲染反而影响效率;
- 离屏渲染缓存内容有时间限制,缓存内容 100ms 内如果没有被使用,那么就会被丢弃,无法进行复用;
- 离屏渲染缓存空间有限,超过 2.5 倍屏幕像素大小的话也会失效,无法复用。
4.圆角的离屏渲染
上面提到过,设置圆角是会自动触发离屏渲染,那这是不是绝对的呢?通常我们为设置圆角需要用到这句代码:
view.layer.cornerRadius = 2
关于cornerRadius的的官方描述如下:
Setting the radius to a value greater than 0.0 causes the layer to begin
drawing rounded corners on its background. By default, the corner radius
does not apply to the image in the layer’s contents property; it applies
only to the background color and border of the layer. However, setting
the masksToBounds property to true causes the content to be clipped to
the rounded corners.
根据上述描述,cornerRadius设置的圆角并不适用于图层的contents,它仅适用于图层的背景色和边框。要想裁剪contents需要将masksToBounds属性设置为true。
那么我们设置view.layer.masksToBounds = true 就一定会触发离屏渲染吗?也不一定。只有当layer叠加,并将layer 以及所有 subLayer 的content都进行裁剪时才会触发离屏渲染。
5.离屏渲染的具体逻辑
上面说过多层layer切圆角时会触发离屏渲染,那这到底是怎么一会事呢?
图层的叠加绘制大概遵循“画家算法”,在这种算法下会按层绘制,首先绘制距离较远的场景,然后用绘制距离较近的场景覆盖较远的部分。
在普通的 layer 绘制中,当sublayer绘制到屏幕上之后,就会将sublayer从帧缓存区中移除,来节省空间。
但是当应用cornerRadius和masksToBounds进行切圆角时,需要将渲染好的sublayer存在offscreen Buffer中。
masksToBounds 裁剪属性会应用到所有的 sublayer 上。这也就意味着所有的 sublayer 必须要重新被应用一次圆角+裁剪。这就触发了离屏渲染。
实际上不只是圆角+裁剪,如果设置了透明度+组透明(layer.allowsGroupOpacity+layer.opacity),阴影属性(shadowOffset 等)都会产生类似的效果,因为组透明度、阴影都是和裁剪类似的,会作用与 layer 以及其所有 sublayer 上,这就导致必然会引起离屏渲染。
5.圆角的实现方式
- 离屏渲染
- 使用带圆角的图片
- 贝塞尔曲线
- 添加遮罩
6.常见触发离屏渲染的几种情况
- 使用了mask的layer (图层叠加)
- 多层layer需要裁剪 (layer.masksToBounds / view.clipsToBounds)
- 设置了组透明度为YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/ layer.opacity)
- 添加了投影的 layer (layer.shadow*)
- 采用了光栅化的 layer (layer.shouldRasterize)
- 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)