前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[086]VSYNC研究-最后的窗户纸

[086]VSYNC研究-最后的窗户纸

作者头像
王小二
发布2023-11-09 08:53:42
5000
发布2023-11-09 08:53:42
举报

背景

最近在精读努比亚团队的SurfaceFlinger模块-VSYNC研究,其中有一段话一直困扰到我,成为了彻底理解vsync的最后一层窗户纸。

3.2.2 nextAnticipatedVsyncTimeFromLocked 有了这个回归系数和截距,就可以传入上一次app或者sf发射的时间,计算出下一次发射的时间

一、我的疑问

按照他的说法,每次nextVsync都是依赖上一次app或者sf发射的时间earliestVsync,那问题来了,如果屏幕界面停止刷新一段时间,然后app再次刷新,那这时候上一次app发射的时间earliestVsync远远早于希望拿到的nextVsync一个Vsync周期以上,按照计算公式,根本算不出正确nextVsync。

举个例子
代码语言:javascript
复制
假设周期16ms,上次vsync的时间为16ms,然后过了160ms,我这个时候请求nextVsync。
因为earliestVsync是16ms,算出来的应该是32ms,但是实际的nextVsync应该是16 + 160 + 16 = 192 ms

二、传入的实际数据

其实传入的实际数据是timing.earliestVsyncnow + timing.workDuration + timing.readyDuration两个数字的中最大值,而且绝大多数还是用后者这个数字

代码语言:javascript
复制
    auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
            std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));

workDurationreadyDuration与我之前文章写的有关[070]一文带你看懂Vsync Phase,建议先看这个文章。

举个例子

我通过dumpsys SurfaceFlinger | grep duration,获取我的设备参数

代码语言:javascript
复制
XXXXX:/ $ dumpsys SurfaceFlinger | grep duration
           app duration:  16666666 ns            SF duration:  15666666 ns

最后作用到app和sf的vsync如下

vsync类型

workDuration

readyDuration

app vsync

app duration(16666666 ns)

SF duration (15666666 ns)

sf vsync

SF duration(15666666 ns)

0

很明显now + timing.workDuration + timing.readyDuration,用这个值作为下面函数timePoint就可以计算出nextVsync。

不知道怎么计算的可以参考我前面的文章[085]SW VSYNC模型更新与校准

代码语言:javascript
复制
nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const {
    auto const [slope, intercept] = getVSyncPredictionModelLocked();

    if (mTimestamps.empty()) {
        traceInt64If("VSP-mode", 1);
        auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
        auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
        return knownTimestamp + numPeriodsOut * mIdealPeriod;
    }

    auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end());

    // See b/145667109, the ordinal calculation must take into account the intercept.
    auto const zeroPoint = oldest + intercept;
    auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
    auto const prediction = (ordinalRequest * slope) + intercept + oldest;

    traceInt64If("VSP-mode", 0);
    traceInt64If("VSP-timePoint", timePoint);
    traceInt64If("VSP-prediction", prediction);

    auto const printer = [&, slope = slope, intercept = intercept] {
        std::stringstream str;
        str << "prediction made from: " << timePoint << "prediction: " << prediction << " (+"
            << prediction - timePoint << ") slope: " << slope << " intercept: " << intercept
            << "oldestTS: " << oldest << " ordinal: " << ordinalRequest;
        return str.str();
    };

    ALOGV("%s", printer().c_str());
    LOG_ALWAYS_FATAL_IF(prediction < timePoint, "VSyncPredictor: model miscalculation: %s",
                        printer().c_str());

    return prediction;
}

三、继续思考

到这里其实还有几点疑惑,需要进一步解答

3.1 等于每次app要申请的时候,会走到resyncAndRefresh中,这个函数就会强制进行一次硬件的VSYNC校准。

这句话明显不可能,因为trace中可以看到hw vsync正常刷新的时候就会关闭。

合理的说法应该是如果两次app vsync的request nextvsync时间差大于750ms,就会触发一下hardware vsync同步。

代码语言:javascript
复制
/frameworks/native/services/surfaceflinger/Scheduler/Scheduler.cpp

void Scheduler::resync() {
    static constexpr nsecs_t kIgnoreDelay = ms2ns(750);//如果两次app vsync的request 

    const nsecs_t now = systemTime();
    const nsecs_t last = mLastResyncTime.exchange(now);

    if (now - last > kIgnoreDelay) {
        resyncToHardwareVsync(false, mRefreshRateConfigs.getCurrentRefreshRate().getVsyncPeriod()); //硬件校准
    }
}

我通过住trace,验证了这个

3.2 假如触发了硬件的VSYNC校准,就会清空模型中的数据,如果算出nextVsync

关键看这个代码,一旦mTimestamps为空,就会用mKnownTimestamp来计算,也就是"VSP-mode"为1.

代码语言:javascript
复制
nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const {
    auto const [slope, intercept] = getVSyncPredictionModelLocked();

    if (mTimestamps.empty()) {
        traceInt64If("VSP-mode", 1);
        auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
        auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
        return knownTimestamp + numPeriodsOut * mIdealPeriod;
    }

mKnownTimestamp 又是在clear的时候被赋值了采样的vsync中的最大值

代码语言:javascript
复制
void VSyncPredictor::clearTimestamps() {
    if (!mTimestamps.empty()) {
        auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
        if (mKnownTimestamp) {
            mKnownTimestamp = std::max(*mKnownTimestamp, maxRb);
        } else {
            mKnownTimestamp = maxRb;
        }

        mTimestamps.clear();
        mLastTimestampIndex = 0;
    }
}
简单总结一下

"VSP-mode"为1,是用采样数据中清空前,最新的时间戳nextvsync "VSP-mode"为0,是用采样数据中最老的时间戳配合上拟合的模型算nextvsync 这个采样的数据会在hw vsync或者present fence signal后被加入进来,采样的数据保留最新的6个,重新计算模型。

我通过trace,也验证了这个事情,开始硬件vsync的第一个nextvsync是"VSP-mode"为1的方式算出来的。

3.3 app vsync和next vsync的关系

我之前以为next vsync就是app vsync,其实大错特错,看这段代码

代码语言:javascript
复制
ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing, VSyncTracker& tracker, nsecs_t now) {
    auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
            std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
    auto nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
 ···
     auto const nextReadyTime = nextVsyncTime - timing.readyDuration;
     mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime};
     return getExpectedCallbackTime(nextVsyncTime, timing);
 }

这里有一个重要的结构体ArmedInfo,解读一下。

代码语言:javascript
复制
    struct ArmingInfo {
        nsecs_t mActualWakeupTime;
        nsecs_t mActualVsyncTime;
        nsecs_t mActualReadyTime;
    };
app vsync的ArmingInfo

nextWakeupTime:下一个app vsync触发时机,触发app绘制

nextVsyncTime:nextWakeupTime触发app绘制,最后送显屏幕的时间。

nextReadyTime:nextWakeupTime触发app绘制完成,触发sf合成的时间。

sf vsync的ArmingInfo

nextWakeupTime:下一个sf vsync触发时机,触发sf合成

nextVsyncTime:nextWakeupTime触发sf合成,最后送显屏幕的时间。

nextReadyTime:等同于nextVsyncTime。

虽然app request和sf request next vsync不在同一时间,但是得到的nextVsyncTime其实是同一个,因为两者最后送显时间是一样的。

3.4 意外的问题分析

我抓trace的发现一个很奇怪的事情,就是明明按照[070]一文带你看懂Vsync Phase相位差计算,我的设备sf和app的vsync不应该有相位差,但是我发现我的设备一直保持有100us左右的间隔。

仔细分析trace,原来是因为TimeDispatcher中对SF vsync的触发的callback,过于耗时,导致delay了app vsync的触发,然后产生了轻微的offset。

四、总结

总算是把SurfaceFlinger模块-VSYNC研究所讲的全部消化了,当然也发现文章很多说的不是很到位的地方,因为正确理解了ArmingInfo ,对之前自己写的文章[070]一文带你看懂Vsync Phase有了更加深刻的理解,最后还是要感谢努比亚团队的文章,终于可以在大脑中形成vsync完整工作流程,哈哈。

白话版Vsync的理解

代码语言:javascript
复制
1.首先采样的数据,只有两个来源,hw vsync和present fence,采样的数据只会保留最近的6个hw vsync 时间戳。
2.根据采样的数据训练出模型参数。
3.然后把std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration))带入模型就可以获得下一个nextvsync
4.然后nextvsync - workDuration - readyDuration就是wakeuptime
5.我的设备app workDuration = 16.6 readyDuration = 15.6 
6.wakeuptime就是真的app vsync触发的时间,设置到timedispatcher里。


对于sf,步骤一样,只是workDuration和readyDuration改了
5.我的设备sf workDuration = 15.6 readyDuration = 0
6.wakeuptime就是真的sf vsync触发的时间,设置到timedispatcher里。

如果上时间界面不更新的话,因为超过750ms,首先会触发硬件采样的vsync,同时清空采样的数据,
然后保存采样数据中的最大值mKnownTimestamp ,这时候就会走vsp-mode 1,会使用mKnownTimestamp来算nextvsync
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-11-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 一、我的疑问
  • 二、传入的实际数据
  • 三、继续思考
    • 3.1 等于每次app要申请的时候,会走到resyncAndRefresh中,这个函数就会强制进行一次硬件的VSYNC校准。
      • 3.2 假如触发了硬件的VSYNC校准,就会清空模型中的数据,如果算出nextVsync
        • 3.3 app vsync和next vsync的关系
          • 这里有一个重要的结构体ArmedInfo,解读一下。
            • 3.4 意外的问题分析
            • 四、总结
            • 白话版Vsync的理解
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档