iOS中离屏渲染触发机制

iOS系统中离屏渲染利与弊

阅读需要约8分钟

前言

性能的优化相信是每一个APP工程师所追求的,而离屏渲染就是一个绕不开的知识点。现在提出几个问题帮助大家更快的理解:

  1. 什么情况下会触发离屏渲染,为什么?
  2. 利、弊都有哪些?
  • 离屏渲染是如何发生的

先通过模拟器来看看是情况下会发生离屏渲染。
打开模拟器 - Debug - Color Off-screen Rendered开关


提起离屏渲染,下意识的就是会想到cornerRadius这个属性,设置圆角就会导致离屏渲染,事实是这样吗?

        let view1 = UIView(frame: rect)
        view1.backgroundColor = UIColor.red
        view1.layer.cornerRadius = 50
        view1.layer.borderWidth = 2
        view.addSubview(view1)

没有触发离屏渲染


我们先来看看苹果官方文档对于cornerRadius的描述:

Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.

我们发现设置cornerRadius大于0时,只为layer的backgroundColorborder设置圆角;而不会对layer的contents设置圆角,除非同时设置了layer.masksToBounds为true或者UIView的clipsToBounds属性。那我们设置这两个属性看一下。

        let view1 = UIView(frame: rect)
        view1.backgroundColor = UIColor.red
        view1.layer.cornerRadius = 50
        view1.layer.borderWidth = 2
        view1.layer.masksToBounds = true
        view1.clipsToBounds = true
        view.addSubview(view1)


事实上还是没有触发离屏渲染,这就很奇怪了。


这时候就需要了解一下几个属性的关系了


发现其实图中3个属性,对应了3个图层。我们看到的图像其实是这3个图层的合并,这面会涉及到一个画家算法,下一节会说到。原来contents是单独的个图层,那我们设置它看一下。

        let view1 = UIView(frame: rect)
        view1.backgroundColor = UIColor.red
        view1.layer.cornerRadius = 50
        view1.layer.borderWidth = 2
        view1.layer.contents = UIImage(named: s)?.cgImage
        view1.layer.masksToBounds = true
        view1.clipsToBounds = true
        view.addSubview(view1)

在当同时设置了backgroundColor|borderWidth,cornerRadius,contents,masksToBounds这四个属性时,导致了离屏渲染。


我们设置了content后触发了离屏渲染,如果我们只设置content看看会不会触发呢?

        let view1 = UIView(frame: rect)
        view1.layer.cornerRadius = 50
        view1.layer.contents = UIImage(named: s)?.cgImage
        view1.layer.masksToBounds = true
        view1.clipsToBounds = true
        view.addSubview(view1)

在其他设置都没有变的情况下,只设置了content并没有触发离屏渲染。有没有隐约感觉到一点触发规律:

  1. 设置了cornerRadius
  2. 同时设置了backgroundColor或者borderWidthcontent
  3. 设置了masksToBounds或者clipsToBounds**

可是为什么呢?想要搞清楚就需要了解画家算法

画家算法

所谓画家算法是计算机将多个图层由远到近进行绘制的一种算法。先的图层会被后的图层所覆盖。
这里接涉及到计算机渲染的原理,其中非常重要的一个点就是:图层被渲染到画布上之后,当前图层就会被永久销毁,所以面对多个图层时从远到近绘制,保证了可视范围内容的完整,最后保存到帧缓存区等待读取。

  • 离屏渲染产生的原理


需要在额外的内存中完成多图层组合绘制工作

GPU中的离屏渲染

现在我对上图中增加一个圆角,而上图是由3个图层组成的,且图层渲染到画布后就会被销毁,导致GPU没办法一次性拿到所有图层来进行圆角切割. 所以现在就需要创建一块空间,将3个图层都保存起来后完成圆角切割。而这个单独的空间就叫做offSet-buffer,离屏渲染就这样产生了,当然这个图层绘制一般是由GPU完成的,也有些特殊情况下CPU也会参与绘制。

CPU中的“离屏渲染”
  • 在CoreAnimation 渲染流程中Display流程的视图层绘制中提过,如果开启drawRect:方法就会触发CPU的“离屏渲染”,该方法里的所有代码都是在CPU中进行执行,知道完成bitmap,转存到帧缓存区中。但是根据苹果工程师的说法,CPU的渲染并不是真正意义的离屏渲染,当然通过Xcode调试也能看出来,该区域并没有被标记为黄色,说明Xcode也认为这不属于离屏渲染。

label没有触发xcode离屏渲染,所以推测绘制文字的 layer (UILabel, CATextLayer, Core Text 等)是使用CPU来进行的渲染,说一下我这样的猜测的理由:文字渲染更多涉及逻辑计算所以CPU更加适合做这件事。

  • 常见离屏渲染场景分析

1. cornerRadius+clipsToBounds

同时打开backgroundColor|borderWidth,cornerRadius,contents,masksToBounds这个情况前面已经分析过了,属性的打开会导致多个图层的裁剪,所以必须在offset-buffer中完成全图图层裁剪后才可以放入帧缓存区中。当然还有其他方法设置圆角但不会触发离屏渲染UIBezierPath

UIBezierPath会涉及到CoreGraphics,在渲染流程
中负责图层的绘制。可知使用了UIBezierPath在每一个单图层绘制的计算中就已经处理了每个图层的圆角,这时画在画布上的图层就已经是圆角了,估避免了离屏渲染。

2. shadow

开启shadow后会增加一个额外的图层,这个图层是在最先被绘制的,可是这时并不知道content的大小,所以还是没法分开绘制,需要offSet-buffer的支持。
也可以使用shadowPath提前告知阴影路劲就可以避免离屏渲染。

3. group opacity(组透明度)

这个很好理解,多个图层都带着透明度,在重叠位置会造成颜色的混合。重叠后的颜色需要计算,而上一层已经被销毁了,计算机并不知道其颜色所以无法计算。这时就需要把所有图层都存到offset-Buffer,不可避免的触发了离屏渲染。

4. mask

增加mask涉及到多图层的重叠,而且有些图层是有透明度的,原理和group opacity类似。

5. shouldRasterize(光栅化)

及时离屏渲染消耗很大,但是面对复杂图层,好不容易绘制好了为什么不想办法复用它呢?这时苹果大大就提供了shouldRasterize这个属性,打开这个属性造成离屏渲染,但是渲染好的内容会被缓存起来,一定条件下就可以复用。如果合理使用这个属性就可以将性能消耗降到最低。
满足这几个条件就可以使用shouldRasterize:

  • layer的内容(包括子layer)必须是静态的,因为一旦发生变化,之前辛苦处理得到的缓存就失效了。如果这件事频繁发生,我们就又回到了“每一帧都需要离屏渲染”的情景,而这正是开发者需要极力避免的。针对这种情况,Xcode提供了“Color Hits Green and Misses Red”的选项,帮助我们查看缓存的使用是否符合预期
  • 由于系统提供的offset-buffer的空间是有限的:屏幕尺寸的2.5倍。所以无法长时间占用,系统只提供了100ms,如果100ms没被使用依旧会被释放。
6. 文字以及drawRect

CPU的'离屏渲染'时已经说明,不在赘述。

后续

最初的两个问题在文中已经有明确的答案,耐心阅读可能会有新发现。

推荐阅读:
极客技术团队-关于iOS离屏渲染的深入研究

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