iOS 离屏渲染(Off-Screen Rendering)剖析

2020-07-28  本文已影响0人  卡卡奇布

  上一篇 我们介绍了图像从数据到屏幕的渲染过程,现在我们来研究一下iOS的离屏渲染。那我们先来看一下渲染模式,iOS下有两种渲染模式:在屏渲染和离屏渲染。

image

一、在屏渲染(On-Screen Rendering)/离屏渲染(Off-Screen Rendering)

  在屏渲染: 上一篇讲到:GPU渲染的数据会放在帧缓冲区,然后视频控制器从帧缓冲区读取数据显示到屏幕上。GPU渲染使用的内存是帧缓冲区(GPU和显存共享物理内存),就是在屏渲染

  离屏渲染: GPU在当前帧缓冲区以外开辟一个缓冲区进行渲染操作。意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。GPU另外开辟的空间是有限制的,最大为屏幕像素点的2.5倍。

  按照这样的说法,如果将不在GPU的当前屏幕缓冲区中进行的渲染都称为离屏渲染,那么就还有另一种特殊的“离屏渲染”方式:CPU渲染。如果我们重写了drawRect方法,并且使用任何Core Graphics的技术进行了绘制操作,就涉及到了CPU渲染。整个渲染过程由CPU在App内同步地完成,渲染得到的bitmap最后再交由GPU用于显示。但是这个过程并不会被Xcode识别。如果你的view实现了drawRect,此时打开Xcode调试的“Color offscreen rendered yellow”开关,你会发现这片区域不会被标记为黄色,说明Xcode并不认为这属于离屏渲染。

二、为什么要离屏渲染

  iOS主要的渲染操作都是由CoreAnimation的Render Server模块,通过调用显卡驱动所提供的OpenGL/Metal接口来执行的。通常对于每一层layer,Render Server会遵循“画家算法”,按次序输出到frame buffer,后一层覆盖前一层,就能得到最终的显示结果。

  但是某些场景并没有那么简单。GPU虽然可以一层一层往画布上进行输出,但是无法在某一层渲染完成之后,再回过头来擦除/改变其中的某个部分——因为在这一层之前的若干层layer像素数据,已经在渲染中被永久覆盖了。比如需要在一张图片上加个遮罩效果,这样就需要有个状态记录上一次图片的内容,将遮罩与图片进行混合,然后再显示出来。这个动作就需要对于每一层layer,要么能找到一种通过单次遍历就能完成渲染的算法,要么就不得不另开一块内存,借助这个临时中转区域来完成一些更复杂的、多次的修改/剪裁操作(由系统自动触发)

  另外,离屏渲染在某些情况带来效率提升。比如一个熏染效果需要重复使用,那么我们就提前渲染好放在OffscreenBuffer,达到复用目的。(开发者手动触发)

三、什么情况会触发GPU离屏渲染?

1、 圆角效果:必须满足条件:cornerRadius+clipsToBounds+存在显示的子图层

2、阴影

  其原因在于,虽然layer本身是一块矩形区域,但是阴影默认是作用在其中”非透明区域“的,而且需要显示在所有layer内容的下方,因此根据画家算法必须被渲染在先。但矛盾在于此时阴影的本体(layer和其子layer)都还没有被组合到一起,怎么可能在第一步就画出只有完成最后一步之后才能知道的形状呢?这样一来又只能另外申请一块内存,把本体内容都先画好,再根据渲染结果的形状,添加阴影到frame buffer,最后把内容画上去。不过如果我们能够预先告诉CoreAnimation(通过shadowPath属性)阴影的几何形状,那么阴影当然可以先被独立渲染出来,不需要依赖layer本体,也就不再需要离屏渲染了。

3、group opacity(透明度)

  其实从名字就可以猜到,alpha并不是分别应用在每一层之上,而是只有到整个layer树画完之后,再统一加上alpha,最后和底下其他layer的像素进行组合。显然也无法通过一次遍历就得到最终结果。将一对蓝色和红色layer叠在一起,然后在父layer上设置opacity=0.5,并复制一份在旁边作对比。左边关闭group opacity,右边保持默认(从iOS7开始,如果没有显式指定,group opacity会默认打开),然后打开offscreen rendering的调试,我们会发现右边的那一组确实是离屏渲染了。

4、mask(遮罩)

  我们知道mask是应用在layer和其所有子layer的组合之上的,而且可能带有透明度,那么其实和group opacity的原理类似,不得不在离屏渲染中完成。

5、UIBlurEffect(模糊效果)

  渲染的位图并不能直接给帧缓存区等待显示,而要经过模糊处理之后才能将最后的渲染数据 -> 帧缓冲区-> 显示。同样无法通过一次遍历完成,其原理在WWDC中提到

image

6、shouldRasterize(光栅化)

  为图层设置layer.shouldRasterize=true

7、 edge antialiasing(抗锯齿)

8、颜色渐变

四、CPU离屏渲染

  使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染文本(任何种类,包括UILabel,CATextLayer,Core Text等)。

五、GPU离屏渲染的性能影响

  (1)创建新缓冲区

  要想进行离屏渲染,首先要创建一个新的缓冲区。

  (2)上下文切换

  离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen),等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上有需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。

六、如何使用离屏渲染

1、 圆角优化:

2、shadow优化

  对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优化性能,能大幅提高性能。

3、shouldRasterize (光栅化使用建议)

4、其他建议

七、如何测试离屏渲染

1、Instruments 卡顿监测

Time Profiler ->Call Tree Options :

  1. Separete By Thread :按线程划分

  2. Invert Call Tree :逆向调用树,方便查看调用顺序

  3. Hide System Libraries:隐藏系统库

Core Animation ->Debug Options :

  1. Color Blended Layers :监测图层混合情况,没有混合的部分为绿色,混合最严重的部分是红色,大量图层混合会消耗GPU的时间。

  2. Color Copied Images :监测图片颜色格式,如果GPU不支持当前图片的颜色格式,会将其交给CPU预先进行格式转化,并且这张图片被标记为蓝色。(Apple 的 GPU值解析32bit的颜色格式,RGBA)

  3. Color Immediately :设置调试颜色每帧更新。(一般不用)

  4. Color Compositing-Fast-Path Blue :对任何直接使用OpenGL绘制的图层高亮。

  5. Flash Updated Regions :对重绘的内容高亮成黄色。(使用Core Graphics绘制的图层)

  6. Color Hits Green and Misses Red :光栅化监测,前面已述。

  7. Color Offscreen-Renderded Yellow :离屏渲染监测,前面已述。

  8. Color Non-Standard Surface Formats:Apple 文档没注解(一般不用)

参考文章

关于iOS离屏渲染的深入研究

iOS圆角的离屏渲染,你真的弄明白了吗

关于 iOS 离屏渲染的分析与处理

iOS 关于离屏渲染的理解 以及解决方案

关于 iOS 离屏渲染的分析与处理

上一篇 下一篇

猜你喜欢

热点阅读