Loading services/surfaceflinger/Android.bp +2 −1 Original line number Diff line number Diff line Loading @@ -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", Loading services/surfaceflinger/Scheduler/VSyncPredictor.cpp 0 → 100644 +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 services/surfaceflinger/Scheduler/VSyncPredictor.h 0 → 100644 +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 services/surfaceflinger/tests/unittests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -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", Loading services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp 0 → 100644 +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 Loading
services/surfaceflinger/Android.bp +2 −1 Original line number Diff line number Diff line Loading @@ -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", Loading
services/surfaceflinger/Scheduler/VSyncPredictor.cpp 0 → 100644 +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
services/surfaceflinger/Scheduler/VSyncPredictor.h 0 → 100644 +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
services/surfaceflinger/tests/unittests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -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", Loading
services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp 0 → 100644 +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