iOS开发-视图渲染与性能优化

前言

关于iOS的视图渲染流程,以及性能优化的建议。
源于WWDC视频
我假设你是一个这样的开发者:

  • 了解OpenGL ES;
  • 了解view hierarchy;
  • 了解instruments;

view hierarchy和instruments网上资料很多,OpenGL ES的你可以看OpenGL ES文集

视图渲染

UIKit是常用的框架,显示、动画都通过CoreAnimation。
CoreAnimation是核心动画,依赖于OpenGL ES做GPU渲染,CoreGraphics做CPU渲染;
最底层的GraphicsHardWare是图形硬件。



下图是另外一种表现的形式。在屏幕上显示视图,需要CPU和GPU一起协作。一部数据通过CoreGraphics、CoreImage由CPU预处理。最终通过OpenGL ES将数据传送到 GPU,最终显示到屏幕。

CoreImage支持CPU、GPU两种处理模式。

显示逻辑

  • 1、CoreAnimation提交会话,包括自己和子树(view hierarchy)的layout状态等;
  • 2、RenderServer解析提交的子树状态,生成绘制指令;
  • 3、GPU执行绘制指令;
  • 4、显示渲染后的数据;


提交流程(以动画为例)

第2步为prepare to commit animation (layoutSubviews,drawRect:);


1、布局(Layout)

调用layoutSubviews方法;
调用addSubview:方法;

会造成CPU和I/O瓶颈;

2、显示(Display)

通过drawRect绘制视图;
绘制string(字符串);

会造成CPU和内存瓶颈;
每个UIView都有CALayer,同时图层有一个像素存储空间,存放视图;调用-setNeedsDisplay的时候,仅会设置图层为dirty。
当渲染系统准备就绪,调用视图的-display方法,同时装配像素存储空间,建立一个CoreGraphics上下文(CGContextRef),将上下文push进上下文堆栈,绘图程序进入对应的内存存储空间。

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(10, 10)];
[path addLineToPoint:CGPointMake(20, 20)];
[path closePath];
path.lineWidth = 1;
[[UIColor redColor] setStroke];
[path stroke];

在-drawRect方法中实现如上代码,UIKit会将自动生成的CGContextRef 放入上下文堆栈。
当绘制完成后,视图的像素会被渲染到屏幕上;当下次再次调用视图的-setNeedsDisplay,将会再次调用-drawRect方法。

3、准备提交(Prepare)

解码图片;
图片格式转换;

GPU不支持的某些图片格式,尽量使用GPU能支持的图片格式;

4、提交(Commit)

打包layers并发送到渲染server;
递归提交子树的layers;
如果子树太复杂,会消耗很大,对性能造成影响;

尽可能简化viewTree;

当显示一个UIImageView时,Core Animation会创建一个OpenGL ES纹理,并确保在这个图层中的位图被上传到对应的纹理中。当你重写-drawInContext方法时,Core Animation会请求分配一个纹理,同时确保Core Graphics会将你在-drawInContext中绘制的东西放入到纹理的位图数据中。

Tile-Based 渲染

这里有PDF文档
Tiled-Based 渲染是移动设备的主流。整个屏幕会分解成N*Npixels组成的瓦片(Tiles),tiles存储于SoC 缓存(SoC=system on chip,片上系统,是在整块芯片上实现一个复杂系统功能,如intel cpu,整合了集显,内存控制器,cpu运核心,缓存,队列、非核心和I/O控制器)。
几何形状会分解成若干个tiles,对于每一块tile,把必须的几何体提交到OpenGL ES,然后进行渲染(光栅化)。完毕后,将tile的数据发送回cpu。

传送数据是非常消耗性能的,相对来说,多次计算比多次发送数据更加经济高效,但是额外的计算也会产生一些性能损耗。
PS:在移动平台控制帧率在一个合适的水平可以节省电能,会有效的延长电池寿命,同时会相对的提高用户体验。这里有详细的介绍

1、普通的Tile-Based渲染流程

1、CommandBuffer,接受OpenGL ES处理完毕的渲染指令;
2、Tiler,调用顶点着色器,把顶点数据进行分块(Tiling);
3、ParameterBuffer,接受分块完毕的tile和对应的渲染参数;
4、Renderer,调用片元着色器,进行像素渲染;
5、RenderBuffer,存储渲染完毕的像素;


2、离屏渲染 —— 遮罩(Mask)

1、渲染layer的mask纹理,同Tile-Based的基本渲染逻辑;
2、渲染layer的content纹理,同Tile-Based的基本渲染逻辑;
3、Compositing操作,合并1、2的纹理;


3、离屏渲染 ——UIVisiualEffectView


使用UIBlurEffect,应该是尽可能小的view,因为性能消耗巨大。


4、渲染等待

由于每一帧的顶点和像素处理相对独立,iOS会将CPU处理,顶点处理,像素处理安排在相邻的三帧中。如图,当一个渲染命令提交后,要在当帧之后的第三帧,渲染结果才会显示出来。


5、光栅化

把视图的内容渲染成纹理并缓存,可以通过CALayer的shouldRasterize属性开启光栅化。
注意,光栅化的元素,总大小限制为2.5倍的屏幕。
更新内容时,会启用离屏渲染,所以更新代价较大,只能用于静态内容;而且如果光栅化的元素100ms没有被使用将被移除,故而不常用元素的光栅化并不会优化显示。

6、组透明度

CALayer的allowsGroupOpacity属性,UIView 的alpha属性等同于 CALayer opacity属性。GroupOpacity=YES,子 layer 在视觉上的透明度的上限是其父 layer 的opacity。当父视图的layer.opacity != 1.0时,会开启离屏渲染。
layer.opacity == 1.0时,父视图不用管子视图,只需显示当前视图即可。

The default value is read from the boolean UIViewGroupOpacity property in the main bundle’s Info.plist file. If no value is found, the default value is YES for apps linked against the iOS 7 SDK or later and NO for apps linked against an earlier SDK.
为了让子视图与父视图保持同样的透明度,从 iOS 7 以后默认全局开启了这个功能。

性能优化

这个是WWDC推荐的检查项目:



1、帧率一般在多少?

60帧每秒;(TimeProfiler)

2、是否存在CPU和GPU瓶颈? (查看占有率)

更少的使用CPU和GPU可以有效的保存电量;

3、额外的使用CPU来进行渲染?

重写了drawRect会导致CPU渲染;在CPU进行渲染时,GPU大多数情况是处于等待状态;

4、是否存在过多离屏渲染?

越少越好;离屏渲染会导致上下文切换,GPU产生idle;

5、是否渲染过多视图?

视图越少越好;透明度为1的视图更受欢迎;

6、使用奇怪的图片格式和大小?

避免格式转换和调整图片大小;一个图片如果不被GPU支持,那么需要CPU来转换。(Xcode有对PNG图片进行特殊的算法优化)

7、使用昂贵的特效?

理解特效的消耗,同时调整合适的大小;例如前面提到的UIBlurEffect;

8、视图树上不必要的元素?

理解视图树上所有点的必要性,去掉不必要的元素;忘记remove视图是很常见的事情,特别是当View的类比较大的时候。


以上,是8个问题对应的工具。遇到性能问题,先分析、定位问题所在,而不是埋头钻进代码的海洋。

性能优化实例

1、阴影


上面的做法,会导致离屏渲染;下面的做法是正确的做法。

2、圆角


不要使用不必要的mask,可以预处理图片为圆形;或者添加中间为圆形透明的白色背景视图。即使添加额外的视图,会导致额外的计算;但仍然会快一点,因为相对于切换上下文,GPU更擅长渲染。
离屏渲染会导致GPU利用率不到100%,帧率却很低。(切换上下文会产生idle time)

3、工具

使用instruments的CoreAnimation工具来检查离屏渲染,黄色是我们不希望看到的颜色。


使用真机来调试,因为模拟器使用的CALayer是OSX的CALayer,不是iOS的CALayer。如果用模拟器调试,会发现所有的视图都是黄色。

总结

视频中的这一句话,让我对iOS的视图渲染茅塞顿开。

CALayer in CA is two triangles.

文章中关于Tile-Based架构,以及像素显示渲染的理解基于我对OpenGL ES学习以及iOS开发收获。
iOS开发收获很容易找到,但是OpenGL ES相对来说很少。越来越觉得自己花时间去研究是值得的。

Core Animation的核心是OpenGL ES的一个抽象物,CoreAnimation让你直接使用OpenGL ES的功能,却不需要处理OpenGL ES的复杂操作。
可是我仍越过CoreAnimation去学习OpenGL ES。因为我不满足用Apple提供的API拼凑界面。

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

推荐阅读更多精彩内容