Loading services/surfaceflinger/Scheduler/VSyncPredictor.cpp +80 −18 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ VSyncPredictor::VSyncPredictor(std::unique_ptr<Clock> clock, ftl::NonNull<Displa kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)), kPredictorThreshold(ms2ns(200)), mDisplayModePtr(modePtr), mNumVsyncsForFrame(numVsyncsPerFrame(mDisplayModePtr)) { resetModel(); Loading @@ -91,7 +92,8 @@ nsecs_t VSyncPredictor::idealPeriod() const { bool VSyncPredictor::validate(nsecs_t timestamp) const { SFTRACE_CALL(); if (mLastTimestampIndex < 0 || mTimestamps.empty()) { if (mLastTimestampIndex < 0 || mTimestamps.empty() || getSampleSizeAndOldestVsync(timestamp).first == 0) { SFTRACE_INSTANT("timestamp valid (first)"); return true; } Loading @@ -110,8 +112,16 @@ bool VSyncPredictor::validate(nsecs_t timestamp) const { } const auto iter = std::min_element(mTimestamps.begin(), mTimestamps.end(), [timestamp](nsecs_t a, nsecs_t b) { return std::abs(timestamp - a) < std::abs(timestamp - b); [timestamp, this](nsecs_t a, nsecs_t b) { nsecs_t diffA = std::abs(timestamp - a); nsecs_t diffB = std::abs(timestamp - b); bool withinThresholdA = diffA <= kPredictorThreshold; bool withinThresholdB = diffB <= kPredictorThreshold; if (withinThresholdA != withinThresholdB) { return withinThresholdA; } return diffA < diffB; }); const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod(); if (distancePercent < kOutlierTolerancePercent) { Loading Loading @@ -180,8 +190,11 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { traceInt64If("VSP-ts", timestamp); const size_t numSamples = mTimestamps.size(); if (numSamples < kMinimumSamplesForPrediction) { const auto [numSamples, oldestTs] = getSampleSizeAndOldestVsync(timestamp); traceInt64If("VSP-numSamples", static_cast<int64_t>(numSamples)); mOldestVsync = oldestTs; const auto minNumSamples = getMinSamplesRequiredForPrediction(); if (numSamples < minNumSamples) { mRateMap[idealPeriod()] = {idealPeriod(), 0}; return true; } Loading @@ -205,12 +218,12 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { std::vector<nsecs_t> ordinals(numSamples); // Normalizing to the oldest timestamp cuts down on error in calculating the intercept. const auto oldestTS = *std::min_element(mTimestamps.begin(), mTimestamps.end()); auto it = mRateMap.find(idealPeriod()); // Calculated slope over the period of time can become outdated as the new timestamps are // stored. Using idealPeriod instead provides a rate which is valid at all the times. auto const currentPeriod = mDisplayModePtr->getVrrConfig() && FlagManager::getInstance().vsync_predictor_recovery() auto const currentPeriod = mDisplayModePtr->getVrrConfig() && (FlagManager::getInstance().vsync_predictor_recovery() || FlagManager::getInstance().vsync_predictor_predicts_within_threshold()) ? idealPeriod() : it->second.slope; Loading @@ -220,18 +233,25 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { nsecs_t meanTS = 0; nsecs_t meanOrdinal = 0; for (size_t i = 0; i < numSamples; i++) { const auto timestamp = mTimestamps[i] - oldestTS; vsyncTS[i] = timestamp; meanTS += timestamp; size_t vsyncIndex = 0; for (size_t i = 0; i < mTimestamps.size(); i++) { // Timestamp outside the threshold are not used for the calculation as the older // timestamps accumulate drifts and causes the anticipatedPeriod and intercept to // reflect that drift which no longer aligns with the current system state. if (!isVsyncWithinThreshold(timestamp, mTimestamps[i])) continue; const auto ts = mTimestamps[i] - oldestTs; vsyncTS[vsyncIndex] = ts; meanTS += ts; const auto ordinal = currentPeriod == 0 ? 0 : (vsyncTS[i] + currentPeriod / 2) / currentPeriod * kScalingFactor; ordinals[i] = ordinal; : (vsyncTS[vsyncIndex] + currentPeriod / 2) / currentPeriod * kScalingFactor; ordinals[vsyncIndex] = ordinal; meanOrdinal += ordinal; ++vsyncIndex; } LOG_FATAL_IF(vsyncIndex != numSamples, "samples size does not match with the number of vsyncs in window for prediction"); meanTS /= numSamples; meanOrdinal /= numSamples; Loading Loading @@ -284,9 +304,11 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { return knownTimestamp + numPeriodsOut * idealPeriod(); } auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end()); // See b/145667109, the ordinal calculation must take into account the intercept. const auto oldest = mDisplayModePtr->getVrrConfig() && FlagManager::getInstance().vsync_predictor_predicts_within_threshold() ? mOldestVsync : *std::min_element(mTimestamps.begin(), mTimestamps.end()); auto const zeroPoint = oldest + intercept; auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope; auto const prediction = (ordinalRequest * slope) + intercept + oldest; Loading @@ -310,6 +332,46 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { return prediction; } bool VSyncPredictor::isVsyncWithinThreshold(nsecs_t currentTimestamp, nsecs_t previousTimestamp) const { if (FlagManager::getInstance().vsync_predictor_predicts_within_threshold() && mDisplayModePtr->getVrrConfig()) { return currentTimestamp - previousTimestamp <= kPredictorThreshold; } return true; } std::pair<size_t, nsecs_t> VSyncPredictor::getSampleSizeAndOldestVsync( nsecs_t currentTimestamp) const { if (FlagManager::getInstance().vsync_predictor_predicts_within_threshold() && mDisplayModePtr->getVrrConfig()) { size_t numSamples = 0; nsecs_t oldestTimestamp = currentTimestamp; for (auto vsync : mTimestamps) { if (isVsyncWithinThreshold(currentTimestamp, vsync)) { ++numSamples; if (vsync < oldestTimestamp) { oldestTimestamp = vsync; } } } return {numSamples, oldestTimestamp}; } return {mTimestamps.size(), *std::min_element(mTimestamps.begin(), mTimestamps.end())}; } size_t VSyncPredictor::getMinSamplesRequiredForPrediction() const { if (FlagManager::getInstance().vsync_predictor_predicts_within_threshold() && mDisplayModePtr->getVrrConfig() && mRenderRateOpt) { const size_t minimumSamplesForPrediction = std::max(static_cast<size_t>(kAbsoluteMinSamplesForPrediction), static_cast<size_t>(kPredictorThreshold / mRenderRateOpt->getPeriodNsecs())); return std::min(kMinimumSamplesForPrediction, minimumSamplesForPrediction); } return kMinimumSamplesForPrediction; } nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t> lastVsyncOpt) { SFTRACE_CALL(); Loading Loading @@ -601,7 +663,7 @@ void VSyncPredictor::clearTimestamps(bool clearTimelines) { bool VSyncPredictor::needsMoreSamples() const { std::lock_guard lock(mMutex); return mTimestamps.size() < kMinimumSamplesForPrediction; return mTimestamps.size() < getMinSamplesRequiredForPrediction(); } void VSyncPredictor::resetModel() { Loading services/surfaceflinger/Scheduler/VSyncPredictor.h +8 −1 Original line number Diff line number Diff line Loading @@ -147,17 +147,24 @@ private: Period minFramePeriodLocked() const REQUIRES(mMutex); Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); void purgeTimelines(android::TimePoint now) REQUIRES(mMutex); bool isVsyncWithinThreshold(nsecs_t currentTimestamp, nsecs_t previousTimestamp) const REQUIRES(mMutex); std::pair<size_t, nsecs_t> getSampleSizeAndOldestVsync(nsecs_t currentTimestamp) const REQUIRES(mMutex); size_t getMinSamplesRequiredForPrediction() const REQUIRES(mMutex); nsecs_t idealPeriod() const REQUIRES(mMutex); bool const mTraceOn; size_t const kHistorySize; size_t const kMinimumSamplesForPrediction; static constexpr size_t kAbsoluteMinSamplesForPrediction = 3; size_t const kOutlierTolerancePercent; nsecs_t const kPredictorThreshold; std::mutex mutable mMutex; std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex); nsecs_t mOldestVsync GUARDED_BY(mMutex); // Map between ideal vsync period and the calculated model std::unordered_map<nsecs_t, Model> mutable mRateMap GUARDED_BY(mMutex); Loading services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +43 −1 Original line number Diff line number Diff line Loading @@ -305,7 +305,7 @@ TEST_F(VSyncPredictorTest, againstOutliersDiscontinuous_500hzLowVariance) { } TEST_F(VSyncPredictorTest, recoverAfterDriftedVSyncAreReplacedWithCorrectVSync) { SET_FLAG_FOR_TEST(flags::vsync_predictor_recovery, true); SET_FLAG_FOR_TEST(flags::vsync_predictor_predicts_within_threshold, true); auto constexpr idealPeriodNs = 4166666; auto constexpr minFrameIntervalNs = 8333333; auto constexpr idealPeriod = Fps::fromPeriodNsecs(idealPeriodNs); Loading Loading @@ -351,6 +351,48 @@ TEST_F(VSyncPredictorTest, recoverAfterDriftedVSyncAreReplacedWithCorrectVSync) EXPECT_THAT(slope, IsCloseTo(idealPeriodNs, mMaxRoundingError)); } TEST_F(VSyncPredictorTest, vsyncsOutsideThresholdDoesNotCauseIncorrectPrediction) { SET_FLAG_FOR_TEST(flags::vsync_predictor_predicts_within_threshold, true); auto constexpr idealPeriodNs = 8'333'333; auto constexpr minFrameIntervalNs = 8'333'333; auto constexpr idealPeriod = Fps::fromPeriodNsecs(idealPeriodNs); auto constexpr minFrameRate = Fps::fromPeriodNsecs(minFrameIntervalNs); hal::VrrConfig vrrConfig{.minFrameIntervalNs = minFrameIntervalNs}; ftl::NonNull<DisplayModePtr> mode = ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), idealPeriod, vrrConfig)); VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), mode, /*kHistorySize*/ 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ true, /*frameRateOverrides*/ {}); // Curated list of VSyncs that causes the VSync drift. std::vector<nsecs_t> const simulatedVsyncs{138174900755, 138191459948, 138208044322, 138224542110, 138274263073, 138307385130, 138340504271, 138373601849, 138406810105, 138423328802, 138456671277, 138506075624, 138539229192, 138555729687, 138572333412, 138605437709, 138638546223, 138672005027, 138704890886, 138737991954, 138771032579, 138804248803, 138837327631, 138870443751, 138903534923, 138936635912, 138969775912, 139002829688, 139036008100}; for (auto const timestamp : simulatedVsyncs) { vrrTracker.addVsyncTimestamp(timestamp); } auto model = vrrTracker.getVSyncPredictionModel(); // slope would be 8278833 otherwise EXPECT_THAT(model.slope, IsCloseTo(8277270, mMaxRoundingError)); // intercept would be 41881 otherwise EXPECT_THAT(model.intercept, IsCloseTo(-4026, mMaxRoundingError)); EXPECT_FALSE(vrrTracker.needsMoreSamples()); EXPECT_TRUE(vrrTracker.addVsyncTimestamp(139069133230)); EXPECT_FALSE(vrrTracker.needsMoreSamples()); model = vrrTracker.getVSyncPredictionModel(); // slope would be 8309405 otherwise with just idealPeriod EXPECT_THAT(model.slope, IsCloseTo(8278712, mMaxRoundingError)); // intercept would be -178060 otherwise EXPECT_THAT(model.intercept, IsCloseTo(-21585, mMaxRoundingError)); } TEST_F(VSyncPredictorTest, handlesVsyncChange) { auto const fastPeriod = 100; auto const fastTimeBase = 100; Loading Loading
services/surfaceflinger/Scheduler/VSyncPredictor.cpp +80 −18 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ VSyncPredictor::VSyncPredictor(std::unique_ptr<Clock> clock, ftl::NonNull<Displa kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)), kPredictorThreshold(ms2ns(200)), mDisplayModePtr(modePtr), mNumVsyncsForFrame(numVsyncsPerFrame(mDisplayModePtr)) { resetModel(); Loading @@ -91,7 +92,8 @@ nsecs_t VSyncPredictor::idealPeriod() const { bool VSyncPredictor::validate(nsecs_t timestamp) const { SFTRACE_CALL(); if (mLastTimestampIndex < 0 || mTimestamps.empty()) { if (mLastTimestampIndex < 0 || mTimestamps.empty() || getSampleSizeAndOldestVsync(timestamp).first == 0) { SFTRACE_INSTANT("timestamp valid (first)"); return true; } Loading @@ -110,8 +112,16 @@ bool VSyncPredictor::validate(nsecs_t timestamp) const { } const auto iter = std::min_element(mTimestamps.begin(), mTimestamps.end(), [timestamp](nsecs_t a, nsecs_t b) { return std::abs(timestamp - a) < std::abs(timestamp - b); [timestamp, this](nsecs_t a, nsecs_t b) { nsecs_t diffA = std::abs(timestamp - a); nsecs_t diffB = std::abs(timestamp - b); bool withinThresholdA = diffA <= kPredictorThreshold; bool withinThresholdB = diffB <= kPredictorThreshold; if (withinThresholdA != withinThresholdB) { return withinThresholdA; } return diffA < diffB; }); const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod(); if (distancePercent < kOutlierTolerancePercent) { Loading Loading @@ -180,8 +190,11 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { traceInt64If("VSP-ts", timestamp); const size_t numSamples = mTimestamps.size(); if (numSamples < kMinimumSamplesForPrediction) { const auto [numSamples, oldestTs] = getSampleSizeAndOldestVsync(timestamp); traceInt64If("VSP-numSamples", static_cast<int64_t>(numSamples)); mOldestVsync = oldestTs; const auto minNumSamples = getMinSamplesRequiredForPrediction(); if (numSamples < minNumSamples) { mRateMap[idealPeriod()] = {idealPeriod(), 0}; return true; } Loading @@ -205,12 +218,12 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { std::vector<nsecs_t> ordinals(numSamples); // Normalizing to the oldest timestamp cuts down on error in calculating the intercept. const auto oldestTS = *std::min_element(mTimestamps.begin(), mTimestamps.end()); auto it = mRateMap.find(idealPeriod()); // Calculated slope over the period of time can become outdated as the new timestamps are // stored. Using idealPeriod instead provides a rate which is valid at all the times. auto const currentPeriod = mDisplayModePtr->getVrrConfig() && FlagManager::getInstance().vsync_predictor_recovery() auto const currentPeriod = mDisplayModePtr->getVrrConfig() && (FlagManager::getInstance().vsync_predictor_recovery() || FlagManager::getInstance().vsync_predictor_predicts_within_threshold()) ? idealPeriod() : it->second.slope; Loading @@ -220,18 +233,25 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { nsecs_t meanTS = 0; nsecs_t meanOrdinal = 0; for (size_t i = 0; i < numSamples; i++) { const auto timestamp = mTimestamps[i] - oldestTS; vsyncTS[i] = timestamp; meanTS += timestamp; size_t vsyncIndex = 0; for (size_t i = 0; i < mTimestamps.size(); i++) { // Timestamp outside the threshold are not used for the calculation as the older // timestamps accumulate drifts and causes the anticipatedPeriod and intercept to // reflect that drift which no longer aligns with the current system state. if (!isVsyncWithinThreshold(timestamp, mTimestamps[i])) continue; const auto ts = mTimestamps[i] - oldestTs; vsyncTS[vsyncIndex] = ts; meanTS += ts; const auto ordinal = currentPeriod == 0 ? 0 : (vsyncTS[i] + currentPeriod / 2) / currentPeriod * kScalingFactor; ordinals[i] = ordinal; : (vsyncTS[vsyncIndex] + currentPeriod / 2) / currentPeriod * kScalingFactor; ordinals[vsyncIndex] = ordinal; meanOrdinal += ordinal; ++vsyncIndex; } LOG_FATAL_IF(vsyncIndex != numSamples, "samples size does not match with the number of vsyncs in window for prediction"); meanTS /= numSamples; meanOrdinal /= numSamples; Loading Loading @@ -284,9 +304,11 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { return knownTimestamp + numPeriodsOut * idealPeriod(); } auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end()); // See b/145667109, the ordinal calculation must take into account the intercept. const auto oldest = mDisplayModePtr->getVrrConfig() && FlagManager::getInstance().vsync_predictor_predicts_within_threshold() ? mOldestVsync : *std::min_element(mTimestamps.begin(), mTimestamps.end()); auto const zeroPoint = oldest + intercept; auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope; auto const prediction = (ordinalRequest * slope) + intercept + oldest; Loading @@ -310,6 +332,46 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { return prediction; } bool VSyncPredictor::isVsyncWithinThreshold(nsecs_t currentTimestamp, nsecs_t previousTimestamp) const { if (FlagManager::getInstance().vsync_predictor_predicts_within_threshold() && mDisplayModePtr->getVrrConfig()) { return currentTimestamp - previousTimestamp <= kPredictorThreshold; } return true; } std::pair<size_t, nsecs_t> VSyncPredictor::getSampleSizeAndOldestVsync( nsecs_t currentTimestamp) const { if (FlagManager::getInstance().vsync_predictor_predicts_within_threshold() && mDisplayModePtr->getVrrConfig()) { size_t numSamples = 0; nsecs_t oldestTimestamp = currentTimestamp; for (auto vsync : mTimestamps) { if (isVsyncWithinThreshold(currentTimestamp, vsync)) { ++numSamples; if (vsync < oldestTimestamp) { oldestTimestamp = vsync; } } } return {numSamples, oldestTimestamp}; } return {mTimestamps.size(), *std::min_element(mTimestamps.begin(), mTimestamps.end())}; } size_t VSyncPredictor::getMinSamplesRequiredForPrediction() const { if (FlagManager::getInstance().vsync_predictor_predicts_within_threshold() && mDisplayModePtr->getVrrConfig() && mRenderRateOpt) { const size_t minimumSamplesForPrediction = std::max(static_cast<size_t>(kAbsoluteMinSamplesForPrediction), static_cast<size_t>(kPredictorThreshold / mRenderRateOpt->getPeriodNsecs())); return std::min(kMinimumSamplesForPrediction, minimumSamplesForPrediction); } return kMinimumSamplesForPrediction; } nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t> lastVsyncOpt) { SFTRACE_CALL(); Loading Loading @@ -601,7 +663,7 @@ void VSyncPredictor::clearTimestamps(bool clearTimelines) { bool VSyncPredictor::needsMoreSamples() const { std::lock_guard lock(mMutex); return mTimestamps.size() < kMinimumSamplesForPrediction; return mTimestamps.size() < getMinSamplesRequiredForPrediction(); } void VSyncPredictor::resetModel() { Loading
services/surfaceflinger/Scheduler/VSyncPredictor.h +8 −1 Original line number Diff line number Diff line Loading @@ -147,17 +147,24 @@ private: Period minFramePeriodLocked() const REQUIRES(mMutex); Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); void purgeTimelines(android::TimePoint now) REQUIRES(mMutex); bool isVsyncWithinThreshold(nsecs_t currentTimestamp, nsecs_t previousTimestamp) const REQUIRES(mMutex); std::pair<size_t, nsecs_t> getSampleSizeAndOldestVsync(nsecs_t currentTimestamp) const REQUIRES(mMutex); size_t getMinSamplesRequiredForPrediction() const REQUIRES(mMutex); nsecs_t idealPeriod() const REQUIRES(mMutex); bool const mTraceOn; size_t const kHistorySize; size_t const kMinimumSamplesForPrediction; static constexpr size_t kAbsoluteMinSamplesForPrediction = 3; size_t const kOutlierTolerancePercent; nsecs_t const kPredictorThreshold; std::mutex mutable mMutex; std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex); nsecs_t mOldestVsync GUARDED_BY(mMutex); // Map between ideal vsync period and the calculated model std::unordered_map<nsecs_t, Model> mutable mRateMap GUARDED_BY(mMutex); Loading
services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +43 −1 Original line number Diff line number Diff line Loading @@ -305,7 +305,7 @@ TEST_F(VSyncPredictorTest, againstOutliersDiscontinuous_500hzLowVariance) { } TEST_F(VSyncPredictorTest, recoverAfterDriftedVSyncAreReplacedWithCorrectVSync) { SET_FLAG_FOR_TEST(flags::vsync_predictor_recovery, true); SET_FLAG_FOR_TEST(flags::vsync_predictor_predicts_within_threshold, true); auto constexpr idealPeriodNs = 4166666; auto constexpr minFrameIntervalNs = 8333333; auto constexpr idealPeriod = Fps::fromPeriodNsecs(idealPeriodNs); Loading Loading @@ -351,6 +351,48 @@ TEST_F(VSyncPredictorTest, recoverAfterDriftedVSyncAreReplacedWithCorrectVSync) EXPECT_THAT(slope, IsCloseTo(idealPeriodNs, mMaxRoundingError)); } TEST_F(VSyncPredictorTest, vsyncsOutsideThresholdDoesNotCauseIncorrectPrediction) { SET_FLAG_FOR_TEST(flags::vsync_predictor_predicts_within_threshold, true); auto constexpr idealPeriodNs = 8'333'333; auto constexpr minFrameIntervalNs = 8'333'333; auto constexpr idealPeriod = Fps::fromPeriodNsecs(idealPeriodNs); auto constexpr minFrameRate = Fps::fromPeriodNsecs(minFrameIntervalNs); hal::VrrConfig vrrConfig{.minFrameIntervalNs = minFrameIntervalNs}; ftl::NonNull<DisplayModePtr> mode = ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), idealPeriod, vrrConfig)); VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), mode, /*kHistorySize*/ 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ true, /*frameRateOverrides*/ {}); // Curated list of VSyncs that causes the VSync drift. std::vector<nsecs_t> const simulatedVsyncs{138174900755, 138191459948, 138208044322, 138224542110, 138274263073, 138307385130, 138340504271, 138373601849, 138406810105, 138423328802, 138456671277, 138506075624, 138539229192, 138555729687, 138572333412, 138605437709, 138638546223, 138672005027, 138704890886, 138737991954, 138771032579, 138804248803, 138837327631, 138870443751, 138903534923, 138936635912, 138969775912, 139002829688, 139036008100}; for (auto const timestamp : simulatedVsyncs) { vrrTracker.addVsyncTimestamp(timestamp); } auto model = vrrTracker.getVSyncPredictionModel(); // slope would be 8278833 otherwise EXPECT_THAT(model.slope, IsCloseTo(8277270, mMaxRoundingError)); // intercept would be 41881 otherwise EXPECT_THAT(model.intercept, IsCloseTo(-4026, mMaxRoundingError)); EXPECT_FALSE(vrrTracker.needsMoreSamples()); EXPECT_TRUE(vrrTracker.addVsyncTimestamp(139069133230)); EXPECT_FALSE(vrrTracker.needsMoreSamples()); model = vrrTracker.getVSyncPredictionModel(); // slope would be 8309405 otherwise with just idealPeriod EXPECT_THAT(model.slope, IsCloseTo(8278712, mMaxRoundingError)); // intercept would be -178060 otherwise EXPECT_THAT(model.intercept, IsCloseTo(-21585, mMaxRoundingError)); } TEST_F(VSyncPredictorTest, handlesVsyncChange) { auto const fastPeriod = 100; auto const fastTimeBase = 100; Loading