From b6c7f880460c81a6ce49ccb3334e2d2e1e020f81 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 29 Jan 2024 14:43:29 -0800 Subject: SF: Introduce VsyncTimeline to VsyncPredictor Add the concept of timeline freezing when switching render rate. This allow us to change render rates in sync with the app and remain jank free across render rate changes. Bug: 326599221 Test: Run TouchLatency, change render rate and examine Perfetto trace Change-Id: Ibc8026434c0c1d50138299da3cb110b317604e92 --- .../Scheduler/VSyncDispatchTimerQueue.cpp | 71 +++-- .../Scheduler/VSyncDispatchTimerQueue.h | 4 +- .../surfaceflinger/Scheduler/VSyncPredictor.cpp | 298 +++++++++++++++------ services/surfaceflinger/Scheduler/VSyncPredictor.h | 60 +++-- services/surfaceflinger/Scheduler/VSyncTracker.h | 6 +- .../surfaceflinger/Scheduler/VsyncSchedule.cpp | 4 +- services/surfaceflinger/Scheduler/VsyncSchedule.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 12 +- .../tests/unittests/SchedulerTest.cpp | 11 +- .../tests/unittests/VSyncDispatchRealtimeTest.cpp | 6 +- .../tests/unittests/VSyncPredictorTest.cpp | 101 ++++--- .../tests/unittests/mock/MockVSyncTracker.h | 4 +- 12 files changed, 408 insertions(+), 171 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index 84ccf8e938..6d6b70d198 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include @@ -44,6 +44,17 @@ ScheduleResult getExpectedCallbackTime(nsecs_t nextVsyncTime, TimePoint::fromNs(nextVsyncTime)}; } +void traceEntry(const VSyncDispatchTimerQueueEntry& entry, nsecs_t now) { + if (!ATRACE_ENABLED() || !entry.wakeupTime().has_value() || !entry.targetVsync().has_value()) { + return; + } + + ftl::Concat trace(ftl::truncated<5>(entry.name()), " alarm in ", + ns2us(*entry.wakeupTime() - now), "us; VSYNC in ", + ns2us(*entry.targetVsync() - now), "us"); + ATRACE_FORMAT_INSTANT(trace.c_str()); +} + } // namespace VSyncDispatch::~VSyncDispatch() = default; @@ -87,6 +98,7 @@ std::optional VSyncDispatchTimerQueueEntry::targetVsync() const { ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing, VSyncTracker& tracker, nsecs_t now) { + ATRACE_NAME("VSyncDispatchTimerQueueEntry::schedule"); auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync, now + timing.workDuration + @@ -98,6 +110,8 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance)); bool const wouldSkipAWakeup = mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance))); + ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(), + wouldSkipAVsyncTarget, wouldSkipAWakeup); if (FlagManager::getInstance().dont_skip_on_early_ro()) { if (wouldSkipAVsyncTarget || wouldSkipAWakeup) { nextVsyncTime = mArmedInfo->mActualVsyncTime; @@ -122,7 +136,7 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim ScheduleResult VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate( VSyncTracker& tracker, nsecs_t now, VSyncDispatch::ScheduleTiming timing) { mWorkloadUpdateInfo = timing; - const auto armedInfo = update(tracker, now, timing, mArmedInfo); + const auto armedInfo = getArmedInfo(tracker, now, timing, mArmedInfo); return {TimePoint::fromNs(armedInfo.mActualWakeupTime), TimePoint::fromNs(armedInfo.mActualVsyncTime)}; } @@ -140,11 +154,13 @@ nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, bool const nextVsyncTooClose = mLastDispatchTime && (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod; if (alreadyDispatchedForVsync) { + ATRACE_FORMAT_INSTANT("alreadyDispatchedForVsync"); return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance, *mLastDispatchTime); } if (nextVsyncTooClose) { + ATRACE_FORMAT_INSTANT("nextVsyncTooClose"); return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod, *mLastDispatchTime + currentPeriod); } @@ -152,9 +168,11 @@ nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, return nextVsyncTime; } -auto VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now, - VSyncDispatch::ScheduleTiming timing, - std::optional armedInfo) const -> ArmingInfo { +auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t now, + VSyncDispatch::ScheduleTiming timing, + std::optional armedInfo) const + -> ArmingInfo { + ATRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo"); const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration; const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync); @@ -165,29 +183,39 @@ auto VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now, const auto nextReadyTime = nextVsyncTime - timing.readyDuration; const auto nextWakeupTime = nextReadyTime - timing.workDuration; - bool const wouldSkipAVsyncTarget = - armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance)); - bool const wouldSkipAWakeup = - armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance)); - if (FlagManager::getInstance().dont_skip_on_early_ro() && - (wouldSkipAVsyncTarget || wouldSkipAWakeup)) { - return *armedInfo; + if (FlagManager::getInstance().dont_skip_on_early_ro()) { + bool const wouldSkipAVsyncTarget = + armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance)); + bool const wouldSkipAWakeup = + armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance)); + ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(), + wouldSkipAVsyncTarget, wouldSkipAWakeup); + if (wouldSkipAVsyncTarget || wouldSkipAWakeup) { + return *armedInfo; + } } return ArmingInfo{nextWakeupTime, nextVsyncTime, nextReadyTime}; } void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) { + ATRACE_NAME("VSyncDispatchTimerQueueEntry::update"); if (!mArmedInfo && !mWorkloadUpdateInfo) { return; } if (mWorkloadUpdateInfo) { + const auto workDelta = mWorkloadUpdateInfo->workDuration - mScheduleTiming.workDuration; + const auto readyDelta = mWorkloadUpdateInfo->readyDuration - mScheduleTiming.readyDuration; + const auto lastVsyncDelta = mWorkloadUpdateInfo->lastVsync - mScheduleTiming.lastVsync; + ATRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64 + " lastVsyncDelta=%" PRId64, + workDelta, readyDelta, lastVsyncDelta); mScheduleTiming = *mWorkloadUpdateInfo; mWorkloadUpdateInfo.reset(); } - mArmedInfo = update(tracker, now, mScheduleTiming, mArmedInfo); + mArmedInfo = getArmedInfo(tracker, now, mScheduleTiming, mArmedInfo); } void VSyncDispatchTimerQueueEntry::disarm() { @@ -282,6 +310,7 @@ void VSyncDispatchTimerQueue::rearmTimer(nsecs_t now) { void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( nsecs_t now, CallbackMap::const_iterator skipUpdateIt) { + ATRACE_CALL(); std::optional min; std::optional targetVsync; std::optional nextWakeupName; @@ -294,7 +323,10 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( if (it != skipUpdateIt) { callback->update(*mTracker, now); } - auto const wakeupTime = *callback->wakeupTime(); + + traceEntry(*callback, now); + + const auto wakeupTime = *callback->wakeupTime(); if (!min || *min > wakeupTime) { nextWakeupName = callback->name(); min = wakeupTime; @@ -303,11 +335,6 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } if (min && min < mIntendedWakeupTime) { - if (ATRACE_ENABLED() && nextWakeupName && targetVsync) { - ftl::Concat trace(ftl::truncated<5>(*nextWakeupName), " alarm in ", ns2us(*min - now), - "us; VSYNC in ", ns2us(*targetVsync - now), "us"); - ATRACE_NAME(trace.c_str()); - } setTimer(*min, now); } else { ATRACE_NAME("cancel timer"); @@ -316,6 +343,7 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } void VSyncDispatchTimerQueue::timerCallback() { + ATRACE_CALL(); struct Invocation { std::shared_ptr callback; nsecs_t vsyncTimestamp; @@ -338,8 +366,9 @@ void VSyncDispatchTimerQueue::timerCallback() { continue; } - auto const readyTime = callback->readyTime(); + traceEntry(*callback, now); + auto const readyTime = callback->readyTime(); auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast(0)); if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) { callback->executing(); @@ -353,6 +382,8 @@ void VSyncDispatchTimerQueue::timerCallback() { } for (auto const& invocation : invocations) { + ftl::Concat trace(ftl::truncated<5>(invocation.callback->name())); + ATRACE_FORMAT("%s: %s", __func__, trace.c_str()); invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp, invocation.deadlineTimestamp); } diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h index 252c09ce53..e4ddc03480 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h @@ -91,8 +91,8 @@ private: }; nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const; - ArmingInfo update(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, - std::optional) const; + ArmingInfo getArmedInfo(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, + std::optional) const; const std::string mName; const VSyncDispatch::Callback mCallback; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 8697696915..2f9dfeaff7 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -45,11 +45,35 @@ using base::StringAppendF; static auto constexpr kMaxPercent = 100u; +namespace { +nsecs_t getVsyncFixup(VSyncPredictor::Model model, Period minFramePeriod, nsecs_t vsyncTime, + std::optional lastVsyncOpt) { + const auto threshold = model.slope / 2; + + if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) { + const auto vsyncDiff = vsyncTime - *lastVsyncOpt; + if (vsyncDiff >= threshold && vsyncDiff <= minFramePeriod.ns() - threshold) { + const auto vsyncFixup = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime; + ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f from prev. " + "adjust by %.2f", + static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, + static_cast(vsyncTime - *lastVsyncOpt) / 1e6f, + static_cast(vsyncFixup) / 1e6f); + return vsyncFixup; + } + } + + return 0; +} +} // namespace + VSyncPredictor::~VSyncPredictor() = default; -VSyncPredictor::VSyncPredictor(ftl::NonNull modePtr, size_t historySize, - size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) - : mId(modePtr->getPhysicalDisplayId()), +VSyncPredictor::VSyncPredictor(std::unique_ptr clock, ftl::NonNull modePtr, + size_t historySize, size_t minimumSamplesForPrediction, + uint32_t outlierTolerancePercent) + : mClock(std::move(clock)), + mId(modePtr->getPhysicalDisplayId()), mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), @@ -147,7 +171,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { mKnownTimestamp = timestamp; } ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago", - (systemTime() - *mKnownTimestamp) / 1e6f); + (mClock->now() - *mKnownTimestamp) / 1e6f); return false; } @@ -250,17 +274,6 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { return true; } -auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence { - const auto vsync = snapToVsync(timestamp); - if (!mLastVsyncSequence) return {vsync, 0}; - - const auto [slope, _] = getVSyncPredictionModelLocked(); - const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; - const auto vsyncSequence = lastVsyncSequence + - static_cast(std::round((vsync - lastVsyncTime) / static_cast(slope))); - return {vsync, vsyncSequence}; -} - nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { auto const [slope, intercept] = getVSyncPredictionModelLocked(); @@ -298,51 +311,32 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { } nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt) const { + std::optional lastVsyncOpt) { ATRACE_CALL(); std::lock_guard lock(mMutex); - const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; - const auto threshold = currentPeriod / 2; - const auto minFramePeriod = minFramePeriodLocked().ns(); - const auto lastFrameMissed = - lastVsyncOpt && std::abs(*lastVsyncOpt - mLastMissedVsync.ns()) < threshold; - const nsecs_t baseTime = - FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt - ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold) - : timePoint; - return snapToVsyncAlignedWithRenderRate(baseTime); -} - -nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const { - // update the mLastVsyncSequence for reference point - mLastVsyncSequence = getVsyncSequenceLocked(timePoint); - const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int { - if (!mRenderRateOpt) return 0; - const auto divisor = - RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()), - *mRenderRateOpt); - if (divisor <= 1) return 0; + const auto now = TimePoint::fromNs(mClock->now()); + purgeTimelines(now); - int mod = mLastVsyncSequence->seq % divisor; - if (mod == 0) return 0; - - // This is actually a bug fix, but guarded with vrr_config since we found it with this - // config - if (FlagManager::getInstance().vrr_config()) { - if (mod < 0) mod += divisor; + std::optional vsyncOpt; + for (auto& timeline : mTimelines) { + vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(getVSyncPredictionModelLocked(), + minFramePeriodLocked(), + snapToVsync(timePoint), mMissedVsync, + lastVsyncOpt); + if (vsyncOpt) { + break; } + } + LOG_ALWAYS_FATAL_IF(!vsyncOpt); - return divisor - mod; - }(); - - if (renderRatePhase == 0) { - return mLastVsyncSequence->vsyncTime; + if (*vsyncOpt > mLastCommittedVsync) { + mLastCommittedVsync = *vsyncOpt; + ATRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms", + float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f); } - auto const [slope, intercept] = getVSyncPredictionModelLocked(); - const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; - return snapToVsync(approximateNextVsync - slope / 2); + return vsyncOpt->ns(); } /* @@ -353,32 +347,28 @@ nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) cons * isVSyncInPhase(33.3, 30) = false * isVSyncInPhase(50.0, 30) = true */ -bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { +bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) { + if (timePoint == 0) { + return true; + } + std::lock_guard lock(mMutex); - const auto divisor = - RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()), - frameRate); - return isVSyncInPhaseLocked(timePoint, static_cast(divisor)); -} + const auto model = getVSyncPredictionModelLocked(); + const nsecs_t period = model.slope; + const nsecs_t justBeforeTimePoint = timePoint - period / 2; + const auto now = TimePoint::fromNs(mClock->now()); + const auto vsync = snapToVsync(justBeforeTimePoint); -bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const { - const TimePoint now = TimePoint::now(); - const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float { - return ticks(TimePoint::fromNs(timePoint) - now); - }; - ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint), - divisor); + purgeTimelines(now); - if (divisor <= 1 || timePoint == 0) { - return true; + for (auto& timeline : mTimelines) { + if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) { + return timeline.isVSyncInPhase(model, vsync, frameRate); + } } - const nsecs_t period = mRateMap[idealPeriod()].slope; - const nsecs_t justBeforeTimePoint = timePoint - period / 2; - const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint); - ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64, - getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq); - return vsyncSequence.seq % divisor == 0; + // The last timeline should always be valid + return mTimelines.back().isVSyncInPhase(model, vsync, frameRate); } void VSyncPredictor::setRenderRate(Fps renderRate) { @@ -386,6 +376,9 @@ void VSyncPredictor::setRenderRate(Fps renderRate) { ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str()); std::lock_guard lock(mMutex); mRenderRateOpt = renderRate; + mTimelines.back().freeze(TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2)); + mTimelines.emplace_back(mIdealPeriod, renderRate); + purgeTimelines(TimePoint::fromNs(mClock->now())); } void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { @@ -415,8 +408,9 @@ void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { clearTimestamps(); } -void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, - TimePoint lastConfirmedPresentTime) { +Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, + TimePoint lastConfirmedPresentTime) { + ATRACE_CALL(); const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; const auto threshold = currentPeriod / 2; const auto minFramePeriod = minFramePeriodLocked().ns(); @@ -442,17 +436,20 @@ void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, if (!mPastExpectedPresentTimes.empty()) { const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime); if (phase > 0ns) { - if (mLastVsyncSequence) { - mLastVsyncSequence->vsyncTime += phase.ns(); + for (auto& timeline : mTimelines) { + timeline.shiftVsyncSequence(phase); } mPastExpectedPresentTimes.clear(); + return phase; } } + + return 0ns; } void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) { - ATRACE_CALL(); + ATRACE_NAME("VSyncPredictor::onFrameBegin"); std::lock_guard lock(mMutex); if (!mDisplayModePtr->getVrrConfig()) return; @@ -482,11 +479,14 @@ void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, } } - ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); + const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); + if (phase > 0ns) { + mMissedVsync = {expectedPresentTime, minFramePeriodLocked()}; + } } void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) { - ATRACE_CALL(); + ATRACE_NAME("VSyncPredictor::onFrameMissed"); std::lock_guard lock(mMutex); if (!mDisplayModePtr->getVrrConfig()) return; @@ -496,14 +496,15 @@ void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) { const auto lastConfirmedPresentTime = TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod); - ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); - mLastMissedVsync = expectedPresentTime; + const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); + if (phase > 0ns) { + mMissedVsync = {expectedPresentTime, Duration::fromNs(0)}; + } } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { std::lock_guard lock(mMutex); - const auto model = VSyncPredictor::getVSyncPredictionModelLocked(); - return {model.slope, model.intercept}; + return VSyncPredictor::getVSyncPredictionModelLocked(); } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const { @@ -524,6 +525,11 @@ void VSyncPredictor::clearTimestamps() { mTimestamps.clear(); mLastTimestampIndex = 0; } + + mTimelines.clear(); + mLastCommittedVsync = TimePoint::fromNs(0); + mIdealPeriod = Period::fromNs(idealPeriod()); + mTimelines.emplace_back(mIdealPeriod, mRenderRateOpt); } bool VSyncPredictor::needsMoreSamples() const { @@ -547,6 +553,130 @@ void VSyncPredictor::dump(std::string& result) const { period / 1e6f, periodInterceptTuple.slope / 1e6f, periodInterceptTuple.intercept); } + StringAppendF(&result, "\tmTimelines.size()=%zu\n", mTimelines.size()); +} + +void VSyncPredictor::purgeTimelines(android::TimePoint now) { + while (mTimelines.size() > 1) { + const auto validUntilOpt = mTimelines.front().validUntil(); + if (validUntilOpt && *validUntilOpt < now) { + mTimelines.pop_front(); + } else { + break; + } + } + LOG_ALWAYS_FATAL_IF(mTimelines.empty()); + LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value()); +} + +VSyncPredictor::VsyncTimeline::VsyncTimeline(Period idealPeriod, std::optional renderRateOpt) + : mIdealPeriod(idealPeriod), mRenderRateOpt(renderRateOpt) {} + +void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) { + LOG_ALWAYS_FATAL_IF(mValidUntil.has_value()); + ATRACE_FORMAT_INSTANT("renderRate %s valid for %.2f", + mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA", + float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f); + mValidUntil = lastVsync; +} + +std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom( + Model model, Period minFramePeriod, nsecs_t vsync, MissedVsync missedVsync, + std::optional lastVsyncOpt) { + ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA"); + + const auto threshold = model.slope / 2; + const auto lastFrameMissed = + lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold; + nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync); + nsecs_t vsyncFixupTime = 0; + if (FlagManager::getInstance().vrr_config() && lastFrameMissed) { + vsyncTime += missedVsync.fixup.ns(); + ATRACE_FORMAT_INSTANT("lastFrameMissed"); + } else { + vsyncFixupTime = getVsyncFixup(model, minFramePeriod, vsyncTime, lastVsyncOpt); + vsyncTime += vsyncFixupTime; + } + + ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f); + if (mValidUntil && vsyncTime > mValidUntil->ns()) { + ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f", + static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f); + return std::nullopt; + } + + if (vsyncFixupTime > 0) { + shiftVsyncSequence(Duration::fromNs(vsyncFixupTime)); + } + + return TimePoint::fromNs(vsyncTime); +} + +auto VSyncPredictor::VsyncTimeline::getVsyncSequenceLocked(Model model, nsecs_t vsync) + -> VsyncSequence { + if (!mLastVsyncSequence) return {vsync, 0}; + + const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; + const auto vsyncSequence = lastVsyncSequence + + static_cast(std::round((vsync - lastVsyncTime) / + static_cast(model.slope))); + return {vsync, vsyncSequence}; +} + +nsecs_t VSyncPredictor::VsyncTimeline::snapToVsyncAlignedWithRenderRate(Model model, + nsecs_t vsync) { + // update the mLastVsyncSequence for reference point + mLastVsyncSequence = getVsyncSequenceLocked(model, vsync); + + const auto renderRatePhase = [&]() -> int { + if (!mRenderRateOpt) return 0; + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod.ns()), + *mRenderRateOpt); + if (divisor <= 1) return 0; + + int mod = mLastVsyncSequence->seq % divisor; + if (mod == 0) return 0; + + // This is actually a bug fix, but guarded with vrr_config since we found it with this + // config + if (FlagManager::getInstance().vrr_config()) { + if (mod < 0) mod += divisor; + } + + return divisor - mod; + }(); + + if (renderRatePhase == 0) { + return mLastVsyncSequence->vsyncTime; + } + + return mLastVsyncSequence->vsyncTime + model.slope * renderRatePhase; +} + +bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, Fps frameRate) { + const auto getVsyncIn = [](TimePoint now, nsecs_t timePoint) -> float { + return ticks(TimePoint::fromNs(timePoint) - now); + }; + + Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns()); + const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate); + const auto now = TimePoint::now(); + + if (divisor <= 1) { + return true; + } + const auto vsyncSequence = getVsyncSequenceLocked(model, vsync); + ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu", + getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor); + return vsyncSequence.seq % divisor == 0; +} + +void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) { + if (mLastVsyncSequence) { + ATRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast(phase.ns()) / 1e6f); + mLastVsyncSequence->vsyncTime += phase.ns(); + } } } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 8fd7e6046d..c175765485 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -22,6 +22,7 @@ #include #include +#include #include #include "VSyncTracker.h" @@ -31,6 +32,7 @@ namespace android::scheduler { class VSyncPredictor : public VSyncTracker { public: /* + * \param [in] Clock The clock abstraction. Useful for unit tests. * \param [in] PhysicalDisplayid The display this corresponds to. * \param [in] modePtr The initial display mode * \param [in] historySize The internal amount of entries to store in the model. @@ -38,13 +40,13 @@ public: * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter * samples that fall outlierTolerancePercent from an anticipated vsync event. */ - VSyncPredictor(ftl::NonNull modePtr, size_t historySize, + VSyncPredictor(std::unique_ptr, ftl::NonNull modePtr, size_t historySize, size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent); ~VSyncPredictor(); bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex); nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt = {}) const final + std::optional lastVsyncOpt = {}) final EXCLUDES(mMutex); nsecs_t currentPeriod() const final EXCLUDES(mMutex); Period minFramePeriod() const final EXCLUDES(mMutex); @@ -62,7 +64,7 @@ public: VSyncPredictor::Model getVSyncPredictionModel() const EXCLUDES(mMutex); - bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); + bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) final EXCLUDES(mMutex); void setDisplayModePtr(ftl::NonNull) final EXCLUDES(mMutex); @@ -75,10 +77,42 @@ public: void dump(std::string& result) const final EXCLUDES(mMutex); private: + struct VsyncSequence { + nsecs_t vsyncTime; + int64_t seq; + }; + + struct MissedVsync { + TimePoint vsync; + Duration fixup = Duration::fromNs(0); + }; + + class VsyncTimeline { + public: + VsyncTimeline(Period idealPeriod, std::optional renderRateOpt); + std::optional nextAnticipatedVSyncTimeFrom( + Model model, Period minFramePeriod, nsecs_t vsyncTime, MissedVsync lastMissedVsync, + std::optional lastVsyncOpt = {}); + void freeze(TimePoint lastVsync); + std::optional validUntil() const { return mValidUntil; } + bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate); + void shiftVsyncSequence(Duration phase); + + private: + nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync); + VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync); + + const Period mIdealPeriod = Duration::fromNs(0); + const std::optional mRenderRateOpt; + std::optional mValidUntil; + std::optional mLastVsyncSequence; + }; + VSyncPredictor(VSyncPredictor const&) = delete; VSyncPredictor& operator=(VSyncPredictor const&) = delete; void clearTimestamps() REQUIRES(mMutex); + const std::unique_ptr mClock; const PhysicalDisplayId mId; inline void traceInt64If(const char* name, int64_t value) const; @@ -88,16 +122,10 @@ private: bool validate(nsecs_t timestamp) const REQUIRES(mMutex); Model getVSyncPredictionModelLocked() const REQUIRES(mMutex); nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex); - nsecs_t snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const REQUIRES(mMutex); - bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex); Period minFramePeriodLocked() const REQUIRES(mMutex); - void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); + Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); + void purgeTimelines(android::TimePoint now) REQUIRES(mMutex); - struct VsyncSequence { - nsecs_t vsyncTime; - int64_t seq; - }; - VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex); nsecs_t idealPeriod() const REQUIRES(mMutex); bool const mTraceOn; @@ -115,13 +143,15 @@ private: std::vector mTimestamps GUARDED_BY(mMutex); ftl::NonNull mDisplayModePtr GUARDED_BY(mMutex); - std::optional mRenderRateOpt GUARDED_BY(mMutex); - - mutable std::optional mLastVsyncSequence GUARDED_BY(mMutex); std::deque mPastExpectedPresentTimes GUARDED_BY(mMutex); - TimePoint mLastMissedVsync GUARDED_BY(mMutex); + MissedVsync mMissedVsync GUARDED_BY(mMutex); + + std::deque mTimelines GUARDED_BY(mMutex); + TimePoint mLastCommittedVsync GUARDED_BY(mMutex) = TimePoint::fromNs(0); + Period mIdealPeriod GUARDED_BY(mMutex) = Duration::fromNs(0); + std::optional mRenderRateOpt GUARDED_BY(mMutex); }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 37bd4b4977..1e55a87254 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -56,8 +56,8 @@ public: * and avoid crossing the minimal frame period of a VRR display. * \return A prediction of the timestamp of a vsync event. */ - virtual nsecs_t nextAnticipatedVSyncTimeFrom( - nsecs_t timePoint, std::optional lastVsyncOpt = {}) const = 0; + virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, + std::optional lastVsyncOpt = {}) = 0; /* * The current period of the vsync signal. @@ -82,7 +82,7 @@ public: * \param [in] timePoint A vsync timestamp * \param [in] frameRate The frame rate to check for */ - virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; + virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) = 0; /* * Sets the active mode of the display which includes the vsync period and other VRR attributes. diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 001938c756..2fa3318560 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -120,8 +120,8 @@ VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull(modePtr, kHistorySize, kMinSamplesForPrediction, - kDiscardOutlierPercent); + return std::make_unique(std::make_unique(), modePtr, kHistorySize, + kMinSamplesForPrediction, kDiscardOutlierPercent); } VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) { diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 85cd3e7c31..881d6789b2 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -81,7 +81,7 @@ public: bool addResyncSample(TimePoint timestamp, ftl::Optional hwcVsyncPeriod); // TODO(b/185535769): Hide behind API. - const VsyncTracker& getTracker() const { return *mTracker; } + VsyncTracker& getTracker() const { return *mTracker; } VsyncTracker& getTracker() { return *mTracker; } VsyncController& getController() { return *mController; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cf5f55d7bd..9e131447a5 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4702,7 +4702,14 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin return TransactionReadiness::NotReady; } - if (!mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) { + const auto vsyncId = VsyncId{transaction.frameTimelineInfo.vsyncId}; + + // Transactions with VsyncId are already throttled by the vsyncId (i.e. Choreographer issued + // the vsyncId according to the frame rate override cadence) so we shouldn't throttle again + // when applying the transaction. Otherwise we might throttle older transactions + // incorrectly as the frame rate of SF changed before it drained the older transactions. + if (ftl::to_underlying(vsyncId) == FrameTimelineInfo::INVALID_VSYNC_ID && + !mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) { ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime, transaction.originUid); return TransactionReadiness::NotReady; @@ -4710,8 +4717,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin // If the client didn't specify desiredPresentTime, use the vsyncId to determine the // expected present time of this transaction. - if (transaction.isAutoTimestamp && - frameIsEarly(expectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) { + if (transaction.isAutoTimestamp && frameIsEarly(expectedPresentTime, vsyncId)) { ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64, transaction.frameTimelineInfo.vsyncId, expectedPresentTime); return TransactionReadiness::NotReady; diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 10e2220ece..049b0923a6 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -25,6 +25,7 @@ #include "Scheduler/EventThread.h" #include "Scheduler/RefreshRateSelector.h" #include "Scheduler/VSyncPredictor.h" +#include "Scheduler/VSyncReactor.h" #include "TestableScheduler.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockDisplayMode.h" @@ -563,7 +564,8 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { hal::VrrConfig{.minFrameIntervalNs = static_cast( frameRate.getPeriodNsecs())})); std::shared_ptr vrrTracker = - std::make_shared(kMode, kHistorySize, kMinimumSamplesForPrediction, + std::make_shared(std::make_unique(), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent); std::shared_ptr vrrSelectorPtr = std::make_shared(makeModes(kMode), kMode->getId()); @@ -578,6 +580,8 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); vrrTracker->addVsyncTimestamp(0); + // Set 1000 as vsync seq #0 + vrrTracker->nextAnticipatedVSyncTimeFrom(700); EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), @@ -587,7 +591,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { TimePoint::fromNs(2000))); // Not crossing the min frame period - EXPECT_EQ(Fps::fromPeriodNsecs(1500), + EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2500))); // Change render rate @@ -595,6 +599,9 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); + // Set 2000 as vsync seq #0 + vrrTracker->nextAnticipatedVSyncTimeFrom(1700); + EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2000))); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index d891008683..c22deaba72 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -48,7 +48,7 @@ public: Period minFramePeriod() const final { return Period::fromNs(currentPeriod()); } void resetModel() final {} bool needsMoreSamples() const final { return false; } - bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } + bool isVSyncInPhase(nsecs_t, Fps) final { return false; } void setDisplayModePtr(ftl::NonNull) final {} void setRenderRate(Fps) final {} void onFrameBegin(TimePoint, TimePoint) final {} @@ -64,7 +64,7 @@ class FixedRateIdealStubTracker : public StubTracker { public: FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional) const final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional) final { auto const floor = timePoint % mPeriod; if (floor == 0) { return timePoint; @@ -77,7 +77,7 @@ class VRRStubTracker : public StubTracker { public: VRRStubTracker(nsecs_t period) : StubTracker(period) {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional) const final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional) final { std::lock_guard lock(mMutex); auto const normalized_to_base = time_point - mBase; auto const floor = (normalized_to_base) % mPeriod; diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index b9f3d70c6b..6b9ea56062 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -75,6 +75,28 @@ ftl::NonNull displayMode(nsecs_t period) { return ftl::as_non_null(createDisplayMode(DisplayModeId(0), refreshRate, kGroup, kResolution, DEFAULT_DISPLAY_ID)); } + +class TestClock : public Clock { +public: + TestClock() = default; + + nsecs_t now() const override { return mNow; } + void setNow(nsecs_t now) { mNow = now; } + +private: + nsecs_t mNow = 0; +}; + +class ClockWrapper : public Clock { +public: + ClockWrapper(std::shared_ptr const& clock) : mClock(clock) {} + + nsecs_t now() const { return mClock->now(); } + +private: + std::shared_ptr const mClock; +}; + } // namespace struct VSyncPredictorTest : testing::Test { @@ -86,8 +108,10 @@ struct VSyncPredictorTest : testing::Test { static constexpr size_t kOutlierTolerancePercent = 25; static constexpr nsecs_t mMaxRoundingError = 100; - VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction, - kOutlierTolerancePercent}; + std::shared_ptr mClock{std::make_shared()}; + + VSyncPredictor tracker{std::make_unique(mClock), mMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; }; TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) { @@ -408,7 +432,8 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) { // See b/151146131 TEST_F(VSyncPredictorTest, hasEnoughPrecision) { const auto mode = displayMode(mPeriod); - VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor tracker{std::make_unique(mClock), mode, 20, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; std::vector const simulatedVsyncs{840873348817, 840890049444, 840906762675, 840923581635, 840940161584, 840956868096, 840973702473, 840990256277, 841007116851, @@ -606,35 +631,6 @@ TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); } -TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) { - auto last = mNow; - for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); - mNow += mPeriod; - last = mNow; - tracker.addVsyncTimestamp(mNow); - } - - const auto refreshRate = Fps::fromPeriodNsecs(mPeriod); - - tracker.setRenderRate(refreshRate / 4); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod)); - - tracker.setRenderRate(refreshRate / 2); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod)); - - tracker.setRenderRate(refreshRate / 6); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod)); -} - TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { @@ -670,8 +666,8 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { .setVrrConfig(std::move(vrrConfig)) .build()); - VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction, - kOutlierTolerancePercent}; + VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; vrrTracker.setRenderRate(minFrameRate); vrrTracker.addVsyncTimestamp(0); @@ -687,7 +683,44 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { vrrTracker.onFrameMissed(TimePoint::fromNs(4500)); EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500)); EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + + vrrTracker.onFrameBegin(TimePoint::fromNs(7000), TimePoint::fromNs(6500)); + EXPECT_EQ(10500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000)); +} + +TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) { + tracker.addVsyncTimestamp(1000); + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); + + tracker.setRenderRate(Fps::fromPeriodNsecs(2000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); + + tracker.setRenderRate(Fps::fromPeriodNsecs(3000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(11001), Eq(14000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(12001), Eq(14000)); + + // Check the purge logic works + mClock->setNow(20000); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(2000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(8000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000)); } + } // namespace android::scheduler // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index 3870983133..6d10a5cc5d 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -29,12 +29,12 @@ public: MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override)); MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional), - (const, override)); + (override)); MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override)); MOCK_METHOD(Period, minFramePeriod, (), (const, override)); MOCK_METHOD(void, resetModel, (), (override)); MOCK_METHOD(bool, needsMoreSamples, (), (const, override)); - MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (const, override)); + MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override)); MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull), (override)); MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override)); -- cgit v1.2.3-59-g8ed1b