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

Commit 6569cd19 authored by ramindani's avatar ramindani
Browse files

SF: Filter the VSyncs below 200ms from VSyncPredictor

When adding the vsync timestamp ignore the timestamps
that are older than 200ms, old vsyncs can become outdated
and causes the intercept and anticipatedPeriod to be incorrect
from the current values.

Bug: 411593328
Bug: 385059265
Flag: com.android.graphics.surfaceflinger.flags.vsync_predictor_predicts_within_threshold
Test: atest VSyncPredictorTest
Change-Id: Idac39c97a87ba4c21b6edcebaa23a1d78491e9b1
parent fe2ccd6c
Loading
Loading
Loading
Loading
+80 −18
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ VSyncPredictor::VSyncPredictor(std::unique_ptr<Clock> clock, ftl::NonNull<Displa
        kHistorySize(historySize),
        kMinimumSamplesForPrediction(minimumSamplesForPrediction),
        kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
        kPredictorThreshold(ms2ns(200)),
        mDisplayModePtr(modePtr),
        mNumVsyncsForFrame(numVsyncsPerFrame(mDisplayModePtr)) {
    resetModel();
@@ -91,7 +92,8 @@ nsecs_t VSyncPredictor::idealPeriod() const {

bool VSyncPredictor::validate(nsecs_t timestamp) const {
    SFTRACE_CALL();
    if (mLastTimestampIndex < 0 || mTimestamps.empty()) {
    if (mLastTimestampIndex < 0 || mTimestamps.empty() ||
        getSampleSizeAndOldestVsync(timestamp).first == 0) {
        SFTRACE_INSTANT("timestamp valid (first)");
        return true;
    }
@@ -110,8 +112,16 @@ bool VSyncPredictor::validate(nsecs_t timestamp) const {
    }

    const auto iter = std::min_element(mTimestamps.begin(), mTimestamps.end(),
                                       [timestamp](nsecs_t a, nsecs_t b) {
                                           return std::abs(timestamp - a) < std::abs(timestamp - b);
                                       [timestamp, this](nsecs_t a, nsecs_t b) {
                                           nsecs_t diffA = std::abs(timestamp - a);
                                           nsecs_t diffB = std::abs(timestamp - b);
                                           bool withinThresholdA = diffA <= kPredictorThreshold;
                                           bool withinThresholdB = diffB <= kPredictorThreshold;

                                           if (withinThresholdA != withinThresholdB) {
                                               return withinThresholdA;
                                           }
                                           return diffA < diffB;
                                       });
    const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod();
    if (distancePercent < kOutlierTolerancePercent) {
@@ -180,8 +190,11 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {

    traceInt64If("VSP-ts", timestamp);

    const size_t numSamples = mTimestamps.size();
    if (numSamples < kMinimumSamplesForPrediction) {
    const auto [numSamples, oldestTs] = getSampleSizeAndOldestVsync(timestamp);
    traceInt64If("VSP-numSamples", static_cast<int64_t>(numSamples));
    mOldestVsync = oldestTs;
    const auto minNumSamples = getMinSamplesRequiredForPrediction();
    if (numSamples < minNumSamples) {
        mRateMap[idealPeriod()] = {idealPeriod(), 0};
        return true;
    }
@@ -205,12 +218,12 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
    std::vector<nsecs_t> ordinals(numSamples);

    // Normalizing to the oldest timestamp cuts down on error in calculating the intercept.
    const auto oldestTS = *std::min_element(mTimestamps.begin(), mTimestamps.end());
    auto it = mRateMap.find(idealPeriod());
    // Calculated slope over the period of time can become outdated as the new timestamps are
    // stored. Using idealPeriod instead provides a rate which is valid at all the times.
    auto const currentPeriod =
            mDisplayModePtr->getVrrConfig() && FlagManager::getInstance().vsync_predictor_recovery()
    auto const currentPeriod = mDisplayModePtr->getVrrConfig() &&
                    (FlagManager::getInstance().vsync_predictor_recovery() ||
                     FlagManager::getInstance().vsync_predictor_predicts_within_threshold())
            ? idealPeriod()
            : it->second.slope;

@@ -220,18 +233,25 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {

    nsecs_t meanTS = 0;
    nsecs_t meanOrdinal = 0;

    for (size_t i = 0; i < numSamples; i++) {
        const auto timestamp = mTimestamps[i] - oldestTS;
        vsyncTS[i] = timestamp;
        meanTS += timestamp;
    size_t vsyncIndex = 0;
    for (size_t i = 0; i < mTimestamps.size(); i++) {
        // Timestamp outside the threshold are not used for the calculation as the older
        // timestamps accumulate drifts and causes the anticipatedPeriod and intercept to
        // reflect that drift which no longer aligns with the current system state.
        if (!isVsyncWithinThreshold(timestamp, mTimestamps[i])) continue;
        const auto ts = mTimestamps[i] - oldestTs;
        vsyncTS[vsyncIndex] = ts;
        meanTS += ts;

        const auto ordinal = currentPeriod == 0
                ? 0
                : (vsyncTS[i] + currentPeriod / 2) / currentPeriod * kScalingFactor;
        ordinals[i] = ordinal;
                : (vsyncTS[vsyncIndex] + currentPeriod / 2) / currentPeriod * kScalingFactor;
        ordinals[vsyncIndex] = ordinal;
        meanOrdinal += ordinal;
        ++vsyncIndex;
    }
    LOG_FATAL_IF(vsyncIndex != numSamples,
                 "samples size does not match with the number of vsyncs in window for prediction");

    meanTS /= numSamples;
    meanOrdinal /= numSamples;
@@ -284,9 +304,11 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const {
        return knownTimestamp + numPeriodsOut * idealPeriod();
    }

    auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end());

    // See b/145667109, the ordinal calculation must take into account the intercept.
    const auto oldest = mDisplayModePtr->getVrrConfig() &&
                    FlagManager::getInstance().vsync_predictor_predicts_within_threshold()
            ? mOldestVsync
            : *std::min_element(mTimestamps.begin(), mTimestamps.end());
    auto const zeroPoint = oldest + intercept;
    auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
    auto const prediction = (ordinalRequest * slope) + intercept + oldest;
@@ -310,6 +332,46 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const {
    return prediction;
}

bool VSyncPredictor::isVsyncWithinThreshold(nsecs_t currentTimestamp,
                                            nsecs_t previousTimestamp) const {
    if (FlagManager::getInstance().vsync_predictor_predicts_within_threshold() &&
        mDisplayModePtr->getVrrConfig()) {
        return currentTimestamp - previousTimestamp <= kPredictorThreshold;
    }
    return true;
}

std::pair<size_t, nsecs_t> VSyncPredictor::getSampleSizeAndOldestVsync(
        nsecs_t currentTimestamp) const {
    if (FlagManager::getInstance().vsync_predictor_predicts_within_threshold() &&
        mDisplayModePtr->getVrrConfig()) {
        size_t numSamples = 0;
        nsecs_t oldestTimestamp = currentTimestamp;
        for (auto vsync : mTimestamps) {
            if (isVsyncWithinThreshold(currentTimestamp, vsync)) {
                ++numSamples;
                if (vsync < oldestTimestamp) {
                    oldestTimestamp = vsync;
                }
            }
        }
        return {numSamples, oldestTimestamp};
    }
    return {mTimestamps.size(), *std::min_element(mTimestamps.begin(), mTimestamps.end())};
}

size_t VSyncPredictor::getMinSamplesRequiredForPrediction() const {
    if (FlagManager::getInstance().vsync_predictor_predicts_within_threshold() &&
        mDisplayModePtr->getVrrConfig() && mRenderRateOpt) {
        const size_t minimumSamplesForPrediction =
                std::max(static_cast<size_t>(kAbsoluteMinSamplesForPrediction),
                         static_cast<size_t>(kPredictorThreshold /
                                             mRenderRateOpt->getPeriodNsecs()));
        return std::min(kMinimumSamplesForPrediction, minimumSamplesForPrediction);
    }
    return kMinimumSamplesForPrediction;
}

nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
                                                     std::optional<nsecs_t> lastVsyncOpt) {
    SFTRACE_CALL();
@@ -601,7 +663,7 @@ void VSyncPredictor::clearTimestamps(bool clearTimelines) {

bool VSyncPredictor::needsMoreSamples() const {
    std::lock_guard lock(mMutex);
    return mTimestamps.size() < kMinimumSamplesForPrediction;
    return mTimestamps.size() < getMinSamplesRequiredForPrediction();
}

void VSyncPredictor::resetModel() {
+8 −1
Original line number Diff line number Diff line
@@ -147,17 +147,24 @@ private:
    Period minFramePeriodLocked() const REQUIRES(mMutex);
    Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
    void purgeTimelines(android::TimePoint now) REQUIRES(mMutex);
    bool isVsyncWithinThreshold(nsecs_t currentTimestamp, nsecs_t previousTimestamp) const
            REQUIRES(mMutex);
    std::pair<size_t, nsecs_t> getSampleSizeAndOldestVsync(nsecs_t currentTimestamp) const
            REQUIRES(mMutex);
    size_t getMinSamplesRequiredForPrediction() const REQUIRES(mMutex);

    nsecs_t idealPeriod() const REQUIRES(mMutex);

    bool const mTraceOn;
    size_t const kHistorySize;
    size_t const kMinimumSamplesForPrediction;
    static constexpr size_t kAbsoluteMinSamplesForPrediction = 3;
    size_t const kOutlierTolerancePercent;
    nsecs_t const kPredictorThreshold;
    std::mutex mutable mMutex;

    std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);

    nsecs_t mOldestVsync GUARDED_BY(mMutex);
    // Map between ideal vsync period and the calculated model
    std::unordered_map<nsecs_t, Model> mutable mRateMap GUARDED_BY(mMutex);

+43 −1
Original line number Diff line number Diff line
@@ -305,7 +305,7 @@ TEST_F(VSyncPredictorTest, againstOutliersDiscontinuous_500hzLowVariance) {
}

TEST_F(VSyncPredictorTest, recoverAfterDriftedVSyncAreReplacedWithCorrectVSync) {
    SET_FLAG_FOR_TEST(flags::vsync_predictor_recovery, true);
    SET_FLAG_FOR_TEST(flags::vsync_predictor_predicts_within_threshold, true);
    auto constexpr idealPeriodNs = 4166666;
    auto constexpr minFrameIntervalNs = 8333333;
    auto constexpr idealPeriod = Fps::fromPeriodNsecs(idealPeriodNs);
@@ -351,6 +351,48 @@ TEST_F(VSyncPredictorTest, recoverAfterDriftedVSyncAreReplacedWithCorrectVSync)
    EXPECT_THAT(slope, IsCloseTo(idealPeriodNs, mMaxRoundingError));
}

TEST_F(VSyncPredictorTest, vsyncsOutsideThresholdDoesNotCauseIncorrectPrediction) {
    SET_FLAG_FOR_TEST(flags::vsync_predictor_predicts_within_threshold, true);
    auto constexpr idealPeriodNs = 8'333'333;
    auto constexpr minFrameIntervalNs = 8'333'333;
    auto constexpr idealPeriod = Fps::fromPeriodNsecs(idealPeriodNs);
    auto constexpr minFrameRate = Fps::fromPeriodNsecs(minFrameIntervalNs);
    hal::VrrConfig vrrConfig{.minFrameIntervalNs = minFrameIntervalNs};
    ftl::NonNull<DisplayModePtr> mode =
            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), idealPeriod, vrrConfig));
    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), mode, /*kHistorySize*/ 20,
                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ true, /*frameRateOverrides*/ {});
    // Curated list of VSyncs that causes the VSync drift.
    std::vector<nsecs_t> const simulatedVsyncs{138174900755, 138191459948, 138208044322,
                                               138224542110, 138274263073, 138307385130,
                                               138340504271, 138373601849, 138406810105,
                                               138423328802, 138456671277, 138506075624,
                                               138539229192, 138555729687, 138572333412,
                                               138605437709, 138638546223, 138672005027,
                                               138704890886, 138737991954, 138771032579,
                                               138804248803, 138837327631, 138870443751,
                                               138903534923, 138936635912, 138969775912,
                                               139002829688, 139036008100};
    for (auto const timestamp : simulatedVsyncs) {
        vrrTracker.addVsyncTimestamp(timestamp);
    }
    auto model = vrrTracker.getVSyncPredictionModel();
    // slope would be 8278833 otherwise
    EXPECT_THAT(model.slope, IsCloseTo(8277270, mMaxRoundingError));
    // intercept would be 41881 otherwise
    EXPECT_THAT(model.intercept, IsCloseTo(-4026, mMaxRoundingError));
    EXPECT_FALSE(vrrTracker.needsMoreSamples());

    EXPECT_TRUE(vrrTracker.addVsyncTimestamp(139069133230));
    EXPECT_FALSE(vrrTracker.needsMoreSamples());
    model = vrrTracker.getVSyncPredictionModel();
    // slope would be 8309405 otherwise with just idealPeriod
    EXPECT_THAT(model.slope, IsCloseTo(8278712, mMaxRoundingError));
    // intercept would be -178060 otherwise
    EXPECT_THAT(model.intercept, IsCloseTo(-21585, mMaxRoundingError));
}

TEST_F(VSyncPredictorTest, handlesVsyncChange) {
    auto const fastPeriod = 100;
    auto const fastTimeBase = 100;