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

Commit 69ff6776 authored by Ady Abraham's avatar Ady Abraham Committed by Gerrit Code Review
Browse files

Merge changes Ia14551e0,Idd32600c,I694e2aa5

* changes:
  SF: adjust kNonExactMatchingPenalty
  SF: give a higher score to frame rates which exact matches
  Tune RefreshRateConfigs for fractional refresh rates
parents c7f87356 76ec9fad
Loading
Loading
Loading
Loading
+64 −25
Original line number Diff line number Diff line
@@ -133,42 +133,37 @@ bool RefreshRateConfigs::isVoteAllowed(const LayerRequirement& layer,
    return true;
}

float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer,
                                                    const RefreshRate& refreshRate,
                                                    bool isSeamlessSwitch) const {
    if (!isVoteAllowed(layer, refreshRate)) {
        return 0;
    }

    // Slightly prefer seamless switches.
    constexpr float kSeamedSwitchPenalty = 0.95f;
    const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;

    // If the layer wants Max, give higher score to the higher refresh rate
    if (layer.vote == LayerVoteType::Max) {
        const auto ratio =
                refreshRate.fps.getValue() / mAppRequestRefreshRates.back()->fps.getValue();
        // use ratio^2 to get a lower score the more we get further from peak
        return ratio * ratio;
    }
float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(
        const LayerRequirement& layer, const RefreshRate& refreshRate) const {
    constexpr float kScoreForFractionalPairs = .8f;

    const auto displayPeriod = refreshRate.getVsyncPeriod();
    const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
    if (layer.vote == LayerVoteType::ExplicitDefault) {
        // Find the actual rate the layer will render, assuming
        // that layerPeriod is the minimal time to render a frame
        // that layerPeriod is the minimal period to render a frame.
        // For example if layerPeriod is 20ms and displayPeriod is 16ms,
        // then the actualLayerPeriod will be 32ms, because it is the
        // smallest multiple of the display period which is >= layerPeriod.
        auto actualLayerPeriod = displayPeriod;
        int multiplier = 1;
        while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {
            multiplier++;
            actualLayerPeriod = displayPeriod * multiplier;
        }

        // Because of the threshold we used above it's possible that score is slightly
        // above 1.
        return std::min(1.0f,
                        static_cast<float>(layerPeriod) / static_cast<float>(actualLayerPeriod));
    }

    if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
        layer.vote == LayerVoteType::Heuristic) {
        if (isFractionalPairOrMultiple(refreshRate.getFps(), layer.desiredRefreshRate)) {
            return kScoreForFractionalPairs;
        }

        // Calculate how many display vsyncs we need to present a single frame for this
        // layer
        const auto [displayFramesQuotient, displayFramesRemainder] =
@@ -176,7 +171,7 @@ float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& laye
        static constexpr size_t MAX_FRAMES_TO_FIT = 10; // Stop calculating when score < 0.1
        if (displayFramesRemainder == 0) {
            // Layer desired refresh rate matches the display rate.
            return 1.0f * seamlessness;
            return 1.0f;
        }

        if (displayFramesQuotient == 0) {
@@ -194,7 +189,29 @@ float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& laye
            iter++;
        }

        return (1.0f / iter) * seamlessness;
        return (1.0f / iter);
    }

    return 0;
}

float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer,
                                                    const RefreshRate& refreshRate,
                                                    bool isSeamlessSwitch) const {
    if (!isVoteAllowed(layer, refreshRate)) {
        return 0;
    }

    // Slightly prefer seamless switches.
    constexpr float kSeamedSwitchPenalty = 0.95f;
    const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;

    // If the layer wants Max, give higher score to the higher refresh rate
    if (layer.vote == LayerVoteType::Max) {
        const auto ratio = refreshRate.getFps().getValue() /
                mAppRequestRefreshRates.back()->getFps().getValue();
        // use ratio^2 to get a lower score the more we get further from peak
        return ratio * ratio;
    }

    if (layer.vote == LayerVoteType::ExplicitExact) {
@@ -209,7 +226,18 @@ float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& laye
        return divider == 1;
    }

    return 0;
    // If the layer frame rate is a divider of the refresh rate it should score
    // the highest score.
    if (getFrameRateDivider(refreshRate.getFps(), layer.desiredRefreshRate) > 0) {
        return 1.0f * seamlessness;
    }

    // The layer frame rate is not a divider of the refresh rate,
    // there is a small penalty attached to the score to favor the frame rates
    // the exactly matches the display refresh rate or a multiple.
    constexpr float kNonExactMatchingPenalty = 0.95f;
    return calculateNonExactMatchingLayerScoreLocked(layer, refreshRate) * seamlessness *
            kNonExactMatchingPenalty;
}

struct RefreshRateScore {
@@ -421,7 +449,7 @@ RefreshRate RefreshRateConfigs::getBestRefreshRateLocked(

            const auto layerScore =
                    calculateLayerScoreLocked(layer, *scores[i].refreshRate, isSeamlessSwitch);
            ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
            ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(),
                  scores[i].refreshRate->getName().c_str(), layerScore);
            scores[i].score += weight * layerScore;
        }
@@ -582,7 +610,7 @@ RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverr

template <typename Iter>
const RefreshRate* RefreshRateConfigs::getBestRefreshRate(Iter begin, Iter end) const {
    constexpr auto EPSILON = 0.001f;
    constexpr auto kEpsilon = 0.0001f;
    const RefreshRate* bestRefreshRate = begin->refreshRate;
    float max = begin->score;
    for (auto i = begin; i != end; ++i) {
@@ -591,7 +619,7 @@ const RefreshRate* RefreshRateConfigs::getBestRefreshRate(Iter begin, Iter end)

        ATRACE_INT(refreshRate->getName().c_str(), round<int>(score * 100));

        if (score > max * (1 + EPSILON)) {
        if (score > max * (1 + kEpsilon)) {
            max = score;
            bestRefreshRate = refreshRate;
        }
@@ -924,6 +952,17 @@ int RefreshRateConfigs::getFrameRateDivider(Fps displayFrameRate, Fps layerFrame
    return static_cast<int>(numPeriodsRounded);
}

bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) {
    if (smaller.getValue() > bigger.getValue()) {
        return isFractionalPairOrMultiple(bigger, smaller);
    }

    const auto multiplier = std::round(bigger.getValue() / smaller.getValue());
    constexpr float kCoef = 1000.f / 1001.f;
    return bigger.equalsWithMargin(Fps(smaller.getValue() * multiplier / kCoef)) ||
            bigger.equalsWithMargin(Fps(smaller.getValue() * multiplier * kCoef));
}

void RefreshRateConfigs::dump(std::string& result) const {
    std::lock_guard lock(mLock);
    base::StringAppendF(&result, "DesiredDisplayModeSpecs (DisplayManager): %s\n\n",
+7 −0
Original line number Diff line number Diff line
@@ -342,6 +342,10 @@ public:
    // layer refresh rate.
    static int getFrameRateDivider(Fps displayFrameRate, Fps layerFrameRate);

    // Returns if the provided frame rates have a ratio t*1000/1001 or t*1001/1000
    // for an integer t.
    static bool isFractionalPairOrMultiple(Fps, Fps);

    using UidToFrameRateOverride = std::map<uid_t, Fps>;
    // Returns the frame rate override for each uid.
    //
@@ -405,6 +409,9 @@ private:
    float calculateLayerScoreLocked(const LayerRequirement&, const RefreshRate&,
                                    bool isSeamlessSwitch) const REQUIRES(mLock);

    float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&,
                                                    const RefreshRate&) const REQUIRES(mLock);

    // The list of refresh rates, indexed by display modes ID. This may change after this
    // object is initialized.
    AllRefreshRatesMapType mRefreshRates GUARDED_BY(mLock);
+241 −2
Original line number Diff line number Diff line
@@ -99,9 +99,15 @@ protected:
    static inline const DisplayModeId HWC_CONFIG_ID_30 = DisplayModeId(4);
    static inline const DisplayModeId HWC_CONFIG_ID_25 = DisplayModeId(5);
    static inline const DisplayModeId HWC_CONFIG_ID_50 = DisplayModeId(6);
    static inline const DisplayModeId HWC_CONFIG_ID_24 = DisplayModeId(7);
    static inline const DisplayModeId HWC_CONFIG_ID_24_FRAC = DisplayModeId(8);
    static inline const DisplayModeId HWC_CONFIG_ID_30_FRAC = DisplayModeId(9);
    static inline const DisplayModeId HWC_CONFIG_ID_60_FRAC = DisplayModeId(10);

    // Test configs
    DisplayModePtr mConfig60 = createDisplayMode(HWC_CONFIG_ID_60, 0, Fps(60.0f).getPeriodNsecs());
    DisplayModePtr mConfig60Frac =
            createDisplayMode(HWC_CONFIG_ID_60_FRAC, 0, Fps(59.94f).getPeriodNsecs());
    DisplayModePtr mConfig90 = createDisplayMode(HWC_CONFIG_ID_90, 0, Fps(90.0f).getPeriodNsecs());
    DisplayModePtr mConfig90DifferentGroup =
            createDisplayMode(HWC_CONFIG_ID_90, 1, Fps(90.0f).getPeriodNsecs());
@@ -117,9 +123,15 @@ protected:
    DisplayModePtr mConfig30 = createDisplayMode(HWC_CONFIG_ID_30, 0, Fps(30.0f).getPeriodNsecs());
    DisplayModePtr mConfig30DifferentGroup =
            createDisplayMode(HWC_CONFIG_ID_30, 1, Fps(30.0f).getPeriodNsecs());
    DisplayModePtr mConfig30Frac =
            createDisplayMode(HWC_CONFIG_ID_30_FRAC, 0, Fps(29.97f).getPeriodNsecs());
    DisplayModePtr mConfig25 = createDisplayMode(HWC_CONFIG_ID_25, 0, Fps(25.0f).getPeriodNsecs());
    DisplayModePtr mConfig25DifferentGroup =
            createDisplayMode(HWC_CONFIG_ID_25, 1, Fps(25.0f).getPeriodNsecs());
    DisplayModePtr mConfig50 = createDisplayMode(HWC_CONFIG_ID_50, 0, Fps(50.0f).getPeriodNsecs());
    DisplayModePtr mConfig24 = createDisplayMode(HWC_CONFIG_ID_24, 0, Fps(24.0f).getPeriodNsecs());
    DisplayModePtr mConfig24Frac =
            createDisplayMode(HWC_CONFIG_ID_24_FRAC, 0, Fps(23.976f).getPeriodNsecs());

    // Test device configurations
    // The positions of the configs in the arrays below MUST match their IDs. For example,
@@ -146,6 +158,11 @@ protected:
                                       mConfig50};
    DisplayModes m60_120Device = {mConfig60, mConfig120};

    // This is a typical TV configuration.
    DisplayModes m24_25_30_50_60WithFracDevice = {mConfig24, mConfig24Frac, mConfig25,
                                                  mConfig30, mConfig30Frac, mConfig50,
                                                  mConfig60, mConfig60Frac};

    // Expected RefreshRate objects
    RefreshRate mExpected60Config = {HWC_CONFIG_ID_60, mConfig60, Fps(60),
                                     RefreshRate::ConstructorTag(0)};
@@ -166,7 +183,6 @@ protected:
    RefreshRate mExpected120Config = {HWC_CONFIG_ID_120, mConfig120, Fps(120),
                                      RefreshRate::ConstructorTag(0)};

private:
    DisplayModePtr createDisplayMode(DisplayModeId modeId, int32_t group, int64_t vsyncPeriod,
                                     ui::Size resolution = ui::Size());
};
@@ -1237,7 +1253,109 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitDefault) {
        const auto& refreshRate =
                refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
        EXPECT_TRUE(refreshRate.getFps().equalsWithMargin(Fps(test.second)))
                << "Expecting " << test.first << "fps => " << test.second << "Hz";
                << "Expecting " << test.first << "fps => " << test.second << "Hz"
                << " but it was " << refreshRate.getFps();
    }
}

TEST_F(RefreshRateConfigsTest,
       getBestRefreshRate_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
    auto& lr = layers[0];

    // Test that 23.976 will choose 24 if 23.976 is not supported
    {
        android::DisplayModes modes = {mConfig24,     mConfig25, mConfig30,
                                       mConfig30Frac, mConfig60, mConfig60Frac};
        auto refreshRateConfigs =
                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);

        lr.vote = LayerVoteType::ExplicitExactOrMultiple;
        lr.desiredRefreshRate = Fps(23.976f);
        lr.name = "ExplicitExactOrMultiple 23.976 fps";
        EXPECT_EQ(HWC_CONFIG_ID_24,
                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                          .getModeId());
    }

    // Test that 24 will choose 23.976 if 24 is not supported
    {
        android::DisplayModes modes = {mConfig24Frac, mConfig25, mConfig30,
                                       mConfig30Frac, mConfig60, mConfig60Frac};
        auto refreshRateConfigs =
                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
        lr.desiredRefreshRate = Fps(24.f);
        lr.name = "ExplicitExactOrMultiple 24 fps";
        EXPECT_EQ(HWC_CONFIG_ID_24_FRAC,
                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                          .getModeId());
    }

    // Test that 29.97 will prefer 59.94 over 60 and 30
    {
        android::DisplayModes modes = {mConfig24, mConfig24Frac, mConfig25,
                                       mConfig30, mConfig60,     mConfig60Frac};
        auto refreshRateConfigs =
                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
        lr.desiredRefreshRate = Fps(29.97f);
        lr.name = "ExplicitExactOrMultiple 29.97f fps";
        EXPECT_EQ(HWC_CONFIG_ID_60_FRAC,
                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                          .getModeId());
    }
}

TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact_WithFractionalRefreshRates) {
    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
    auto& lr = layers[0];

    // Test that voting for supported refresh rate will select this refresh rate
    {
        auto refreshRateConfigs =
                std::make_unique<RefreshRateConfigs>(m24_25_30_50_60WithFracDevice,
                                                     /*currentConfigId=*/HWC_CONFIG_ID_60);

        for (auto desiredRefreshRate : {23.976f, 24.f, 25.f, 29.97f, 30.f, 50.f, 59.94f, 60.f}) {
            lr.vote = LayerVoteType::ExplicitExact;
            lr.desiredRefreshRate = Fps(desiredRefreshRate);
            std::stringstream ss;
            ss << "ExplicitExact " << desiredRefreshRate << " fps";
            lr.name = ss.str();

            auto selecteRefreshRate =
                    refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});

            EXPECT_TRUE(selecteRefreshRate.getFps().equalsWithMargin(lr.desiredRefreshRate))
                    << "Expecting " << lr.desiredRefreshRate << " but it was "
                    << selecteRefreshRate.getFps();
        }
    }

    // Test that 23.976 will choose 24 if 23.976 is not supported
    {
        android::DisplayModes modes = {mConfig24,     mConfig25, mConfig30,
                                       mConfig30Frac, mConfig60, mConfig60Frac};
        auto refreshRateConfigs =
                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
        lr.vote = LayerVoteType::ExplicitExact;
        lr.desiredRefreshRate = Fps(23.976f);
        lr.name = "ExplicitExact 23.976 fps";
        EXPECT_EQ(HWC_CONFIG_ID_24,
                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                          .getModeId());
    }

    // Test that 24 will choose 23.976 if 24 is not supported
    {
        android::DisplayModes modes = {mConfig24Frac, mConfig25, mConfig30,
                                       mConfig30Frac, mConfig60, mConfig60Frac};
        auto refreshRateConfigs =
                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
        lr.desiredRefreshRate = Fps(24.f);
        lr.name = "ExplicitExact 24 fps";
        EXPECT_EQ(HWC_CONFIG_ID_24_FRAC,
                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                          .getModeId());
    }
}

@@ -2035,6 +2153,100 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactTouchBoost) {
              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
}

TEST_F(RefreshRateConfigsTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) {
    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
    auto refreshRateConfigs =
            std::make_unique<RefreshRateConfigs>(m24_25_30_50_60WithFracDevice,
                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);

    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 0.5f},
                                                LayerRequirement{.weight = 0.5f}};
    auto& explicitDefaultLayer = layers[0];
    auto& explicitExactOrMultipleLayer = layers[1];

    explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
    explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
    explicitExactOrMultipleLayer.desiredRefreshRate = Fps(60);

    explicitDefaultLayer.vote = LayerVoteType::ExplicitDefault;
    explicitDefaultLayer.name = "ExplicitDefault";
    explicitDefaultLayer.desiredRefreshRate = Fps(59.94f);

    EXPECT_EQ(mExpected60Config,
              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}

// b/190578904
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_deviceWithCloseRefreshRates) {
    constexpr int kMinRefreshRate = 10;
    constexpr int kMaxRefreshRate = 240;

    DisplayModes displayModes;
    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
        constexpr int32_t kGroup = 0;
        const auto refreshRate = Fps(static_cast<float>(fps));
        displayModes.push_back(
                createDisplayMode(DisplayModeId(fps), kGroup, refreshRate.getPeriodNsecs()));
    }

    const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false};
    auto refreshRateConfigs =
            std::make_unique<RefreshRateConfigs>(displayModes,
                                                 /*currentConfigId=*/displayModes[0]->getId());

    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
        layers[0].desiredRefreshRate = fps;
        layers[0].vote = vote;
        EXPECT_EQ(fps.getIntValue(),
                  refreshRateConfigs->getBestRefreshRate(layers, globalSignals)
                          .getFps()
                          .getIntValue())
                << "Failed for " << RefreshRateConfigs::layerVoteTypeString(vote);
    };

    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
        const auto refreshRate = Fps(static_cast<float>(fps));
        testRefreshRate(refreshRate, LayerVoteType::Heuristic);
        testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault);
        testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple);
        testRefreshRate(refreshRate, LayerVoteType::ExplicitExact);
    }
}

// b/190578904
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_conflictingVotes) {
    const DisplayModes displayModes = {
            createDisplayMode(DisplayModeId(0), 0, Fps(43.0f).getPeriodNsecs()),
            createDisplayMode(DisplayModeId(1), 0, Fps(53.0f).getPeriodNsecs()),
            createDisplayMode(DisplayModeId(2), 0, Fps(55.0f).getPeriodNsecs()),
            createDisplayMode(DisplayModeId(3), 0, Fps(60.0f).getPeriodNsecs()),
    };

    const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false};
    auto refreshRateConfigs =
            std::make_unique<RefreshRateConfigs>(displayModes,
                                                 /*currentConfigId=*/displayModes[0]->getId());

    const auto layers = std::vector<LayerRequirement>{
            LayerRequirement{
                    .vote = LayerVoteType::ExplicitDefault,
                    .desiredRefreshRate = Fps(43.0f),
                    .seamlessness = Seamlessness::SeamedAndSeamless,
                    .weight = 0.41f,
            },
            LayerRequirement{
                    .vote = LayerVoteType::ExplicitExactOrMultiple,
                    .desiredRefreshRate = Fps(53.0f),
                    .seamlessness = Seamlessness::SeamedAndSeamless,
                    .weight = 0.41f,
            },
    };

    EXPECT_EQ(53,
              refreshRateConfigs->getBestRefreshRate(layers, globalSignals).getFps().getIntValue());
}

TEST_F(RefreshRateConfigsTest, testComparisonOperator) {
    EXPECT_TRUE(mExpected60Config < mExpected90Config);
    EXPECT_FALSE(mExpected60Config < mExpected60Config);
@@ -2130,6 +2342,33 @@ TEST_F(RefreshRateConfigsTest, getFrameRateDivider) {
    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivider(Fps(60.f), Fps(59.94f)));
}

TEST_F(RefreshRateConfigsTest, isFractionalPairOrMultiple) {
    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(23.976f), Fps(24.f)));
    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(24.f), Fps(23.976f)));

    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(29.97f), Fps(30.f)));
    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(30.f), Fps(29.97f)));

    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(59.94f), Fps(60.f)));
    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(60.f), Fps(59.94f)));

    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(29.97f), Fps(60.f)));
    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(60.f), Fps(29.97f)));

    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(59.94f), Fps(30.f)));
    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(30.f), Fps(59.94f)));

    const std::vector<float> refreshRates = {23.976f, 24.f, 25.f, 29.97f, 30.f, 50.f, 59.94f, 60.f};
    for (auto refreshRate : refreshRates) {
        EXPECT_FALSE(
                RefreshRateConfigs::isFractionalPairOrMultiple(Fps(refreshRate), Fps(refreshRate)));
    }

    EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(24.f), Fps(25.f)));
    EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(23.978f), Fps(25.f)));
    EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(29.97f), Fps(59.94f)));
}

TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_noLayers) {
    auto refreshRateConfigs =
            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/