Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e88d62f authored by Dominik Laskowski's avatar Dominik Laskowski
Browse files

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
parent 703c1c04
Loading
Loading
Loading
Loading
+27 −7
Original line number Diff line number Diff line
@@ -474,21 +474,23 @@ float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& lay
}

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 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequi

    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");
+18 −5
Original line number Diff line number Diff line
@@ -233,14 +233,18 @@ public:
    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 @@ private:
    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 @@ private:
    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);

+19 −26
Original line number Diff line number Diff line
@@ -1146,38 +1146,31 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals
auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
    ATRACE_CALL();

    using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
    ui::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
    DisplayModeChoiceMap modeChoices;
    const auto globalSignals = makeGlobalSignals();
    Fps pacesetterFps;

    for (const auto& [id, display] : mDisplays) {
    const Fps pacesetterFps = [&]() REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext) {
        auto rankedFrameRates =
                display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements,
                pacesetterSelectorPtrLocked()->getRankedFrameRates(mPolicy.contentRequirements,
                                                                   globalSignals);
        if (id == *mPacesetterDisplayId) {
            pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
        }
        perDisplayRanking.push_back(std::move(rankedFrameRates));
    }

    DisplayModeChoiceMap modeChoices;
    using fps_approx_ops::operator==;
        const Fps pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;

    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(*mPacesetterDisplayId,
                                DisplayModeChoice::from(std::move(rankedFrameRates)));
        return pacesetterFps;
    }();

    for (const auto& [id, display] : mDisplays) {
        if (id == *mPacesetterDisplayId) continue;

        modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(),
                                DisplayModeChoice{chosenFrameRateMode, signals});
        auto rankedFrameRates =
                display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals,
                                                         pacesetterFps);

        modeChoices.try_emplace(id, DisplayModeChoice::from(std::move(rankedFrameRates)));
    }

    return modeChoices;
}

+5 −0
Original line number Diff line number Diff line
@@ -402,6 +402,11 @@ private:
        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;

+46 −19
Original line number Diff line number Diff line
@@ -103,8 +103,9 @@ struct TestableRefreshRateSelector : RefreshRateSelector {
    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 @@ struct TestableRefreshRateSelector : RefreshRateSelector {

    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, getMaxRefreshRatesByPolicyOutsideTheGroup) {
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, powerOnImminentConsidered) {
    }
}

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 @@ TEST_P(RefreshRateSelectorTest,
    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 @@ TEST_P(RefreshRateSelectorTest,
    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 @@ TEST_P(RefreshRateSelectorTest, idle) {
        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 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ReadsCache) {
    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 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {

    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 @@ TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) {
        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.
Loading