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

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<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步 :

    1. appmArmedInfo进行重置
    1. 如果appwakeupTime是下一次定时结束的时间,则重新定时。 说明定时间已经为这个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其实就是代码分析的WakeupTimemIntendedWakeupTime(假设这个时候只有 app 申请定时)。

然后17.6ms - 应用触发申请的时间(10.6ms) = 7ms

这个7ms就是应用执行 EventThread::requestNextVsync 后一堆逻辑最终触发定时器的定时时间,定时结束后在 17.6ms 迎来第一个 VSYNC-app 。

根据这个规则,后面的 VSYNC-app 也都能计算出来。

3.2 VSYNC-sf 图示时间计算

VSYNC-sf的时间计算和VSYNC-app本质上是一样的,只不过sf readyduration0ms

应用绘制的时间(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-VSYNC49.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种情况:

    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. mStatenextState这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...

相关推荐
AD钙奶-lalala2 小时前
某车企面试备忘
android
我爱拉臭臭3 小时前
kotlin音乐app之自定义点击缩放组件Shrink Layout
android·java·kotlin
匹马夕阳4 小时前
(二十五)安卓开发一个完整的登录页面-支持密码登录和手机验证码登录
android·智能手机
吃饭了呀呀呀4 小时前
🐳 深度解析:Android 下拉选择控件优化方案——NiceSpinner 实践指南
android·java
吃饭了呀呀呀5 小时前
🐳 《Android》 安卓开发教程 - 三级地区联动
android·java·后端
_祝你今天愉快6 小时前
深入剖析Java中ThreadLocal原理
android
张力尹7 小时前
谈谈 kotlin 和 java 中的锁!你是不是在协程中使用 synchronized?
android
流浪汉kylin7 小时前
Android 斜切图片
android
PuddingSama8 小时前
Android 视图转换工具 Matrix
android·前端·面试
RichardLai888 小时前
[Flutter学习之Dart基础] - 控制语句
android·flutter