CPU 和 GPU渲染
OpenGL中,GPU屏幕渲染有以下两种方式:
-
On-Screen Rendering
意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。 -
Off-Screen Rendering
意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
按照这样的说法,如果将不在GPU的当前屏幕缓冲区中进行的渲染都称为离屏渲染,那么就还有另一种特殊的“离屏渲染”方式:CPU渲染。如果我们重写了drawRect方法,并且使用任何Core Graphics的技术进行了绘制操作,就涉及到了CPU渲染。整个渲染过程由CPU在App内同步地完成,渲染得到的bitmap最后再交由GPU用于显示。
相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在两个方面:
-
创建新缓冲区
要想进行离屏渲染,首先要创建一个新的缓冲区。 -
上下文切换
离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上有需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。
设置了以下属性时,都会触发离屏绘制:
-
shouldRasterize(光栅化)
-
masks(遮罩)
-
shadows(阴影)
-
edge antialiasing(抗锯齿)
-
group opacity(不透明)
需要注意的是,如果shouldRasterize被设置成YES,在触发离屏绘制的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用。这将在很大程度上提升渲染性能。
而其它属性如果是开启的,就不会有缓存,离屏绘制会在每一帧都发生。
在开发时需要根据实际情况来选择最优的实现方式,尽量使用On-Screen Rendering。简单的Off-Screen Rendering可以考虑使用Core Graphics让CPU来渲染。
减少离屏渲染
CALayer 的 border、圆角、阴影、遮罩(mask),CAShapeLayer 的矢量图形显示,通常会触发离屏渲染(offscreen rendering).与之相对的是当前屏幕渲染(On-Screen Rendering),指的是渲染操作是用于在当前屏幕显示的缓冲区进行.离屏渲染的概念来自于 OpenGL 中 GPU 渲染屏幕的两种方式: On-Screen Rendering(当前屏幕渲染)Off-Screen Rendering(离屏渲染)。 指的是:在当前屏幕以外新开辟一个缓冲区进行渲染操作。离屏渲染造成卡顿的原因是:离屏渲染需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen),等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕,而上下文环境的切换是一项高开销的动作。
通常我们会一个 view 设置阴影会使用 shadowoffset
1 2 3 4 | UIView *diamondView = [[UIView alloc] init]; diamondView.layer.shadowOffset = CGSizeMake(1.0f, 1.0f); diamondView.layer.shadowRadius = 5.0f; diamondView.layer.shadowOpacity = 0.5; |
但是这种方式会触发离屏渲染造成不必要的开销,那么既要实现阴影图层,又要减少离屏渲染,提高性能的话.有什么更好的方式么?
1 2 3 | UIView *diamondView = [[UIView alloc] init]; diamondView.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(diamondView.bounds.origin.x + 1, diamondView.bounds.origin.y + 1, diamondView.bounds.size.width, diamondView.bounds.size.height)].CGPath; imageView.layer.shadowOpacity = 0.5; |
但是 shadowPath
只适用于给规则的矩形生成阴影路径.如果我们迫不得已要使用 shadowoffset
,可以尝试开启 CALayer.shouldRasterize 属性, 图像将会被缓存起来并绘制到实际图层的 contents 和子图层.将原本在 GPU 中的一些工作让 CPU 来做,让两者达到一个平衡.但是这并不是有一个全优解,因为光栅化原始图像需要时间,而且会消耗额外的内存.所以一定要避免在内容不断变动的图层上使用,不然缓存的优势将荡然无存.
最完美的解决方案是使用 Core Graphics 绘制圆角 UIImage 设置给 UIImageView 然后插入到 UIView 中去。