diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-01-26 00:06:24 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-01-26 00:06:24 +0000 |
commit | 2afabf94376109277d07111c27f0c79eb991db1d (patch) | |
tree | f2639eab9bfbd81590befae74169237207c65748 | |
parent | 9380af21e0eaa08831e573580938b1444b870798 (diff) | |
parent | 3ef084b180f7d1449d9aebec0402a1fc1887ebde (diff) | |
download | native-2afabf94376109277d07111c27f0c79eb991db1d.tar.gz |
Snap for 8116027 from 3ef084b180f7d1449d9aebec0402a1fc1887ebde to sc-qpr3-release
Change-Id: I10e00d8995ebe6cc2ea3561a50bb7657790eb264
3 files changed, 312 insertions, 27 deletions
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp index 0ee9fb353d..a56827e8f1 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp @@ -134,42 +134,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.getFps().getValue() / - mAppRequestRefreshRates.back()->getFps().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] = @@ -177,7 +172,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) { @@ -195,7 +190,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) { @@ -210,7 +227,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 { @@ -422,7 +450,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; } @@ -583,7 +611,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) { @@ -592,7 +620,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; } @@ -945,6 +973,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", diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h index 21867ccac9..99f217c2e2 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h @@ -344,6 +344,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. // @@ -446,6 +450,9 @@ private: float calculateLayerScoreLocked(const LayerRequirement&, const RefreshRate&, bool isSeamlessSwitch) const REQUIRES(mLock); + float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&, + const RefreshRate&) const REQUIRES(mLock); + void updateDisplayModes(const DisplayModes& mode, DisplayModeId currentModeId) EXCLUDES(mLock); void initializeIdleTimer(); diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp index c1dba2bd61..77b75ab767 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp @@ -98,9 +98,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()); @@ -116,9 +122,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, @@ -145,6 +157,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 = {mConfig60, RefreshRate::ConstructorTag(0)}; RefreshRate mExpectedAlmost60Config = {createDisplayMode(HWC_CONFIG_ID_60, 0, 16666665), @@ -158,7 +175,6 @@ protected: RefreshRate mExpected30Config = {mConfig30, RefreshRate::ConstructorTag(0)}; RefreshRate mExpected120Config = {mConfig120, RefreshRate::ConstructorTag(0)}; -private: DisplayModePtr createDisplayMode(DisplayModeId modeId, int32_t group, int64_t vsyncPeriod, ui::Size resolution = ui::Size()); }; @@ -1230,7 +1246,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()); } } @@ -2028,6 +2146,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); @@ -2123,6 +2335,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=*/ |