Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b6c7f880 authored by Ady Abraham's avatar Ady Abraham
Browse files

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
parent 50229b4a
Loading
Loading
Loading
Loading
+51 −20
Original line number Diff line number Diff line
@@ -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,
auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t now,
                                                VSyncDispatch::ScheduleTiming timing,
                                          std::optional<ArmingInfo> armedInfo) const -> ArmingInfo {
                                                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;

    if (FlagManager::getInstance().dont_skip_on_early_ro()) {
        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)) {
        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);
    }
+2 −2
Original line number Diff line number Diff line
@@ -91,7 +91,7 @@ private:
    };

    nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const;
    ArmingInfo update(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming,
    ArmingInfo getArmedInfo(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming,
                            std::optional<ArmingInfo>) const;

    const std::string mName;
+214 −84
Original line number Diff line number Diff line
@@ -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;

        int mod = mLastVsyncSequence->seq % divisor;
        if (mod == 0) return 0;
    const auto now = TimePoint::fromNs(mClock->now());
    purgeTimelines(now);

        // 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 {
    std::lock_guard lock(mMutex);
    const auto divisor =
            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
                                                     frameRate);
    return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(divisor));
bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) {
    if (timePoint == 0) {
        return true;
    }

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);
    std::lock_guard lock(mMutex);
    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);

    if (divisor <= 1 || timePoint == 0) {
        return true;
    purgeTimelines(now);

    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,
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
+45 −15

File changed.

Preview size limit exceeded, changes collapsed.

+3 −3

File changed.

Preview size limit exceeded, changes collapsed.

Loading