离屏渲染定义
所有渲染操作将渲染到当前绑定的帧缓冲的附加缓冲中,由于我们的帧缓冲不是默认的帧缓冲,渲染命令对窗口的视频输出不会产生任何影响。出于这个原因,它被称为离屏渲染(off-screen rendering),就是渲染到一个另外的缓冲中。
GPU渲染
GPU屏幕渲染有以下两种方式:
On-Screen Rendering意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。
Off-Screen Rendering意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在两个方面:
-
创建新缓冲区
要想进行离屏渲染,首先要创建一个新的缓冲区。
-
上下文切换
离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen),等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上有需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。
CPU渲染
特殊的离屏渲染:如果将不在GPU的当前屏幕缓冲区中进行的渲染都称为离屏渲染,那么就还有另一种特殊的“离屏渲染”方式: CPU渲染。Core Graphics做绘制的时候,会有上下文Context,也有一个Bitmap画布,但是这个Bitmap画布是在CPU内存上面的,上下文Context也和上面说的环境转换:昂贵的环境转换(转换环境到屏幕外缓冲区,然后转换环境到帧缓冲区)不是一码概念。因此把CPU绘制归为离屏渲染个人感觉非常不妥。
其主要意思是:
Core Graphics 的绘制 API 的确会触发离屏渲染,但不是那种 GPU 的离屏渲染。使用 Core Graphics 绘制 API 是在 CPU 上执行,触发的是 CPU 版本的离屏渲染。
离屏渲染产生原因
当视图中有多个图层(Layer)需要借助离屏缓冲区完成完成一些更复杂的、多次的修改/剪裁操作,便会触发离屏渲染。比如下面讲的离屏渲染的触发方式
离屏渲染的触发方式
设置了以下属性时,都会触发离屏绘制:
-
shadows(阴影)
其原因在于需要显示在所有layer内容的下方,因此必须被渲染在先。但此时阴影的本体(layer
和其子layer
)都还没有被组合到一起,只能另外申请一块内存,把本体内容都先画好,再根据渲染结果的形状,添加阴影到帧缓冲区frame buffer
,最后把内容画上去。不过如果我们能够预先告诉CoreAnmation
(通过shadowPath
属性)阴影的几何形状,那么阴影当然可以先被独立渲染出来,不需要依赖layer
本体,也就不再需要离屏渲染了。
- 设置了组透明度为YES,并且透明度不为1的layer(不透明)
产生离屏渲染的条件是layer.opacity != 1.0并且有子 layer 或者背景图。alpha并不是分别应用在每一层之上,而是只有到整个layer树画完之后,再统一加上alpha,最后和底下其他layer的像素进行组合。显然也无法通过一次遍历就得到最终结果。
-
masks(遮罩)
我们知道mask是应用在layer和其所有子layer的组合之上的,而且可能带有透明度,那么其实和group opacity的原理类似,不得不在离屏渲染中完成。
-
cornerRadius+maskToBounds
容器的子layer
因为父容器有圆角,那么也会需要被裁剪,而这时它们还在渲染队列中排队,尚未被组合到一块画布上,自然也无法统一裁剪,不得已只能另开一块内存来操作。而如果只是设置cornerRadius
,并不会触发离屏渲染。这个情况下 用 CAShapeLayer 就可以避免这个问题了。
// 用 CAShapeLayer 画一个圆角矩形
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *layerView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//create shape layer
CAShapeLayer *blueLayer = [CAShapeLayer layer];
blueLayer.frame = CGRectMake(50, 50, 100, 100);
blueLayer.fillColor = [UIColor blueColor].CGColor;
blueLayer.path = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, 100, 100) cornerRadius:20].CGPath;
//add it to our view
self.layerView.layer addSublayer:blueLayer];
}
@end
-
shouldRasterize(光栅化)
- 如果
layer
不是静态的,我们更新已光栅化的layer
,会造成大量的离屏渲染。
例如UITableViewCell
因为复用的原因,重绘是很频繁的。如果此时设置了光栅化,会造成大量离屏渲染,降低性能。 - GPU都会应对重绘 2.5 *
Screen Size
依然保持 60Hz 帧率的渲染。并不要过度使用,系统限制了缓存的大小为 2.5 *Screen Size
。超出缓存之后,同样会造成大量的离屏渲染。 - 离屏渲染缓存内容有时间限制,被光栅化的图片(即缓存内容)如果超过100ms没有被使用,那么它就会丢弃,无法进行复用。(所以光栅化只能用在图像内容不变的前提下,且只对连续不断使用的图片进行缓存:用于避免静态内容的复杂特效的重绘,例如
UIBlurEffect
用于避免多个View
嵌套的复杂View
的重绘。) - 有时候我们可以把那些需要屏幕外绘制的图层开启光栅化以作为一个优化方式, 前提是这些图层并不会被频繁地重绘。
- 如果
-
edge antialiasing(抗锯齿)
网友评论