背景
最近在精读努比亚团队的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.earliestVsync
和now timing.workDuration timing.readyDuration
两个数字的中最大值,而且绝大多数还是用后者这个数字
auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
std::max(timing.earliestVsync, now timing.workDuration timing.readyDuration));
workDuration
和readyDuration
与我之前文章写的有关[070]一文带你看懂Vsync Phase,建议先看这个文章。
举个例子
我通过dumpsys SurfaceFlinger | grep duration
,获取我的设备参数
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