1、撕裂
撕裂原因:
其本质是拿到图像后,GPU进⾏渲染->帧缓存区⾥ ->视频控制器->读取帧缓存区信息(位图) -> 数模转化(数字信号处->模 拟型号) ->(逐⾏扫描)显示,当第一帧图像扫描到某个位置时,GPU拿到新的数据并存到帧缓冲区,这个时候视频控制器从帧缓冲区扫描的是新拿到的一帧的图像,最后就形成了在我们肉眼看到的断层现象,即我们看到的一张图片其本质是两张图片组合而来,究其原因就是视频控制器显示速度小于了GPU处理图形的速度。
解决方案:
为了解决撕裂,苹果引入了: 垂直同步Vsync + 双缓存区 DoubleBuffering
(1)垂直同步Vsync:帧缓存区加锁 防⽌出现撕裂情况
(2)双缓存区 DoubleBuffering :就是GPU开辟AB两个帧缓冲区
执行流程就是当A帧缓冲区拿到第一帧数据,给A缓冲区加上一把锁,屏幕控制器从A拿到数据并逐行扫描完成,A帧缓冲区解锁,并且屏幕控制器指向B帧缓冲区,B帧缓冲区加锁并逐行扫描显示,在屏幕控制器扫描B帧缓冲区的时候,A帧缓冲区拿到GPU传过来的新一帧数据,以此类推,解决撕裂问题。
2、掉帧
为了解决撕裂问题而引入二级缓冲区机制后,出现了一个新的问题-(掉帧)
每帧画面的处理时间大概在16.7ms(1s/60 ≈16.7ms),当超过这个时间就会出现掉帧,如上图:当接收接收Vsync ,由于cpu/gpu图⽚数据(速度大于了16.7ms) -> 拿不到FrameBuffer ->这个时候屏幕控制器只能显示同一帧的数据,即: 掉帧(重复渲染同⼀帧数据)
为了减少掉帧(注意不是解决,掉帧问题只能尽量的减少,而不是解决,三级缓冲区也有可能出现掉帧),引入三级缓存区,三级缓冲区是为了充分利用CPU/GPU的空余时间,开辟ABC三个帧缓冲区,A显示屏幕, B也渲染好,C再从GPU拿取渲染数据,当屏幕缓冲区和帧缓冲区都弄好了,然后视频控制器再指向帧缓冲区的另外一个,再显示,这样交替,达到减少掉帧的情况,这样做就比二级缓冲区多了一个确认的操作
3、屏幕卡顿的原因
- CPU和GPU在渲染的流水线中耗时过长,导致从缓存区获取位图显示时,下一帧的数据还没有准备好,获取的仍是上一帧的数据,产生掉帧现象,掉帧就会导致屏幕卡顿
- 苹果官方针对屏幕撕裂问题,目前一直使用的方案是垂直同步+双缓存区,可以从根本上防止和解决屏幕撕裂,但是同时也导致了新的问题掉帧。虽然我们采用了双缓存区,但是我们并不能解决CPU和GPU处理图形图像的速度问题,导致屏幕在接收到垂直信号时,数据尚未准备好,缓存区仍是上一帧的数据,因此导致掉帧。
- 在垂直同步+双缓存区的方案上,再次进行优化,将双缓存区,改为三缓存区,这样其实也并不能从根本上解决掉帧的问题,只是比双缓存区掉帧的概率小了很多,仍有掉帧的可能性。
4、iOS中的渲染流程
iOS中渲染的整体流程如下
- App通过调用CoreGraphics、CoreAnimation、CoreImage等框架的接口触发图形渲染操作
- CoreGraphics、CoreAnimation、CoreImage等框架将渲染交由OpenGL ES, 由OpenGL ES来驱动GPU做渲染,最后显示到屏幕上
- 由于OpenGL ES 是跨平台的,所以在他的实现中,是不能有任何窗口相关的代码,而是让各自的平台为OpenGL ES提供载体。在iOS中,如果需要使用OpenGL ES,就是通过CoreAnimation提供窗口,让App可以去调用。
View 与 CALayer 的关系
首先分别简单了解下UIView和CALayer
UIView
- UIView属于UIKIt
- 负责绘制图形和动画操作
- 用于界面布局和子视图的管理
- 处理用户的点击事件
CALayer
- CALayer属于CoreAnimation
- 只负责显示,且显示的是位图
- CALayer既用于UIKit,也用于APPKit,
UIKit是iOS平台的渲染框架,APPKit是Mac OSX系统下的渲染框架,
由于iOS和Mac两个系统的界面布局并不是一致的,iOS是基于多点触控的交互方式,而Mac OSX是基于鼠标键盘的交互方式,且分别在对应的框架中做了布局的操作,所以并不需要layer载体去布局,且不用迎合任何布局方式。
总结:
UIView基于UIKit框架,可以处理用户触摸事件,并管理子视图
CALayer基于CoreAnimation,而CoreAnimation是基于QuartzCode的。所以CALayer只负责显示,不能处理用户的触摸事件
从父类来说,CALayer继承的是NSObject,而UIView是直接继承自UIResponder的,所以UIVIew相比CALayer而言,只是多了事件处理功能,
从底层来说,UIView属于UIKit的组件,而UIKit的组件到最后都会被分解成layer,存储到图层树中
在应用层面来说,需要与用户交互时,使用UIView,不需要交互时,使用两者都可以
UIView 和 CALayer的渲染
- 触发渲染的两种方式
1> 通过loadView中子View的drawRect方法触发:会回调CoreAnimation中监听Runloop的BeforeWaiting的RunloopObserver,通过RunloopObserver来进一步调用CoreAnimation内部的CA::Transaction::commit(),进而一步步走到drawRect方法
2> 用户点击事件触发:唤醒Runloop,由source1处理(__IOHIDEventSystemClientQueueCallback),并且在下一个runloop里由source0转发给UIApplication(_UIApplicationHandleEventQueue),从而能通过source0里的事件队列来调用CoreAnimation内部的CA::Transaction::commit();方法,进而一步一步的调用drawRect。 - 最终都会走到CoreAnimation中的CA::Transaction::commit()方法,从而来触发UIView和CALayer的渲染
- 这时,已经到了CoreAnimation的内部,即调用CA::Transaction::commit();来创建CATrasaction,然后进一步调用 CALayer drawInContext:()
- 回调CALayer的Delegate(UIView),问UIView没有需要画的内容,即回调到drawRect:方法
- 在drawRect:方法里可以通过CoreGraphics函数或UIKit中对CoreGraphics封装的方法进行画图操作
- 将绘制好的位图交由CALayer,由OpenGL ES 传送到GPU的帧缓冲区
- 等屏幕接收到垂直信号后,就读取帧缓冲区的数据,显示到屏幕上
CoreAnimation
在苹果官方的描述中,Render、Compose,and animate visual elements,CoreAnimationg中的动画只是一部分,它其实是一个复合引擎,主要的职责包括 渲染、构建和动画实现。
iOS 下CoreAnimation
- iOS中基于CoreAnimation构建的框架有两个:UIKit和APPKit
- CoreAnimation 又是基于Metal 、CoreGraphics封装的
CoreAnimation中的渲染流水线
CoreAnimation部分
- App处理UIView、UIButton等载体的事件,然后通过CPU完成对显示内容的计算,并将计算后的图层进行打包,在下一次runloop时,发送到渲染服务器
- Render Server中主要对收到的准备显示的内容进行解码,然后执行OpenGL等相关程序,并调用GPU进行渲染
Render Server 操作分析
GPU部分
- GPU中通过顶点着色器、片元着色器完成对显示内容的渲染,将结果存入帧缓存区
- GPU通过帧缓存区、视频控制器等相关部件,将其显示到屏幕上