• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 // TODO(b/129481165): remove the #pragma below and fix conversion issues
18 #pragma clang diagnostic push
19 #pragma clang diagnostic ignored "-Wextra"
20 
21 #undef LOG_TAG
22 #define LOG_TAG "VSyncPredictor"
23 
24 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
25 
26 #include <algorithm>
27 #include <chrono>
28 #include <sstream>
29 
30 #include <android-base/logging.h>
31 #include <android-base/stringprintf.h>
32 #include <cutils/compiler.h>
33 #include <cutils/properties.h>
34 #include <ftl/concat.h>
35 #include <gui/TraceUtils.h>
36 #include <utils/Log.h>
37 
38 #include "RefreshRateSelector.h"
39 #include "VSyncPredictor.h"
40 
41 namespace android::scheduler {
42 
43 using base::StringAppendF;
44 
45 static auto constexpr kMaxPercent = 100u;
46 
47 VSyncPredictor::~VSyncPredictor() = default;
48 
VSyncPredictor(PhysicalDisplayId id,nsecs_t idealPeriod,size_t historySize,size_t minimumSamplesForPrediction,uint32_t outlierTolerancePercent)49 VSyncPredictor::VSyncPredictor(PhysicalDisplayId id, nsecs_t idealPeriod, size_t historySize,
50                                size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
51       : mId(id),
52         mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
53         kHistorySize(historySize),
54         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
55         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
56         mIdealPeriod(idealPeriod) {
57     resetModel();
58 }
59 
traceInt64If(const char * name,int64_t value) const60 inline void VSyncPredictor::traceInt64If(const char* name, int64_t value) const {
61     if (CC_UNLIKELY(mTraceOn)) {
62         traceInt64(name, value);
63     }
64 }
65 
traceInt64(const char * name,int64_t value) const66 inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const {
67     ATRACE_INT64(ftl::Concat(ftl::truncated<14>(name), " ", mId.value).c_str(), value);
68 }
69 
next(size_t i) const70 inline size_t VSyncPredictor::next(size_t i) const {
71     return (i + 1) % mTimestamps.size();
72 }
73 
validate(nsecs_t timestamp) const74 bool VSyncPredictor::validate(nsecs_t timestamp) const {
75     if (mLastTimestampIndex < 0 || mTimestamps.empty()) {
76         return true;
77     }
78 
79     auto const aValidTimestamp = mTimestamps[mLastTimestampIndex];
80     auto const percent = (timestamp - aValidTimestamp) % mIdealPeriod * kMaxPercent / mIdealPeriod;
81     if (percent >= kOutlierTolerancePercent &&
82         percent <= (kMaxPercent - kOutlierTolerancePercent)) {
83         return false;
84     }
85 
86     const auto iter = std::min_element(mTimestamps.begin(), mTimestamps.end(),
87                                        [timestamp](nsecs_t a, nsecs_t b) {
88                                            return std::abs(timestamp - a) < std::abs(timestamp - b);
89                                        });
90     const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / mIdealPeriod;
91     if (distancePercent < kOutlierTolerancePercent) {
92         // duplicate timestamp
93         return false;
94     }
95     return true;
96 }
97 
currentPeriod() const98 nsecs_t VSyncPredictor::currentPeriod() const {
99     std::lock_guard lock(mMutex);
100     return mRateMap.find(mIdealPeriod)->second.slope;
101 }
102 
addVsyncTimestamp(nsecs_t timestamp)103 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
104     std::lock_guard lock(mMutex);
105 
106     if (!validate(timestamp)) {
107         // VSR could elect to ignore the incongruent timestamp or resetModel(). If ts is ignored,
108         // don't insert this ts into mTimestamps ringbuffer. If we are still
109         // in the learning phase we should just clear all timestamps and start
110         // over.
111         if (mTimestamps.size() < kMinimumSamplesForPrediction) {
112             // Add the timestamp to mTimestamps before clearing it so we could
113             // update mKnownTimestamp based on the new timestamp.
114             mTimestamps.push_back(timestamp);
115             clearTimestamps();
116         } else if (!mTimestamps.empty()) {
117             mKnownTimestamp =
118                     std::max(timestamp, *std::max_element(mTimestamps.begin(), mTimestamps.end()));
119         } else {
120             mKnownTimestamp = timestamp;
121         }
122         return false;
123     }
124 
125     if (mTimestamps.size() != kHistorySize) {
126         mTimestamps.push_back(timestamp);
127         mLastTimestampIndex = next(mLastTimestampIndex);
128     } else {
129         mLastTimestampIndex = next(mLastTimestampIndex);
130         mTimestamps[mLastTimestampIndex] = timestamp;
131     }
132 
133     traceInt64If("VSP-ts", timestamp);
134 
135     const size_t numSamples = mTimestamps.size();
136     if (numSamples < kMinimumSamplesForPrediction) {
137         mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
138         return true;
139     }
140 
141     // This is a 'simple linear regression' calculation of Y over X, with Y being the
142     // vsync timestamps, and X being the ordinal of vsync count.
143     // The calculated slope is the vsync period.
144     // Formula for reference:
145     // Sigma_i: means sum over all timestamps.
146     // mean(variable): statistical mean of variable.
147     // X: snapped ordinal of the timestamp
148     // Y: vsync timestamp
149     //
150     //         Sigma_i( (X_i - mean(X)) * (Y_i - mean(Y) )
151     // slope = -------------------------------------------
152     //         Sigma_i ( X_i - mean(X) ) ^ 2
153     //
154     // intercept = mean(Y) - slope * mean(X)
155     //
156     std::vector<nsecs_t> vsyncTS(numSamples);
157     std::vector<nsecs_t> ordinals(numSamples);
158 
159     // Normalizing to the oldest timestamp cuts down on error in calculating the intercept.
160     const auto oldestTS = *std::min_element(mTimestamps.begin(), mTimestamps.end());
161     auto it = mRateMap.find(mIdealPeriod);
162     auto const currentPeriod = it->second.slope;
163 
164     // The mean of the ordinals must be precise for the intercept calculation, so scale them up for
165     // fixed-point arithmetic.
166     constexpr int64_t kScalingFactor = 1000;
167 
168     nsecs_t meanTS = 0;
169     nsecs_t meanOrdinal = 0;
170 
171     for (size_t i = 0; i < numSamples; i++) {
172         const auto timestamp = mTimestamps[i] - oldestTS;
173         vsyncTS[i] = timestamp;
174         meanTS += timestamp;
175 
176         const auto ordinal = currentPeriod == 0
177                 ? 0
178                 : (vsyncTS[i] + currentPeriod / 2) / currentPeriod * kScalingFactor;
179         ordinals[i] = ordinal;
180         meanOrdinal += ordinal;
181     }
182 
183     meanTS /= numSamples;
184     meanOrdinal /= numSamples;
185 
186     for (size_t i = 0; i < numSamples; i++) {
187         vsyncTS[i] -= meanTS;
188         ordinals[i] -= meanOrdinal;
189     }
190 
191     nsecs_t top = 0;
192     nsecs_t bottom = 0;
193     for (size_t i = 0; i < numSamples; i++) {
194         top += vsyncTS[i] * ordinals[i];
195         bottom += ordinals[i] * ordinals[i];
196     }
197 
198     if (CC_UNLIKELY(bottom == 0)) {
199         it->second = {mIdealPeriod, 0};
200         clearTimestamps();
201         return false;
202     }
203 
204     nsecs_t const anticipatedPeriod = top * kScalingFactor / bottom;
205     nsecs_t const intercept = meanTS - (anticipatedPeriod * meanOrdinal / kScalingFactor);
206 
207     auto const percent = std::abs(anticipatedPeriod - mIdealPeriod) * kMaxPercent / mIdealPeriod;
208     if (percent >= kOutlierTolerancePercent) {
209         it->second = {mIdealPeriod, 0};
210         clearTimestamps();
211         return false;
212     }
213 
214     traceInt64If("VSP-period", anticipatedPeriod);
215     traceInt64If("VSP-intercept", intercept);
216 
217     it->second = {anticipatedPeriod, intercept};
218 
219     ALOGV("model update ts %" PRIu64 ": %" PRId64 " slope: %" PRId64 " intercept: %" PRId64,
220           mId.value, timestamp, anticipatedPeriod, intercept);
221     return true;
222 }
223 
getVsyncSequenceLocked(nsecs_t timestamp) const224 auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence {
225     const auto vsync = nextAnticipatedVSyncTimeFromLocked(timestamp);
226     if (!mLastVsyncSequence) return {vsync, 0};
227 
228     const auto [slope, _] = getVSyncPredictionModelLocked();
229     const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence;
230     const auto vsyncSequence = lastVsyncSequence +
231             static_cast<int64_t>(std::round((vsync - lastVsyncTime) / static_cast<float>(slope)));
232     return {vsync, vsyncSequence};
233 }
234 
nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const235 nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const {
236     auto const [slope, intercept] = getVSyncPredictionModelLocked();
237 
238     if (mTimestamps.empty()) {
239         traceInt64("VSP-mode", 1);
240         auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
241         auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
242         return knownTimestamp + numPeriodsOut * mIdealPeriod;
243     }
244 
245     auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end());
246 
247     // See b/145667109, the ordinal calculation must take into account the intercept.
248     auto const zeroPoint = oldest + intercept;
249     auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
250     auto const prediction = (ordinalRequest * slope) + intercept + oldest;
251 
252     traceInt64("VSP-mode", 0);
253     traceInt64If("VSP-timePoint", timePoint);
254     traceInt64If("VSP-prediction", prediction);
255 
256     auto const printer = [&, slope = slope, intercept = intercept] {
257         std::stringstream str;
258         str << "prediction made from: " << timePoint << "prediction: " << prediction << " (+"
259             << prediction - timePoint << ") slope: " << slope << " intercept: " << intercept
260             << "oldestTS: " << oldest << " ordinal: " << ordinalRequest;
261         return str.str();
262     };
263 
264     ALOGV("%s", printer().c_str());
265     LOG_ALWAYS_FATAL_IF(prediction < timePoint, "VSyncPredictor: model miscalculation: %s",
266                         printer().c_str());
267 
268     return prediction;
269 }
270 
nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const271 nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
272     std::lock_guard lock(mMutex);
273 
274     // update the mLastVsyncSequence for reference point
275     mLastVsyncSequence = getVsyncSequenceLocked(timePoint);
276 
277     const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int {
278         if (!mRenderRate) return 0;
279 
280         const auto divisor =
281                 RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod),
282                                                          *mRenderRate);
283         if (divisor <= 1) return 0;
284 
285         const int mod = mLastVsyncSequence->seq % divisor;
286         if (mod == 0) return 0;
287 
288         return divisor - mod;
289     }();
290 
291     if (renderRatePhase == 0) {
292         return mLastVsyncSequence->vsyncTime;
293     }
294 
295     auto const [slope, intercept] = getVSyncPredictionModelLocked();
296     const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
297     return nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2);
298 }
299 
300 /*
301  * Returns whether a given vsync timestamp is in phase with a frame rate.
302  * If the frame rate is not a divisor of the refresh rate, it is always considered in phase.
303  * For example, if the vsync timestamps are (16.6,33.3,50.0,66.6):
304  * isVSyncInPhase(16.6, 30) = true
305  * isVSyncInPhase(33.3, 30) = false
306  * isVSyncInPhase(50.0, 30) = true
307  */
isVSyncInPhase(nsecs_t timePoint,Fps frameRate) const308 bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const {
309     std::lock_guard lock(mMutex);
310     const auto divisor =
311             RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate);
312     return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(divisor));
313 }
314 
isVSyncInPhaseLocked(nsecs_t timePoint,unsigned divisor) const315 bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const {
316     const TimePoint now = TimePoint::now();
317     const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float {
318         return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
319     };
320     ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint),
321                   divisor);
322 
323     if (divisor <= 1 || timePoint == 0) {
324         return true;
325     }
326 
327     const nsecs_t period = mRateMap[mIdealPeriod].slope;
328     const nsecs_t justBeforeTimePoint = timePoint - period / 2;
329     const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint);
330     ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64,
331                           getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq);
332     return vsyncSequence.seq % divisor == 0;
333 }
334 
setRenderRate(Fps fps)335 void VSyncPredictor::setRenderRate(Fps fps) {
336     ALOGV("%s %s: %s", __func__, to_string(mId).c_str(), to_string(fps).c_str());
337     std::lock_guard lock(mMutex);
338     mRenderRate = fps;
339 }
340 
getVSyncPredictionModel() const341 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
342     std::lock_guard lock(mMutex);
343     const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
344     return {model.slope, model.intercept};
345 }
346 
getVSyncPredictionModelLocked() const347 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
348     return mRateMap.find(mIdealPeriod)->second;
349 }
350 
setPeriod(nsecs_t period)351 void VSyncPredictor::setPeriod(nsecs_t period) {
352     ATRACE_FORMAT("%s %s", __func__, to_string(mId).c_str());
353     traceInt64("VSP-setPeriod", period);
354 
355     std::lock_guard lock(mMutex);
356     static constexpr size_t kSizeLimit = 30;
357     if (CC_UNLIKELY(mRateMap.size() == kSizeLimit)) {
358         mRateMap.erase(mRateMap.begin());
359     }
360 
361     mIdealPeriod = period;
362     if (mRateMap.find(period) == mRateMap.end()) {
363         mRateMap[mIdealPeriod] = {period, 0};
364     }
365 
366     clearTimestamps();
367 }
368 
clearTimestamps()369 void VSyncPredictor::clearTimestamps() {
370     if (!mTimestamps.empty()) {
371         auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
372         if (mKnownTimestamp) {
373             mKnownTimestamp = std::max(*mKnownTimestamp, maxRb);
374         } else {
375             mKnownTimestamp = maxRb;
376         }
377 
378         mTimestamps.clear();
379         mLastTimestampIndex = 0;
380     }
381 }
382 
needsMoreSamples() const383 bool VSyncPredictor::needsMoreSamples() const {
384     std::lock_guard lock(mMutex);
385     return mTimestamps.size() < kMinimumSamplesForPrediction;
386 }
387 
resetModel()388 void VSyncPredictor::resetModel() {
389     std::lock_guard lock(mMutex);
390     mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
391     clearTimestamps();
392 }
393 
dump(std::string & result) const394 void VSyncPredictor::dump(std::string& result) const {
395     std::lock_guard lock(mMutex);
396     StringAppendF(&result, "\tmIdealPeriod=%.2f\n", mIdealPeriod / 1e6f);
397     StringAppendF(&result, "\tRefresh Rate Map:\n");
398     for (const auto& [idealPeriod, periodInterceptTuple] : mRateMap) {
399         StringAppendF(&result,
400                       "\t\tFor ideal period %.2fms: period = %.2fms, intercept = %" PRId64 "\n",
401                       idealPeriod / 1e6f, periodInterceptTuple.slope / 1e6f,
402                       periodInterceptTuple.intercept);
403     }
404 }
405 
406 } // namespace android::scheduler
407 
408 // TODO(b/129481165): remove the #pragma below and fix conversion issues
409 #pragma clang diagnostic pop // ignored "-Wextra"
410