blob: 643c5d2a5a95e6069852846ef0ed598b5e421d4d [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
57void VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
58 std::lock_guard<std::mutex> lk(mMutex);
59
60 if (!validate(timestamp)) {
61 ALOGW("timestamp was too far off the last known timestamp");
62 return;
63 }
64
65 if (timestamps.size() != kHistorySize) {
66 timestamps.push_back(timestamp);
67 lastTimestampIndex = next(lastTimestampIndex);
68 } else {
69 lastTimestampIndex = next(lastTimestampIndex);
70 timestamps[lastTimestampIndex] = timestamp;
71 }
72
73 if (timestamps.size() < kMinimumSamplesForPrediction) {
74 mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
75 return;
76 }
77
78 // This is a 'simple linear regression' calculation of Y over X, with Y being the
79 // vsync timestamps, and X being the ordinal of vsync count.
80 // The calculated slope is the vsync period.
81 // Formula for reference:
82 // Sigma_i: means sum over all timestamps.
83 // mean(variable): statistical mean of variable.
84 // X: snapped ordinal of the timestamp
85 // Y: vsync timestamp
86 //
87 // Sigma_i( (X_i - mean(X)) * (Y_i - mean(Y) )
88 // slope = -------------------------------------------
89 // Sigma_i ( X_i - mean(X) ) ^ 2
90 //
91 // intercept = mean(Y) - slope * mean(X)
92 //
93 std::vector<nsecs_t> vsyncTS(timestamps.size());
94 std::vector<nsecs_t> ordinals(timestamps.size());
95
96 // normalizing to the oldest timestamp cuts down on error in calculating the intercept.
97 auto const oldest_ts = *std::min_element(timestamps.begin(), timestamps.end());
98 auto it = mRateMap.find(mIdealPeriod);
99 auto const currentPeriod = std::get<0>(it->second);
100 // TODO (b/144707443): its important that there's some precision in the mean of the ordinals
101 // for the intercept calculation, so scale the ordinals by 10 to continue
102 // fixed point calculation. Explore expanding
103 // scheduler::utils::calculate_mean to have a fixed point fractional part.
104 static constexpr int kScalingFactor = 10;
105
106 for (auto i = 0u; i < timestamps.size(); i++) {
107 vsyncTS[i] = timestamps[i] - oldest_ts;
108 ordinals[i] = ((vsyncTS[i] + (currentPeriod / 2)) / currentPeriod) * kScalingFactor;
109 }
110
111 auto meanTS = scheduler::calculate_mean(vsyncTS);
112 auto meanOrdinal = scheduler::calculate_mean(ordinals);
113 for (auto i = 0; i < vsyncTS.size(); i++) {
114 vsyncTS[i] -= meanTS;
115 ordinals[i] -= meanOrdinal;
116 }
117
118 auto top = 0ll;
119 auto bottom = 0ll;
120 for (auto i = 0; i < vsyncTS.size(); i++) {
121 top += vsyncTS[i] * ordinals[i];
122 bottom += ordinals[i] * ordinals[i];
123 }
124
125 if (CC_UNLIKELY(bottom == 0)) {
126 it->second = {mIdealPeriod, 0};
127 return;
128 }
129
130 nsecs_t const anticipatedPeriod = top / bottom * kScalingFactor;
131 nsecs_t const intercept = meanTS - (anticipatedPeriod * meanOrdinal / kScalingFactor);
132
133 it->second = {anticipatedPeriod, intercept};
134
135 ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp,
136 anticipatedPeriod, intercept);
137}
138
139nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
140 std::lock_guard<std::mutex> lk(mMutex);
141
142 auto const [slope, intercept] = getVSyncPredictionModel(lk);
143
144 if (timestamps.empty()) {
145 auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
146 auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
147 return knownTimestamp + numPeriodsOut * mIdealPeriod;
148 }
149
150 auto const oldest = *std::min_element(timestamps.begin(), timestamps.end());
151 auto const ordinalRequest = (timePoint - oldest + slope) / slope;
152 auto const prediction = (ordinalRequest * slope) + intercept + oldest;
153
154 ALOGV("prediction made from: %" PRId64 " prediction: %" PRId64 " (+%" PRId64 ") slope: %" PRId64
155 " intercept: %" PRId64,
156 timePoint, prediction, prediction - timePoint, slope, intercept);
157 return prediction;
158}
159
160std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel() const {
161 std::lock_guard<std::mutex> lk(mMutex);
162 return VSyncPredictor::getVSyncPredictionModel(lk);
163}
164
165std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel(
166 std::lock_guard<std::mutex> const&) const {
167 return mRateMap.find(mIdealPeriod)->second;
168}
169
170void VSyncPredictor::setPeriod(nsecs_t period) {
171 ATRACE_CALL();
172
173 std::lock_guard<std::mutex> lk(mMutex);
174 static constexpr size_t kSizeLimit = 30;
175 if (CC_UNLIKELY(mRateMap.size() == kSizeLimit)) {
176 mRateMap.erase(mRateMap.begin());
177 }
178
179 mIdealPeriod = period;
180 if (mRateMap.find(period) == mRateMap.end()) {
181 mRateMap[mIdealPeriod] = {period, 0};
182 }
183
184 if (!timestamps.empty()) {
185 mKnownTimestamp = *std::max_element(timestamps.begin(), timestamps.end());
186 timestamps.clear();
187 lastTimestampIndex = 0;
188 }
189}
190
191bool VSyncPredictor::needsMoreSamples(nsecs_t now) const {
192 using namespace std::literals::chrono_literals;
193 std::lock_guard<std::mutex> lk(mMutex);
194 bool needsMoreSamples = true;
195 if (timestamps.size() >= kMinimumSamplesForPrediction) {
196 nsecs_t constexpr aLongTime =
197 std::chrono::duration_cast<std::chrono::nanoseconds>(500ms).count();
198 if (!(lastTimestampIndex < 0 || timestamps.empty())) {
199 auto const lastTimestamp = timestamps[lastTimestampIndex];
200 needsMoreSamples = !((lastTimestamp + aLongTime) > now);
201 }
202 }
203
204 ATRACE_INT(kNeedsSamplesTag, needsMoreSamples);
205 return needsMoreSamples;
206}
207
208} // namespace android::scheduler