一、屏幕成像原理
1、过去CRT显示器,是利用CRT电子枪,从上到下一行行扫描,扫描完成后显示器就显示一帧画面,然后电子枪又回初始位置继续下一次扫描了。
2、为了使显示器显示的过程和视频控制器进行同步,显示器会用硬件时钟产生一系列的定时信号。
- 当电子枪换行扫描时,显示器会发出一个水平同步信号(HSync);
- 当一帧画面绘制完成后,电子枪会复位到初始位置,显示器会触发一个垂直同步信号(VSync)。
3、显示器同时以固定的频率进行刷新,这个频率就是VSync信号产生的频率。
4、屏幕的成像过程就是CPU和GPU共同协作完成的。 CPU对文本的计算和排版、视图的布局、图片格式转换和解码等操作,把处理好的数据交给GPU渲染,GPU渲染后会把数据放到帧缓冲区,随后视频读取器会按照垂直同步信号(VSync)读取数据,显示到屏幕上。
5、iOS系统是双缓冲区,分前缓冲区、后缓冲区。能有效提高效率问题。
引发新问题:画面撕裂问题。
视频读取器在读取帧缓冲区的数据时,即屏幕内容刚显示一半时,GPU提交新的一帧数据到缓冲区,并把2个缓冲区交换,视频控制器就会把新的一帧数据的下半段显示到屏幕。
引入GPU的垂直同步信号机制。GPU会等待显示器的VSync信号发出后,才会进行新的一帧渲染和缓冲区更新。
优点:解决了画面撕裂问题,增加了画面的流畅度
缺点:消耗了更多的计算资源,带来了部分延迟
二、卡断原因
--CPU-->--GPU-->|VSync来了
1、CPU把处理好的数据,交给GPU渲染,放到帧缓存区,VSync来时,视频控制器读取数据显示。
2、如果CPU计算时间过长或者GPU渲染时间过长,在VSync来时,这一帧还没渲染完,就会出现掉帧,视频控制器读取的还是上一帧的数据,这样就产生的卡顿。当下一个VSync来时,才会显示丢帧的数据。
三、离屏渲染
当前屏幕渲染:在当前用于显示的缓冲区进行渲染
离屏渲染:在当前缓冲区以外新开辟一块缓冲区就行渲染操作(不能直接拿出来,渲染到屏幕上),GPU的一些操作比较消耗资源、消耗性能,所以会用到离屏渲染。一些复杂的操作无法直接渲染,需要分步渲染再组合起来。
图像在显示之前,GPU在当前缓冲区以外的区域开辟一开新的缓冲区进行渲染操作。离屏渲染耗时是因为要创建缓冲区和上下文的切换。
离屏渲染整个过程,需要多次切换上下文,先从当前屏幕切换到离屏,等到离屏渲染完成后,将渲染的结果显示到屏幕时,又需要将上下文从离屏切换回当前屏幕
触发离屏操作
- 光栅化,layer.shouldRasterize = YES(将几何图元变成二维图像的过程,将图层绘制到一个屏幕外的图像,然后这个图像会被缓存起来并绘制到实际的图层的contents和子图层)
- 遮罩,layer.mask
- 圆角,同时设置圆角和masksToBounds=YES
- 阴影,layer.shadowXXX,如果设置了shadowPath就不会离屏渲染
四、CPU和GPU的优化
减轻CPU和GPU的资源消耗就能避免卡顿现象
CPU的卡顿
- 尽量使用轻量级的对象,比如用CALayer取代UIView,用NSInteger取代NSNumber
- 不要频繁的调用UIView的相关属性,比如frame、bounds、tranform等属性,尽量减少不必要的修改
- 尽量提前计算好布局,一次性调整对应的属性
- 图片的size和ImageView的Size尽量保持一致(不一致会对图片伸缩操作)
- AutoLaout会比直接设置frame更消耗CPU的资源,它是一个解矩阵的过程。
- 控制线程的最大并发数
- 尽量把耗时操作放到子线程,文本处理(尺寸计算、排版、绘制),可以使用CoreText对文本异步绘制。CoreText对象创建好后,可以获取文本的宽高等属性,避免多次计算,占用的内存少。
- 异步对图片处理(解码、绘制)。把图像绘制到画布中,再从画布创建图片并显示的过程,消耗资源
- 解码:图片解压出来得到的像素数据,CPU需要转换处理才能渲染。使用Image创建图片时,为了节省内存,并不会立刻解码。CALayer.contents设置时,才会解码,而且是在主线程。
- 尽量不用Xib和Storyboard,因为它们涉及到反序列化操作,消耗的资源比纯代码更多
GPU的卡顿优化
GPU:纹理、顶点描述、应用变换、混合并渲染,输出到屏幕(CALayer大多属性都是GPU绘制)
- 减少视图层级和数量
- 图片的尺寸不要超过4096x4096,因为超过这个尺寸就回交给CPU处理,占用CPU资源
- 避免短时间内大量图片的显示,尽可能将多张图片合成一张
- 尽量透明的视图,不透明就设置
opaque为YES,有透明的视图,图层重叠时,需要进行混合计算,重新渲染 - 尽量避免离屏渲染