1.图像显示原理简介
有关概念:
时钟信号:垂直同步信号V-Sync / 水平同步信号H-Sync
iOS设备双缓冲机制:前/后帧缓冲区
1.CPU和GPU通过总线连接起来,工作流程如上图示:
在CPU中的工作完成会生成一个位图(bitmap),位图再经由总线在合适的时机上传给GPU处理,GPU拿到位图之后会进行相应位图的图层渲染(顶点变换、纹理混合等操作),之后会把结果放到帧缓冲区(Frame Buffer)中,由视屏控制器根据Vsync信号,在指定时间前提取对应帧缓冲区中的屏幕显示内容,最终显示到手机屏幕上。
2.UI视图显示到屏幕上的大概流程如上图示:创建一个UIView,它的显示部分是由CALayer负责的,CALayer有一个contents属性(即最终要绘制到屏幕上的位图),比如说创建的是一个显示“Hello world”的UILable,那么contents中放置的结果就是一个关于"Hello world"的文字位图。另外系统会在合适的时机回调一个drawRect:方法,在此基础上我们就可以绘制一些想要自定义的内容,绘制好的位图,会经由CoreAnimation框架,提交给GPU部分的OpenGL渲染管线进行位图的渲染,最终显示到屏幕上。
3.CPU工作:负责UI布局、文本计算,绘制,图片解码,绘制纹理交给GPU
4.GPU工作:纹理混合,顶点变换,渲染到帧缓冲区
2.UI掉帧、卡顿原因
1.通常来说页面滑动的流畅性是60fps(指每一秒会有60帧画面更新),人眼看到的是流畅的效果。也因此每隔 s(即16.7ms)就要产生一帧画面,也就是说在这16.7ms时间内GUP和CPU要协同完成产生一帧的数据,比如CPU花了一定的时间做UI布局、文本计算、视图的绘制和图片解码,并把产生的位图提交给GPU,GPU又要花一定的时间进行纹理混合渲染,然后在下一帧的VSync垂直信号到来之前显示这一帧画面。
在上图中,如果CPU和GPU协同工作的时间在16.7ms内,那么图像的显示是流畅的;如果CPU花费了大量时间来做frame的布局、视图绘制和图片解码,那么留给GPU的时间就不多了,GPU要想把图层合成、纹理渲染全部完成就要超过16.7ms,那么在下一帧的垂直信号VSync到来之前,还没有准备好当前的一帧画面,这就产生了掉帧,体现在滑动上的就是上下滑动卡顿的效果。
总而言之,在规定的16.7ms内,CPU和GPU并没有在下一帧的Vsync信号到来之前把当前的一帧画面生产完成,由此产生了掉帧卡顿。
2.滑动优化方案
CPU资源消耗分析
•对象创建:对象的创建会分配内存、调整属性、甚至还有读取文件等操作,比较消耗CPU资源。尽量采取轻量级对象,尽量放到后台线程处理,尽量推迟对象的创建时间。(如UIView / CALayer)
•对象调整:frame、bounds、transform及视图层次等属性调整很耗费CPU资源。尽量减少不必要属性的修改,尽量避免调整视图层次、添加和移除视图。
•布局计算:随着视图数量的增长,Autolayout带来的CPU消耗会呈指数级增长,所以尽量提前算好布局,在需要时一次性调整好对应属性。
•文本渲染:屏幕上能看到的所有文本内容控件,包括UIWebView,在底层都是通过CoreText排版、绘制为位图显示的。常见的文本控件,其排版与绘制都是在主线程进行的,显示大量文时是,CPU压力很大。对此解决方案唯一就是自定义文本控件,用CoreText对文本异步绘制。(很麻烦,开发成本高)
•图片解码:当用UIImage或CGImageSource创建图片时,图片数据并不会立刻解码。图片设置到UIImageView或CALayer.contents中去,并且CALayer被提交到GPU前,CGImage中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。SD_WebImage处理方式:在后台线程先把图片绘制到CGBitmapContext中,然后从Bitmap直接创建图片。
•图像绘制:图像的绘制通常是指用那些以CG开头的方法把图像绘制到画布中,然后从画布创建图片并显示的一个过程。CoreGraphics方法是线程安全的,可以异步绘制,主线程回调。
GPU资源消耗分析
•纹理渲染:尽量减少短时间内大量图片的显示,尽可能将多张图片合成一张进行显示。
•视图混合:尽量减少视图层次和数量,并在不透明的视图里标明opaque属性以避免无用的Alpha通道合成。
•图形生成:尽量避免离屏渲染,尽量采用异步绘制,尽量避免使用圆角、阴影、遮罩等属性。必要时用静态图片实现展示效果,也可尝试光栅化缓存复用属性。
基于上述的分析保持界面流畅,优化的时候就要从CPU和GPU两方面考虑。
优化参考iOS如何优化项目
3.离屏渲染
1.有关概念:
•On-Screen-Rendering: 意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行
•Off-Screen-Rendering: 离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
CPU渲染及非GPU缓冲区的渲染统称为离屏渲染
2.离屏渲染消耗性能的原因
•需要创建新的缓冲区
•离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
3.何时会触发?
•圆角(当和masksToBounds或者clipsToBounds为YES一起使用时)(对于UIimageview的圆角iOS9之后做了优化,在不加背景颜色的情况下,同时设置两者不会触发离屏渲染。但是UIButton等控件依然会触发,考虑通过 CoreGraphics 绘制裁剪image矩形圆角,或者叫美工提供圆角图片)
•图层蒙版 (layer.mask) 无法取消离屏渲染
•抗锯齿(edgeAntialiasing)
•阴影 (layer.shadowXXX,如果设置了 layer.shadowPath 就不会产生离屏渲染)可以通过指定路径来取消离屏渲染
•不透明(GroupOpacity)
•光栅化 (layer.shouldRasterize置为YES)
4.检测离屏渲染
模拟器:Debug->Color Offscreen-Rendered离屏渲染的图层高亮成黄,可能存在性能问题真机:Xcode->Debug->View Debugging->Rendering->选中 Color Offscreen-Rendered Yellow 或者 Instrument->Core Animation->选中 Color Offscreen-Rendered Yellow
5.光栅化
光栅化简介:隐式创建一个位图,各种阴影遮罩等效果也会保存到位图中缓存起来,从而减少渲染的频度,把GPU的操作转到CPU上,生成位图缓存,直接读取调用。(注:对于经常变动的内容,不要开启光栅化,防止性能浪费,如Cell的复用)
光栅化的检测:Color Hits Green and Misses Red 开启后,若shouldRasterize设置为YES,对应的渲染结果会缓存,如果图层是绿色,表示缓存被复用;如果是红色,就表示缓存被重复创建,可能存在性能问题。
任何时候优先考虑避免触发离屏渲染,无法避免时优化方案有两种:
•Rasterization:适用于静态内容的视图,也就是内部结构和内容不发生变化的视图,对上面的所有效果而言,在实现成本以及性能上最均衡的。即使是动态变化的视图,开启 Rasterization 后能够有效降低 GPU 的负荷,不过在动态视图里是否启用还是看 Instruments 的数据。
•规避离屏渲染,用其他手法来模拟效果,混合图层是个性能最好、耗能最少的通用优化方案,尤其对于 rounded corer 和 mask
关于离屏渲染触发及优化可以看下面的文章:
4.图像撕裂
图像撕裂原因:当视频控制器还未读取完成时,GPU将新的一帧内容提交到帧缓冲区并把两个帧缓冲区进行更新后,视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂的现象。
•解决方案:垂直同步机制
•弊端:GPU会等待显示的V-Sync信号发出后,才进行新的一帧渲染和缓存区更新。能解决画面撕裂现象,也增加了画面流畅度,但需要消耗更多的计算资源,由此可能导致卡顿。
参考文章ios图像显示原理
Texture相关参考资料
官方文档:http://texturegroup.org/docs/getting-started.html
官方文档部分译文一:https://juejin.im/post/5a16acf56fb9a04509092ce5
官方文档部分译文二(布局系统):https://juejin.im/post/5a1be41351882561a20a32e9#heading-17
即刻技术团队关于ASDK:
一、https://zhuanlan.zhihu.com/p/25371361
二、https://zhuanlan.zhihu.com/p/26283742
三、https://zhuanlan.zhihu.com/p/29537687
iOS 保持界面流畅的技巧:https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
ASDK源码剖析:http://beelearning.cn/2017/11/ASDK/
从 Auto Layout 的布局算法谈性能 :https://draveness.me/layout-performance