接每日一问02,知道了渲染的大致流程以后,我们在开发的时候要注意些什么呢?有什么会影响到渲染的性能?知识的搬运工就是我,我又来了
接上文,我知道了图像渲染主要是由GPU完成的,那么会不会有一些情况影响GPU的性能呢?
图像对GPU的影响
1. 图像多层次的合成—为何设置透明会增加GPU工作量
在图形世界中,合成是一个描述不同位图如何放到一起来创建你最终在屏幕上看到图像的过程。
一个不透明的红色盖在蓝色上那我们看到的就是一个蓝色,但一个半透明的红色盖在蓝色让我们得到的却是一个紫色,这便是合成所要做的工作。
我们可以用下面这个公式来计算每一个像素:
R = S + D * ( 1 – Sa )
结果的颜色是源色彩(顶端纹理)+目标颜色(低一层的纹理)*(1-源颜色的透明度)。在这个公式中所有的颜色都假定已经预先乘以了他们的透明度。
假定两个纹理都完全不透明,比如 alpha=1.如果目标纹理(低一层的纹理)是蓝色(RGB=0,0,1),并且源纹理(顶层的纹理)颜色是红色(RGB=1,0,0),因为 Sa 为1,所以结果为:
R = S
如果源颜色层为50%的透明,比如 alpha=0.5,既然 alpha 组成部分需要预先乘进 RGB 的值中,那么 S 的 RGB 值为(0.5, 0, 0),公式看起来便会像这样:
所以当源纹理是完全不透明的时候,目标像素就等于源纹理。这可以省下 GPU 很大的工作量
这也是为什么 CALayer 有一个叫做 opaque 的属性了。如果这个属性为 NO,GPU 将不会做任何合成,而是简单从这个层拷贝,不需要考虑它下方的任何东西(因为都被它遮挡住了)
优化点:在多图层叠加的情况下,图层不需要透明度时,最好将opaque设置为NO
2.图层对齐—为何图片缩放会增加GPU工作量
简单的情况:
1.当所有的像素是对齐的时候我们得到相对简单的计算公式。每当 GPU 需要计算出屏幕上一个像素是什么颜色的时候,它只需要考虑在这个像素之上的所有 layer 中对应的单个像素,并把这些像素合并到一起。
2.如果最顶层的纹理是不透明的(即图层树的最底层),这时候 GPU 就可以简单的拷贝它的像素到屏幕上。
当一个 layer 上所有的像素和屏幕上的像素完美的对应整齐,那这个 layer 就是像素对齐的。主要有两个原因可能会造成不对齐。
第一个便是滚动;当一个纹理上下滚动的时候,纹理的像素便不会和屏幕的像素排列对齐。
另一个原因便是当纹理的起点不在一个像素的边界上。
在这两种情况下,GPU 需要再做额外的计算。它需要将源纹理上多个像素混合起来,生成一个用来合成的值。
当所有的像素都是对齐的时候,GPU 只剩下很少的工作要做。
离屏渲染
1.什么是离屏渲染
On-Screen Rendering
意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。
Off-Screen Rendering
意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
当图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制时,屏幕外渲染就被唤起了。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)
。
离屏渲染可以被 Core Animation 自动触发,或者被应用程序强制触发。屏幕外的渲染会合并/渲染图层树的一部分到一个新的缓冲区,然后该缓冲区被渲染到屏幕上。
这里提到的offscreen rendering主要讲的是通过GPU执行的offscreen,事实上还有的offscreen rendering是通过CPU来执行的。
如果我们重写了drawRect方法,并且使用任何Core Graphics的技术进行了绘制操作,就涉及到了CPU渲染。整个渲染过程由CPU在App内同步地完成,渲染得到的bitmap最后再交由GPU用于显示。其它类似cornerRadios, masks, shadows等触发的offscreen是基于GPU的。
2.为什么要谨慎避免离屏渲染
离屏渲染主要在两个地方开销较大:
创建新缓冲区
要想进行离屏渲染,首先要创建一个新的缓冲区。
上下文切换
离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上有需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。
3.一些触发离屏渲染的方式
设置了以下属性时,都会触发离屏绘制:
shouldRasterize(光栅化)
masks(遮罩)
shadows(阴影)
edge antialiasing(抗锯齿)
group opacity(不透明)
PS:光栅化
在CALayer中,设置shouldRasterize = YES便会触发光栅化,且会将光栅化后的内容缓存起来。相当于光栅化是把GPU的操作转到CPU上了,生成位图缓存,直接读取复用。
因为离屏渲染本身开销较大,所以对于是否需要光栅化,应该因地制宜地使用。且系统设置了对这个光栅化的内存使用限制,有两点需要注意:
不要过度使用,系统限制了缓存的大小为2.5X Screen Size.如果过度使用,超出缓存之后,同样会造成大量的offscreen渲染。
被光栅化的图片如果超过100ms没有被使用,则会被移除
因此我们应该只对连续不断使用的图片进行缓存。对于不常使用的图片缓存是没有意义,且耗费资源的。
对于那些需要动画而且要在屏幕外渲染的图层来说,你可以用CAShapeLayer,contentsCenter或者shadowPath来获得同样的表现而且较少地影响到性能
小结:
在上一章中。我们已经知道了影响界面卡顿的主要原因是在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃
。所以,我们要尽可能的节约CPU,GPU的开销。在知道本文中讲述的影响渲染性能的关键点以后,开发中就可以注意使用相关功能来减少对CPU/GPU的开销。
参考文章:
iOS 保持界面流畅的技巧
iOS图形原理与离屏渲染
iOS 事件处理机制与图像渲染过程