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

Commit 6db41fd6 authored by Ram Indani's avatar Ram Indani Committed by Android (Google) Code Review
Browse files

Merge "SF: Filter the VSyncs below 200ms from VSyncPredictor" into main

parents 6bbdcd82 6569cd19
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;