背景
最近在精讀努比亞團(tuán)隊(duì)的SurfaceFlinger模塊-VSYNC研究,其中有一段話一直困擾到我,成為了徹底理解vsync的最后一層窗戶紙茴肥。
3.2.2 nextAnticipatedVsyncTimeFromLocked
有了這個(gè)回歸系數(shù)和截距瓤狐,就可以傳入上一次app或者sf發(fā)射的時(shí)間础锐,計(jì)算出下一次發(fā)射的時(shí)間
一皆警、我的疑問(wèn)
按照他的說(shuō)法,每次nextVsync都是依賴上一次app或者sf發(fā)射的時(shí)間earliestVsync鸵隧,那問(wèn)題來(lái)了豆瘫,如果屏幕界面停止刷新一段時(shí)間外驱,然后app再次刷新昵宇,那這時(shí)候上一次app發(fā)射的時(shí)間earliestVsync遠(yuǎn)遠(yuǎn)早于希望拿到的nextVsync一個(gè)Vsync周期以上磅崭,按照計(jì)算公式,根本算不出正確nextVsync趟薄。
舉個(gè)例子
假設(shè)周期16ms绽诚,上次vsync的時(shí)間為16ms,然后過(guò)了160ms杭煎,我這個(gè)時(shí)候請(qǐng)求nextVsync恩够。
因?yàn)閑arliestVsync是16ms,算出來(lái)的應(yīng)該是32ms羡铲,但是實(shí)際的nextVsync應(yīng)該是16 + 160 + 16 = 192 ms
二蜂桶、傳入的實(shí)際數(shù)據(jù)
其實(shí)傳入的實(shí)際數(shù)據(jù)是timing.earliestVsync
和now + timing.workDuration + timing.readyDuration
兩個(gè)數(shù)字的中最大值也切,而且絕大多數(shù)還是用后者這個(gè)數(shù)字
auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
workDuration
和readyDuration
與我之前文章寫(xiě)的有關(guān)[070]一文帶你看懂Vsync Phase扑媚,建議先看這個(gè)文章。
舉個(gè)例子
我通過(guò)dumpsys SurfaceFlinger | grep duration
雷恃,獲取我的設(shè)備參數(shù)
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
疆股,用這個(gè)值作為下面函數(shù)timePoint就可以計(jì)算出nextVsync。
不知道怎么計(jì)算的可以參考我前面的文章[085]SW VSYNC模型更新與校準(zhǔn)
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;
}
三倒槐、繼續(xù)思考
到這里其實(shí)還有幾點(diǎn)疑惑旬痹,需要進(jìn)一步解答
3.1 等于每次app要申請(qǐng)的時(shí)候,會(huì)走到resyncAndRefresh中讨越,這個(gè)函數(shù)就會(huì)強(qiáng)制進(jìn)行一次硬件的VSYNC校準(zhǔn)两残。
這句話明顯不可能,因?yàn)閠race中可以看到hw vsync正常刷新的時(shí)候就會(huì)關(guān)閉把跨。
合理的說(shuō)法應(yīng)該是如果兩次app vsync的request nextvsync時(shí)間差大于750ms人弓,就會(huì)觸發(fā)一下hardware vsync同步。
/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()); //硬件校準(zhǔn)
}
}
我通過(guò)住trace着逐,驗(yàn)證了這個(gè)
3.2 假如觸發(fā)了硬件的VSYNC校準(zhǔn)崔赌,就會(huì)清空模型中的數(shù)據(jù)意蛀,如果算出nextVsync
關(guān)鍵看這個(gè)代碼,一旦mTimestamps為空峰鄙,就會(huì)用mKnownTimestamp來(lái)計(jì)算浸间,也就是"VSP-mode"為1.
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的時(shí)候被賦值了采樣的vsync中的最大值
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;
}
}
簡(jiǎn)單總結(jié)一下
"VSP-mode"為1,是用采樣數(shù)據(jù)中清空前吟榴,最新的時(shí)間戳nextvsync
"VSP-mode"為0魁蒜,是用采樣數(shù)據(jù)中最老的時(shí)間戳配合上擬合的模型算nextvsync
這個(gè)采樣的數(shù)據(jù)會(huì)在hw vsync或者present fence signal后被加入進(jìn)來(lái),采樣的數(shù)據(jù)保留最新的6個(gè)吩翻,重新計(jì)算模型兜看。
我通過(guò)trace,也驗(yàn)證了這個(gè)事情狭瞎,開(kāi)始硬件vsync的第一個(gè)nextvsync是"VSP-mode"為1的方式算出來(lái)的细移。
3.3 app vsync和next vsync的關(guān)系
我之前以為next vsync就是app vsync,其實(shí)大錯(cuò)特錯(cuò)熊锭,看這段代碼
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);
}
這里有一個(gè)重要的結(jié)構(gòu)體ArmedInfo弧轧,解讀一下。
struct ArmingInfo {
nsecs_t mActualWakeupTime;
nsecs_t mActualVsyncTime;
nsecs_t mActualReadyTime;
};
app vsync的ArmingInfo
nextWakeupTime
:下一個(gè)app vsync觸發(fā)時(shí)機(jī)碗殷,觸發(fā)app繪制
nextVsyncTime
:nextWakeupTime觸發(fā)app繪制精绎,最后送顯屏幕的時(shí)間。
nextReadyTime
:nextWakeupTime觸發(fā)app繪制完成锌妻,觸發(fā)sf合成的時(shí)間代乃。
sf vsync的ArmingInfo
nextWakeupTime
:下一個(gè)sf vsync觸發(fā)時(shí)機(jī),觸發(fā)sf合成
nextVsyncTime
:nextWakeupTime觸發(fā)sf合成仿粹,最后送顯屏幕的時(shí)間搁吓。
nextReadyTime
:等同于nextVsyncTime。
雖然app request和sf request next vsync不在同一時(shí)間吭历,但是得到的nextVsyncTime其實(shí)是同一個(gè)堕仔,因?yàn)閮烧咦詈笏惋@時(shí)間是一樣的。
3.4 意外的問(wèn)題分析
我抓trace的發(fā)現(xiàn)一個(gè)很奇怪的事情晌区,就是明明按照[070]一文帶你看懂Vsync Phase相位差計(jì)算摩骨,我的設(shè)備sf和app的vsync不應(yīng)該有相位差,但是我發(fā)現(xiàn)我的設(shè)備一直保持有100us左右的間隔契讲。
仔細(xì)分析trace,原來(lái)是因?yàn)門(mén)imeDispatcher中對(duì)SF vsync的觸發(fā)的callback滑频,過(guò)于耗時(shí)捡偏,導(dǎo)致delay了app vsync的觸發(fā),然后產(chǎn)生了輕微的offset峡迷。
四银伟、總結(jié)
總算是把SurfaceFlinger模塊-VSYNC研究所講的全部消化了你虹,當(dāng)然也發(fā)現(xiàn)文章很多說(shuō)的不是很到位的地方,因?yàn)檎_理解了ArmingInfo 彤避,對(duì)之前自己寫(xiě)的文章[070]一文帶你看懂Vsync Phase有了更加深刻的理解傅物,最后還是要感謝努比亞團(tuán)隊(duì)的文章,終于可以在大腦中形成vsync完整工作流程琉预,哈哈董饰。
白話版Vsync的理解
1.首先采樣的數(shù)據(jù),只有兩個(gè)來(lái)源圆米,hw vsync和present fence卒暂,采樣的數(shù)據(jù)只會(huì)保留最近的6個(gè)hw vsync 時(shí)間戳。
2.根據(jù)采樣的數(shù)據(jù)訓(xùn)練出模型參數(shù)娄帖。
3.然后把std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration))帶入模型就可以獲得下一個(gè)nextvsync
4.然后nextvsync - workDuration - readyDuration就是wakeuptime
5.我的設(shè)備app workDuration = 16.6 readyDuration = 15.6
6.wakeuptime就是真的app vsync觸發(fā)的時(shí)間也祠,設(shè)置到timedispatcher里。
對(duì)于sf近速,步驟一樣诈嘿,只是workDuration和readyDuration改了
5.我的設(shè)備sf workDuration = 15.6 readyDuration = 0
6.wakeuptime就是真的sf vsync觸發(fā)的時(shí)間,設(shè)置到timedispatcher里削葱。
如果上時(shí)間界面不更新的話奖亚,因?yàn)槌^(guò)750ms,首先會(huì)觸發(fā)硬件采樣的vsync佩耳,同時(shí)清空采樣的數(shù)據(jù)遂蛀,
然后保存采樣數(shù)據(jù)中的最大值mKnownTimestamp ,這時(shí)候就會(huì)走vsp-mode 1干厚,會(huì)使用mKnownTimestamp來(lái)算nextvsync