diff options
| author | 2024-02-28 05:02:40 +0000 | |
|---|---|---|
| committer | 2024-02-28 05:02:40 +0000 | |
| commit | e4ddd10846cf2fcf7758a52cbcc04d1b2e3ca6a1 (patch) | |
| tree | 696daf685cfdacafea3532c55c4e0c473e3c1387 | |
| parent | 55397a309fc80bd4a82efb2c96b86a691704de5e (diff) | |
| parent | b6c7f880460c81a6ce49ccb3334e2d2e1e020f81 (diff) | |
Merge "SF: Introduce VsyncTimeline to VsyncPredictor" into main
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 85bdc1c89c..46a2259b3c 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<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)); |