Loading services/surfaceflinger/Scheduler/VSyncPredictor.cpp +27 −8 Original line number Diff line number Diff line Loading @@ -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); } } Loading Loading @@ -394,10 +398,16 @@ void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) { mTimelines.clear(); mLastCommittedVsync = TimePoint::fromNs(0); } else { 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())); } Loading Loading @@ -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; Loading Loading @@ -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. 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 Loading @@ -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; Loading services/surfaceflinger/Scheduler/VSyncPredictor.h +18 −0 Original line number Diff line number Diff line Loading @@ -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); Loading services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +30 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading
services/surfaceflinger/Scheduler/VSyncPredictor.cpp +27 −8 Original line number Diff line number Diff line Loading @@ -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); } } Loading Loading @@ -394,10 +398,16 @@ void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) { mTimelines.clear(); mLastCommittedVsync = TimePoint::fromNs(0); } else { 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())); } Loading Loading @@ -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; Loading Loading @@ -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. 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 Loading @@ -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; Loading
services/surfaceflinger/Scheduler/VSyncPredictor.h +18 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +30 −0 Original line number Diff line number Diff line Loading @@ -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); Loading