SF: Simplify per-display refresh rate selection

Remove verbose, single-use helper functions/types to centralize the
selection logic and merge two passes. Avoid allocation and hashing.

Fix the algorithm to not choose a refresh rate based on total score
unless it is common to all displays, and not be thrown off by equal
scores.

Bug: 241285191
Test: libsurfaceflinger_unittest
Change-Id: I355dea767c6b564a04a51476f0cc235a1fceb879
diff --git a/services/surfaceflinger/Display/DisplayMap.h b/services/surfaceflinger/Display/DisplayMap.h
index baf0da9..0d59706 100644
--- a/services/surfaceflinger/Display/DisplayMap.h
+++ b/services/surfaceflinger/Display/DisplayMap.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <ftl/small_map.h>
+#include <ftl/small_vector.h>
 
 namespace android::display {
 
@@ -28,4 +29,7 @@
 template <typename Key, typename Value>
 using PhysicalDisplayMap = ftl::SmallMap<Key, Value, 3>;
 
+template <typename T>
+using PhysicalDisplayVector = ftl::SmallVector<T, 3>;
+
 } // namespace android::display
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 12949d6..30f2c27 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -26,6 +26,7 @@
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
 #include <configstore/Utils.h>
 #include <ftl/fake_guard.h>
+#include <ftl/small_map.h>
 #include <gui/WindowInfo.h>
 #include <system/window.h>
 #include <utils/Timers.h>
@@ -41,6 +42,7 @@
 
 #include "../Layer.h"
 #include "DispSyncSource.h"
+#include "Display/DisplayMap.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "OneShotTimer.h"
@@ -56,39 +58,6 @@
         }                                                            \
     } while (false)
 
-namespace {
-
-using android::Fps;
-using android::FpsApproxEqual;
-using android::FpsHash;
-using android::scheduler::AggregatedFpsScore;
-using android::scheduler::RefreshRateRankingsAndSignals;
-
-// Returns the aggregated score per Fps for the RefreshRateRankingsAndSignals sourced.
-auto getAggregatedScoresPerFps(
-        const std::vector<RefreshRateRankingsAndSignals>& refreshRateRankingsAndSignalsPerDisplay)
-        -> std::unordered_map<Fps, AggregatedFpsScore, FpsHash, FpsApproxEqual> {
-    std::unordered_map<Fps, AggregatedFpsScore, FpsHash, FpsApproxEqual> aggregatedScoresPerFps;
-
-    for (const auto& refreshRateRankingsAndSignal : refreshRateRankingsAndSignalsPerDisplay) {
-        const auto& refreshRateRankings = refreshRateRankingsAndSignal.refreshRateRankings;
-
-        std::for_each(refreshRateRankings.begin(), refreshRateRankings.end(), [&](const auto& it) {
-            const auto [score, result] =
-                    aggregatedScoresPerFps.try_emplace(it.displayModePtr->getFps(),
-                                                       AggregatedFpsScore{it.score,
-                                                                          /* numDisplays */ 1});
-            if (!result) { // update
-                score->second.totalScore += it.score;
-                score->second.numDisplays++;
-            }
-        });
-    }
-    return aggregatedScoresPerFps;
-}
-
-} // namespace
-
 namespace android::scheduler {
 
 Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features)
@@ -157,6 +126,15 @@
     mRefreshRateConfigs->startIdleTimer();
 }
 
+void Scheduler::registerDisplay(sp<const DisplayDevice> display) {
+    const bool ok = mDisplays.try_emplace(display->getPhysicalId(), std::move(display)).second;
+    ALOGE_IF(!ok, "%s: Duplicate display", __func__);
+}
+
+void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+    mDisplays.erase(displayId);
+}
+
 void Scheduler::run() {
     while (true) {
         waitMessage();
@@ -711,66 +689,86 @@
     return consideredSignals;
 }
 
-void Scheduler::registerDisplay(const sp<const DisplayDevice>& display) {
-    const bool ok = mDisplays.try_emplace(display->getPhysicalId(), display).second;
-    ALOGE_IF(!ok, "Duplicate display registered");
-}
-
-void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
-    mDisplays.erase(displayId);
-}
-
 std::vector<DisplayModeConfig> Scheduler::getBestDisplayModeConfigs() const {
     ATRACE_CALL();
 
-    std::vector<RefreshRateRankingsAndSignals> refreshRateRankingsAndSignalsPerDisplay;
-    refreshRateRankingsAndSignalsPerDisplay.reserve(mDisplays.size());
+    using Rankings = std::pair<std::vector<RefreshRateRanking>, GlobalSignals>;
+    display::PhysicalDisplayVector<Rankings> perDisplayRankings;
+
+    // Tallies the score of a refresh rate across `displayCount` displays.
+    struct RefreshRateTally {
+        explicit RefreshRateTally(float score) : score(score) {}
+
+        float score;
+        size_t displayCount = 1;
+    };
+
+    // Chosen to exceed a typical number of refresh rates across displays.
+    constexpr size_t kStaticCapacity = 8;
+    ftl::SmallMap<Fps, RefreshRateTally, kStaticCapacity, FpsApproxEqual> refreshRateTallies;
+
+    const auto globalSignals = makeGlobalSignals();
 
     for (const auto& [id, display] : mDisplays) {
-        const auto [rankings, signals] =
+        auto [rankings, signals] =
                 display->holdRefreshRateConfigs()
-                        ->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals());
+                        ->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
 
-        refreshRateRankingsAndSignalsPerDisplay.emplace_back(
-                RefreshRateRankingsAndSignals{rankings, signals});
+        for (const auto& [modePtr, score] : rankings) {
+            const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score);
+
+            if (!inserted) {
+                auto& tally = it->second;
+                tally.score += score;
+                tally.displayCount++;
+            }
+        }
+
+        perDisplayRankings.emplace_back(std::move(rankings), signals);
     }
 
-    // FPS and their Aggregated score.
-    std::unordered_map<Fps, AggregatedFpsScore, FpsHash, FpsApproxEqual> aggregatedScoresPerFps =
-            getAggregatedScoresPerFps(refreshRateRankingsAndSignalsPerDisplay);
+    auto maxScoreIt = refreshRateTallies.cbegin();
 
-    auto maxScoreIt = aggregatedScoresPerFps.cbegin();
-    // Selects the max Fps that is present on all the displays.
-    for (auto it = aggregatedScoresPerFps.cbegin(); it != aggregatedScoresPerFps.cend(); ++it) {
-        const auto [fps, aggregatedScore] = *it;
-        if (aggregatedScore.numDisplays == mDisplays.size() &&
-            aggregatedScore.totalScore >= maxScoreIt->second.totalScore) {
-            maxScoreIt = it;
+    // Find the first refresh rate common to all displays.
+    while (maxScoreIt != refreshRateTallies.cend() &&
+           maxScoreIt->second.displayCount != mDisplays.size()) {
+        ++maxScoreIt;
+    }
+
+    if (maxScoreIt != refreshRateTallies.cend()) {
+        // Choose the highest refresh rate common to all displays, if any.
+        for (auto it = maxScoreIt + 1; it != refreshRateTallies.cend(); ++it) {
+            const auto [fps, tally] = *it;
+
+            if (tally.displayCount == mDisplays.size() && tally.score > maxScoreIt->second.score) {
+                maxScoreIt = it;
+            }
         }
     }
-    return getDisplayModeConfigsForTheChosenFps(maxScoreIt->first,
-                                                refreshRateRankingsAndSignalsPerDisplay);
-}
 
-std::vector<DisplayModeConfig> Scheduler::getDisplayModeConfigsForTheChosenFps(
-        Fps chosenFps,
-        const std::vector<RefreshRateRankingsAndSignals>& refreshRateRankingsAndSignalsPerDisplay)
-        const {
+    const std::optional<Fps> chosenFps = maxScoreIt != refreshRateTallies.cend()
+            ? std::make_optional(maxScoreIt->first)
+            : std::nullopt;
+
     std::vector<DisplayModeConfig> displayModeConfigs;
     displayModeConfigs.reserve(mDisplays.size());
+
     using fps_approx_ops::operator==;
-    std::for_each(refreshRateRankingsAndSignalsPerDisplay.begin(),
-                  refreshRateRankingsAndSignalsPerDisplay.end(),
-                  [&](const auto& refreshRateRankingsAndSignal) {
-                      for (const auto& ranking : refreshRateRankingsAndSignal.refreshRateRankings) {
-                          if (ranking.displayModePtr->getFps() == chosenFps) {
-                              displayModeConfigs.emplace_back(
-                                      DisplayModeConfig{refreshRateRankingsAndSignal.globalSignals,
-                                                        ranking.displayModePtr});
-                              break;
-                          }
-                      }
-                  });
+
+    for (const auto& [rankings, signals] : perDisplayRankings) {
+        if (!chosenFps) {
+            displayModeConfigs.emplace_back(signals, rankings.front().displayModePtr);
+            continue;
+        }
+
+        for (const auto& ranking : rankings) {
+            const auto& modePtr = ranking.displayModePtr;
+            if (modePtr->getFps() == *chosenFps) {
+                displayModeConfigs.emplace_back(signals, modePtr);
+                break;
+            }
+        }
+    }
     return displayModeConfigs;
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 25fa714..6633b05 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -33,11 +33,12 @@
 #include <ui/GraphicTypes.h>
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
-#include <DisplayDevice.h>
 #include <scheduler/Features.h>
 #include <scheduler/Time.h>
+#include <ui/DisplayId.h>
 
 #include "Display/DisplayMap.h"
+#include "DisplayDevice.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "LayerHistory.h"
@@ -108,19 +109,6 @@
     ~ISchedulerCallback() = default;
 };
 
-// Holds the total score of the FPS and
-// number of displays the FPS is found in.
-struct AggregatedFpsScore {
-    float totalScore;
-    size_t numDisplays;
-};
-
-// Represents the RefreshRateRankings and GlobalSignals for the selected RefreshRateRankings.
-struct RefreshRateRankingsAndSignals {
-    std::vector<RefreshRateRanking> refreshRateRankings;
-    GlobalSignals globalSignals;
-};
-
 class Scheduler : android::impl::MessageQueue {
     using Impl = android::impl::MessageQueue;
 
@@ -132,6 +120,9 @@
     void setRefreshRateConfigs(std::shared_ptr<RefreshRateConfigs>)
             EXCLUDES(mRefreshRateConfigsLock);
 
+    void registerDisplay(sp<const DisplayDevice>);
+    void unregisterDisplay(PhysicalDisplayId);
+
     void run();
 
     void createVsyncSchedule(FeatureFlags);
@@ -257,9 +248,6 @@
         return mLayerHistory.getLayerFramerate(now, id);
     }
 
-    void registerDisplay(const sp<const DisplayDevice>&);
-    void unregisterDisplay(PhysicalDisplayId);
-
 private:
     friend class TestableScheduler;
 
@@ -293,10 +281,6 @@
     // Returns the best display mode per display.
     std::vector<DisplayModeConfig> getBestDisplayModeConfigs() const REQUIRES(mPolicyLock);
 
-    // Returns the list of DisplayModeConfigs per display for the chosenFps.
-    std::vector<DisplayModeConfig> getDisplayModeConfigsForTheChosenFps(
-            Fps chosenFps, const std::vector<RefreshRateRankingsAndSignals>&) const;
-
     GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock);
 
     bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock);
@@ -344,9 +328,7 @@
 
     mutable std::mutex mPolicyLock;
 
-    // Holds the Physical displays registered through the SurfaceFlinger, used for making
-    // the refresh rate selections.
-    display::PhysicalDisplayMap<PhysicalDisplayId, const sp<const DisplayDevice>> mDisplays;
+    display::PhysicalDisplayMap<PhysicalDisplayId, sp<const DisplayDevice>> mDisplays;
 
     struct Policy {
         // Policy for choosing the display mode.
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index 2c77142..bd4f409 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -138,10 +138,6 @@
     bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); }
 };
 
-struct FpsHash {
-    size_t operator()(Fps fps) const { return std::hash<nsecs_t>()(fps.getPeriodNsecs()); }
-};
-
 inline std::string to_string(Fps fps) {
     return base::StringPrintf("%.2f Hz", fps.getValue());
 }