SF: add render frame rate to the scheduler

Schedule SF at the rate of the render frame rate instead of
the display refresh rate.

Test: SF unit tests
Bug: 257072060
Change-Id: Idaf9be5f25373d38c0ef6440f9f401dc90de7a91
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index fd1a733..4be1ac7 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -213,7 +213,7 @@
     std::vector<FrameRateMode> frameRateModes;
     frameRateModes.reserve(ratesMap.size());
     for (const auto& [key, mode] : ratesMap) {
-        frameRateModes.emplace_back(FrameRateMode{key.fps, mode->second});
+        frameRateModes.emplace_back(FrameRateMode{key.fps, ftl::as_non_null(mode->second)});
     }
 
     // We always want that the lowest frame rate will be corresponding to the
@@ -409,7 +409,7 @@
     ATRACE_CALL();
     ALOGV("%s: %zu layers", __func__, layers.size());
 
-    const auto& activeMode = *getActiveModeItLocked()->second;
+    const auto& activeMode = *getActiveModeLocked().modePtr;
 
     // Keep the display at max frame rate for the duration of powering on the display.
     if (signals.powerOnImminent) {
@@ -842,7 +842,7 @@
 
     const DisplayModePtr& current = desiredActiveModeId
             ? mDisplayModes.get(*desiredActiveModeId)->get()
-            : getActiveModeItLocked()->second;
+            : getActiveModeLocked().modePtr.get();
 
     const DisplayModePtr& min = mMinRefreshRateModeIt->second;
     if (current == min) {
@@ -854,11 +854,11 @@
 }
 
 const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const {
-    const auto& activeMode = *getActiveModeItLocked()->second;
+    const auto& activeMode = *getActiveModeLocked().modePtr;
 
     for (const FrameRateMode& mode : mPrimaryFrameRates) {
         if (activeMode.getGroup() == mode.modePtr->getGroup()) {
-            return mode.modePtr;
+            return mode.modePtr.get();
         }
     }
 
@@ -866,12 +866,12 @@
           to_string(activeMode).c_str());
 
     // Default to the lowest refresh rate.
-    return mPrimaryFrameRates.front().modePtr;
+    return mPrimaryFrameRates.front().modePtr.get();
 }
 
 const DisplayModePtr& RefreshRateSelector::getMaxRefreshRateByPolicyLocked(int anchorGroup) const {
-    const DisplayModePtr* maxByAnchor = &mPrimaryFrameRates.back().modePtr;
-    const DisplayModePtr* max = &mPrimaryFrameRates.back().modePtr;
+    const ftl::NonNull<DisplayModePtr>* maxByAnchor = &mPrimaryFrameRates.back().modePtr;
+    const ftl::NonNull<DisplayModePtr>* max = &mPrimaryFrameRates.back().modePtr;
 
     bool maxByAnchorFound = false;
     for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) {
@@ -888,13 +888,13 @@
     }
 
     if (maxByAnchorFound) {
-        return *maxByAnchor;
+        return maxByAnchor->get();
     }
 
     ALOGE("Can't find max refresh rate by policy with the same group %d", anchorGroup);
 
     // Default to the highest refresh rate.
-    return *max;
+    return max->get();
 }
 
 auto RefreshRateSelector::rankFrameRates(std::optional<int> anchorGroupOpt,
@@ -946,31 +946,26 @@
     return rankFrameRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
 }
 
-DisplayModePtr RefreshRateSelector::getActiveModePtr() const {
+FrameRateMode RefreshRateSelector::getActiveMode() const {
     std::lock_guard lock(mLock);
-    return getActiveModeItLocked()->second;
+    return getActiveModeLocked();
 }
 
-const DisplayMode& RefreshRateSelector::getActiveMode() const {
-    // Reads from kMainThreadContext do not require mLock.
-    ftl::FakeGuard guard(mLock);
-    return *mActiveModeIt->second;
+const FrameRateMode& RefreshRateSelector::getActiveModeLocked() const {
+    return *mActiveModeOpt;
 }
 
-DisplayModeIterator RefreshRateSelector::getActiveModeItLocked() const {
-    // Reads under mLock do not require kMainThreadContext.
-    return FTL_FAKE_GUARD(kMainThreadContext, mActiveModeIt);
-}
-
-void RefreshRateSelector::setActiveModeId(DisplayModeId modeId) {
+void RefreshRateSelector::setActiveMode(DisplayModeId modeId, Fps renderFrameRate) {
     std::lock_guard lock(mLock);
 
     // Invalidate the cached invocation to getRankedFrameRates. This forces
     // the refresh rate to be recomputed on the next call to getRankedFrameRates.
     mGetRankedFrameRatesCache.reset();
 
-    mActiveModeIt = mDisplayModes.find(modeId);
-    LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
+    const auto activeModeOpt = mDisplayModes.get(modeId);
+    LOG_ALWAYS_FATAL_IF(!activeModeOpt);
+
+    mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())});
 }
 
 RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId,
@@ -1007,8 +1002,10 @@
     mGetRankedFrameRatesCache.reset();
 
     mDisplayModes = std::move(modes);
-    mActiveModeIt = mDisplayModes.find(activeModeId);
-    LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
+    const auto activeModeOpt = mDisplayModes.get(activeModeId);
+    LOG_ALWAYS_FATAL_IF(!activeModeOpt);
+    mActiveModeOpt =
+            FrameRateMode{activeModeOpt->get()->getFps(), ftl::as_non_null(activeModeOpt->get())};
 
     const auto sortedModes = sortByRefreshRate(mDisplayModes);
     mMinRefreshRateModeIt = sortedModes.front();
@@ -1064,6 +1061,7 @@
 
 auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult {
     Policy oldPolicy;
+    PhysicalDisplayId displayId;
     {
         std::lock_guard lock(mLock);
         oldPolicy = *getCurrentPolicyLocked();
@@ -1103,9 +1101,10 @@
             return SetPolicyResult::Unchanged;
         }
         constructAvailableRefreshRates();
+
+        displayId = getActiveModeLocked().modePtr->getPhysicalDisplayId();
     }
 
-    const auto displayId = getActiveMode().getPhysicalDisplayId();
     const unsigned numModeChanges = std::exchange(mNumModeSwitchesInPolicy, 0u);
 
     ALOGI("Display %s policy changed\n"
@@ -1132,12 +1131,10 @@
     return mDisplayManagerPolicy;
 }
 
-bool RefreshRateSelector::isModeAllowed(DisplayModeId modeId) const {
+bool RefreshRateSelector::isModeAllowed(const FrameRateMode& mode) const {
     std::lock_guard lock(mLock);
-    return std::any_of(mAppRequestFrameRates.begin(), mAppRequestFrameRates.end(),
-                       [modeId](const FrameRateMode& frameRateMode) {
-                           return frameRateMode.modePtr->getId() == modeId;
-                       });
+    return std::find(mAppRequestFrameRates.begin(), mAppRequestFrameRates.end(), mode) !=
+            mAppRequestFrameRates.end();
 }
 
 void RefreshRateSelector::constructAvailableRefreshRates() {
@@ -1211,7 +1208,7 @@
     }
 
     const DisplayModePtr& maxByPolicy =
-            getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup());
+            getMaxRefreshRateByPolicyLocked(getActiveModeLocked().modePtr->getGroup());
     if (minByPolicy == maxByPolicy) {
         // Turn on the timer when the min of the primary range is below the device min.
         if (const Policy* currentPolicy = getCurrentPolicyLocked();
@@ -1257,8 +1254,8 @@
 
     std::lock_guard lock(mLock);
 
-    const auto activeModeId = getActiveModeItLocked()->first;
-    dumper.dump("activeModeId"sv, std::to_string(activeModeId.value()));
+    const auto activeMode = getActiveModeLocked();
+    dumper.dump("activeMode"sv, to_string(activeMode));
 
     dumper.dump("displayModes"sv);
     {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 89ebeea..1ed16c6 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -133,7 +133,7 @@
     Policy getDisplayManagerPolicy() const EXCLUDES(mLock);
 
     // Returns true if mode is allowed by the current policy.
-    bool isModeAllowed(DisplayModeId) const EXCLUDES(mLock);
+    bool isModeAllowed(const FrameRateMode&) const EXCLUDES(mLock);
 
     // Describes the different options the layer voted for refresh rate
     enum class LayerVoteType {
@@ -243,11 +243,10 @@
     std::optional<Fps> onKernelTimerChanged(std::optional<DisplayModeId> desiredActiveModeId,
                                             bool timerExpired) const EXCLUDES(mLock);
 
-    void setActiveModeId(DisplayModeId) EXCLUDES(mLock) REQUIRES(kMainThreadContext);
+    void setActiveMode(DisplayModeId, Fps renderFrameRate) EXCLUDES(mLock);
 
-    // See mActiveModeIt for thread safety.
-    DisplayModePtr getActiveModePtr() const EXCLUDES(mLock);
-    const DisplayMode& getActiveMode() const REQUIRES(kMainThreadContext);
+    // See mActiveModeOpt for thread safety.
+    FrameRateMode getActiveMode() const EXCLUDES(mLock);
 
     // Returns a known frame rate that is the closest to frameRate
     Fps findClosestKnownFrameRate(Fps frameRate) const;
@@ -399,8 +398,8 @@
 
     void constructAvailableRefreshRates() REQUIRES(mLock);
 
-    // See mActiveModeIt for thread safety.
-    DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock);
+    // See mActiveModeOpt for thread safety.
+    const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock);
 
     RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
                                                GlobalSignals signals) const REQUIRES(mLock);
@@ -478,9 +477,7 @@
     // when FrameRateOverride::AppOverrideNativeRefreshRates is in use.
     ftl::SmallMap<Fps, ftl::Unit, 8, FpsApproxEqual> mAppOverrideNativeRefreshRates;
 
-    // Written under mLock exclusively from kMainThreadContext, so reads from kMainThreadContext
-    // need not be under mLock.
-    DisplayModeIterator mActiveModeIt GUARDED_BY(mLock) GUARDED_BY(kMainThreadContext);
+    ftl::Optional<FrameRateMode> mActiveModeOpt GUARDED_BY(mLock);
 
     DisplayModeIterator mMinRefreshRateModeIt GUARDED_BY(mLock);
     DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock);
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 0c541f9..7f8f600 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -174,7 +174,7 @@
 
 impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
     return [this](uid_t uid) {
-        const Fps refreshRate = leaderSelectorPtr()->getActiveModePtr()->getFps();
+        const Fps refreshRate = leaderSelectorPtr()->getActiveMode().fps;
         const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs();
 
         const auto frameRate = getFrameRateOverride(uid);
@@ -282,7 +282,7 @@
     thread->onFrameRateOverridesChanged(displayId, std::move(overrides));
 }
 
-void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
+void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
     {
         std::lock_guard<std::mutex> lock(mPolicyLock);
         // Cache the last reported modes for primary display.
@@ -297,7 +297,7 @@
 
 void Scheduler::dispatchCachedReportedMode() {
     // Check optional fields first.
-    if (!mPolicy.mode) {
+    if (!mPolicy.modeOpt) {
         ALOGW("No mode ID found, not dispatching cached mode.");
         return;
     }
@@ -309,28 +309,28 @@
     // If the mode is not the current mode, this means that a
     // mode change is in progress. In that case we shouldn't dispatch an event
     // as it will be dispatched when the current mode changes.
-    if (leaderSelectorPtr()->getActiveModePtr() != mPolicy.mode) {
+    if (leaderSelectorPtr()->getActiveMode() != mPolicy.modeOpt) {
         return;
     }
 
     // If there is no change from cached mode, there is no need to dispatch an event
-    if (mPolicy.mode == mPolicy.cachedModeChangedParams->mode) {
+    if (*mPolicy.modeOpt == mPolicy.cachedModeChangedParams->mode) {
         return;
     }
 
-    mPolicy.cachedModeChangedParams->mode = mPolicy.mode;
+    mPolicy.cachedModeChangedParams->mode = *mPolicy.modeOpt;
     onNonPrimaryDisplayModeChanged(mPolicy.cachedModeChangedParams->handle,
                                    mPolicy.cachedModeChangedParams->mode);
 }
 
-void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
+void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
     android::EventThread* thread;
     {
         std::lock_guard<std::mutex> lock(mConnectionsLock);
         RETURN_IF_INVALID_HANDLE(handle);
         thread = mConnections[handle].thread.get();
     }
-    thread->onModeChanged(mode);
+    thread->onModeChanged(mode.modePtr.get());
 }
 
 size_t Scheduler::getEventThreadConnectionCount(ConnectionHandle handle) {
@@ -395,6 +395,24 @@
     setVsyncPeriod(refreshRate.getPeriodNsecs());
 }
 
+void Scheduler::setRenderRate(Fps renderFrameRate) {
+    const auto mode = leaderSelectorPtr()->getActiveMode();
+
+    using fps_approx_ops::operator!=;
+    LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps,
+                        "Mismatch in render frame rates. Selector: %s, Scheduler: %s",
+                        to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str());
+
+    ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
+          to_string(mode.modePtr->getFps()).c_str());
+
+    const auto divisor = RefreshRateSelector::getFrameRateDivisor(mode.modePtr->getFps(), mode.fps);
+    LOG_ALWAYS_FATAL_IF(divisor == 0, "%s <> %s -- not divisors", to_string(mode.fps).c_str(),
+                        to_string(mode.fps).c_str());
+
+    mVsyncSchedule->getTracker().setDivisor(static_cast<unsigned>(divisor));
+}
+
 void Scheduler::resync() {
     static constexpr nsecs_t kIgnoreDelay = ms2ns(750);
 
@@ -402,7 +420,7 @@
     const nsecs_t last = mLastResyncTime.exchange(now);
 
     if (now - last > kIgnoreDelay) {
-        const auto refreshRate = leaderSelectorPtr()->getActiveModePtr()->getFps();
+        const auto refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps();
         resyncToHardwareVsync(false, refreshRate);
     }
 }
@@ -517,7 +535,7 @@
 
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
-    const Fps refreshRate = leaderSelectorPtr()->getActiveModePtr()->getFps();
+    const Fps refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps();
 
     constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz;
     using namespace fps_approx_ops;
@@ -657,7 +675,7 @@
         currentState = std::forward<T>(newState);
 
         DisplayModeChoiceMap modeChoices;
-        DisplayModePtr modePtr;
+        ftl::Optional<FrameRateMode> modeOpt;
         {
             std::scoped_lock lock(mDisplayLock);
             ftl::FakeGuard guard(kMainThreadContext);
@@ -666,10 +684,10 @@
 
             // TODO(b/240743786): The leader display's mode must change for any DisplayModeRequest
             // to go through. Fix this by tracking per-display Scheduler::Policy and timers.
-            std::tie(modePtr, consideredSignals) =
+            std::tie(modeOpt, consideredSignals) =
                     modeChoices.get(*mLeaderDisplayId)
                             .transform([](const DisplayModeChoice& choice) {
-                                return std::make_pair(choice.modePtr, choice.consideredSignals);
+                                return std::make_pair(choice.mode, choice.consideredSignals);
                             })
                             .value();
         }
@@ -677,15 +695,14 @@
         modeRequests.reserve(modeChoices.size());
         for (auto& [id, choice] : modeChoices) {
             modeRequests.emplace_back(
-                    display::DisplayModeRequest{.modePtr =
-                                                        ftl::as_non_null(std::move(choice.modePtr)),
+                    display::DisplayModeRequest{.mode = std::move(choice.mode),
                                                 .emitEvent = !choice.consideredSignals.idle});
         }
 
-        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modePtr->getFps());
+        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modeOpt->fps);
 
-        if (mPolicy.mode != modePtr) {
-            mPolicy.mode = modePtr;
+        if (mPolicy.modeOpt != modeOpt) {
+            mPolicy.modeOpt = modeOpt;
             refreshRateChanged = true;
         } else {
             // We don't need to change the display mode, but we might need to send an event
@@ -725,12 +742,11 @@
     const auto globalSignals = makeGlobalSignals();
 
     for (const auto& [id, selectorPtr] : mRefreshRateSelectors) {
-        auto rankedRefreshRates =
+        auto rankedFrameRates =
                 selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals);
 
-        for (const auto& [frameRateMode, score] : rankedRefreshRates.ranking) {
-            const auto& modePtr = frameRateMode.modePtr;
-            const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score);
+        for (const auto& [frameRateMode, score] : rankedFrameRates.ranking) {
+            const auto [it, inserted] = refreshRateTallies.try_emplace(frameRateMode.fps, score);
 
             if (!inserted) {
                 auto& tally = it->second;
@@ -739,7 +755,7 @@
             }
         }
 
-        perDisplayRanking.push_back(std::move(rankedRefreshRates));
+        perDisplayRanking.push_back(std::move(rankedFrameRates));
     }
 
     auto maxScoreIt = refreshRateTallies.cbegin();
@@ -773,17 +789,15 @@
     for (auto& [ranking, signals] : perDisplayRanking) {
         if (!chosenFps) {
             const auto& [frameRateMode, _] = ranking.front();
-            const auto& modePtr = frameRateMode.modePtr;
-            modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
-                                    DisplayModeChoice{modePtr, signals});
+            modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(),
+                                    DisplayModeChoice{frameRateMode, signals});
             continue;
         }
 
         for (auto& [frameRateMode, _] : ranking) {
-            const auto& modePtr = frameRateMode.modePtr;
-            if (modePtr->getFps() == *chosenFps) {
-                modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
-                                        DisplayModeChoice{modePtr, signals});
+            if (frameRateMode.fps == *chosenFps) {
+                modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(),
+                                        DisplayModeChoice{frameRateMode, signals});
                 break;
             }
         }
@@ -801,18 +815,18 @@
             .powerOnImminent = powerOnImminent};
 }
 
-DisplayModePtr Scheduler::getPreferredDisplayMode() {
+ftl::Optional<FrameRateMode> Scheduler::getPreferredDisplayMode() {
     std::lock_guard<std::mutex> lock(mPolicyLock);
     // Make sure the stored mode is up to date.
-    if (mPolicy.mode) {
+    if (mPolicy.modeOpt) {
         const auto ranking =
                 leaderSelectorPtr()
                         ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals())
                         .ranking;
 
-        mPolicy.mode = ranking.front().frameRateMode.modePtr;
+        mPolicy.modeOpt = ranking.front().frameRateMode;
     }
-    return mPolicy.mode;
+    return mPolicy.modeOpt;
 }
 
 void Scheduler::onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index fb23071..cf2ffb8 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -149,8 +149,8 @@
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
     void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
-    void onPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr) EXCLUDES(mPolicyLock);
-    void onNonPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr);
+    void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock);
+    void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&);
     void onScreenAcquired(ConnectionHandle);
     void onScreenReleased(ConnectionHandle);
 
@@ -161,6 +161,9 @@
     void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration,
                      std::chrono::nanoseconds readyDuration);
 
+    // Sets the render rate for the scheduler to run at.
+    void setRenderRate(Fps);
+
     void enableHardwareVsync();
     void disableHardwareVsync(bool makeUnavailable);
 
@@ -207,7 +210,7 @@
     void dumpVsync(std::string&) const;
 
     // Get the appropriate refresh for current conditions.
-    DisplayModePtr getPreferredDisplayMode();
+    ftl::Optional<FrameRateMode> getPreferredDisplayMode();
 
     // Notifies the scheduler about a refresh rate timeline change.
     void onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline);
@@ -235,7 +238,7 @@
     std::optional<Fps> getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock);
 
     nsecs_t getLeaderVsyncPeriod() const EXCLUDES(mDisplayLock) {
-        return leaderSelectorPtr()->getActiveModePtr()->getFps().getPeriodNsecs();
+        return leaderSelectorPtr()->getActiveMode().fps.getPeriodNsecs();
     }
 
     // Returns the framerate of the layer with the given sequence ID
@@ -283,19 +286,19 @@
     GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock);
 
     struct DisplayModeChoice {
-        DisplayModeChoice(DisplayModePtr modePtr, GlobalSignals consideredSignals)
-              : modePtr(std::move(modePtr)), consideredSignals(consideredSignals) {}
+        DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals)
+              : mode(std::move(mode)), consideredSignals(consideredSignals) {}
 
-        DisplayModePtr modePtr;
+        FrameRateMode mode;
         GlobalSignals consideredSignals;
 
         bool operator==(const DisplayModeChoice& other) const {
-            return modePtr == other.modePtr && consideredSignals == other.consideredSignals;
+            return mode == other.mode && consideredSignals == other.consideredSignals;
         }
 
         // For tests.
         friend std::ostream& operator<<(std::ostream& stream, const DisplayModeChoice& choice) {
-            return stream << '{' << to_string(*choice.modePtr) << " considering "
+            return stream << '{' << to_string(*choice.mode.modePtr) << " considering "
                           << choice.consideredSignals.toString().c_str() << '}';
         }
     };
@@ -382,11 +385,11 @@
         hal::PowerMode displayPowerMode = hal::PowerMode::ON;
 
         // Chosen display mode.
-        DisplayModePtr mode;
+        ftl::Optional<FrameRateMode> modeOpt;
 
         struct ModeChangedParams {
             ConnectionHandle handle;
-            DisplayModePtr mode;
+            FrameRateMode mode;
         };
 
         // Parameters for latest dispatch of mode change event.
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 0ad4236..ed4d25e 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -253,7 +253,13 @@
 
 nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
     std::lock_guard lock(mMutex);
-    return nextAnticipatedVSyncTimeFromLocked(timePoint);
+
+    // TODO(b/246164114): This implementation is not efficient at all. Refactor.
+    nsecs_t nextVsync = nextAnticipatedVSyncTimeFromLocked(timePoint);
+    while (!isVSyncInPhaseLocked(nextVsync, mDivisor)) {
+        nextVsync = nextAnticipatedVSyncTimeFromLocked(nextVsync + 1);
+    }
+    return nextVsync;
 }
 
 /*
@@ -265,6 +271,13 @@
  * isVSyncInPhase(50.0, 30) = true
  */
 bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const {
+    std::lock_guard lock(mMutex);
+    const auto divisor =
+            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate);
+    return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(divisor));
+}
+
+bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const {
     struct VsyncError {
         nsecs_t vsyncTimestamp;
         float error;
@@ -272,9 +285,6 @@
         bool operator<(const VsyncError& other) const { return error < other.error; }
     };
 
-    std::lock_guard lock(mMutex);
-    const auto divisor =
-            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate);
     if (divisor <= 1 || timePoint == 0) {
         return true;
     }
@@ -312,6 +322,12 @@
     return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2;
 }
 
+void VSyncPredictor::setDivisor(unsigned divisor) {
+    ALOGV("%s: %d", __func__, divisor);
+    std::lock_guard lock(mMutex);
+    mDivisor = divisor;
+}
+
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
     std::lock_guard lock(mMutex);
     const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 3181102..4a3ba67 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -67,6 +67,8 @@
 
     bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex);
 
+    void setDivisor(unsigned divisor) final EXCLUDES(mMutex);
+
     void dump(std::string& result) const final EXCLUDES(mMutex);
 
 private:
@@ -89,6 +91,8 @@
 
     nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex);
 
+    bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex);
+
     nsecs_t mIdealPeriod GUARDED_BY(mMutex);
     std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);
 
@@ -100,6 +104,8 @@
 
     size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
+
+    unsigned mDivisor GUARDED_BY(mMutex) = 1;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 76315d2..8d1629f 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -79,6 +79,17 @@
      */
     virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0;
 
+    /*
+     * Sets a divisor on the rate (which is a multiplier of the period).
+     * The tracker will continue to track the vsync timeline and expect it
+     * to match the current period, however, nextAnticipatedVSyncTimeFrom will
+     * return vsyncs according to the divisor set. Setting a divisor is useful
+     * when a display is running at 120Hz but the render frame rate is 60Hz.
+     *
+     * \param [in] divisor   The rate divisor the tracker should operate at.
+     */
+    virtual void setDivisor(unsigned divisor) = 0;
+
     virtual void dump(std::string& result) const = 0;
 
 protected:
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
index 670ab45..db38ebe 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <ftl/non_null.h>
 #include <scheduler/Fps.h>
 
 // TODO(b/241285191): Pull this to <ui/DisplayMode.h>
@@ -25,7 +26,7 @@
 
 struct FrameRateMode {
     Fps fps; // The render frame rate, which is a divisor of modePtr->getFps().
-    DisplayModePtr modePtr;
+    ftl::NonNull<DisplayModePtr> modePtr;
 
     bool operator==(const FrameRateMode& other) const {
         return isApproxEqual(fps, other.fps) && modePtr == other.modePtr;
@@ -35,10 +36,7 @@
 };
 
 inline std::string to_string(const FrameRateMode& mode) {
-    if (mode.modePtr) {
-        return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")";
-    }
-    return "{invalid}";
+    return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")";
 }
 
-} // namespace android::scheduler
\ No newline at end of file
+} // namespace android::scheduler