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

  上一篇 我们介绍了图像从数据到屏幕的渲染过程,现在我们来研究一下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+存在显示的子图层

  •  设置cornerRadius+clipsToBounds就一定会触发离屏渲染么?如果你草率回答是,那么面试估计就GG了。

  •  iOS 9.0 之前:UIimageView跟UIButton设置圆角都会触发离屏渲染。

  •  iOS 9.0 之后:UIImageView里仅图片设置圆角不会触发离屏渲染了。如果设置其他背景、阴影效果之类的还是会触发离屏渲染的。UIbutton仅背景颜色和边框情况下设置圆角不会触发离屏渲染。如果设置其他背景图片、阴影效果之类的还是会触发离屏渲染的。

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、 圆角优化:

  • 如果Button单纯的需要圆角效果,不需要裁剪,则不设置button.clipsToBounds 就不会触发离屏渲染。
  • 使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角。
  • 使用CAShapeLayer和UIBezierPath设置圆角。

2、shadow优化

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

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

  • 如果layer不能被复用,则没有必要打开光栅化。
  • 如果layer不是静态的,需要别频繁的修改,比如处于动画之中,开启光栅化反而会影响效率。
  • 离屏渲染缓存内容有时间限制,缓存内容100ms内如果没有被使用,就会被丢弃,无法被复用了。
  • 离屏渲染缓存空间有限,超过2.5倍屏幕像素大小,也会失效,无法进行复用。

4、其他建议

  • 当我们需要圆角效果时,可以使用一张中间透明图片蒙上去
  • 使用ShadowPath指定layer阴影效果路径
  • 使用异步进行layer渲染(Facebook开源的异步绘制框架AsyncDisplayKit)
  • 设置layer的opaque值为YES,减少复杂图层合成
  • 尽量使用不包含透明(alpha)通道的图片资源
  • 尽量设置layer的大小值为整形值
  • 直接让美工把图片切成圆角进行显示,这是效率最高的一种方案
  • 很多情况下用户上传图片进行显示,可以让服务端处理圆角
  • 使用代码手动生成圆角Image设置到要显示的View上,利用UIBezierPath(CoreGraphics框架)画出来圆角图
  • 渲染不是CPU的强项,调用CoreGraphics会消耗其相当一部分计算时间,并且我们也不愿意因此阻塞用户操作,因此一般来说CPU渲染都在后台线程完成,然后再回到主线程上,把渲染结果传回CoreAnimation。这样一来,多线程间数据同步会增加一定的复杂度。同样因为CPU渲染速度不够快,因此只适合渲染静态的元素,如文字。
  • 作为渲染结果的bitmap数据量较大(形式上一般为解码后的UIImage),消耗内存较多,所以应该在使用完及时释放,并在需要的时候重新生成,否则很容易导致OOM。
  • 如果你选择使用CPU来做渲染,那么就没有理由再触发GPU的离屏渲染了,否则会同时存在两块内容相同的内存,而且CPU和GPU都会比较辛苦
    一定要使用Instruments的不同工具来测试性能,而不是仅凭猜测来做决定。

七、如何测试离屏渲染

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 离屏渲染的分析与处理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267