SurfaceFlinger: Consider resolution when switching refresh rate

Do not change refresh rate across configs with multiple resolutions

Bug: 150237118
Test: adb shell /data/nativetest64/libsurfaceflinger_unittest/libsurfaceflinger_unittest
Change-Id: I9b0f355a72343cf120229b98e6ad082ea75d3080
diff --git a/services/surfaceflinger/Scheduler/HwcStrongTypes.h b/services/surfaceflinger/Scheduler/HwcStrongTypes.h
index cfbbdfe..8ba4f20 100644
--- a/services/surfaceflinger/Scheduler/HwcStrongTypes.h
+++ b/services/surfaceflinger/Scheduler/HwcStrongTypes.h
@@ -22,6 +22,5 @@
 
 // Strong types for the different indexes as they are referring to a different base.
 using HwcConfigIndexType = StrongTyping<int, struct HwcConfigIndexTypeTag, Compare, Add, Hash>;
-using HwcConfigGroupType = StrongTyping<int, struct HwcConfigGroupTypeTag, Compare>;
 
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/PhaseOffsets.cpp b/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
index 43883fb..d9aaa05 100644
--- a/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
+++ b/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
@@ -43,7 +43,7 @@
     refreshRates.reserve(allRefreshRates.size());
 
     for (const auto& [ignored, refreshRate] : allRefreshRates) {
-        refreshRates.emplace_back(refreshRate->fps);
+        refreshRates.emplace_back(refreshRate->getFps());
     }
 
     return refreshRates;
@@ -59,7 +59,7 @@
 
 PhaseOffsets::PhaseOffsets(const scheduler::RefreshRateConfigs& refreshRateConfigs)
       : PhaseOffsets(getRefreshRatesFromConfigs(refreshRateConfigs),
-                     refreshRateConfigs.getCurrentRefreshRate().fps,
+                     refreshRateConfigs.getCurrentRefreshRate().getFps(),
                      // Below defines the threshold when an offset is considered to be negative,
                      // i.e. targeting for the N+2 vsync instead of N+1. This means that: For offset
                      // < threshold, SF wake up (vsync_duration - offset) before HW vsync. For
@@ -275,7 +275,7 @@
 
 PhaseDurations::PhaseDurations(const scheduler::RefreshRateConfigs& refreshRateConfigs)
       : PhaseDurations(getRefreshRatesFromConfigs(refreshRateConfigs),
-                       refreshRateConfigs.getCurrentRefreshRate().fps,
+                       refreshRateConfigs.getCurrentRefreshRate().getFps(),
                        getProperty("debug.sf.late.sf.duration").value_or(-1),
                        getProperty("debug.sf.late.app.duration").value_or(-1),
                        getProperty("debug.sf.early.sf.duration").value_or(mSfDuration),
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 14ef733..5634adb 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -53,7 +53,7 @@
     if (explicitContentFramerate != 0) {
         contentFramerate = explicitContentFramerate;
     } else if (contentFramerate == 0) {
-        contentFramerate = round<int>(mMaxSupportedRefreshRate->fps);
+        contentFramerate = round<int>(mMaxSupportedRefreshRate->getFps());
     }
     ATRACE_INT("ContentFPS", contentFramerate);
 
@@ -177,7 +177,7 @@
                 continue;
             }
 
-            const auto displayPeriod = scores[i].first->vsyncPeriod;
+            const auto displayPeriod = scores[i].first->hwcConfig->getVsyncPeriod();
             const auto layerPeriod = round<nsecs_t>(1e9f / layer.desiredRefreshRate);
             if (layer.vote == LayerVoteType::ExplicitDefault) {
                 const auto layerScore = [&]() {
@@ -309,21 +309,30 @@
     mCurrentRefreshRate = mRefreshRates.at(configId).get();
 }
 
-RefreshRateConfigs::RefreshRateConfigs(const std::vector<InputConfig>& configs,
-                                       HwcConfigIndexType currentHwcConfig) {
-    init(configs, currentHwcConfig);
-}
-
 RefreshRateConfigs::RefreshRateConfigs(
         const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
         HwcConfigIndexType currentConfigId) {
-    std::vector<InputConfig> inputConfigs;
-    for (size_t configId = 0; configId < configs.size(); ++configId) {
-        auto configGroup = HwcConfigGroupType(configs[configId]->getConfigGroup());
-        inputConfigs.push_back({HwcConfigIndexType(static_cast<int>(configId)), configGroup,
-                                configs[configId]->getVsyncPeriod()});
+    LOG_ALWAYS_FATAL_IF(configs.empty());
+    LOG_ALWAYS_FATAL_IF(currentConfigId.value() >= configs.size());
+
+    for (auto configId = HwcConfigIndexType(0); configId.value() < configs.size(); configId++) {
+        const auto& config = configs.at(static_cast<size_t>(configId.value()));
+        const float fps = 1e9f / config->getVsyncPeriod();
+        mRefreshRates.emplace(configId,
+                              std::make_unique<RefreshRate>(configId, config,
+                                                            base::StringPrintf("%2.ffps", fps), fps,
+                                                            RefreshRate::ConstructorTag(0)));
+        if (configId == currentConfigId) {
+            mCurrentRefreshRate = mRefreshRates.at(configId).get();
+        }
     }
-    init(inputConfigs, currentConfigId);
+
+    std::vector<const RefreshRate*> sortedConfigs;
+    getSortedRefreshRateList([](const RefreshRate&) { return true; }, &sortedConfigs);
+    mDisplayManagerPolicy.defaultConfig = currentConfigId;
+    mMinSupportedRefreshRate = sortedConfigs.front();
+    mMaxSupportedRefreshRate = sortedConfigs.back();
+    constructAvailableRefreshRates();
 }
 
 bool RefreshRateConfigs::isPolicyValid(const Policy& policy) {
@@ -406,10 +415,13 @@
 
     std::sort(outRefreshRates->begin(), outRefreshRates->end(),
               [](const auto refreshRate1, const auto refreshRate2) {
-                  if (refreshRate1->vsyncPeriod != refreshRate2->vsyncPeriod) {
-                      return refreshRate1->vsyncPeriod > refreshRate2->vsyncPeriod;
+                  if (refreshRate1->hwcConfig->getVsyncPeriod() !=
+                      refreshRate2->hwcConfig->getVsyncPeriod()) {
+                      return refreshRate1->hwcConfig->getVsyncPeriod() >
+                              refreshRate2->hwcConfig->getVsyncPeriod();
                   } else {
-                      return refreshRate1->configGroup > refreshRate2->configGroup;
+                      return refreshRate1->hwcConfig->getConfigGroup() >
+                              refreshRate2->hwcConfig->getConfigGroup();
                   }
               });
 }
@@ -417,13 +429,20 @@
 void RefreshRateConfigs::constructAvailableRefreshRates() {
     // Filter configs based on current policy and sort based on vsync period
     const Policy* policy = getCurrentPolicyLocked();
-    HwcConfigGroupType group = mRefreshRates.at(policy->defaultConfig)->configGroup;
+    const auto& defaultConfig = mRefreshRates.at(policy->defaultConfig)->hwcConfig;
     ALOGV("constructAvailableRefreshRates: default %d group %d min %.2f max %.2f",
-          policy->defaultConfig.value(), group.value(), policy->minRefreshRate,
+          policy->defaultConfig.value(), defaultConfig->getConfigGroup(), policy->minRefreshRate,
           policy->maxRefreshRate);
     getSortedRefreshRateList(
             [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
-                return (policy->allowGroupSwitching || refreshRate.configGroup == group) &&
+                const auto& hwcConfig = refreshRate.hwcConfig;
+
+                return hwcConfig->getHeight() == defaultConfig->getHeight() &&
+                        hwcConfig->getWidth() == defaultConfig->getWidth() &&
+                        hwcConfig->getDpiX() == defaultConfig->getDpiX() &&
+                        hwcConfig->getDpiY() == defaultConfig->getDpiY() &&
+                        (policy->allowGroupSwitching ||
+                         hwcConfig->getConfigGroup() == defaultConfig->getConfigGroup()) &&
                         refreshRate.inPolicy(policy->minRefreshRate, policy->maxRefreshRate);
             },
             &mAvailableRefreshRates);
@@ -440,30 +459,4 @@
                         policy->maxRefreshRate);
 }
 
-// NO_THREAD_SAFETY_ANALYSIS since this is called from the constructor
-void RefreshRateConfigs::init(const std::vector<InputConfig>& configs,
-                              HwcConfigIndexType currentHwcConfig) NO_THREAD_SAFETY_ANALYSIS {
-    LOG_ALWAYS_FATAL_IF(configs.empty());
-    LOG_ALWAYS_FATAL_IF(currentHwcConfig.value() >= configs.size());
-
-    for (const auto& config : configs) {
-        const float fps = 1e9f / config.vsyncPeriod;
-        mRefreshRates.emplace(config.configId,
-                              std::make_unique<RefreshRate>(config.configId, config.vsyncPeriod,
-                                                            config.configGroup,
-                                                            base::StringPrintf("%2.ffps", fps),
-                                                            fps));
-        if (config.configId == currentHwcConfig) {
-            mCurrentRefreshRate = mRefreshRates.at(config.configId).get();
-        }
-    }
-
-    std::vector<const RefreshRate*> sortedConfigs;
-    getSortedRefreshRateList([](const RefreshRate&) { return true; }, &sortedConfigs);
-    mDisplayManagerPolicy.defaultConfig = currentHwcConfig;
-    mMinSupportedRefreshRate = sortedConfigs.front();
-    mMaxSupportedRefreshRate = sortedConfigs.back();
-    constructAvailableRefreshRates();
-}
-
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index e749f8f..dea7e90 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -29,6 +29,8 @@
 #include "Scheduler/StrongTyping.h"
 
 namespace android::scheduler {
+class RefreshRateConfigsTest;
+
 using namespace std::chrono_literals;
 
 enum class RefreshRateConfigEvent : unsigned { None = 0b0, Changed = 0b1 };
@@ -49,30 +51,27 @@
     static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION =
         std::chrono::nanoseconds(800us).count();
 
-    struct RefreshRate {
-        // The tolerance within which we consider FPS approximately equals.
-        static constexpr float FPS_EPSILON = 0.001f;
+    class RefreshRate {
+    private:
+        // Effectively making the constructor private while allowing
+        // std::make_unique to create the object
+        struct ConstructorTag {
+            explicit ConstructorTag(int) {}
+        };
 
-        RefreshRate(HwcConfigIndexType configId, nsecs_t vsyncPeriod,
-                    HwcConfigGroupType configGroup, std::string name, float fps)
-              : configId(configId),
-                vsyncPeriod(vsyncPeriod),
-                configGroup(configGroup),
-                name(std::move(name)),
-                fps(fps) {}
+    public:
+        RefreshRate(HwcConfigIndexType configId,
+                    std::shared_ptr<const HWC2::Display::Config> config, std::string name,
+                    float fps, ConstructorTag)
+              : configId(configId), hwcConfig(config), name(std::move(name)), fps(fps) {}
 
         RefreshRate(const RefreshRate&) = delete;
-        // This config ID corresponds to the position of the config in the vector that is stored
-        // on the device.
-        const HwcConfigIndexType configId;
-        // Vsync period in nanoseconds.
-        const nsecs_t vsyncPeriod;
-        // This configGroup for the config.
-        const HwcConfigGroupType configGroup;
-        // Human readable name of the refresh rate.
-        const std::string name;
-        // Refresh rate in frames per second
-        const float fps = 0;
+
+        HwcConfigIndexType getConfigId() const { return configId; }
+        nsecs_t getVsyncPeriod() const { return hwcConfig->getVsyncPeriod(); }
+        int32_t getConfigGroup() const { return hwcConfig->getConfigGroup(); }
+        const std::string& getName() const { return name; }
+        float getFps() const { return fps; }
 
         // Checks whether the fps of this RefreshRate struct is within a given min and max refresh
         // rate passed in. FPS_EPSILON is applied to the boundaries for approximation.
@@ -81,11 +80,27 @@
         }
 
         bool operator!=(const RefreshRate& other) const {
-            return configId != other.configId || vsyncPeriod != other.vsyncPeriod ||
-                    configGroup != other.configGroup;
+            return configId != other.configId || hwcConfig != other.hwcConfig;
         }
 
         bool operator==(const RefreshRate& other) const { return !(*this != other); }
+
+    private:
+        friend RefreshRateConfigs;
+        friend RefreshRateConfigsTest;
+
+        // The tolerance within which we consider FPS approximately equals.
+        static constexpr float FPS_EPSILON = 0.001f;
+
+        // This config ID corresponds to the position of the config in the vector that is stored
+        // on the device.
+        const HwcConfigIndexType configId;
+        // The config itself
+        std::shared_ptr<const HWC2::Display::Config> hwcConfig;
+        // Human readable name of the refresh rate.
+        const std::string name;
+        // Refresh rate in frames per second
+        const float fps = 0;
     };
 
     using AllRefreshRatesMapType =
@@ -208,20 +223,10 @@
     // Stores the current configId the device operates at
     void setCurrentConfigId(HwcConfigIndexType configId) EXCLUDES(mLock);
 
-    struct InputConfig {
-        HwcConfigIndexType configId = HwcConfigIndexType(0);
-        HwcConfigGroupType configGroup = HwcConfigGroupType(0);
-        nsecs_t vsyncPeriod = 0;
-    };
-
-    RefreshRateConfigs(const std::vector<InputConfig>& configs,
-                       HwcConfigIndexType currentHwcConfig);
     RefreshRateConfigs(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
                        HwcConfigIndexType currentConfigId);
 
 private:
-    void init(const std::vector<InputConfig>& configs, HwcConfigIndexType currentHwcConfig);
-
     void constructAvailableRefreshRates() REQUIRES(mLock);
 
     void getSortedRefreshRateList(
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index e44cd52..66d4a03 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -78,10 +78,10 @@
         // Multiple configs may map to the same name, e.g. "60fps". Add the
         // times for such configs together.
         for (const auto& [configId, time] : mConfigModesTotalTime) {
-            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).name] = 0;
+            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).getName()] = 0;
         }
         for (const auto& [configId, time] : mConfigModesTotalTime) {
-            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).name] += time;
+            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).getName()] += time;
         }
         totalTime["ScreenOff"] = mScreenOffTime;
         return totalTime;
@@ -115,7 +115,7 @@
             }
             mConfigModesTotalTime[mCurrentConfigMode] += timeElapsedMs;
             fps = static_cast<uint32_t>(std::round(
-                    mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode).fps));
+                    mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode).getFps()));
         } else {
             mScreenOffTime += timeElapsedMs;
         }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index cd6075f..9a9523f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -332,7 +332,7 @@
     const nsecs_t last = mLastResyncTime.exchange(now);
 
     if (now - last > kIgnoreDelay) {
-        resyncToHardwareVsync(false, mRefreshRateConfigs.getCurrentRefreshRate().vsyncPeriod);
+        resyncToHardwareVsync(false, mRefreshRateConfigs.getCurrentRefreshRate().getVsyncPeriod());
     }
 }
 
@@ -389,34 +389,34 @@
     // keep the layer history, since we use it for other features (like Frame Rate API), so layers
     // still need to be registered.
     if (!mUseContentDetection) {
-        mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
-                                     mRefreshRateConfigs.getMaxRefreshRate().fps,
+        mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().getFps(),
+                                     mRefreshRateConfigs.getMaxRefreshRate().getFps(),
                                      scheduler::LayerHistory::LayerVoteType::NoVote);
         return;
     }
 
     // In V1 of content detection, all layers are registered as Heuristic (unless it's wallpaper).
     if (!mUseContentDetectionV2) {
-        const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().fps;
+        const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().getFps();
         const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
                 ? lowFps
-                : mRefreshRateConfigs.getMaxRefreshRate().fps;
+                : mRefreshRateConfigs.getMaxRefreshRate().getFps();
 
         mLayerHistory->registerLayer(layer, lowFps, highFps,
                                      scheduler::LayerHistory::LayerVoteType::Heuristic);
     } else {
         if (layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER) {
             // Running Wallpaper at Min is considered as part of content detection.
-            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
-                                         mRefreshRateConfigs.getMaxRefreshRate().fps,
+            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().getFps(),
+                                         mRefreshRateConfigs.getMaxRefreshRate().getFps(),
                                          scheduler::LayerHistory::LayerVoteType::Min);
         } else if (layer->getWindowType() == InputWindowInfo::TYPE_STATUS_BAR) {
-            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
-                                         mRefreshRateConfigs.getMaxRefreshRate().fps,
+            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().getFps(),
+                                         mRefreshRateConfigs.getMaxRefreshRate().getFps(),
                                          scheduler::LayerHistory::LayerVoteType::NoVote);
         } else {
-            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
-                                         mRefreshRateConfigs.getMaxRefreshRate().fps,
+            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().getFps(),
+                                         mRefreshRateConfigs.getMaxRefreshRate().getFps(),
                                          scheduler::LayerHistory::LayerVoteType::Heuristic);
         }
     }
@@ -503,12 +503,13 @@
     // magic number
     const auto& refreshRate = mRefreshRateConfigs.getCurrentRefreshRate();
     constexpr float FPS_THRESHOLD_FOR_KERNEL_TIMER = 65.0f;
-    if (state == TimerState::Reset && refreshRate.fps > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+    if (state == TimerState::Reset && refreshRate.getFps() > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // If we're not in performance mode then the kernel timer shouldn't do
         // anything, as the refresh rate during DPU power collapse will be the
         // same.
-        resyncToHardwareVsync(true /* makeAvailable */, refreshRate.vsyncPeriod);
-    } else if (state == TimerState::Expired && refreshRate.fps <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+        resyncToHardwareVsync(true /* makeAvailable */, refreshRate.getVsyncPeriod());
+    } else if (state == TimerState::Expired &&
+               refreshRate.getFps() <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // Disable HW VSYNC if the timer expired, as we don't need it enabled if
         // we're not pushing frames, and if we're in PERFORMANCE mode then we'll
         // need to update the DispSync model anyway.
@@ -580,30 +581,31 @@
     if (mDisplayPowerTimer &&
         (!mFeatures.isDisplayPowerStateNormal ||
          mFeatures.displayPowerTimer == TimerState::Reset)) {
-        return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
+        return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
     }
 
     if (!mUseContentDetectionV2) {
         // As long as touch is active we want to be in performance mode.
         if (mTouchTimer && mFeatures.touch == TouchState::Active) {
-            return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
+            return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
         }
     }
 
     // If timer has expired as it means there is no new content on the screen.
     if (mIdleTimer && mFeatures.idleTimer == TimerState::Expired) {
-        return mRefreshRateConfigs.getMinRefreshRateByPolicy().configId;
+        return mRefreshRateConfigs.getMinRefreshRateByPolicy().getConfigId();
     }
 
     if (!mUseContentDetectionV2) {
         // If content detection is off we choose performance as we don't know the content fps.
         if (mFeatures.contentDetectionV1 == ContentDetectionState::Off) {
             // NOTE: V1 always calls this, but this is not a default behavior for V2.
-            return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
+            return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
         }
 
         // Content detection is on, find the appropriate refresh rate with minimal error
-        return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements).configId;
+        return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements)
+                .getConfigId();
     }
 
     bool touchConsidered;
@@ -613,7 +615,7 @@
                                                 mTouchTimer &&
                                                         mFeatures.touch == TouchState::Active,
                                                 &touchConsidered)
-                    .configId;
+                    .getConfigId();
     if (touchConsidered) {
         // Clear layer history if refresh rate was selected based on touch to allow
         // the hueristic to pick up with the new rate.