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

Commit 1678e2c1 authored by Kevin DuBois's avatar Kevin DuBois
Browse files

SF: add VSyncPredictor implementation

Adds an implementation of VSyncPredictor, an object that will
predict vsync events for multiple fixed-rate vsync systems.
The prediction is based on both the HWVsync signal and the
presentation fence timings for the VSyncDispatch object.

Bug: 140201379
Fixes: 140302888
Test: 13 new unit tests

Change-Id: I195902cc70561d028741d822e4001ad21ab391cf
parent cc27b50f
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -165,8 +165,9 @@ filegroup {
        "Scheduler/PhaseOffsets.cpp",
        "Scheduler/Scheduler.cpp",
        "Scheduler/SchedulerUtils.cpp",
        "Scheduler/VSyncDispatchTimerQueue.cpp",
        "Scheduler/Timer.cpp",
        "Scheduler/VSyncDispatchTimerQueue.cpp",
        "Scheduler/VSyncPredictor.cpp",
        "Scheduler/VSyncModulator.cpp",
        "StartPropertySetThread.cpp",
        "SurfaceFlinger.cpp",
+208 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define ATRACE_TAG ATRACE_TAG_GRAPHICS
//#define LOG_NDEBUG 0
#include "VSyncPredictor.h"
#include <android-base/logging.h>
#include <cutils/compiler.h>
#include <utils/Log.h>
#include <utils/Trace.h>
#include <algorithm>
#include <chrono>
#include "SchedulerUtils.h"

namespace android::scheduler {
static auto constexpr kNeedsSamplesTag = "SamplesRequested";
static auto constexpr kMaxPercent = 100u;

VSyncPredictor::~VSyncPredictor() = default;

VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize,
                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
      : kHistorySize(historySize),
        kMinimumSamplesForPrediction(minimumSamplesForPrediction),
        kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
        mIdealPeriod(idealPeriod) {
    mRateMap[mIdealPeriod] = {idealPeriod, 0};
}

inline size_t VSyncPredictor::next(int i) const {
    return (i + 1) % timestamps.size();
}

bool VSyncPredictor::validate(nsecs_t timestamp) const {
    if (lastTimestampIndex < 0 || timestamps.empty()) {
        return true;
    }

    auto const aValidTimestamp = timestamps[lastTimestampIndex];
    auto const percent = (timestamp - aValidTimestamp) % mIdealPeriod * kMaxPercent / mIdealPeriod;
    return percent < kOutlierTolerancePercent || percent > (kMaxPercent - kOutlierTolerancePercent);
}

void VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
    std::lock_guard<std::mutex> lk(mMutex);

    if (!validate(timestamp)) {
        ALOGW("timestamp was too far off the last known timestamp");
        return;
    }

    if (timestamps.size() != kHistorySize) {
        timestamps.push_back(timestamp);
        lastTimestampIndex = next(lastTimestampIndex);
    } else {
        lastTimestampIndex = next(lastTimestampIndex);
        timestamps[lastTimestampIndex] = timestamp;
    }

    if (timestamps.size() < kMinimumSamplesForPrediction) {
        mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
        return;
    }

    // This is a 'simple linear regression' calculation of Y over X, with Y being the
    // vsync timestamps, and X being the ordinal of vsync count.
    // The calculated slope is the vsync period.
    // Formula for reference:
    // Sigma_i: means sum over all timestamps.
    // mean(variable): statistical mean of variable.
    // X: snapped ordinal of the timestamp
    // Y: vsync timestamp
    //
    //         Sigma_i( (X_i - mean(X)) * (Y_i - mean(Y) )
    // slope = -------------------------------------------
    //         Sigma_i ( X_i - mean(X) ) ^ 2
    //
    // intercept = mean(Y) - slope * mean(X)
    //
    std::vector<nsecs_t> vsyncTS(timestamps.size());
    std::vector<nsecs_t> ordinals(timestamps.size());

    // normalizing to the oldest timestamp cuts down on error in calculating the intercept.
    auto const oldest_ts = *std::min_element(timestamps.begin(), timestamps.end());
    auto it = mRateMap.find(mIdealPeriod);
    auto const currentPeriod = std::get<0>(it->second);
    // TODO (b/144707443): its important that there's some precision in the mean of the ordinals
    //                     for the intercept calculation, so scale the ordinals by 10 to continue
    //                     fixed point calculation. Explore expanding
    //                     scheduler::utils::calculate_mean to have a fixed point fractional part.
    static constexpr int kScalingFactor = 10;

    for (auto i = 0u; i < timestamps.size(); i++) {
        vsyncTS[i] = timestamps[i] - oldest_ts;
        ordinals[i] = ((vsyncTS[i] + (currentPeriod / 2)) / currentPeriod) * kScalingFactor;
    }

    auto meanTS = scheduler::calculate_mean(vsyncTS);
    auto meanOrdinal = scheduler::calculate_mean(ordinals);
    for (auto i = 0; i < vsyncTS.size(); i++) {
        vsyncTS[i] -= meanTS;
        ordinals[i] -= meanOrdinal;
    }

    auto top = 0ll;
    auto bottom = 0ll;
    for (auto i = 0; i < vsyncTS.size(); i++) {
        top += vsyncTS[i] * ordinals[i];
        bottom += ordinals[i] * ordinals[i];
    }

    if (CC_UNLIKELY(bottom == 0)) {
        it->second = {mIdealPeriod, 0};
        return;
    }

    nsecs_t const anticipatedPeriod = top / bottom * kScalingFactor;
    nsecs_t const intercept = meanTS - (anticipatedPeriod * meanOrdinal / kScalingFactor);

    it->second = {anticipatedPeriod, intercept};

    ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp,
          anticipatedPeriod, intercept);
}

nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
    std::lock_guard<std::mutex> lk(mMutex);

    auto const [slope, intercept] = getVSyncPredictionModel(lk);

    if (timestamps.empty()) {
        auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
        auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
        return knownTimestamp + numPeriodsOut * mIdealPeriod;
    }

    auto const oldest = *std::min_element(timestamps.begin(), timestamps.end());
    auto const ordinalRequest = (timePoint - oldest + slope) / slope;
    auto const prediction = (ordinalRequest * slope) + intercept + oldest;

    ALOGV("prediction made from: %" PRId64 " prediction: %" PRId64 " (+%" PRId64 ") slope: %" PRId64
          " intercept: %" PRId64,
          timePoint, prediction, prediction - timePoint, slope, intercept);
    return prediction;
}

std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel() const {
    std::lock_guard<std::mutex> lk(mMutex);
    return VSyncPredictor::getVSyncPredictionModel(lk);
}

std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel(
        std::lock_guard<std::mutex> const&) const {
    return mRateMap.find(mIdealPeriod)->second;
}

void VSyncPredictor::setPeriod(nsecs_t period) {
    ATRACE_CALL();

    std::lock_guard<std::mutex> lk(mMutex);
    static constexpr size_t kSizeLimit = 30;
    if (CC_UNLIKELY(mRateMap.size() == kSizeLimit)) {
        mRateMap.erase(mRateMap.begin());
    }

    mIdealPeriod = period;
    if (mRateMap.find(period) == mRateMap.end()) {
        mRateMap[mIdealPeriod] = {period, 0};
    }

    if (!timestamps.empty()) {
        mKnownTimestamp = *std::max_element(timestamps.begin(), timestamps.end());
        timestamps.clear();
        lastTimestampIndex = 0;
    }
}

bool VSyncPredictor::needsMoreSamples(nsecs_t now) const {
    using namespace std::literals::chrono_literals;
    std::lock_guard<std::mutex> lk(mMutex);
    bool needsMoreSamples = true;
    if (timestamps.size() >= kMinimumSamplesForPrediction) {
        nsecs_t constexpr aLongTime =
                std::chrono::duration_cast<std::chrono::nanoseconds>(500ms).count();
        if (!(lastTimestampIndex < 0 || timestamps.empty())) {
            auto const lastTimestamp = timestamps[lastTimestampIndex];
            needsMoreSamples = !((lastTimestamp + aLongTime) > now);
        }
    }

    ATRACE_INT(kNeedsSamplesTag, needsMoreSamples);
    return needsMoreSamples;
}

} // namespace android::scheduler
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <android-base/thread_annotations.h>
#include <mutex>
#include <unordered_map>
#include <vector>
#include "VSyncTracker.h"

namespace android::scheduler {

class VSyncPredictor : public VSyncTracker {
public:
    /*
     * \param [in] idealPeriod  The initial ideal period to use.
     * \param [in] historySize  The internal amount of entries to store in the model.
     * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before
     * 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(nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction,
                   uint32_t outlierTolerancePercent);
    ~VSyncPredictor();

    void addVsyncTimestamp(nsecs_t timestamp) final;
    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final;

    /*
     * Inform the model that the period is anticipated to change to a new value.
     * model will use the period parameter to predict vsync events until enough
     * timestamps with the new period have been collected.
     *
     * \param [in] period   The new period that should be used.
     */
    void setPeriod(nsecs_t period);

    /* Query if the model is in need of more samples to make a prediction at timePoint.
     * \param [in] timePoint    The timePoint to inquire of.
     * \return  True, if model would benefit from more samples, False if not.
     */
    bool needsMoreSamples(nsecs_t timePoint) const;

    std::tuple<nsecs_t /* slope */, nsecs_t /* intercept */> getVSyncPredictionModel() const;

private:
    VSyncPredictor(VSyncPredictor const&) = delete;
    VSyncPredictor& operator=(VSyncPredictor const&) = delete;

    size_t const kHistorySize;
    size_t const kMinimumSamplesForPrediction;
    size_t const kOutlierTolerancePercent;

    std::mutex mutable mMutex;
    size_t next(int i) const REQUIRES(mMutex);
    bool validate(nsecs_t timestamp) const REQUIRES(mMutex);
    std::tuple<nsecs_t, nsecs_t> getVSyncPredictionModel(std::lock_guard<std::mutex> const&) const
            REQUIRES(mMutex);

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

    std::unordered_map<nsecs_t, std::tuple<nsecs_t, nsecs_t>> mutable mRateMap GUARDED_BY(mMutex);

    int lastTimestampIndex GUARDED_BY(mMutex) = 0;
    std::vector<nsecs_t> timestamps GUARDED_BY(mMutex);
};

} // namespace android::scheduler
+1 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ cc_test {
        "StrongTypingTest.cpp",
        "VSyncDispatchTimerQueueTest.cpp",
        "VSyncDispatchRealtimeTest.cpp",
        "VSyncPredictorTest.cpp",
        "mock/DisplayHardware/MockComposer.cpp",
        "mock/DisplayHardware/MockDisplay.cpp",
        "mock/DisplayHardware/MockPowerAdvisor.cpp",
+322 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
#define LOG_NDEBUG 0

#include "Scheduler/VSyncPredictor.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <chrono>
#include <utility>

using namespace testing;
using namespace std::literals;

namespace android::scheduler {

MATCHER_P2(IsCloseTo, value, tolerance, "is within tolerance") {
    return arg <= value + tolerance && value >= value - tolerance;
}

std::vector<nsecs_t> generateVsyncTimestamps(size_t count, nsecs_t period, nsecs_t bias) {
    std::vector<nsecs_t> vsyncs(count);
    std::generate(vsyncs.begin(), vsyncs.end(),
                  [&, n = 0]() mutable { return n++ * period + bias; });
    return vsyncs;
}

struct VSyncPredictorTest : testing::Test {
    nsecs_t mNow = 0;
    nsecs_t mPeriod = 1000;
    static constexpr size_t kHistorySize = 10;
    static constexpr size_t kMinimumSamplesForPrediction = 6;
    static constexpr size_t kOutlierTolerancePercent = 25;
    static constexpr nsecs_t mMaxRoundingError = 100;

    VSyncPredictor tracker{mPeriod, kHistorySize, kMinimumSamplesForPrediction,
                           kOutlierTolerancePercent};
};

TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
    auto [slope, intercept] = tracker.getVSyncPredictionModel();

    EXPECT_THAT(slope, Eq(mPeriod));
    EXPECT_THAT(intercept, Eq(0));

    auto const changedPeriod = 2000;
    tracker.setPeriod(changedPeriod);
    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, Eq(changedPeriod));
    EXPECT_THAT(intercept, Eq(0));
}

TEST_F(VSyncPredictorTest, reportsSamplesNeededWhenHasNoDataPoints) {
    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
        EXPECT_TRUE(tracker.needsMoreSamples(mNow += mPeriod));
        tracker.addVsyncTimestamp(mNow);
    }
    EXPECT_FALSE(tracker.needsMoreSamples(mNow));
}

TEST_F(VSyncPredictorTest, reportsSamplesNeededAfterExplicitRateChange) {
    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
        tracker.addVsyncTimestamp(mNow += mPeriod);
    }
    EXPECT_FALSE(tracker.needsMoreSamples(mNow));

    auto const changedPeriod = mPeriod * 2;
    tracker.setPeriod(changedPeriod);
    EXPECT_TRUE(tracker.needsMoreSamples(mNow));

    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
        EXPECT_TRUE(tracker.needsMoreSamples(mNow += changedPeriod));
        tracker.addVsyncTimestamp(mNow);
    }
    EXPECT_FALSE(tracker.needsMoreSamples(mNow));
}

TEST_F(VSyncPredictorTest, transitionsToModelledPointsAfterSynthetic) {
    auto last = mNow;
    auto const bias = 10;
    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
        mNow += mPeriod - bias;
        last = mNow;
        tracker.addVsyncTimestamp(mNow);
        mNow += bias;
    }

    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod - bias));
    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod - bias));
    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 990), Eq(mNow + 2 * mPeriod - bias));
}

TEST_F(VSyncPredictorTest, uponNotifiedOfInaccuracyUsesSynthetic) {
    auto const slightlyLessPeriod = mPeriod - 10;
    auto const changedPeriod = mPeriod - 1;
    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
        tracker.addVsyncTimestamp(mNow += slightlyLessPeriod);
    }

    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + slightlyLessPeriod));
    tracker.setPeriod(changedPeriod);
    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + changedPeriod));
}

TEST_F(VSyncPredictorTest, adaptsToFenceTimelines_60hzHighVariance) {
    // these are precomputed simulated 16.6s vsyncs with uniform distribution +/- 1.6ms error
    std::vector<nsecs_t> const simulatedVsyncs{
            15492949,  32325658,  49534984,  67496129,  84652891,
            100332564, 117737004, 132125931, 149291099, 165199602,
    };
    auto constexpr idealPeriod = 16600000;
    auto constexpr expectedPeriod = 16639242;
    auto constexpr expectedIntercept = 1049341;

    tracker.setPeriod(idealPeriod);
    for (auto const& timestamp : simulatedVsyncs) {
        tracker.addVsyncTimestamp(timestamp);
    }
    auto [slope, intercept] = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
    EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
}

TEST_F(VSyncPredictorTest, adaptsToFenceTimelines_90hzLowVariance) {
    // these are precomputed simulated 11.1 vsyncs with uniform distribution +/- 1ms error
    std::vector<nsecs_t> const simulatedVsyncs{
            11167047, 22603464, 32538479, 44938134, 56321268,
            66730346, 78062637, 88171429, 99707843, 111397621,
    };
    auto idealPeriod = 11110000;
    auto expectedPeriod = 11089413;
    auto expectedIntercept = 94421;

    tracker.setPeriod(idealPeriod);
    for (auto const& timestamp : simulatedVsyncs) {
        tracker.addVsyncTimestamp(timestamp);
    }
    auto [slope, intercept] = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
    EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
}

TEST_F(VSyncPredictorTest, adaptsToFenceTimelinesDiscontinuous_22hzLowVariance) {
    // these are 11.1s vsyncs with low variance, randomly computed, between -1 and 1ms
    std::vector<nsecs_t> const simulatedVsyncs{
            45259463,   // 0
            91511026,   // 1
            136307650,  // 2
            1864501714, // 40
            1908641034, // 41
            1955278544, // 42
            4590180096, // 100
            4681594994, // 102
            5499224734, // 120
            5591378272, // 122
    };
    auto idealPeriod = 45454545;
    auto expectedPeriod = 45450152;
    auto expectedIntercept = 469647;

    tracker.setPeriod(idealPeriod);
    for (auto const& timestamp : simulatedVsyncs) {
        tracker.addVsyncTimestamp(timestamp);
    }
    auto [slope, intercept] = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
    EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
}

TEST_F(VSyncPredictorTest, againstOutliersDiscontinuous_500hzLowVariance) {
    std::vector<nsecs_t> const simulatedVsyncs{
            1992548,    // 0
            4078038,    // 1
            6165794,    // 2
            7958171,    // 3
            10193537,   // 4
            2401840200, // 1200
            2403000000, // an outlier that should be excluded (1201 and a half)
            2405803629, // 1202
            2408028599, // 1203
            2410121051, // 1204
    };
    auto idealPeriod = 2000000;
    auto expectedPeriod = 1999892;
    auto expectedIntercept = 175409;

    tracker.setPeriod(idealPeriod);
    for (auto const& timestamp : simulatedVsyncs) {
        tracker.addVsyncTimestamp(timestamp);
    }

    auto [slope, intercept] = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
    EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
}

TEST_F(VSyncPredictorTest, handlesVsyncChange) {
    auto const fastPeriod = 100;
    auto const fastTimeBase = 100;
    auto const slowPeriod = 400;
    auto const slowTimeBase = 800;
    auto const simulatedVsyncsFast =
            generateVsyncTimestamps(kMinimumSamplesForPrediction, fastPeriod, fastTimeBase);
    auto const simulatedVsyncsSlow =
            generateVsyncTimestamps(kMinimumSamplesForPrediction, slowPeriod, slowTimeBase);

    tracker.setPeriod(fastPeriod);
    for (auto const& timestamp : simulatedVsyncsFast) {
        tracker.addVsyncTimestamp(timestamp);
    }

    auto const mMaxRoundingError = 100;
    auto [slope, intercept] = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, IsCloseTo(fastPeriod, mMaxRoundingError));
    EXPECT_THAT(intercept, IsCloseTo(0, mMaxRoundingError));

    tracker.setPeriod(slowPeriod);
    for (auto const& timestamp : simulatedVsyncsSlow) {
        tracker.addVsyncTimestamp(timestamp);
    }
    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, IsCloseTo(slowPeriod, mMaxRoundingError));
    EXPECT_THAT(intercept, IsCloseTo(0, mMaxRoundingError));
}

TEST_F(VSyncPredictorTest, willBeAccurateUsingPriorResultsForRate) {
    auto const fastPeriod = 101000;
    auto const fastTimeBase = fastPeriod - 500;
    auto const fastPeriod2 = 99000;

    auto const slowPeriod = 400000;
    auto const slowTimeBase = 800000 - 201;
    auto const simulatedVsyncsFast =
            generateVsyncTimestamps(kMinimumSamplesForPrediction, fastPeriod, fastTimeBase);
    auto const simulatedVsyncsSlow =
            generateVsyncTimestamps(kMinimumSamplesForPrediction, slowPeriod, slowTimeBase);
    auto const simulatedVsyncsFast2 =
            generateVsyncTimestamps(kMinimumSamplesForPrediction, fastPeriod2, fastTimeBase);

    auto idealPeriod = 100000;
    tracker.setPeriod(idealPeriod);
    for (auto const& timestamp : simulatedVsyncsFast) {
        tracker.addVsyncTimestamp(timestamp);
    }
    auto [slope, intercept] = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, Eq(fastPeriod));
    EXPECT_THAT(intercept, Eq(0));

    tracker.setPeriod(slowPeriod);
    for (auto const& timestamp : simulatedVsyncsSlow) {
        tracker.addVsyncTimestamp(timestamp);
    }

    // we had a model for 100ns mPeriod before, use that until the new samples are
    // sufficiently built up
    tracker.setPeriod(idealPeriod);
    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, Eq(fastPeriod));
    EXPECT_THAT(intercept, Eq(0));

    for (auto const& timestamp : simulatedVsyncsFast2) {
        tracker.addVsyncTimestamp(timestamp);
    }
    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
    EXPECT_THAT(slope, Eq(fastPeriod2));
    EXPECT_THAT(intercept, Eq(0));
}

TEST_F(VSyncPredictorTest, willBecomeInaccurateAfterA_longTimeWithNoSamples) {
    auto const simulatedVsyncs = generateVsyncTimestamps(kMinimumSamplesForPrediction, mPeriod, 0);

    for (auto const& timestamp : simulatedVsyncs) {
        tracker.addVsyncTimestamp(timestamp);
    }
    auto const mNow = *simulatedVsyncs.rbegin();
    EXPECT_FALSE(tracker.needsMoreSamples(mNow));

    // TODO: would be better to decay this as a result of the variance of the samples
    static auto constexpr aLongTimeOut = 1000000000;
    EXPECT_TRUE(tracker.needsMoreSamples(mNow + aLongTimeOut));
}

TEST_F(VSyncPredictorTest, idealModelPredictionsBeforeRegressionModelIsBuilt) {
    auto const simulatedVsyncs =
            generateVsyncTimestamps(kMinimumSamplesForPrediction + 1, mPeriod, 0);
    nsecs_t const mNow = 0;
    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mPeriod));

    nsecs_t const aBitOfTime = 422;

    for (auto i = 0; i < kMinimumSamplesForPrediction; i++) {
        tracker.addVsyncTimestamp(simulatedVsyncs[i]);
        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(simulatedVsyncs[i] + aBitOfTime),
                    Eq(mPeriod + simulatedVsyncs[i]));
    }

    for (auto i = kMinimumSamplesForPrediction; i < simulatedVsyncs.size(); i++) {
        tracker.addVsyncTimestamp(simulatedVsyncs[i]);
        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(simulatedVsyncs[i] + aBitOfTime),
                    Eq(mPeriod + simulatedVsyncs[i]));
    }
}

} // namespace android::scheduler