1. 前言
忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。
-- 服装学院的IT男
本篇为 VSync 系列的第六篇,上一篇看到在第一次 VSYNC-app 产生的时候,还触发了下一次的申请,申请的逻辑还是和之前一样,定时结束后又回调到 CallbackRepeater::callback 。 本篇继续看看第二次的逻辑。
本系列为之前学习 SurfaceFlinger 整理的一些笔记,现在分享出来,希望能帮助到有需要的同学。 代码基于 Android 13 ,虽然最新源码源码中部分逻辑已经了,但总体思路还是没有变的,不影响对 VSync 整体逻辑的理解。
VSync 系列目录:
正文
1. 第二次 VSYNC-app
arduino
# DispSyncSource.cpp
class CallbackRepeater {
......
private:
void callback(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
......
// 重点* 1. 间接执行 DispSyncSource::onVsyncCallback
mCallback(vsyncTime, wakeupTime, readyTime);
{
std::lock_guard lock(mMutex);
// 当前mStarted还是true
if (!mStarted) {
ATRACE_NAME("callback return not mRegistration.schedule");
return;
}
// 重点* 2. 请求 VSync
auto const scheduleResult =
mRegistration.schedule({.workDuration = mWorkDuration.count(),
.readyDuration = mReadyDuration.count(),
.earliestVsync = vsyncTime});
}
}
}
还是和之前一样执行回调并触发下一次申请。但是这里要注意,我们说过VSyncRequest::Single
只会触发两次VSync
,这里又申请了那等会岂不是来第三次?这里似乎有问题。 带着这个疑问,继续看后续流程。
mCallback
的执行又会触发到EventThread::onVSyncEvent
。
arduino
# EventThread.cpp
void EventThread::onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) {
std::lock_guard<std::mutex> lock(mMutex);
LOG_FATAL_IF(!mVSyncState);
// 1. 构建一个event添加到mPendingEvents
mPendingEvents.push_back(makeVSync(mVSyncState->displayId, timestamp, ++mVSyncState->count,
vsyncData.expectedPresentationTime,
vsyncData.deadlineTimestamp));
// 2. 唤醒线程
mCondition.notify_all();
}
所以又要看EventThread::threadMain
方法 。
1.1 第三次执行 EventThread::threadMain
这一次的执行和 "5. 第二次执行 EventThread::threadMain " 基本上是一样的,区别就是这次知道到shouldConsumeEvent
里时,vsyncRequest = VSyncRequest::SingleSuppressCallback
,所以执行完shouldConsumeEvent
方法时会被设置为 vsyncRequest = VSyncRequest::None
。 这点很关键!!!
1.2 continue 再次执行 while
然后又是因为有event
所以执行continue
,所以又回到了EventThread::threadMain
方法的下一次while
语句。
这一次的continue
和第一次VSYNC-app
来的continue
大致也是一样的,区别就是这次的 connection->vsyncRequest = VSyncRequest::None ,所以 vsyncRequested = false 了。
php
# EventThread.cpp
void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
// 定义一个要消费 Vsync 事件的应用集合
DisplayEventConsumers consumers;
// 只要状态不是退出,则一直循环执行(没任务的时候内部会等待)
while (mState != State::Quit) {
// 定义 event
std::optional<DisplayEventReceiver::Event> event;
// mPendingEvents 的唯一事件上一次执行的时候被移除了,现在是空
if (!mPendingEvents.empty()) {
......
}
// 是否有应用请求了 vsync
bool vsyncRequested = false;
// 开始遍历mDisplayEventConnections的各个链接
// Find connections that should consume this event.
auto it = mDisplayEventConnections.begin();
while (it != mDisplayEventConnections.end()) {
if (const auto connection = it->promote()) {
// 当前应用为 VSyncRequest::None 了,所以 vsyncRequest = false
vsyncRequested |= connection->vsyncRequest != VSyncRequest::None;
// 当前没有event,下面不执行
if (event && shouldConsumeEvent(*event, connection)) {
......
}
++it;
} else {
it = mDisplayEventConnections.erase(it);
}
}
// 为空
if (!consumers.empty()) {
......
}
{
// vsyncRequested = false
State nextState;
if (mVSyncState && vsyncRequested) {
nextState = mVSyncState->synthetic ? State::SyntheticVSync : State::VSync;
} else {
ALOGW_IF(!mVSyncState, "Ignoring VSYNC request while display is disconnected");
// vsyncRequested = false 所以走这
nextState = State::Idle;
}
// mState = State::VSync 而 nextState = State::Idle
if (mState != nextState) {
if (mState == State::VSync) {
// 重点* 所以这次走这,关闭 Vsync-app
mVSyncSource->setVSyncEnabled(false);
} else if (nextState == State::VSync) {
mVSyncSource->setVSyncEnabled(true);
}
// 状态同步
mState = nextState;
//现在 mState 和 nextState 都为State::Idle
}
// 目前没有event
if (event) {
continue;
}
// Wait for event or client registration/request.
if (mState == State::Idle) {
// 当状态为Idle时,进入无期限等待
mCondition.wait(lock);
} else {
......// 超时处理,暂时忽略
}
}
}
}
这次执行的重点就是执行了mVSyncSource->setVSyncEnabled(false);
。 之前看这个方法是传递true
开启了VSYNC-app
的产生,现在传递false
是要会关闭VSYNC-app
。
1.3 DispSyncSource::setVSyncEnabled 关闭 VSYNC-app
还记得前面看第二次触发VSYNC-app
的时候,会执行到CallbackRepeater::callback
方法,这里又触发了一次VSYNC-app
申请,按照VSyncRequest::Single
的注释只会触发2次VSync
,所以阻止第三次的关键就在于执行了 mVSyncSource->setVSyncEnabled(false);
。
下面看看这个方法是如何阻止三次VSYNC-app
的。
scss
# DispSyncSource.cpp
void DispSyncSource::setVSyncEnabled(bool enable) {
std::lock_guard lock(mVsyncMutex);
if (enable) {
mCallbackRepeater->start(mWorkDuration, mReadyDuration);
} else {
// 关闭
mCallbackRepeater->stop();
}
// 设置为false
mEnabled = enable;
}
这里的false
很重要 。
csharp
# DispSyncSource.cpp
class CallbackRepeater {
public:
void stop() {
std::lock_guard lock(mMutex);
LOG_ALWAYS_FATAL_IF(!mStarted, "DispSyncInterface misuse: callback already stopped");
mStarted = false;
// 重点
mRegistration.cancel();
}
}
流程来到VSyncCallbackRegistration
scss
# VSyncDispatchTimerQueue.cpp
CancelResult VSyncCallbackRegistration::cancel() {
if (!mValidToken) {
return CancelResult::Error;
}
// 交给节拍器处理
return mDispatch.get().cancel(mToken);
}
CancelResult VSyncDispatchTimerQueue::cancel(CallbackToken token) {
std::lock_guard lock(mMutex);
ATRACE_CALL();
auto it = mCallbacks.find(token);
if (it == mCallbacks.end()) {
return CancelResult::Error;
}
// 拿到对应的 entry
auto& callback = it->second;
// 对应的wakeupTime
auto const wakeupTime = callback->wakeupTime();
// 如果有就执行内部取消定时的逻辑
if (wakeupTime) {
// 1. 置空这个entry的定时时间变量
callback->disarm();
//
if (*wakeupTime == mIntendedWakeupTime) {
// 设置无穷大
mIntendedWakeupTime = kInvalidTime;
// 2. 触发新的定时
rearmTimer(mTimeKeeper->now());
}
return CancelResult::Cancelled;
}
return CancelResult::TooLate;
}
这里有2步 :
-
- 把
app
的mArmedInfo
进行重置
- 把
-
- 如果
app
的wakeupTime
是下一次定时结束的时间,则重新定时。 说明定时间已经为这个app
开始定时了,而现在又执行了cancel
方法所以立即取消定时
- 如果
看看一下disarm
的处理。
javascript
# VSyncDispatchTimerQueue.cpp
void VSyncDispatchTimerQueueEntry::disarm() {
ATRACE_FORMAT("%s disarm",mName.c_str());
// 但是因为这里的重置,下一次定时的时候会取消定时
mArmedInfo.reset();
}
这里把mArmedInfo
置空了,然后当前的案例wakeupTime
是和mIntendedWakeupTime
相等的,所以要取消这次定时,也就是阻止第三次VSYNC-app
的产生。
arduino
# VSyncDispatchTimerQueue.cpp
void VSyncDispatchTimerQueue::rearmTimer(nsecs_t now) {
rearmTimerSkippingUpdateFor(now, mCallbacks.end());
}
rearmTimerSkippingUpdateFor
方法在第3小节看过了,这次的执行因为没有wakeupTime
所以min
变量也就没有值,会走cancelTimer
方法来取消定时。
也就达到了阻止第三次VSYNC-app
产生的目的,这样应用的VSYNC-app
也就停止了。
1.4 setVSyncEnabled 处理流程图

2. 应用持续申请 VSync
前面的内容介绍应用触发一次请求VSync
的完整流程,但是应用不可能只请求一次,那多次连续请求的场景是怎么保证不走取消定时的逻辑呢?
因为每次请求都会走EventThread::requestNextVsync
rust
# EventThread.cpp
void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
......
if (connection->vsyncRequest == VSyncRequest::None) {
connection->vsyncRequest = VSyncRequest::Single;
mCondition.notify_all();
} else if (connection->vsyncRequest == VSyncRequest::SingleSuppressCallback) {
// 后续的连续请求走这
connection->vsyncRequest = VSyncRequest::Single;
}
}
应用收到一次VSYNC-app
后把vsyncRequest
设置为VSyncRequest::SingleSuppressCallback
,然后再下一次就会停止VSYNC-app
。但是执行了EventThread::requestNextVsync
又会把 vsyncRequest
再次设置为VSyncRequest::Single
,所以在EventThread::threadMain
的时候就不会走setVSyncEnabled(false)
逻辑,也就不会停止VSYNC-app
的产生了。
3. 补充 WakeupTime 计算方式
以60 HZ
的设备来说,一帧的时间是16.6ms
。
这里涉及到了以下几个时间 app workduration:16.6ms 应用工作时间,其实就是绘制一帧的时间 app readyduration:15.6ms 应用绘制完一帧后的等待时间,等待啥呢?等待SF合成,所以这个时候和下面的 sf workduration 是一样的。
sf workduration:15.6ms SurfaceFlinger 的工作时间,其实就是 SurfaceFlinger 合成 Layer 的时间 sf readyduration:0 ms
所以一个应用的app workduration + app readyduration
就是他下一次上帧的时间。
这里的workduration
是手机设置了帧率后固定的,是说他必须工作这么久, 大部分时间都是提前结束, 比如应用花了10ms
就绘制完了,这种属于正常情况。 但是如果绘制超时了,超过 16.6ms ,这种就属于丢帧了。
HW-VSYNC
是固定的, 应用和SurfaceFlinger
的工作时间也是固定的,软件就可以计算出SW-VSYNC
的时间了。
3.1 VSYNC-app 图示时间计算

如图,假设app
请求的时间是10.6ms
,则10.6ms + app workduration + app readyduration = 10.6m + 16.6ms + 15.6ms = 42.8 ms
然后往后退,能找到这个时间点后的HW-VYSNC
也就是在49.8ms
,这个49.8ms
也是应用第一次上帧的时间。 然后开始反推什么时候给VSYNC-app
给到应用,计算规则如下图:

下一个HW-VYSNC - app readyduration - app workduration = 49.8ms - 15.6ms - 16.6ms = 17.6ms
这个17.6ms
时间点就是应用要在这个时候收到第一个VSYNC-app
,这样应用经过UI绘制(app workduration)再经过SurfaceFlinger
的合成(app readyduration)就可以在HW-VSYNC
的时间点上帧了。
然后17.6ms
其实就是代码分析的WakeupTime
和mIntendedWakeupTime
(假设这个时候只有 app 申请定时)。
然后17.6ms - 应用触发申请的时间(10.6ms) = 7ms
。
这个7ms
就是应用执行 EventThread::requestNextVsync 后一堆逻辑最终触发定时器的定时时间,定时结束后在 17.6ms 迎来第一个 VSYNC-app 。
根据这个规则,后面的 VSYNC-app 也都能计算出来。

3.2 VSYNC-sf 图示时间计算
VSYNC-sf
的时间计算和VSYNC-app
本质上是一样的,只不过sf readyduration
是0ms
。
应用绘制的时间(app workduration)虽然是16.6ms
,但是正常情况下不会要这么久。 以之前的例子,应用在17.6ms
收到第一个VSYNC-app
开始绘制UI, 假设10ms
绘制完了,那么就会在
27.6ms
的时候通知SurfaceFlinger
去合成,这个时间点也是SurfaceFlinger
开始申请VSync
的时间。
27.6ms + sf workduration( 15.6ms ) = 43.2 ms
根据这个时间点算到下一次是SW-VSYNC
为49.8ms
,再反推 49.8ms - sf workduration( 15.6ms ) = 34.2ms
所以SurfaceFlinger
这次请求需要定时34.2ms - 27.6ms = 6.6 ms
目前还没分析 SurfaceFlinger 的申请逻辑,但是定时计算是一样的 。 代码逻辑执行定时的时候会发生 app 已经申请了定时,所以 sf 这次不会再定时了,而且 sf 的 wakeupTime 和下一次的 mIntendedWakeupTime 是一样的,也就是 下一次的 SW -VSYNC 来的时间段是 app 和 sf 都要的时间。

4. 总结
到这里为止,应用请求VSYNC-app
的完整流程已经介绍完毕了。
整个流程代码量相对交代,而且EventThread::threadMain
方法的执行不同条件逻辑也不一样。
想要完整了解这块内容还是需要自己跟一遍完成的执行流程才能有正确的印象。
下面对这个流程遇到的几个方法和变量单独小结一下
4.1 EventThread::requestNextVsync
应用请求VSYNC-app
时触发,有2种情况:
-
- 应用首次请求,这个时候
vsyncRequest = VSyncRequest::None
所以要把vsyncRequest
设置成VSyncRequest::Single
然后唤醒线程
- 应用首次请求,这个时候
-
- 应用持续请求,这时候只需要把
vsyncRequest
设置成VSyncRequest::Single
就好。
- 应用持续请求,这时候只需要把
这里需要理解vsyncRequest
状态各个值在的意义,在后面执行EventThread::threadMain
时是怎么控制流程的。
4.2 EventThread::threadMain
这个方法是核心方法,会多次执行,读者需要自己完整梳理各个条件下的执行逻辑。
- 首先是会根据
mPendingEvents
集合有没有值,这个集合的元素就是一个VSYNC-app
时间,集合有没有值影响对局部变量event
的赋值,进而影响后续逻辑 - 然后会遍历
mDisplayEventConnections
这个集合,这个集合存的是应用链接,可以理解为每个都代码这个一个具体的应用。遍历集合,看有没有应用请求了VSYNC-app
。如果有任何一个应用请求了,就说明需要产生VSYNC-app
给这个应用了。
应用有没有请求VSYNC-app
的判断条件是看这个应用链接的vsyncRequest
的值,不等于VSyncRequest::None
就说明需要给一次VSYNC-app
给应用。 shouldConsumeEvent
方法本身是控制应用链接的vsyncRequest
值的切换的,返回值代表这个应用是否需要消费当前这个VSYNC-app
,如果需要的会就添加进consumers
集合,后续就会通过socket
通信向应用端发送VSync
事件。mState
和nextState
这2个状态变量也很重要,直接影响了是否需要通过DispSyncSource::setVSyncEnabled
来启动/关闭VSYNC-app
和当前线程是否需要进入等待。
4.3 DispSyncSource::setVSyncEnabled
控制VSYNC-app
的启动与关闭,VSYNC-app
如果之前处于关闭状态,就会调用这个方法传递true
来启动 ,VSYNC-app
的产生。 如果没有应用需要VSYNC-app
了,则会执行该方法,传递 false 来关闭VSYNC-app
的产生。 没有应用需要VSYNC-app
了的体现是在执行EventThread::threadMain
时遍历应用链接集合时,每个应用的VSYNC-app
请求状态vsyncRequest
都为VSyncRequest::None
。 内部会通过CallbackRepeater
来执行后续的启动/关闭逻辑。
4.4 CallbackRepeater::start/stop
这个方法本身也没做什么,后面是调用的VSyncCallbackRegistration
相关方法,最终是通过计时器来完成定时的,这里需要注意定时的时间是怎么来的以及几个时间相关的变量是什么意思。
大致内容都完成了,唯一没详细说的是软件模型是如何计算出下一个SW-VSYNC 的代码
,也就是wakeupTime
的计算。这个是在VSyncDispatchTimerQueueEntry::schedule
方法完整的,大致逻辑和第3节是一样的。 另外还有相位差的概念没有提到。
参考
www.jianshu.com/p/5e9c558d1...