一 概要
整个vsync可以分为4个角色:
APP:通过gui::DisplayEventReceiver与服务端建立连接,请求和接收vsync信号,用于驱动app侧绘制。
SurfaceFlinger:安卓重要角色之一,在这里有3个重要功能:(1)初始化和管理vsyn模块;(2)向app侧提供vsync接口;(3)获取自身上屏用的vsync信号
Scheduler:vsync管理模块对外的接口。负责vsync预测、分发等核心功能。
hardware vsync:硬件vysnc信号原始来源。

二 四角色一次拆解
2.1 APP
gui::DisplayEventReceiver创建和持有EventThreadConnection对象,通过该对象请求和接收vsync信号,APP侧如何基于gui::DisplayEventReceiver进行更多层次的封装,给上层提供更友好的接口,不在本文讨论范围。这里有个小技术点:BitTube,用于接受vsync事件的对象,BitTube是对socket的封装,为什么不用回调,或者广播的形式呢?socket更高效,不阻塞vsync事件分发。

2.2 SurfaceFlinger(简称sf)
在vysnc模块中,sf负责与Scheduler模块协作,初始化Vsync模块,注册sf和app vsync事件回调,向vsync模块添加present fence时间戳和硬件vsync原始时间戳,用于预测模块构建。

2.3 Scheduler
理解该模块,有几个核心概念。
预测模型计算:根据硬件vsync时间戳建立标准预测模型,通过present fence时间戳更新模型,并当误差大于一定值后,开启硬件vsync采样,重建标准预测模型。在采样时间戳时,有各种逻辑需要处理,如可变分辨率vrr,渲染率render rate,显示模式display mode等改变时,period如何管理。
schedule:当vsync请求发起时,如何调度。有多个请求者时,如何综合设置定时器。schedule()/update()/pendingWorkloadUpdate()三者在什么情况下使用?计算好的nextVsyncTime为什么还需要adjust?
回调注册和响应:通过MessageQueue和EventThread注册回调。关注初始化时机,使用场景。MessageQueue为什么可以直接被分发,EventThread为什么需要维护一个线程异步分发?
nextVsyncTime预测:两个核心问题:nextVsyncTime预测和修正。预测是很简单的,根据之前构建的预测模型Model(slope, intercept),直接计算next vsync timestamp。为什么需要修正呢?由于可变分辨率vrr,显示模型display mode切换等原因,需要对next vsync timestamp进行修正。

2.4 Hardware vsync
硬件vsync,吐出最原始的、标准的vsync信号,为什么不直接用vsync信号,非要搞出这么麻烦的vsync软件模块呢?核心原因是功耗和性能问题,硬件vsync信号频率太高,从内核态传到用户态,有大量的开销。另外也可能处于对vsync更好的管理和优化,减少上屏延迟。
至于SurfaceFlinger如何拿到Hardware vsync,有个专门的一个模块,叫HWComposer,不在本文讨论范围,简单的理解,通过一个callback事件,可以拿到硬件vsync事件。
三 细节拆解
3.1 三大流程
(1) 核心对象创建和callback绑定
重点看EventThread回调和MessageQueue回调注册时机

(2)sf申请vsync
重点看nextVsyncTime在什么时候计算,定时器如何设置,callback如何分发
一个技术点:epoll实现的Timer,为什么不直接用thread sleep?

(3)app申请vsync
重点看threadMain,为什么需要pendingEvents, connection建立时机,如何分发等。
需要关注的一个技术点:BitTube,socket建立双向连接,高效、高频收发数据。

3.2 timestamp管理
几个核心模块:
预测模型计算:简单线性回归计算斜率和截距,得到模型Model(slope, intercept),发生在VsyncPredictor。
PresentFence采样:把present fence上屏时间戳,用于模型调整,如果误差较大,则开启硬件采样。这里需要处理FenceTime还没及时唤醒的情况
onComposerHalVsync:标准硬件vsync时间戳,正常情况下,硬件时间戳直接收集并计算预测模型,但当display mode变化时,需要管理period,比较麻烦(这块本文没有细化,待补充)。
DisplayMode变化时period管理:待补充
VsyncModulator:待补充

3.3 schedule流程
三个重要逻辑:立即更新逻辑(workloadUpdate);schedule逻辑,update逻辑
大逻辑非常简单:如果now很大,则说明有负载瓶颈,依赖最近timer唤醒,本次无需改变定时器;否则预测当前请求者(callback→schedule()),update每一个callback(除当前请求者外),然后取最小唤醒时间,重新设定定时器

- adjustVsyncIfNeeded()拆解
nextVsyncTime与mLastDispatchTime相比,上下波动mMinVsyncDistance(3ms),则说明已经分发过,则向mMinVsyncDistance后再预测一个
nextVsyncTime与mLastDispatchTime相比少于currentPeriod,则说明too close,则向+currentPeriod后,再预测一个
nextVsyncTime否则,直接使用
- getExpectedCallbackTime()拆解
nextVsyncTime直接使用,不折腾
- getArmedInfo(tracker, now, timing)拆解
now + timing.workDuration + timing.readDuration依此向后预测
- mIntendedWakeupTime
初始化时,被设置为max()
timeCallback()被唤醒时,被重置为max()
cancel(CallbackToken)/cancelTimer()时,被重置为max()
setTimer(nsecs_t targetTime)时,被设置为有效值
- mArmedInfo
schedule() / update()时,会赋有效值
timeCallback()满足唤醒条件时,被reset()
3.4 nextAnticaptedVsyncFrom下一个vsync计算和修正
根据预测模型Model(slope, intercept)计算vsync是非常简单的,由于vrr可变渲染率和多个渲染配置,导致vsync计算非常复杂,这里介绍主要流程和几个重要函数。

- std::unordered_map<nsecs_t, Mode> mRateMap
存储ideal period对应的预测模型Model(slope, intercept),在addVsyncTimestamp()时赋值。
Map between ideal vsync period and the calculated model
- snapToVsync(nsecs_t timePoint)拆解
根据计算好的预算模型Model(slope, intercept),计算timePoint对应的预测值
- nextAnticipatedVSyncTimeFrom()拆解
- 预测的vsync,相对于render state做适当修正:snapToVsyncAlignedWithRenderState()
- 如果开启vrr(动态渲染率),则做适当修正
- mTimelines拆解
render rate渲染率发生变化时,cache参数,用于修正预测值
- purgeTimelines()拆解
清空过时的timelines
- vrr模式下相关修正
onFrameBegin() / onFrameMissed()
ensureMinFrameDurationIsKept()
本质:vrr模式下,显示模式改变,则导致period变化,相关计算参数重置,需要重新计算
相关函数:setRenderState() / setDisplayModePtr(); onFrameBegin() / onFrameMissed()
ensureMinFrameDurationIsKept()
- todo vrr/render rate修正细节逻辑