diff options
3 files changed, 75 insertions, 8 deletions
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index dd3c4b074c..0644acaee2 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -360,7 +360,11 @@ bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) { purgeTimelines(now); for (auto& timeline : mTimelines) { - if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) { + const bool isVsyncValid = FlagManager::getInstance().vrr_bugfix_24q4() + ? timeline.isWithin(TimePoint::fromNs(vsync)) == + VsyncTimeline::VsyncOnTimeline::Unique + : timeline.validUntil() && timeline.validUntil()->ns() > vsync; + if (isVsyncValid) { return timeline.isVSyncInPhase(model, vsync, frameRate); } } @@ -395,8 +399,14 @@ void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) { mLastCommittedVsync = TimePoint::fromNs(0); } else { - mTimelines.back().freeze( - TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2)); + if (FlagManager::getInstance().vrr_bugfix_24q4()) { + // We need to freeze the timeline at the committed vsync so that we don't + // overshoot the deadline. + mTimelines.back().freeze(mLastCommittedVsync); + } else { + mTimelines.back().freeze( + TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2)); + } } mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, renderRate); purgeTimelines(TimePoint::fromNs(mClock->now())); @@ -611,7 +621,10 @@ void VSyncPredictor::purgeTimelines(android::TimePoint now) { while (mTimelines.size() > 1) { const auto validUntilOpt = mTimelines.front().validUntil(); - if (validUntilOpt && *validUntilOpt < now) { + const bool isTimelineOutDated = FlagManager::getInstance().vrr_bugfix_24q4() + ? mTimelines.front().isWithin(now) == VsyncTimeline::VsyncOnTimeline::Outside + : validUntilOpt && *validUntilOpt < now; + if (isTimelineOutDated) { mTimelines.pop_front(); } else { break; @@ -660,9 +673,12 @@ std::optional<TimePoint> VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime vsyncTime += missedVsync.fixup.ns(); ATRACE_FORMAT_INSTANT("lastFrameMissed"); } else if (mightBackpressure && lastVsyncOpt) { - // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it - // first before trying to use it. - lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); + if (!FlagManager::getInstance().vrr_bugfix_24q4()) { + // lastVsyncOpt does not need to be corrected with the new rate, and + // it should be used as is to avoid skipping a frame when changing rates are + // aligned at vsync time. + lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); + } const auto vsyncDiff = vsyncTime - *lastVsyncOpt; if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) { // avoid a duplicate vsync @@ -681,7 +697,10 @@ std::optional<TimePoint> VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime } ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f); - if (mValidUntil && vsyncTime > mValidUntil->ns()) { + const bool isVsyncInvalid = FlagManager::getInstance().vrr_bugfix_24q4() + ? isWithin(TimePoint::fromNs(vsyncTime)) == VsyncOnTimeline::Outside + : mValidUntil && vsyncTime > mValidUntil->ns(); + if (isVsyncInvalid) { ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f", static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f); return std::nullopt; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 8ce61d86c6..66a7d71435 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -106,6 +106,24 @@ private: void shiftVsyncSequence(Duration phase); void setRenderRate(std::optional<Fps> renderRateOpt) { mRenderRateOpt = renderRateOpt; } + enum class VsyncOnTimeline { + Unique, // Within timeline, not shared with next timeline. + Shared, // Within timeline, shared with next timeline. + Outside, // Outside of the timeline. + }; + VsyncOnTimeline isWithin(TimePoint vsync) { + const auto threshold = mIdealPeriod.ns() / 2; + if (!mValidUntil || vsync.ns() < mValidUntil->ns() - threshold) { + // if mValidUntil is absent then timeline is not frozen and + // vsync should be unique to that timeline. + return VsyncOnTimeline::Unique; + } + if (vsync.ns() > mValidUntil->ns() + threshold) { + return VsyncOnTimeline::Outside; + } + return VsyncOnTimeline::Shared; + } + private: nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync); VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync); diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 5109ea6793..f36a8a6fd7 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -673,6 +673,36 @@ TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod)); } +TEST_F(VSyncPredictorTest, setRenderRateWhenRenderRateGoesDown) { + SET_FLAG_FOR_TEST(flags::vrr_config, true); + SET_FLAG_FOR_TEST(flags::vrr_bugfix_24q4, true); + + const int32_t kGroup = 0; + const auto kResolution = ui::Size(1920, 1080); + const auto vsyncRate = Fps::fromPeriodNsecs(500); + const auto minFrameRate = Fps::fromPeriodNsecs(1000); + hal::VrrConfig vrrConfig; + vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs(); + const ftl::NonNull<DisplayModePtr> kMode = + ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup, + kResolution, DEFAULT_DISPLAY_ID) + .setVrrConfig(std::move(vrrConfig)) + .build()); + + VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + + Fps frameRate = Fps::fromPeriodNsecs(1000); + vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false); + vrrTracker.addVsyncTimestamp(0); + EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700)); + EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000)); + + frameRate = Fps::fromPeriodNsecs(3000); + vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false); + EXPECT_TRUE(vrrTracker.isVSyncInPhase(2000, frameRate)); +} + TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) { SET_FLAG_FOR_TEST(flags::vrr_config, true); |