首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

理解VSync-6-应用申请与接收VSync(下)

  • 25-04-16 13:00
  • 3784
  • 11844
juejin.cn

1. 前言

忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。

                        -- 服装学院的IT男

本篇为 VSync 系列的第六篇,上一篇看到在第一次 VSYNC-app 产生的时候,还触发了下一次的申请,申请的逻辑还是和之前一样,定时结束后又回调到 CallbackRepeater::callback 。 本篇继续看看第二次的逻辑。

本系列为之前学习 SurfaceFlinger 整理的一些笔记,现在分享出来,希望能帮助到有需要的同学。 代码基于 Android 13 ,虽然最新源码源码中部分逻辑已经了,但总体思路还是没有变的,不影响对 VSync 整体逻辑的理解。

VSync 系列目录:

理解VSync-1-软件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 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_lockmutex>& 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步 :

    1. 把app的mArmedInfo进行重置
    1. 如果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 处理流程图

setVSyncEnabled处理.png

2. 应用持续申请 VSync

前面的内容介绍应用触发一次请求VSync的完整流程,但是应用不可能只请求一次,那多次连续请求的场景是怎么保证不走取消定时的逻辑呢?

因为每次请求都会走EventThread::requestNextVsync

rust
代码解读
复制代码
# EventThread.cpp void EventThread::requestNextVsync(const sp& 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 图示时间计算

计算时间1.png

如图,假设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给到应用,计算规则如下图:

计算时间2.png

下一个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.png

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.png

4. 总结

到这里为止,应用请求VSYNC-app的完整流程已经介绍完毕了。

整个流程代码量相对交代,而且EventThread::threadMain方法的执行不同条件逻辑也不一样。

想要完整了解这块内容还是需要自己跟一遍完成的执行流程才能有正确的印象。

下面对这个流程遇到的几个方法和变量单独小结一下

4.1 EventThread::requestNextVsync

应用请求VSYNC-app时触发,有2种情况:

    1. 应用首次请求,这个时候vsyncRequest = VSyncRequest::None所以要把vsyncRequest设置成VSyncRequest::Single然后唤醒线程
    1. 应用持续请求,这时候只需要把vsyncRequest设置成VSyncRequest::Single就好。

这里需要理解vsyncRequest状态各个值在的意义,在后面执行EventThread::threadMain时是怎么控制流程的。

4.2 EventThread::threadMain

这个方法是核心方法,会多次执行,读者需要自己完整梳理各个条件下的执行逻辑。

  1. 首先是会根据mPendingEvents集合有没有值,这个集合的元素就是一个VSYNC-app时间,集合有没有值影响对局部变量event的赋值,进而影响后续逻辑
  2. 然后会遍历mDisplayEventConnections这个集合,这个集合存的是应用链接,可以理解为每个都代码这个一个具体的应用。遍历集合,看有没有应用请求了VSYNC-app。如果有任何一个应用请求了,就说明需要产生VSYNC-app给这个应用了。
    应用有没有请求VSYNC-app的判断条件是看这个应用链接的vsyncRequest的值,不等于VSyncRequest::None就说明需要给一次VSYNC-app给应用。
  3. shouldConsumeEvent方法本身是控制应用链接的 vsyncRequest值的切换的,返回值代表这个应用是否需要消费当前这个VSYNC-app,如果需要的会就添加进consumers集合,后续就会通过socket通信向应用端发送VSync事件。
  4. 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节是一样的。 另外还有相位差的概念没有提到。

参考

blog.csdn.net/tkwxty/arti…

www.jianshu.com/p/5e9c558d1…

mp.weixin.qq.com/s/gAcBEqjYA…

source.android.com/docs/core/g…

注:本文转载自juejin.cn的biubiubiuu的文章"https://juejin.cn/post/7492621778320359474"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top