blob: 3894992ad14c5be772d69bd8d5cf1dfcb0408446 [file] [log] [blame]
Kevin DuBois1678e2c2019-08-22 12:26:24 -07001/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define ATRACE_TAG ATRACE_TAG_GRAPHICS
18//#define LOG_NDEBUG 0
19#include "VSyncPredictor.h"
20#include <android-base/logging.h>
21#include <cutils/compiler.h>
22#include <utils/Log.h>
23#include <utils/Trace.h>
24#include <algorithm>
25#include <chrono>
26#include "SchedulerUtils.h"
27
28namespace android::scheduler {
29static auto constexpr kNeedsSamplesTag = "SamplesRequested";
30static auto constexpr kMaxPercent = 100u;
31
32VSyncPredictor::~VSyncPredictor() = default;
33
34VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize,
35 size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
36 : kHistorySize(historySize),
37 kMinimumSamplesForPrediction(minimumSamplesForPrediction),
38 kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
39 mIdealPeriod(idealPeriod) {
40 mRateMap[mIdealPeriod] = {idealPeriod, 0};
41}
42
43inline size_t VSyncPredictor::next(int i) const {
44 return (i + 1) % timestamps.size();
45}
46
47bool VSyncPredictor::validate(nsecs_t timestamp) const {
48 if (lastTimestampIndex < 0 || timestamps.empty()) {
49 return true;
50 }
51
52 auto const aValidTimestamp = timestamps[lastTimestampIndex];
53 auto const percent = (timestamp - aValidTimestamp) % mIdealPeriod * kMaxPercent / mIdealPeriod;
54 return percent < kOutlierTolerancePercent || percent > (kMaxPercent - kOutlierTolerancePercent);
55}
56
Kevin DuBois2fd3cea2019-11-14 08:52:45 -080057nsecs_t VSyncPredictor::currentPeriod() const {
58 std::lock_guard<std::mutex> lk(mMutex);
59 return std::get<0>(mRateMap.find(mIdealPeriod)->second);
60}
61
Kevin DuBois1678e2c2019-08-22 12:26:24 -070062void VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
63 std::lock_guard<std::mutex> lk(mMutex);
64
65 if (!validate(timestamp)) {
66 ALOGW("timestamp was too far off the last known timestamp");
67 return;
68 }
69
70 if (timestamps.size() != kHistorySize) {
71 timestamps.push_back(timestamp);
72 lastTimestampIndex = next(lastTimestampIndex);
73 } else {
74 lastTimestampIndex = next(lastTimestampIndex);
75 timestamps[lastTimestampIndex] = timestamp;
76 }
77
78 if (timestamps.size() < kMinimumSamplesForPrediction) {
79 mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
80 return;
81 }
82
83 // This is a 'simple linear regression' calculation of Y over X, with Y being the
84 // vsync timestamps, and X being the ordinal of vsync count.
85 // The calculated slope is the vsync period.
86 // Formula for reference:
87 // Sigma_i: means sum over all timestamps.
88 // mean(variable): statistical mean of variable.
89 // X: snapped ordinal of the timestamp
90 // Y: vsync timestamp
91 //
92 // Sigma_i( (X_i - mean(X)) * (Y_i - mean(Y) )
93 // slope = -------------------------------------------
94 // Sigma_i ( X_i - mean(X) ) ^ 2
95 //
96 // intercept = mean(Y) - slope * mean(X)
97 //
98 std::vector<nsecs_t> vsyncTS(timestamps.size());
99 std::vector<nsecs_t> ordinals(timestamps.size());
100
101 // normalizing to the oldest timestamp cuts down on error in calculating the intercept.
102 auto const oldest_ts = *std::min_element(timestamps.begin(), timestamps.end());
103 auto it = mRateMap.find(mIdealPeriod);
104 auto const currentPeriod = std::get<0>(it->second);
105 // TODO (b/144707443): its important that there's some precision in the mean of the ordinals
106 // for the intercept calculation, so scale the ordinals by 10 to continue
107 // fixed point calculation. Explore expanding
108 // scheduler::utils::calculate_mean to have a fixed point fractional part.
109 static constexpr int kScalingFactor = 10;
110
111 for (auto i = 0u; i < timestamps.size(); i++) {
112 vsyncTS[i] = timestamps[i] - oldest_ts;
113 ordinals[i] = ((vsyncTS[i] + (currentPeriod / 2)) / currentPeriod) * kScalingFactor;
114 }
115
116 auto meanTS = scheduler::calculate_mean(vsyncTS);
117 auto meanOrdinal = scheduler::calculate_mean(ordinals);
118 for (auto i = 0; i < vsyncTS.size(); i++) {
119 vsyncTS[i] -= meanTS;
120 ordinals[i] -= meanOrdinal;
121 }
122
123 auto top = 0ll;
124 auto bottom = 0ll;
125 for (auto i = 0; i < vsyncTS.size(); i++) {
126 top += vsyncTS[i] * ordinals[i];
127 bottom += ordinals[i] * ordinals[i];
128 }
129
130 if (CC_UNLIKELY(bottom == 0)) {
131 it->second = {mIdealPeriod, 0};
132 return;
133 }
134
135 nsecs_t const anticipatedPeriod = top / bottom * kScalingFactor;
136 nsecs_t const intercept = meanTS - (anticipatedPeriod * meanOrdinal / kScalingFactor);
137
138 it->second = {anticipatedPeriod, intercept};
139
140 ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp,
141 anticipatedPeriod, intercept);
142}
143
144nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
145 std::lock_guard<std::mutex> lk(mMutex);
146
147 auto const [slope, intercept] = getVSyncPredictionModel(lk);
148
149 if (timestamps.empty()) {
150 auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
151 auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
152 return knownTimestamp + numPeriodsOut * mIdealPeriod;
153 }
154
155 auto const oldest = *std::min_element(timestamps.begin(), timestamps.end());
156 auto const ordinalRequest = (timePoint - oldest + slope) / slope;
157 auto const prediction = (ordinalRequest * slope) + intercept + oldest;
158
159 ALOGV("prediction made from: %" PRId64 " prediction: %" PRId64 " (+%" PRId64 ") slope: %" PRId64
160 " intercept: %" PRId64,
161 timePoint, prediction, prediction - timePoint, slope, intercept);
162 return prediction;
163}
164
165std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel() const {
166 std::lock_guard<std::mutex> lk(mMutex);
167 return VSyncPredictor::getVSyncPredictionModel(lk);
168}
169
170std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel(
171 std::lock_guard<std::mutex> const&) const {
172 return mRateMap.find(mIdealPeriod)->second;
173}
174
175void VSyncPredictor::setPeriod(nsecs_t period) {
176 ATRACE_CALL();
177
178 std::lock_guard<std::mutex> lk(mMutex);
179 static constexpr size_t kSizeLimit = 30;
180 if (CC_UNLIKELY(mRateMap.size() == kSizeLimit)) {
181 mRateMap.erase(mRateMap.begin());
182 }
183
184 mIdealPeriod = period;
185 if (mRateMap.find(period) == mRateMap.end()) {
186 mRateMap[mIdealPeriod] = {period, 0};
187 }
188
189 if (!timestamps.empty()) {
190 mKnownTimestamp = *std::max_element(timestamps.begin(), timestamps.end());
191 timestamps.clear();
192 lastTimestampIndex = 0;
193 }
194}
195
196bool VSyncPredictor::needsMoreSamples(nsecs_t now) const {
197 using namespace std::literals::chrono_literals;
198 std::lock_guard<std::mutex> lk(mMutex);
199 bool needsMoreSamples = true;
200 if (timestamps.size() >= kMinimumSamplesForPrediction) {
201 nsecs_t constexpr aLongTime =
202 std::chrono::duration_cast<std::chrono::nanoseconds>(500ms).count();
203 if (!(lastTimestampIndex < 0 || timestamps.empty())) {
204 auto const lastTimestamp = timestamps[lastTimestampIndex];
205 needsMoreSamples = !((lastTimestamp + aLongTime) > now);
206 }
207 }
208
209 ATRACE_INT(kNeedsSamplesTag, needsMoreSamples);
210 return needsMoreSamples;
211}
212
213} // namespace android::scheduler