×
广告

view.color = .red背后的故事(渲染机制)

96
亲爱的八路
2018.09.19 11:43* 字数 1919

问题:view.backgroundColor = UIColor.red 后屏幕怎么知道颜色改变了

也就是CPU执行完这句代码后,怎么告知屏幕?
UI更新信息传达到屏幕采是显示器定时取数据的方式,有个东西叫做vsync信号,UI更新后数据会被放在一个缓冲区,vsync信号来临后,显示器会把缓冲区的数据显示出来。取数据的频率(vsync信号的频率)就是显示器的刷新频率,iOS中这个周期是16.67ms,也就是iOS的最大帧率60ps。值得一提的是,iOS中屏幕触摸扫描周期一般和屏幕刷新周期是重叠的,也就是在两个vsync信号中间屏幕会进行扫描触摸点,然后在下次vsync信号时传递给app触摸事件。
(tip:iOS每秒最多捕捉60个点)

问题:那屏幕没有任何变化的时候呢,也会更新吗

不知道大家有没有想起一个绘制方法——draw rect,我们可以在draw rect中加入自己想要绘制的内容,然后就会被显示出来,每秒刷新60次是不是意味着draw rect每秒被调用60次?
并不是的,有变化的layer都会被做上标记,下次渲染只会渲染这些layer,如果没有layer有变化,就没有数据被渲染,显示器显示的就是上一帧的画面
学习draw rect的时候,学习资料会告诉你,draw rect不能自己调用,它是系统调用的方法,只有系统知道它什么时候该调用,那它什么时候调用呢
等到主线程忙完的时候,也就是runloop将要休息的时候,Core Animation就会调用一遍被标记layer的view的layout subviews和draw rect方法,当然,是这两个方法被重写的情况下才会调用。那Core Animation怎么知道runloop即将休息了呢,因为Core Animation注册了runloop状态的观察者

在Core Animation做完了它自己的以及调完了我们重写的方法后,更新了的layer的数据会被打包送到一个叫做render process 的进程等待下一步的工作
runloop即将休息时调用layout subviews、draw rect方法,还有做了其他的一些事,然后把layer数据送去render process进程,这整个动作叫做commit,在draw rect或者layout subviews方法打断点,可以看到堆栈上有这样的方法:CA::Transaction::commit()
哎,等等,刚才提到说“等到主线程忙完的时候”,那么意思是说,如果主线程一直有事做,那么layer的更新数据就一直不会被提交?那屏幕不就不动了?
对的,如果主线程做其他的事,或者commit的方法要做的事情太多,屏幕就不动了,就掉帧了

其实不止app有Core Animation,render process也有一个Core Animation,只不过跟app的Core Animation有些不同。render process 的 Core Animation 可以生成GPU指令,不确定app 的 Core Animation能不能,不过app的 Core Animation不需要这么做,从配合上来看,app的 Core Animation和 render process的 Core Animation应该是互补的

V-Sync体系

来给大家看个图


渲染流程图1

这个是wwdc上的一个渲染流程图,这个一格一格的就是vsync信号间隔,可以看到render process的动作是从接受到vsync信号才开始的,确实是这样的,其实vsync信号并不是为了让屏幕知道数据变化了才出现的,屏幕有自己的稳定的刷新频率,把GPU输出频率调整和屏幕的刷新频率一样,没有vsync信号,我们设置的背景颜色也会被屏幕的自己稳定的刷新频率显示到屏幕上。vsync信号的出现是为了解决GPU输出频率不稳定而导致上一帧和下一帧混合产生的撕裂画面产生的。但由于这种改动,导致vsync现在在系统很多地方都有影响,vsync本身的影响已经超过了它的初衷,比如跟屏幕扫描周期重叠、在动画实现的流程中起到关键作用,比原先预想的控制渲染开始时间,防止上一帧和下一帧混合影响范围要大,已经形成自己的体系,说是vsync体系的渲染也不足为过
可以从图上看出来,渲染和显示都是踩着vsync信号的点进行的
再给大家看一张图


渲染流程图2.png

从这张图可以看出来,不止渲染和显示,连屏幕捕捉点、app进行处理touch事件和commit都是踩着vsync信号进行的
如果是动画的话,这里的app处理就只是vsync信号唤起runloop,然后进行commit操作
如果是touch事件的话,这里的app处理还会touch事件机制和相关我们app对点击的处理,如果屏幕有变化,会在runloop最后进行commit(注意,runloop最后这个时间点跟vsync信号完全没关系,app处理可以很快,只需要1ms就处理好,那commit就发生在上个vsync信号后的1ms,如果app处理的很慢,20ms都没处理好,此时下一个vsync信号已经来了,这次的commit就会被丢弃,就发生了掉帧)

动画的大致原理

app 的Core Animation会管理三种tree,layer tree、presentation tree 和 render tree,这三种层次结构是实现动画的基础,layer tree存放目标tree,presentation tree存放实时tree。动画要进行下一帧时vsync会唤醒runloop,然后app更新presentation tree,再commit进行渲染。这样渲染流程就像下面这样


动画渲染流程.png

总结:

动画更新画面,或者屏幕扫描到touch,app会在下一个vsync信号来临时接受到相关信息,并进行处理。app更新的layer会被标记并通过CATransaction提交到一个汇总的地方,等到主线程没事干的时候(runloop将要睡眠或退出),Core Animation就会把这些提交汇总合并(比如runloop周期内先设置了color = .red,然后设置了 color = .black,最后合并后是color = .black)(Core Animation会注册runloop状态通知),并调用layout subviews、draw rect方法,进行图片解压,然后commit到render process。第二个vsync信号来了的时候render process开始工作,工作完了交接给GPU,GPU进行渲染,render process和GPU的工作时间被限制在2*16.67ms内(16.67ms内完成,系统是双缓冲模式,超过16.67ms,系统会开启三缓冲模式,但是如果不能在2*16.67ms内完成工作,就会被丢弃),GPU渲染好后会放在back buffer中,第三次vsync信号来临就会转移到front buffer中,然后显示器会显示出来

引用:
Advanced Touch Input on iOS
iOS事件处理机制与图像渲染过程
Advanced Graphics and Animations for iOS Apps

日记本
Web note ad 1