离屏渲染的基本概念
离屏渲染(Off-Screen Rendering)是指GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作.
通常情况下来说, 离屏渲染非常消耗性能, 主要在两个方面:
- 创建新缓冲区: 要想进行离屏渲染,首先要创建一个新的缓冲区(iOS使用双framebuffer策略, 这里创建的是除这两个buffer以外的缓冲区)
- 渲染的上下文切换: 进行两次上下文环境切换, 先切换到屏幕外环境, 离屏渲染完成后再切换到当前屏幕, 上下文的切换是很高昂的消耗。
产生离屏渲染的原因就是一些图层不能直接绘制在屏幕上, 而是渲染到额外开辟的纹理中, 必须进行多个渲染的中间纹理结果进行预合成. 另外, 触发离屏渲染后这种转换发生在每一帧, 在界面的滚动过程中如果有大量的离屏渲染发生时会严重影响帧率。
tips: 注意区分两种离屏渲染
Core Graphics 的绘制 API 的确会触发离屏渲染,但不是那种 GPU 的离屏渲染。使用 Core Graphics 绘制 API 是在 CPU 上执行,触发的是 CPU 版本的离屏渲染.
我们这里研究的更多是GPU的离屏渲染, 在Core Animation Debug中
Color Offscreen-Renderd Yellow
就是为了检测这种内容
离屏渲染存在的原因
主要是因为下面这两种原因:
- 一些特殊效果需要使用额外的
Offscreen Buffer
来保存渲染的中间状态,所以不得不使用离屏渲染。例如mask, 还有 UIBlurEffectView等 - 处于效率目的,如果是非常复杂的静态页面, 可以将内容提前渲染保存在
Offscreen Buffer
中,达到复用的目的. 例如CALayer的shouldRasterize属性, 开启以后Render Server 会强制将 CALayer 的渲染位图结果 bitmap 保存下来,这样下次再需要渲染时就可以直接复用,从而提高效率
哪些常见的场景会触发离屏渲染
在Apple 的WWDC2011 - session 121 understanding uikit rendering
中解释CALayer的如下4个属性会导致离屏渲染: mask, shadow, group opacity, edge antialiasing, 另外一个是我们在开发中最常见的场景是 — 圆角:
backgroundColor, layer.content, layer.cornerRadius, layer.masksToBounds
(UIView中是clipToBounds
)组合场景!!!- 给图层设置 mask. 这个可以参考
文章1中的离屏渲染 -- MASK
- layer的
allowsGroupOpacity
属性为YES且opacity
小于1.0,GroupOpacity是指子图层的透明度值不能大于父图层的 - 设置了shadow(阴影)
关于cornerRadius
在下面会单独说明
重中之重: UIView的圆角场景!!!
这里先来看一下, 下面这一句会导致什么情况:
view.layer.cornerRadius = 5
复制代码
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 YES causes the content to be clipped to the rounded corners.
复制代码
layer的background color
border of layer
以及layer content
的关系如下:
总结起来就是:
-
layer.cornerRadius
只会对background color
和border of the layer
起作用, 但是对layer.content
并不起作用!!! -
cornerRaidus
如果需要对layer.content
起作用必增加layer.masksToBounds
另外有一些文章, 分析了layer.content
与上面这个几个属性在各种情况下是否会发生离屏渲染的结论, 具体如下:
-
设置 backgroundColor, borderWidth, borderColor, cornerRadius=> 不会触发离屏渲染
-
在1的条件下增加
masksToBounds
=> 不会触发离屏渲染 -
在2的基础上增加
layer.content
==> 触发离屏渲染 -
或者在该view上增加带图的subLayer …. ==> 离屏渲染
具体的实践文章可以参考:
文章中总结了离屏渲染的真正原因:
图层的叠加绘制大概遵循“画家算法”, 具体 油画算法:先绘制场景中的离观察者较远的物体,再绘制较近的物体。本来我们从后往前绘制,绘制完一个图层就可以丢弃了。但现在需要依次在 Offscreen Buffer中保存,等待圆角+裁剪处理,即引发了 离屏渲染
- 背景色、边框、背景色+边框,再加上圆角+裁剪,根据文档说明,因为 contents = nil 没有需要裁剪处理的内容,所以
masksToBounds
设置为YES
或者NO
都没有影响。 - 一旦我们 为contents设置了内容 ,无论是图片、绘制内容、有图像信息的子视图等,再加上圆角+裁剪,就会触发离屏渲染。
iOS9以后的优化
layer.contents/imageView.image 复制代码
我们只设置
contents
或者UIImageView
的image
,并加上圆角+裁剪,是不会产生离屏渲染.但是如果增加backgroundColor, borderWidth/color, 或者增加其他图层, 就会导致离屏渲染
UIButton的特例, UIButton中增加一个图会增加一个 UIImageView, 所以iOS9以后依然会产生离屏渲染
一句话总结:
只要裁剪的内容需要画家算法未完成之前的内容参与就会触发offscreen rendering
另外, UIView圆角的内容解决方案:
- 如果不需要对外部来源的图片做圆角, 由设计师直接画成圆角图片是最方便的
- 混合图层:在要添加圆角的视图上再叠加一个部分透明的视图,只对圆角部分进行遮挡
- 上述两种方法不行, 在使用的图像, 使用CoreGraphic直接异步重绘成一个圆角图!!!
其他场景的渲染
对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath
来优化性能,能大幅提高性能。示例如下:
imageView.layer.shadowColor = [UIColor grayColor].CGColor;
imageView.layer.shadowOpacity = 1.0;
imageView.layer.shadowRadius = 2.0;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath = path.CGPath;
复制代码