视图内容的绘制是CPU的工作,GPU负责内容的渲染,包括多个视图的拼接(Compositing)、纹理的渲染(Texture)等,CPU计算好显示内容提交到GPU,GPU渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
如果满足视觉上流畅,屏幕刷新就需要达到60fps,否则就会感到卡顿、掉帧。60fps,即60帧/秒,折合每帧16.67ms。在16.67ms中,需要CPU和GPU协同合成一帧画面。如果在16.67ms之内,CPU和GPU没有完成当前帧的合成,在下一个VSync信号开始前需要显示的帧画面没有准备好,就会导致页面卡顿、掉帧。
渲染的过程如下:
- UIView的layer层有一个context,指向一块缓存,即backing store
- UIView绘制时,会调用drawRect方法,通过context将数据写入backing store
- 在backing store写完后,通过render server交给GPU去渲染,将backing store中的bitmap数据显示在屏幕上
定义
GPU有两种渲染方式:
-
On-Screen Rendering (当前屏幕渲染)
指的是GPU在当前用于显示的屏幕缓冲区进行渲染操作。 -
Off-Screen Rendering (离屏渲染)
真正的离屏渲染发生在 GPU,指的是 GPU 在当前屏幕缓冲区外新开辟一个缓冲区进行渲染操作。通俗讲当我们设置某些UIView的图层属性,标记为在没有预合成之前(下一个VSync信号开始前)不能直接在屏幕中绘制的时候就触发了离屏渲染。
还有一种:CPU离屏渲染 的说法,如果我们重写了drawRect方法,并且使用任何Core Graphics的技术进行了绘制操作,就涉及到了CPU离屏渲染。因为这种新开一块CGContext来画图,的操作,像素数据是暂时存入CGContext,而不是直接到了帧缓冲区,只能暂时先放在另一块内存汇总,说起来是”离屏渲染“,CPU渲染并非真正意义上的离屏渲染。”CoreGraphic通常是线程安全的,所以可以进行异步绘制,显示的时候再放回主线程。
GPU离屏渲染:
主要的渲染操作都是由CoreAnimation的Render Server模块,通过调用显卡驱动所提供的OpenGL/Metal接口来执行的。通常对于每一层layer,render server 会遵循“画家算法”,按次序输入到frame buffer,后一层覆盖前一层,最终得到显示结果。但有些场景比较特殊,画家算法虽然可以一层一层往画布上进行输出,但是无法在某一层渲染完成后,再回过头来擦除/改变其中某部分,因为在这一层之前的若干层layer像素数据,已经在渲染中被永久覆盖,意味着对于每一层layer,要么能够找到一种通过单次便利就能完成的渲染算法,要么另开一块内存,借助这个临时中转区域来完成更加复杂的,多次的修改,裁剪操作。
怎样会触发
被动触发
- 圆角(同时设置layer.cornerRadius大于0 ,layer.masksToBounds=YES时才会触发!);
- 阴影,layer.shadowXXX (如果设置了layer.shadowPath则不会产生离屏渲染)
- 遮罩(layer.mask),模糊;
- 重写 -drawRect: 方法,视图只要设置背景颜色,也会触发离屏渲染。
屏幕上的每个像素点的颜色是由当前像素点上的多层layer(如果存在)共同决定的,GPU会对重叠视图的每个像素的RGBA值进行计算得出出最终混合颜色的RGBA值,显示在屏幕上。对于完全不透明的layer,GPU会忽略下面所有的layer,节约计算量。
layer的合成计算是非常消耗GPU性能,多层级大尺寸视图尤为明显。体现在代码层面,CALayer.opaque=YES,GPU就不会进行图层的合成。
opaque 表示当前UIView是否不透明,但是决定不了当前UIView是不是不透明,比如你将opaque设为NO,该UIView照样是可见的(是否可见是由alpha或hidden属性决定的),如果opaque被设置成YES,而对应UIView的alpha属性不为1.0的时候,就会有不可预料的情况发生。
主动触发
- 光栅化。利用 CALayer.shouldRasterize 属性,开启光栅化。光栅化的作用是强制将 CALayer 以 bitmap 的形式进行缓存,等价于屏幕外的缓存,也属于离屏渲染的概念。
为什么要避免?
- 离屏渲染需要在屏幕外创建新的缓冲区,即需要开辟新的内存空间,内存开销大。
- 离屏渲染会增加GPU的工作量,虽然在GPU面临性能瓶颈时,将压力转移一部分给CPU,然而CPU的渲染能力远没有GPU高效。如果工作量的增加导致在下一次帧信号到来时需要显示的帧画面没有准备好,就会导致页面卡顿、掉帧。
- 离屏渲染触发多通道管线,渲染上下文会在当前屏幕缓冲区和屏幕外新建的渲染缓冲区之间频繁切换,而这种上下文切换的代价非常昂贵。
- 创建缓冲区和切换上下文最消耗性能,而绘制其实不是性能损耗的主要原因。
所以离屏渲染会带来各方面的开销,要尽可能的避免。
怎样减少或避免
- 对于圆角、阴影,可以使用使用贝塞尔曲线UIBezierPath实现;
- 设置CALayer.opaque=YES,减少复杂图层合成;
- backgroundColor和父视图颜色一致且不透明。如无特殊需要,不要设置低于1的alpha值;
- 尽量使用不包含透明(alpha)通道的图片资源;
- 确保图片大小和frame一致,不要在滑动时缩放图片;
- 确保图片颜色格式被GPU支持,避免转给CPU转换。
网友评论