离屏渲染

542 阅读4分钟

如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。

CPU离屏渲染(软件渲染)

在UIView中实现了drawRect方法,就算它的函数体内部实际没有代码,系统也会为这个view申请一块内存区域,等待CoreGraphics可能的绘画操作。

新开一块CGContext来画图,像素数据是暂时存入了CGContext。

CPU进行的光栅化操作(如文字渲染、图片解码),都无法直接绘制到由GPU掌管的frame buffer,只能暂时先放在另一块内存之中。

GPU离屏渲染

在iOS中,设备主存和GPU的显存共享物理内存,这样可以省去一些数据传输开销。

对于每一层layer,要么能找到一种通过单次遍历就能完成渲染的算法,要么就不得不另开一块内存,借助这个临时中转区域来完成一些更复杂的、多次的修改/剪裁操作。

如果只有一个layer( cornerRadius +  masksToBounds)不会发生离屏渲染。只有多个layer才会。

UIImageView( cornerRadius +  masksToBounds)单独设置图片不会产生离屏渲染,同时设置背景颜色会发生离屏渲染。

UIButton( cornerRadius +  masksToBounds)设置背景颜色不会产生离屏渲染,设置背景图片setBackgroundImage会产生离屏渲染。

常见离屏渲染场景分析

1、cornerRadius+clipsToBounds

clipsToBounds(UIView)是指视图上的子视图,如果超出父视图的部分就截取掉 masksToBounds(CALayer)却是指视图的图层上的子图层,如果超出父图层的部分就截取掉

2、shadow

如果我们能够预先告诉CoreAnimation(通过shadowPath属性)阴影的几何形状,那么阴影当然可以先被独立渲染出来,不需要依赖layer本体,也就不再需要离屏渲染了。

3、opacity

alpha并不是分别应用在每一层之上,而是只有到整个layer树画完之后,再统一加上alpha,最后和底下其他layer的像素进行组合。

4、mask

mask是应用在layer和其所有子layer的组合之上的,而且可能带有透明度。

5、UIBlurEffect

在tableView或者collectionView中,滚动的每一帧变化都会触发每个cell的重新绘制。

shouldRasterize设置为true,Render Server就会强制把layer的渲染结果(包括其子layer,以及圆角、阴影、group opacity等等)保存在一块内存中,在下一帧仍然可以被复用,而不会再次触发离屏渲染。

如果你的layer本来并不复杂,也没有圆角阴影等等,打开这个开关反而会增加一次不必要的离屏渲染。

  • 离屏渲染缓存有空间上限,最多不超过屏幕总像素的2.5倍大小。
  • 一旦缓存超过100ms没有被使用,会自动被丢弃。

layer的内容(包括子layer)必须是静态的,因为一旦发生变化(如resize,动画),之前处理得到的缓存就失效了。

如果layer的子结构非常复杂,可以打开这个开关,把layer绘制到一块缓存,就不需要每次都重新绘制整个layer树。

项目中应用:

在iOS10之后,系统的设计风格慢慢从扁平化转变成圆角卡片。

加入了大量圆角与阴影效果,如果在处理上稍有不慎,就很容易触发离屏渲染。

1、大量应用Texture作为主要渲染框架,对于文字和图片的异步渲染操作交由框架来处理。

2、对于图片的圆角,不经由容器来做剪切,预先使用CoreGraphics为图片裁剪圆角。

3、对于视频的圆角,由于实时剪切非常消耗性能,我们会创建四个白色弧形的layer盖住四个角,从视觉上制造圆角的效果。

4、对于view的圆形边框,如果没有backgroundColor,使用cornerRadius来实现。

5、对于所有的阴影,使用shadowPath来规避离屏渲染。

6、对于特殊形状的view,使用layer mask并打开shouldRasterize来对渲染结果进行缓存。

7、对于模糊效果,不采用系统提供的UIVisualEffect,而是另外实现模糊效果(CIGaussianBlur),并手动管理渲染结果。