diff options
| author | 2024-03-05 01:32:49 +0000 | |
|---|---|---|
| committer | 2024-06-05 13:51:18 -0700 | |
| commit | 9a9ddeb08b9cfa31deee87c84864fef14163334e (patch) | |
| tree | 50aea0c25f2887695a1ac10a02ef2bdcff6f4397 | |
| parent | 34bda03840f7a9af3376b442dd5bea705bfdb343 (diff) | |
Revert^2 "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
Merged-In: Ic91d482593d6b978c28e3c6c0d19e2c055d5f149
Change-Id: Ic91d482593d6b978c28e3c6c0d19e2c055d5f149
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 <android-base/stringprintf.h> #include <ftl/concat.h> -#include <utils/Trace.h> +#include <gui/TraceUtils.h> #include <log/log_main.h> #include <scheduler/TimeKeeper.h> @@ -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<nsecs_t> 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<ArmingInfo> armedInfo) const -> ArmingInfo { +auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t now, + VSyncDispatch::ScheduleTiming timing, + std::optional<ArmingInfo> 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<nsecs_t> min; std::optional<nsecs_t> targetVsync; std::optional<std::string_view> 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<VSyncDispatchTimerQueueEntry> 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<nsecs_t>(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<ArmingInfo>) const; + ArmingInfo getArmedInfo(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, + std::optional<ArmingInfo>) 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<nsecs_t> 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<float>(vsyncTime - TimePoint::now().ns()) / 1e6f, + static_cast<float>(vsyncTime - *lastVsyncOpt) / 1e6f, + static_cast<float>(vsyncFixup) / 1e6f); + return vsyncFixup; + } + } + + return 0; +} +} // namespace + VSyncPredictor::~VSyncPredictor() = default; -VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize, - size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) - : mId(modePtr->getPhysicalDisplayId()), +VSyncPredictor::VSyncPredictor(std::unique_ptr<Clock> clock, ftl::NonNull<DisplayModePtr> 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<int64_t>(std::round((vsync - lastVsyncTime) / static_cast<float>(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<nsecs_t> lastVsyncOpt) const { + std::optional<nsecs_t> 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<TimePoint> 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<unsigned>(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<std::milli, float>(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<DisplayModePtr> modePtr) { @@ -415,8 +408,9 @@ void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> 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<Fps> 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<TimePoint> VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom( + Model model, Period minFramePeriod, nsecs_t vsync, MissedVsync missedVsync, + std::optional<nsecs_t> 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<float>(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<int64_t>(std::round((vsync - lastVsyncTime) / + static_cast<float>(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<std::milli, float>(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<float>(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 <vector> #include <android-base/thread_annotations.h> +#include <scheduler/TimeKeeper.h> #include <ui/DisplayId.h> #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<DisplayModePtr> modePtr, size_t historySize, + VSyncPredictor(std::unique_ptr<Clock>, ftl::NonNull<DisplayModePtr> 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<nsecs_t> lastVsyncOpt = {}) const final + std::optional<nsecs_t> 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<DisplayModePtr>) 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<Fps> renderRateOpt); + std::optional<TimePoint> nextAnticipatedVSyncTimeFrom( + Model model, Period minFramePeriod, nsecs_t vsyncTime, MissedVsync lastMissedVsync, + std::optional<nsecs_t> lastVsyncOpt = {}); + void freeze(TimePoint lastVsync); + std::optional<TimePoint> 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<Fps> mRenderRateOpt; + std::optional<TimePoint> mValidUntil; + std::optional<VsyncSequence> mLastVsyncSequence; + }; + VSyncPredictor(VSyncPredictor const&) = delete; VSyncPredictor& operator=(VSyncPredictor const&) = delete; void clearTimestamps() REQUIRES(mMutex); + const std::unique_ptr<Clock> 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<nsecs_t> mTimestamps GUARDED_BY(mMutex); ftl::NonNull<DisplayModePtr> mDisplayModePtr GUARDED_BY(mMutex); - std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex); - - mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex); std::deque<TimePoint> mPastExpectedPresentTimes GUARDED_BY(mMutex); - TimePoint mLastMissedVsync GUARDED_BY(mMutex); + MissedVsync mMissedVsync GUARDED_BY(mMutex); + + std::deque<VsyncTimeline> mTimelines GUARDED_BY(mMutex); + TimePoint mLastCommittedVsync GUARDED_BY(mMutex) = TimePoint::fromNs(0); + Period mIdealPeriod GUARDED_BY(mMutex) = Duration::fromNs(0); + std::optional<Fps> 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<nsecs_t> lastVsyncOpt = {}) const = 0; + virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, + std::optional<nsecs_t> 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<DisplayModeP constexpr size_t kMinSamplesForPrediction = 6; constexpr uint32_t kDiscardOutlierPercent = 20; - return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction, - kDiscardOutlierPercent); + return std::make_unique<VSyncPredictor>(std::make_unique<SystemClock>(), 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<Period> 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 0b3f1695c6..0f8e3bfb86 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4722,7 +4722,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; @@ -4730,8 +4737,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<int32_t>( frameRate.getPeriodNsecs())})); std::shared_ptr<VSyncPredictor> vrrTracker = - std::make_shared<VSyncPredictor>(kMode, kHistorySize, kMinimumSamplesForPrediction, + std::make_shared<VSyncPredictor>(std::make_unique<SystemClock>(), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent); std::shared_ptr<RefreshRateSelector> vrrSelectorPtr = std::make_shared<RefreshRateSelector>(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<DisplayModePtr>) 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<nsecs_t>) const final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t>) 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<nsecs_t>) const final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional<nsecs_t>) 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<DisplayModePtr> 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<Clock> const& clock) : mClock(clock) {} + + nsecs_t now() const { return mClock->now(); } + +private: + std::shared_ptr<Clock> 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<TestClock> mClock{std::make_shared<TestClock>()}; + + VSyncPredictor tracker{std::make_unique<ClockWrapper>(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<ClockWrapper>(mClock), mode, 20, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; std::vector<nsecs_t> 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<ClockWrapper>(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<nsecs_t>), - (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<DisplayModePtr>), (override)); MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override)); |