SF: Match followers' refresh rate to pacesetter's
Multi-display refresh rate selection was flawed: The scheduler runs the
refresh rate selection algorithm for each display, and then filters the
candidate modes of each follower to match the pacesetter's refresh rate.
This means that:
1. The followers incorrectly consider refresh rates that don't match the
pacesetter. Because the DM-specified constraint is [59, 61] Hz, some
situations caused selection of a fractional rate (e.g. 59.94) instead
of 60 on certain external displays. The result would be black screens
for several seconds due to mode sets if the selection was not stable.
2. The followers incorrectly evaluate heuristics that should only affect
the pacesetter, e.g. per-surface votes, global signals.
Fix this by teaching RefreshRateSelector about follower displays.
Foldables also benefit from no longer running the algorithm twice.
Fixes: 324188430
Bug: 329111930
Test: No black screen for 4 seconds upon rotating mirrored YouTube.
Test: 60+60 still works on foldables.
Test: RefreshRateSelectorTest.pacesetterConsidered
Change-Id: Ie1b27e81d860a709c85651f068fedb2b496861de
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 56c29e2..de4fd90 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -474,21 +474,23 @@
}
auto RefreshRateSelector::getRankedFrameRates(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const -> RankedFrameRates {
+ GlobalSignals signals, Fps pacesetterFps) const
+ -> RankedFrameRates {
+ GetRankedFrameRatesCache cache{layers, signals, pacesetterFps};
+
std::lock_guard lock(mLock);
- if (mGetRankedFrameRatesCache &&
- mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) {
+ if (mGetRankedFrameRatesCache && mGetRankedFrameRatesCache->matches(cache)) {
return mGetRankedFrameRatesCache->result;
}
- const auto result = getRankedFrameRatesLocked(layers, signals);
- mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result};
- return result;
+ cache.result = getRankedFrameRatesLocked(layers, signals, pacesetterFps);
+ mGetRankedFrameRatesCache = std::move(cache);
+ return mGetRankedFrameRatesCache->result;
}
auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const
+ GlobalSignals signals, Fps pacesetterFps) const
-> RankedFrameRates {
using namespace fps_approx_ops;
ATRACE_CALL();
@@ -496,6 +498,24 @@
const auto& activeMode = *getActiveModeLocked().modePtr;
+ if (pacesetterFps.isValid()) {
+ ALOGV("Follower display");
+
+ const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending,
+ std::nullopt, [&](FrameRateMode mode) {
+ return mode.modePtr->getPeakFps() == pacesetterFps;
+ });
+
+ if (!ranking.empty()) {
+ ATRACE_FORMAT_INSTANT("%s (Follower display)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+
+ return {ranking, kNoSignals, pacesetterFps};
+ }
+
+ ALOGW("Follower display cannot follow the pacesetter");
+ }
+
// Keep the display at max frame rate for the duration of powering on the display.
if (signals.powerOnImminent) {
ALOGV("Power On Imminent");
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 6051e89..a500063 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -233,14 +233,18 @@
struct RankedFrameRates {
FrameRateRanking ranking; // Ordered by descending score.
GlobalSignals consideredSignals;
+ Fps pacesetterFps;
bool operator==(const RankedFrameRates& other) const {
- return ranking == other.ranking && consideredSignals == other.consideredSignals;
+ return ranking == other.ranking && consideredSignals == other.consideredSignals &&
+ isApproxEqual(pacesetterFps, other.pacesetterFps);
}
};
- RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals) const
- EXCLUDES(mLock);
+ // If valid, `pacesetterFps` (used by follower displays) filters the ranking to modes matching
+ // that refresh rate.
+ RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals,
+ Fps pacesetterFps = {}) const EXCLUDES(mLock);
FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
std::lock_guard lock(mLock);
@@ -415,7 +419,8 @@
const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock);
RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const REQUIRES(mLock);
+ GlobalSignals signals, Fps pacesetterFps) const
+ REQUIRES(mLock);
// Returns number of display frames and remainder when dividing the layer refresh period by
// display refresh period.
@@ -534,8 +539,16 @@
Config::FrameRateOverride mFrameRateOverrideConfig;
struct GetRankedFrameRatesCache {
- std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
+ std::vector<LayerRequirement> layers;
+ GlobalSignals signals;
+ Fps pacesetterFps;
+
RankedFrameRates result;
+
+ bool matches(const GetRankedFrameRatesCache& other) const {
+ return layers == other.layers && signals == other.signals &&
+ isApproxEqual(pacesetterFps, other.pacesetterFps);
+ }
};
mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index d92edb8..610af2f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -1146,38 +1146,31 @@
auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
ATRACE_CALL();
- using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
- ui::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
+ DisplayModeChoiceMap modeChoices;
const auto globalSignals = makeGlobalSignals();
- Fps pacesetterFps;
+
+ const Fps pacesetterFps = [&]() REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext) {
+ auto rankedFrameRates =
+ pacesetterSelectorPtrLocked()->getRankedFrameRates(mPolicy.contentRequirements,
+ globalSignals);
+
+ const Fps pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
+
+ modeChoices.try_emplace(*mPacesetterDisplayId,
+ DisplayModeChoice::from(std::move(rankedFrameRates)));
+ return pacesetterFps;
+ }();
for (const auto& [id, display] : mDisplays) {
+ if (id == *mPacesetterDisplayId) continue;
+
auto rankedFrameRates =
- display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements,
- globalSignals);
- if (id == *mPacesetterDisplayId) {
- pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
- }
- perDisplayRanking.push_back(std::move(rankedFrameRates));
+ display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals,
+ pacesetterFps);
+
+ modeChoices.try_emplace(id, DisplayModeChoice::from(std::move(rankedFrameRates)));
}
- DisplayModeChoiceMap modeChoices;
- using fps_approx_ops::operator==;
-
- for (auto& [rankings, signals] : perDisplayRanking) {
- const auto chosenFrameRateMode =
- ftl::find_if(rankings,
- [&](const auto& ranking) {
- return ranking.frameRateMode.fps == pacesetterFps;
- })
- .transform([](const auto& scoredFrameRate) {
- return scoredFrameRate.get().frameRateMode;
- })
- .value_or(rankings.front().frameRateMode);
-
- modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(),
- DisplayModeChoice{chosenFrameRateMode, signals});
- }
return modeChoices;
}
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 494a91b..c408a56 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -402,6 +402,11 @@
DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals)
: mode(std::move(mode)), consideredSignals(consideredSignals) {}
+ static DisplayModeChoice from(RefreshRateSelector::RankedFrameRates rankedFrameRates) {
+ return {rankedFrameRates.ranking.front().frameRateMode,
+ rankedFrameRates.consideredSignals};
+ }
+
FrameRateMode mode;
GlobalSignals consideredSignals;
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index a155f5d..0ede612 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -103,8 +103,9 @@
auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals = {}) const {
- const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals);
+ GlobalSignals signals = {}, Fps pacesetterFps = {}) const {
+ const auto result =
+ RefreshRateSelector::getRankedFrameRates(layers, signals, pacesetterFps);
EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
ScoredFrameRate::DescendingScore{}));
@@ -114,8 +115,8 @@
auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
GlobalSignals signals) const {
- const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals);
- return std::make_pair(ranking, consideredSignals);
+ const auto result = getRankedFrameRates(layers, signals);
+ return std::make_pair(result.ranking, result.consideredSignals);
}
FrameRateMode getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
@@ -1387,7 +1388,7 @@
TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) {
auto selector = createSelector(kModes_60_90, kModeId60);
- auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
+ auto [refreshRates, signals, _] = selector.getRankedFrameRates({}, {});
EXPECT_FALSE(signals.powerOnImminent);
auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
@@ -1471,10 +1472,32 @@
}
}
+TEST_P(RefreshRateSelectorTest, pacesetterConsidered) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+ constexpr RefreshRateSelector::GlobalSignals kNoSignals;
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].vote = LayerVoteType::Min;
+
+ // The pacesetterFps takes precedence over the LayerRequirement.
+ {
+ const auto result = selector.getRankedFrameRates(layers, {}, 90_Hz);
+ EXPECT_EQ(kMode90, result.ranking.front().frameRateMode.modePtr);
+ EXPECT_EQ(kNoSignals, result.consideredSignals);
+ }
+
+ // The pacesetterFps takes precedence over GlobalSignals.
+ {
+ const auto result = selector.getRankedFrameRates(layers, {.touch = true}, 60_Hz);
+ EXPECT_EQ(kMode60, result.ranking.front().frameRateMode.modePtr);
+ EXPECT_EQ(kNoSignals, result.consideredSignals);
+ }
+}
+
TEST_P(RefreshRateSelectorTest, touchConsidered) {
auto selector = createSelector(kModes_60_90, kModeId60);
- auto [_, signals] = selector.getRankedFrameRates({}, {});
+ auto signals = selector.getRankedFrameRates({}, {}).consideredSignals;
EXPECT_FALSE(signals.touch);
std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true});
@@ -2363,7 +2386,7 @@
lr.name = "60Hz ExplicitDefault";
lr.focused = true;
- const auto [rankedFrameRate, signals] =
+ const auto [rankedFrameRate, signals, _] =
selector.getRankedFrameRates(layers, {.touch = true, .idle = true});
EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60);
@@ -2587,7 +2610,7 @@
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
- const auto [ranking, signals] = selector.getRankedFrameRates({}, {});
+ const auto [ranking, signals, _] = selector.getRankedFrameRates({}, {});
EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90);
EXPECT_FALSE(signals.touch);
@@ -2971,7 +2994,7 @@
layers[0].vote = voteType;
layers[0].desiredRefreshRate = 90_Hz;
- const auto [ranking, signals] =
+ const auto [ranking, signals, _] =
selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
// Refresh rate will be chosen by either touch state or idle state.
@@ -3121,16 +3144,17 @@
auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
using GlobalSignals = RefreshRateSelector::GlobalSignals;
- const auto args = std::make_pair(std::vector<LayerRequirement>{},
- GlobalSignals{.touch = true, .idle = true});
-
const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{
{90_Hz, kMode90}}},
GlobalSignals{.touch = true}};
- selector.mutableGetRankedRefreshRatesCache() = {args, result};
+ selector.mutableGetRankedRefreshRatesCache() = {.layers = std::vector<LayerRequirement>{},
+ .signals = GlobalSignals{.touch = true,
+ .idle = true},
+ .result = result};
- EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second));
+ const auto& cache = *selector.mutableGetRankedRefreshRatesCache();
+ EXPECT_EQ(result, selector.getRankedFrameRates(cache.layers, cache.signals));
}
TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {
@@ -3138,15 +3162,18 @@
EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache());
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
- RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+ const std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+ const RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+ const Fps pacesetterFps = 60_Hz;
- const auto result = selector.getRankedFrameRates(layers, globalSignals);
+ const auto result = selector.getRankedFrameRates(layers, globalSignals, pacesetterFps);
const auto& cache = selector.mutableGetRankedRefreshRatesCache();
ASSERT_TRUE(cache);
- EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
+ EXPECT_EQ(cache->layers, layers);
+ EXPECT_EQ(cache->signals, globalSignals);
+ EXPECT_EQ(cache->pacesetterFps, pacesetterFps);
EXPECT_EQ(cache->result, result);
}
@@ -4073,7 +4100,7 @@
layers[0].vote = voteType;
layers[0].desiredRefreshRate = 90_Hz;
- const auto [ranking, signals] =
+ const auto [ranking, signals, _] =
selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
// Refresh rate will be chosen by either touch state or idle state.
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index d4735c7..8ac8eda 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -362,7 +362,7 @@
globalSignals)(kDisplayId2,
FrameRateMode{60_Hz,
kDisplay2Mode60},
- globalSignals);
+ GlobalSignals{});
std::vector<RefreshRateSelector::LayerRequirement> layers = {{.weight = 1.f},
{.weight = 1.f}};
@@ -381,7 +381,7 @@
globalSignals)(kDisplayId2,
FrameRateMode{120_Hz,
kDisplay2Mode120},
- globalSignals);
+ GlobalSignals{});
mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
@@ -400,7 +400,7 @@
globalSignals)(kDisplayId2,
FrameRateMode{120_Hz,
kDisplay2Mode120},
- globalSignals);
+ GlobalSignals{});
const auto actualChoices = mScheduler->chooseDisplayModes();
EXPECT_EQ(expectedChoices, actualChoices);
@@ -422,10 +422,10 @@
DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120},
globalSignals)(kDisplayId2,
FrameRateMode{120_Hz, kDisplay2Mode120},
- globalSignals)(kDisplayId3,
- FrameRateMode{60_Hz,
- kDisplay3Mode60},
- globalSignals);
+ GlobalSignals{})(kDisplayId3,
+ FrameRateMode{60_Hz,
+ kDisplay3Mode60},
+ GlobalSignals{});
const auto actualChoices = mScheduler->chooseDisplayModes();
EXPECT_EQ(expectedChoices, actualChoices);
@@ -440,12 +440,12 @@
expectedChoices = ftl::init::map<
const PhysicalDisplayId&,
DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60},
- globalSignals)(kDisplayId2,
- FrameRateMode{60_Hz, kDisplay2Mode60},
- globalSignals)(kDisplayId3,
- FrameRateMode{60_Hz,
- kDisplay3Mode60},
- globalSignals);
+ GlobalSignals{})(kDisplayId2,
+ FrameRateMode{60_Hz, kDisplay2Mode60},
+ GlobalSignals{})(kDisplayId3,
+ FrameRateMode{60_Hz,
+ kDisplay3Mode60},
+ globalSignals);
const auto actualChoices = mScheduler->chooseDisplayModes();
EXPECT_EQ(expectedChoices, actualChoices);