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 <common/FlagManager.h>
33 #include <common/trace.h>
34 #include <cutils/compiler.h>
35 #include <cutils/properties.h>
36 #include <ftl/concat.h>
37 #include <utils/Log.h>
38
39 #include "RefreshRateSelector.h"
40 #include "VSyncPredictor.h"
41
42 namespace android::scheduler {
43
44 using base::StringAppendF;
45
46 static auto constexpr kMaxPercent = 100u;
47
48 namespace {
numVsyncsPerFrame(const ftl::NonNull<DisplayModePtr> & displayModePtr)49 int numVsyncsPerFrame(const ftl::NonNull<DisplayModePtr>& displayModePtr) {
50 const auto idealPeakRefreshPeriod = displayModePtr->getPeakFps().getPeriodNsecs();
51 const auto idealRefreshPeriod = displayModePtr->getVsyncRate().getPeriodNsecs();
52 return static_cast<int>(std::round(static_cast<float>(idealPeakRefreshPeriod) /
53 static_cast<float>(idealRefreshPeriod)));
54 }
55 } // namespace
56
57 VSyncPredictor::~VSyncPredictor() = default;
58
VSyncPredictor(std::unique_ptr<Clock> clock,ftl::NonNull<DisplayModePtr> modePtr,size_t historySize,size_t minimumSamplesForPrediction,uint32_t outlierTolerancePercent)59 VSyncPredictor::VSyncPredictor(std::unique_ptr<Clock> clock, ftl::NonNull<DisplayModePtr> modePtr,
60 size_t historySize, size_t minimumSamplesForPrediction,
61 uint32_t outlierTolerancePercent)
62 : mClock(std::move(clock)),
63 mId(modePtr->getPhysicalDisplayId()),
64 mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
65 kHistorySize(historySize),
66 kMinimumSamplesForPrediction(minimumSamplesForPrediction),
67 kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
68 mDisplayModePtr(modePtr),
69 mNumVsyncsForFrame(numVsyncsPerFrame(mDisplayModePtr)) {
70 resetModel();
71 }
72
traceInt64If(const char * name,int64_t value) const73 inline void VSyncPredictor::traceInt64If(const char* name, int64_t value) const {
74 if (CC_UNLIKELY(mTraceOn)) {
75 traceInt64(name, value);
76 }
77 }
78
traceInt64(const char * name,int64_t value) const79 inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const {
80 SFTRACE_INT64(ftl::Concat(ftl::truncated<14>(name), " ", mId.value).c_str(), value);
81 }
82
next(size_t i) const83 inline size_t VSyncPredictor::next(size_t i) const {
84 return (i + 1) % mTimestamps.size();
85 }
86
idealPeriod() const87 nsecs_t VSyncPredictor::idealPeriod() const {
88 return mDisplayModePtr->getVsyncRate().getPeriodNsecs();
89 }
90
validate(nsecs_t timestamp) const91 bool VSyncPredictor::validate(nsecs_t timestamp) const {
92 SFTRACE_CALL();
93 if (mLastTimestampIndex < 0 || mTimestamps.empty()) {
94 SFTRACE_INSTANT("timestamp valid (first)");
95 return true;
96 }
97
98 const auto aValidTimestamp = mTimestamps[mLastTimestampIndex];
99 const auto percent =
100 (timestamp - aValidTimestamp) % idealPeriod() * kMaxPercent / idealPeriod();
101 if (percent >= kOutlierTolerancePercent &&
102 percent <= (kMaxPercent - kOutlierTolerancePercent)) {
103 SFTRACE_FORMAT_INSTANT("timestamp not aligned with model. aValidTimestamp %.2fms ago"
104 ", timestamp %.2fms ago, idealPeriod=%.2 percent=%d",
105 (mClock->now() - aValidTimestamp) / 1e6f,
106 (mClock->now() - timestamp) / 1e6f,
107 idealPeriod() / 1e6f, percent);
108 return false;
109 }
110
111 const auto iter = std::min_element(mTimestamps.begin(), mTimestamps.end(),
112 [timestamp](nsecs_t a, nsecs_t b) {
113 return std::abs(timestamp - a) < std::abs(timestamp - b);
114 });
115 const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod();
116 if (distancePercent < kOutlierTolerancePercent) {
117 // duplicate timestamp
118 SFTRACE_FORMAT_INSTANT("duplicate timestamp");
119 return false;
120 }
121 return true;
122 }
123
currentPeriod() const124 nsecs_t VSyncPredictor::currentPeriod() const {
125 std::lock_guard lock(mMutex);
126 return mRateMap.find(idealPeriod())->second.slope;
127 }
128
minFramePeriod() const129 Period VSyncPredictor::minFramePeriod() const {
130 if (!FlagManager::getInstance().vrr_config()) {
131 return Period::fromNs(currentPeriod());
132 }
133
134 std::lock_guard lock(mMutex);
135 return minFramePeriodLocked();
136 }
137
minFramePeriodLocked() const138 Period VSyncPredictor::minFramePeriodLocked() const {
139 const auto slope = mRateMap.find(idealPeriod())->second.slope;
140 return Period::fromNs(slope * mNumVsyncsForFrame);
141 }
142
addVsyncTimestamp(nsecs_t timestamp)143 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
144 SFTRACE_CALL();
145
146 std::lock_guard lock(mMutex);
147
148 if (!validate(timestamp)) {
149 // VSR could elect to ignore the incongruent timestamp or resetModel(). If ts is ignored,
150 // don't insert this ts into mTimestamps ringbuffer. If we are still
151 // in the learning phase we should just clear all timestamps and start
152 // over.
153 if (mTimestamps.size() < kMinimumSamplesForPrediction) {
154 // Add the timestamp to mTimestamps before clearing it so we could
155 // update mKnownTimestamp based on the new timestamp.
156 mTimestamps.push_back(timestamp);
157
158 // Do not clear timelines as we don't want to break the phase while
159 // we are still learning.
160 clearTimestamps(/* clearTimelines */ false);
161 } else if (!mTimestamps.empty()) {
162 mKnownTimestamp =
163 std::max(timestamp, *std::max_element(mTimestamps.begin(), mTimestamps.end()));
164 } else {
165 mKnownTimestamp = timestamp;
166 }
167 SFTRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
168 (mClock->now() - *mKnownTimestamp) / 1e6f);
169 return false;
170 }
171
172 if (mTimestamps.size() != kHistorySize) {
173 mTimestamps.push_back(timestamp);
174 mLastTimestampIndex = next(mLastTimestampIndex);
175 } else {
176 mLastTimestampIndex = next(mLastTimestampIndex);
177 mTimestamps[mLastTimestampIndex] = timestamp;
178 }
179
180 traceInt64If("VSP-ts", timestamp);
181
182 const size_t numSamples = mTimestamps.size();
183 if (numSamples < kMinimumSamplesForPrediction) {
184 mRateMap[idealPeriod()] = {idealPeriod(), 0};
185 return true;
186 }
187
188 // This is a 'simple linear regression' calculation of Y over X, with Y being the
189 // vsync timestamps, and X being the ordinal of vsync count.
190 // The calculated slope is the vsync period.
191 // Formula for reference:
192 // Sigma_i: means sum over all timestamps.
193 // mean(variable): statistical mean of variable.
194 // X: snapped ordinal of the timestamp
195 // Y: vsync timestamp
196 //
197 // Sigma_i( (X_i - mean(X)) * (Y_i - mean(Y) )
198 // slope = -------------------------------------------
199 // Sigma_i ( X_i - mean(X) ) ^ 2
200 //
201 // intercept = mean(Y) - slope * mean(X)
202 //
203 std::vector<nsecs_t> vsyncTS(numSamples);
204 std::vector<nsecs_t> ordinals(numSamples);
205
206 // Normalizing to the oldest timestamp cuts down on error in calculating the intercept.
207 const auto oldestTS = *std::min_element(mTimestamps.begin(), mTimestamps.end());
208 auto it = mRateMap.find(idealPeriod());
209 // Calculated slope over the period of time can become outdated as the new timestamps are
210 // stored. Using idealPeriod instead provides a rate which is valid at all the times.
211 auto const currentPeriod =
212 mDisplayModePtr->getVrrConfig() && FlagManager::getInstance().vsync_predictor_recovery()
213 ? idealPeriod()
214 : it->second.slope;
215
216 // The mean of the ordinals must be precise for the intercept calculation, so scale them up for
217 // fixed-point arithmetic.
218 constexpr int64_t kScalingFactor = 1000;
219
220 nsecs_t meanTS = 0;
221 nsecs_t meanOrdinal = 0;
222
223 for (size_t i = 0; i < numSamples; i++) {
224 const auto timestamp = mTimestamps[i] - oldestTS;
225 vsyncTS[i] = timestamp;
226 meanTS += timestamp;
227
228 const auto ordinal = currentPeriod == 0
229 ? 0
230 : (vsyncTS[i] + currentPeriod / 2) / currentPeriod * kScalingFactor;
231 ordinals[i] = ordinal;
232 meanOrdinal += ordinal;
233 }
234
235 meanTS /= numSamples;
236 meanOrdinal /= numSamples;
237
238 for (size_t i = 0; i < numSamples; i++) {
239 vsyncTS[i] -= meanTS;
240 ordinals[i] -= meanOrdinal;
241 }
242
243 nsecs_t top = 0;
244 nsecs_t bottom = 0;
245 for (size_t i = 0; i < numSamples; i++) {
246 top += vsyncTS[i] * ordinals[i];
247 bottom += ordinals[i] * ordinals[i];
248 }
249
250 if (CC_UNLIKELY(bottom == 0)) {
251 it->second = {idealPeriod(), 0};
252 clearTimestamps(/* clearTimelines */ true);
253 return false;
254 }
255
256 nsecs_t const anticipatedPeriod = top * kScalingFactor / bottom;
257 nsecs_t const intercept = meanTS - (anticipatedPeriod * meanOrdinal / kScalingFactor);
258
259 auto const percent = std::abs(anticipatedPeriod - idealPeriod()) * kMaxPercent / idealPeriod();
260 if (percent >= kOutlierTolerancePercent) {
261 it->second = {idealPeriod(), 0};
262 clearTimestamps(/* clearTimelines */ true);
263 return false;
264 }
265
266 traceInt64If("VSP-period", anticipatedPeriod);
267 traceInt64If("VSP-intercept", intercept);
268
269 it->second = {anticipatedPeriod, intercept};
270
271 ALOGV("model update ts %" PRIu64 ": %" PRId64 " slope: %" PRId64 " intercept: %" PRId64,
272 mId.value, timestamp, anticipatedPeriod, intercept);
273 return true;
274 }
275
snapToVsync(nsecs_t timePoint) const276 nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const {
277 auto const [slope, intercept] = getVSyncPredictionModelLocked();
278
279 if (mTimestamps.empty()) {
280 traceInt64("VSP-mode", 1);
281 auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
282 auto const numPeriodsOut = ((timePoint - knownTimestamp) / idealPeriod()) + 1;
283 return knownTimestamp + numPeriodsOut * idealPeriod();
284 }
285
286 auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end());
287
288 // See b/145667109, the ordinal calculation must take into account the intercept.
289 auto const zeroPoint = oldest + intercept;
290 auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
291 auto const prediction = (ordinalRequest * slope) + intercept + oldest;
292
293 traceInt64("VSP-mode", 0);
294 traceInt64If("VSP-timePoint", timePoint);
295 traceInt64If("VSP-prediction", prediction);
296
297 auto const printer = [&, slope = slope, intercept = intercept] {
298 std::stringstream str;
299 str << "prediction made from: " << timePoint << "prediction: " << prediction << " (+"
300 << prediction - timePoint << ") slope: " << slope << " intercept: " << intercept
301 << "oldestTS: " << oldest << " ordinal: " << ordinalRequest;
302 return str.str();
303 };
304
305 ALOGV("%s", printer().c_str());
306 LOG_ALWAYS_FATAL_IF(prediction < timePoint, "VSyncPredictor: model miscalculation: %s",
307 printer().c_str());
308
309 return prediction;
310 }
311
nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,std::optional<nsecs_t> lastVsyncOpt)312 nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
313 std::optional<nsecs_t> lastVsyncOpt) {
314 SFTRACE_CALL();
315 std::lock_guard lock(mMutex);
316
317 const auto now = TimePoint::fromNs(mClock->now());
318 purgeTimelines(now);
319
320 if (lastVsyncOpt && *lastVsyncOpt > timePoint) {
321 timePoint = *lastVsyncOpt;
322 }
323
324 const auto model = getVSyncPredictionModelLocked();
325 const auto threshold = model.slope / 2;
326 std::optional<Period> minFramePeriodOpt;
327
328 if (mNumVsyncsForFrame > 1) {
329 minFramePeriodOpt = minFramePeriodLocked();
330 }
331
332 std::optional<TimePoint> vsyncOpt;
333 for (auto& timeline : mTimelines) {
334 vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(model, minFramePeriodOpt,
335 snapToVsync(timePoint), mMissedVsync,
336 lastVsyncOpt ? snapToVsync(*lastVsyncOpt -
337 threshold)
338 : lastVsyncOpt);
339 if (vsyncOpt) {
340 break;
341 }
342 }
343 LOG_ALWAYS_FATAL_IF(!vsyncOpt);
344
345 if (*vsyncOpt > mLastCommittedVsync) {
346 mLastCommittedVsync = *vsyncOpt;
347 SFTRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms",
348 float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f);
349 }
350
351 return vsyncOpt->ns();
352 }
353
354 /*
355 * Returns whether a given vsync timestamp is in phase with a frame rate.
356 * If the frame rate is not a divisor of the refresh rate, it is always considered in phase.
357 * For example, if the vsync timestamps are (16.6,33.3,50.0,66.6):
358 * isVSyncInPhase(16.6, 30) = true
359 * isVSyncInPhase(33.3, 30) = false
360 * isVSyncInPhase(50.0, 30) = true
361 */
isVSyncInPhase(nsecs_t timePoint,Fps frameRate)362 bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) {
363 if (timePoint == 0) {
364 return true;
365 }
366
367 std::lock_guard lock(mMutex);
368 const auto model = getVSyncPredictionModelLocked();
369 const nsecs_t period = model.slope;
370 const nsecs_t justBeforeTimePoint = timePoint - period / 2;
371 const auto now = TimePoint::fromNs(mClock->now());
372 const auto vsync = snapToVsync(justBeforeTimePoint);
373
374 purgeTimelines(now);
375
376 for (auto& timeline : mTimelines) {
377 const bool isVsyncValid = FlagManager::getInstance().vrr_bugfix_24q4()
378 ? timeline.isWithin(TimePoint::fromNs(vsync)) ==
379 VsyncTimeline::VsyncOnTimeline::Unique
380 : timeline.validUntil() && timeline.validUntil()->ns() > vsync;
381 if (isVsyncValid) {
382 return timeline.isVSyncInPhase(model, vsync, frameRate);
383 }
384 }
385
386 // The last timeline should always be valid
387 return mTimelines.back().isVSyncInPhase(model, vsync, frameRate);
388 }
389
setRenderRate(Fps renderRate,bool applyImmediately)390 void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) {
391 SFTRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
392 ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str());
393 std::lock_guard lock(mMutex);
394 const auto prevRenderRate = mRenderRateOpt;
395 mRenderRateOpt = renderRate;
396 const auto renderPeriodDelta =
397 prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0;
398 if (applyImmediately) {
399 SFTRACE_FORMAT_INSTANT("applyImmediately");
400 while (mTimelines.size() > 1) {
401 mTimelines.pop_front();
402 }
403
404 mTimelines.front().setRenderRate(renderRate);
405 return;
406 }
407
408 const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() &&
409 mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs();
410 if (newRenderRateIsHigher) {
411 SFTRACE_FORMAT_INSTANT("newRenderRateIsHigher");
412 mTimelines.clear();
413 mLastCommittedVsync = TimePoint::fromNs(0);
414
415 } else {
416 if (FlagManager::getInstance().vrr_bugfix_24q4()) {
417 // We need to freeze the timeline at the committed vsync, and
418 // then use with threshold adjustments when required to avoid
419 // marginal errors when checking the vsync on the timeline.
420 mTimelines.back().freeze(mLastCommittedVsync);
421 } else {
422 mTimelines.back().freeze(
423 TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
424 }
425 }
426 mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, renderRate);
427 purgeTimelines(TimePoint::fromNs(mClock->now()));
428 }
429
setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr)430 void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) {
431 LOG_ALWAYS_FATAL_IF(mId != modePtr->getPhysicalDisplayId(),
432 "mode does not belong to the display");
433 SFTRACE_FORMAT("%s %s", __func__, to_string(*modePtr).c_str());
434 const auto timeout = modePtr->getVrrConfig()
435 ? modePtr->getVrrConfig()->notifyExpectedPresentConfig
436 : std::nullopt;
437 ALOGV("%s %s: DisplayMode %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(),
438 to_string(*modePtr).c_str(),
439 timeout ? std::to_string(timeout->timeoutNs).c_str() : "N/A");
440 std::lock_guard lock(mMutex);
441
442 // do not clear the timelines on VRR displays if we didn't change the mode
443 const bool isVrr = modePtr->getVrrConfig().has_value();
444 const bool clearTimelines = !isVrr || mDisplayModePtr->getId() != modePtr->getId();
445 mDisplayModePtr = modePtr;
446 mNumVsyncsForFrame = numVsyncsPerFrame(mDisplayModePtr);
447 traceInt64("VSP-setPeriod", modePtr->getVsyncRate().getPeriodNsecs());
448
449 static constexpr size_t kSizeLimit = 30;
450 if (CC_UNLIKELY(mRateMap.size() == kSizeLimit)) {
451 mRateMap.erase(mRateMap.begin());
452 }
453
454 if (mRateMap.find(idealPeriod()) == mRateMap.end()) {
455 mRateMap[idealPeriod()] = {idealPeriod(), 0};
456 }
457
458 if (clearTimelines) {
459 mTimelines.clear();
460 }
461 clearTimestamps(clearTimelines);
462 }
463
ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,TimePoint lastConfirmedPresentTime)464 Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
465 TimePoint lastConfirmedPresentTime) {
466 SFTRACE_FORMAT("%s mNumVsyncsForFrame=%d mPastExpectedPresentTimes.size()=%zu", __func__,
467 mNumVsyncsForFrame, mPastExpectedPresentTimes.size());
468
469 if (mNumVsyncsForFrame <= 1) {
470 return 0ns;
471 }
472
473 const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
474 const auto threshold = currentPeriod / 2;
475 const auto minFramePeriod = minFramePeriodLocked();
476
477 auto prev = lastConfirmedPresentTime.ns();
478 for (auto& current : mPastExpectedPresentTimes) {
479 SFTRACE_FORMAT_INSTANT("current %.2f past last signaled fence",
480 static_cast<float>(current.ns() - prev) / 1e6f);
481
482 const auto minPeriodViolation = current.ns() - prev + threshold < minFramePeriod.ns();
483 if (minPeriodViolation) {
484 SFTRACE_NAME("minPeriodViolation");
485 current = TimePoint::fromNs(prev + minFramePeriod.ns());
486 prev = current.ns();
487 } else {
488 break;
489 }
490 }
491
492 if (!mPastExpectedPresentTimes.empty()) {
493 const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime);
494 if (phase > 0ns) {
495 for (auto& timeline : mTimelines) {
496 timeline.shiftVsyncSequence(phase, minFramePeriod);
497 }
498 mPastExpectedPresentTimes.clear();
499 return phase;
500 }
501 }
502
503 return 0ns;
504 }
505
onFrameBegin(TimePoint expectedPresentTime,FrameTime lastSignaledFrameTime)506 void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, FrameTime lastSignaledFrameTime) {
507 SFTRACE_NAME("VSyncPredictor::onFrameBegin");
508 std::lock_guard lock(mMutex);
509
510 if (!mDisplayModePtr->getVrrConfig()) return;
511
512 const auto [lastConfirmedPresentTime, lastConfirmedExpectedPresentTime] = lastSignaledFrameTime;
513 if (CC_UNLIKELY(mTraceOn)) {
514 SFTRACE_FORMAT_INSTANT("vsync is %.2f past last signaled fence",
515 static_cast<float>(expectedPresentTime.ns() -
516 lastConfirmedPresentTime.ns()) /
517 1e6f);
518 }
519 const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
520 const auto threshold = currentPeriod / 2;
521 mPastExpectedPresentTimes.push_back(expectedPresentTime);
522
523 while (!mPastExpectedPresentTimes.empty()) {
524 const auto front = mPastExpectedPresentTimes.front().ns();
525 const bool frontIsBeforeConfirmed = front < lastConfirmedPresentTime.ns() + threshold;
526 if (frontIsBeforeConfirmed) {
527 SFTRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
528 static_cast<float>(lastConfirmedPresentTime.ns() - front) /
529 1e6f);
530 mPastExpectedPresentTimes.pop_front();
531 } else {
532 break;
533 }
534 }
535
536 if (lastConfirmedExpectedPresentTime.ns() - lastConfirmedPresentTime.ns() > threshold) {
537 SFTRACE_FORMAT_INSTANT("lastFramePresentedEarly");
538 return;
539 }
540
541 const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
542 if (phase > 0ns) {
543 mMissedVsync = {expectedPresentTime, minFramePeriodLocked()};
544 }
545 }
546
onFrameMissed(TimePoint expectedPresentTime)547 void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) {
548 SFTRACE_NAME("VSyncPredictor::onFrameMissed");
549
550 std::lock_guard lock(mMutex);
551 if (!mDisplayModePtr->getVrrConfig()) return;
552
553 // We don't know when the frame is going to be presented, so we assume it missed one vsync
554 const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
555 const auto lastConfirmedPresentTime =
556 TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod);
557
558 const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
559 if (phase > 0ns) {
560 mMissedVsync = {expectedPresentTime, Duration::fromNs(0)};
561 }
562 }
563
getVSyncPredictionModel() const564 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
565 std::lock_guard lock(mMutex);
566 return VSyncPredictor::getVSyncPredictionModelLocked();
567 }
568
getVSyncPredictionModelLocked() const569 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
570 return mRateMap.find(idealPeriod())->second;
571 }
572
clearTimestamps(bool clearTimelines)573 void VSyncPredictor::clearTimestamps(bool clearTimelines) {
574 SFTRACE_FORMAT("%s: clearTimelines=%d", __func__, clearTimelines);
575
576 if (!mTimestamps.empty()) {
577 auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
578 if (mKnownTimestamp) {
579 mKnownTimestamp = std::max(*mKnownTimestamp, maxRb);
580 SFTRACE_FORMAT_INSTANT("mKnownTimestamp was %.2fms ago",
581 (mClock->now() - *mKnownTimestamp) / 1e6f);
582 } else {
583 mKnownTimestamp = maxRb;
584 SFTRACE_FORMAT_INSTANT("mKnownTimestamp (maxRb) was %.2fms ago",
585 (mClock->now() - *mKnownTimestamp) / 1e6f);
586 }
587
588 mTimestamps.clear();
589 mLastTimestampIndex = 0;
590 }
591
592 mIdealPeriod = Period::fromNs(idealPeriod());
593 if (mTimelines.empty()) {
594 mLastCommittedVsync = TimePoint::fromNs(0);
595 mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
596 } else if (clearTimelines) {
597 while (mTimelines.size() > 1) {
598 mTimelines.pop_front();
599 }
600 mTimelines.front().setRenderRate(mRenderRateOpt);
601 // set mLastCommittedVsync to a valid vsync but don't commit too much in the future
602 const auto vsyncOpt = mTimelines.front().nextAnticipatedVSyncTimeFrom(
603 getVSyncPredictionModelLocked(),
604 /* minFramePeriodOpt */ std::nullopt,
605 snapToVsync(mClock->now()), MissedVsync{},
606 /* lastVsyncOpt */ std::nullopt);
607 mLastCommittedVsync = *vsyncOpt;
608 }
609 }
610
needsMoreSamples() const611 bool VSyncPredictor::needsMoreSamples() const {
612 std::lock_guard lock(mMutex);
613 return mTimestamps.size() < kMinimumSamplesForPrediction;
614 }
615
resetModel()616 void VSyncPredictor::resetModel() {
617 SFTRACE_CALL();
618 std::lock_guard lock(mMutex);
619 mRateMap[idealPeriod()] = {idealPeriod(), 0};
620 clearTimestamps(/* clearTimelines */ true);
621 }
622
dump(std::string & result) const623 void VSyncPredictor::dump(std::string& result) const {
624 std::lock_guard lock(mMutex);
625 StringAppendF(&result, "\tmDisplayModePtr=%s\n", to_string(*mDisplayModePtr).c_str());
626 StringAppendF(&result, "\tRefresh Rate Map:\n");
627 for (const auto& [period, periodInterceptTuple] : mRateMap) {
628 StringAppendF(&result,
629 "\t\tFor ideal period %.2fms: period = %.2fms, intercept = %" PRId64 "\n",
630 period / 1e6f, periodInterceptTuple.slope / 1e6f,
631 periodInterceptTuple.intercept);
632 }
633 StringAppendF(&result, "\tmTimelines.size()=%zu\n", mTimelines.size());
634 }
635
purgeTimelines(android::TimePoint now)636 void VSyncPredictor::purgeTimelines(android::TimePoint now) {
637 const auto kEnoughFramesToBreakPhase = 5;
638 if (mRenderRateOpt &&
639 mLastCommittedVsync.ns() + mRenderRateOpt->getPeriodNsecs() * kEnoughFramesToBreakPhase <
640 mClock->now()) {
641 SFTRACE_FORMAT_INSTANT("kEnoughFramesToBreakPhase");
642 mTimelines.clear();
643 mLastCommittedVsync = TimePoint::fromNs(0);
644 mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
645 return;
646 }
647
648 while (mTimelines.size() > 1) {
649 const auto validUntilOpt = mTimelines.front().validUntil();
650 const bool isTimelineOutDated = FlagManager::getInstance().vrr_bugfix_24q4()
651 ? mTimelines.front().isWithin(now) == VsyncTimeline::VsyncOnTimeline::Outside
652 : validUntilOpt && *validUntilOpt < now;
653 if (isTimelineOutDated) {
654 mTimelines.pop_front();
655 } else {
656 break;
657 }
658 }
659 LOG_ALWAYS_FATAL_IF(mTimelines.empty());
660 LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value());
661 }
662
makeVsyncSequence(TimePoint knownVsync)663 auto VSyncPredictor::VsyncTimeline::makeVsyncSequence(TimePoint knownVsync)
664 -> std::optional<VsyncSequence> {
665 if (knownVsync.ns() == 0) return std::nullopt;
666 return std::make_optional<VsyncSequence>({knownVsync.ns(), 0});
667 }
668
VsyncTimeline(TimePoint knownVsync,Period idealPeriod,std::optional<Fps> renderRateOpt)669 VSyncPredictor::VsyncTimeline::VsyncTimeline(TimePoint knownVsync, Period idealPeriod,
670 std::optional<Fps> renderRateOpt)
671 : mIdealPeriod(idealPeriod),
672 mRenderRateOpt(renderRateOpt),
673 mLastVsyncSequence(makeVsyncSequence(knownVsync)) {}
674
freeze(TimePoint lastVsync)675 void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) {
676 LOG_ALWAYS_FATAL_IF(mValidUntil.has_value());
677 SFTRACE_FORMAT_INSTANT("renderRate %s valid for %.2f",
678 mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA",
679 float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f);
680 mValidUntil = lastVsync;
681 }
682
nextAnticipatedVSyncTimeFrom(Model model,std::optional<Period> minFramePeriodOpt,nsecs_t vsync,MissedVsync missedVsync,std::optional<nsecs_t> lastVsyncOpt)683 std::optional<TimePoint> VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom(
684 Model model, std::optional<Period> minFramePeriodOpt, nsecs_t vsync,
685 MissedVsync missedVsync, std::optional<nsecs_t> lastVsyncOpt) {
686 SFTRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
687
688 nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync);
689 const auto threshold = model.slope / 2;
690 const auto lastFrameMissed =
691 lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold;
692 const auto mightBackpressure = minFramePeriodOpt && mRenderRateOpt &&
693 mRenderRateOpt->getPeriod() < 2 * (*minFramePeriodOpt);
694 if (FlagManager::getInstance().vrr_config()) {
695 if (lastFrameMissed) {
696 // If the last frame missed is the last vsync, we already shifted the timeline. Depends
697 // on whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a
698 // different fixup if we are violating the minFramePeriod.
699 // There is no need to shift the vsync timeline again.
700 if (vsyncTime - missedVsync.vsync.ns() < minFramePeriodOpt->ns()) {
701 vsyncTime += missedVsync.fixup.ns();
702 SFTRACE_FORMAT_INSTANT("lastFrameMissed");
703 }
704 } else if (mightBackpressure && lastVsyncOpt) {
705 if (!FlagManager::getInstance().vrr_bugfix_24q4()) {
706 // lastVsyncOpt does not need to be corrected with the new rate, and
707 // it should be used as is to avoid skipping a frame when changing rates are
708 // aligned at vsync time.
709 lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
710 }
711 const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
712 if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
713 // avoid a duplicate vsync
714 SFTRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f "
715 "which "
716 "is %.2f "
717 "from "
718 "prev. "
719 "adjust by %.2f",
720 static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
721 static_cast<float>(vsyncDiff) / 1e6f,
722 static_cast<float>(mRenderRateOpt->getPeriodNsecs()) / 1e6f);
723 vsyncTime += mRenderRateOpt->getPeriodNsecs();
724 }
725 }
726 }
727
728 SFTRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
729 const bool isVsyncInvalid = FlagManager::getInstance().vrr_bugfix_24q4()
730 ? isWithin(TimePoint::fromNs(vsyncTime)) == VsyncOnTimeline::Outside
731 : mValidUntil && vsyncTime > mValidUntil->ns();
732 if (isVsyncInvalid) {
733 SFTRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
734 static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
735 return std::nullopt;
736 }
737
738 return TimePoint::fromNs(vsyncTime);
739 }
740
getVsyncSequenceLocked(Model model,nsecs_t vsync)741 auto VSyncPredictor::VsyncTimeline::getVsyncSequenceLocked(Model model, nsecs_t vsync)
742 -> VsyncSequence {
743 if (!mLastVsyncSequence) return {vsync, 0};
744
745 const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence;
746 const auto vsyncSequence = lastVsyncSequence +
747 static_cast<int64_t>(std::round((vsync - lastVsyncTime) /
748 static_cast<float>(model.slope)));
749 return {vsync, vsyncSequence};
750 }
751
snapToVsyncAlignedWithRenderRate(Model model,nsecs_t vsync)752 nsecs_t VSyncPredictor::VsyncTimeline::snapToVsyncAlignedWithRenderRate(Model model,
753 nsecs_t vsync) {
754 // update the mLastVsyncSequence for reference point
755 mLastVsyncSequence = getVsyncSequenceLocked(model, vsync);
756
757 const auto renderRatePhase = [&]() -> int {
758 if (!mRenderRateOpt) return 0;
759 const auto divisor =
760 RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod.ns()),
761 *mRenderRateOpt);
762 if (divisor <= 1) return 0;
763
764 int mod = mLastVsyncSequence->seq % divisor;
765 if (mod == 0) return 0;
766
767 // This is actually a bug fix, but guarded with vrr_config since we found it with this
768 // config
769 if (FlagManager::getInstance().vrr_config()) {
770 if (mod < 0) mod += divisor;
771 }
772
773 return divisor - mod;
774 }();
775
776 if (renderRatePhase == 0) {
777 return mLastVsyncSequence->vsyncTime;
778 }
779
780 return mLastVsyncSequence->vsyncTime + model.slope * renderRatePhase;
781 }
782
isVSyncInPhase(Model model,nsecs_t vsync,Fps frameRate)783 bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, Fps frameRate) {
784 const auto getVsyncIn = [](TimePoint now, nsecs_t timePoint) -> float {
785 return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
786 };
787
788 Fps displayFps = !FlagManager::getInstance().vrr_bugfix_24q4() && mRenderRateOpt
789 ? *mRenderRateOpt
790 : Fps::fromPeriodNsecs(mIdealPeriod.ns());
791 const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate);
792 const auto now = TimePoint::now();
793
794 if (divisor <= 1) {
795 return true;
796 }
797 const auto vsyncSequence = getVsyncSequenceLocked(model, vsync);
798 SFTRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu",
799 getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor);
800 return vsyncSequence.seq % divisor == 0;
801 }
802
shiftVsyncSequence(Duration phase,Period minFramePeriod)803 void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase, Period minFramePeriod) {
804 if (mLastVsyncSequence) {
805 const auto renderRate = mRenderRateOpt.value_or(Fps::fromPeriodNsecs(mIdealPeriod.ns()));
806 const auto threshold = mIdealPeriod.ns() / 2;
807 if (renderRate.getPeriodNsecs() - phase.ns() + threshold >= minFramePeriod.ns()) {
808 SFTRACE_FORMAT_INSTANT("Not-Adjusting vsync by %.2f",
809 static_cast<float>(phase.ns()) / 1e6f);
810 return;
811 }
812 SFTRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast<float>(phase.ns()) / 1e6f);
813 mLastVsyncSequence->vsyncTime += phase.ns();
814 }
815 }
816
isWithin(TimePoint vsync)817 VSyncPredictor::VsyncTimeline::VsyncOnTimeline VSyncPredictor::VsyncTimeline::isWithin(
818 TimePoint vsync) {
819 const auto threshold = mIdealPeriod.ns() / 2;
820 if (!mValidUntil || vsync.ns() < mValidUntil->ns() - threshold) {
821 // if mValidUntil is absent then timeline is not frozen and
822 // vsync should be unique to that timeline.
823 return VsyncOnTimeline::Unique;
824 }
825 if (vsync.ns() > mValidUntil->ns() + threshold) {
826 return VsyncOnTimeline::Outside;
827 }
828 return VsyncOnTimeline::Shared;
829 }
830
831 } // namespace android::scheduler
832
833 // TODO(b/129481165): remove the #pragma below and fix conversion issues
834 #pragma clang diagnostic pop // ignored "-Wextra"
835