Loading services/surfaceflinger/Fps.h +79 −63 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. Loading @@ -19,94 +19,110 @@ #include <cmath> #include <ostream> #include <string> #include <type_traits> #include <android-base/stringprintf.h> #include <utils/Timers.h> namespace android { // Value which represents "frames per second". This class is a wrapper around // float, providing some useful utilities, such as comparisons with tolerance // and converting between period duration and frequency. // Frames per second, stored as floating-point frequency. Provides conversion from/to period in // nanoseconds, and relational operators with precision threshold. // // const Fps fps = 60_Hz; // // using namespace fps_approx_ops; // assert(fps == Fps::fromPeriodNsecs(16'666'667)); // class Fps { public: static constexpr Fps fromPeriodNsecs(nsecs_t period) { return Fps(1e9f / period, period); } constexpr Fps() = default; Fps() = default; explicit constexpr Fps(float fps) : fps(fps), period(fps == 0.0f ? 0 : static_cast<nsecs_t>(1e9f / fps)) {} static constexpr Fps fromValue(float frequency) { return frequency > 0.f ? Fps(frequency, static_cast<nsecs_t>(1e9f / frequency)) : Fps(); } constexpr float getValue() const { return fps; } static constexpr Fps fromPeriodNsecs(nsecs_t period) { return period > 0 ? Fps(1e9f / period, period) : Fps(); } constexpr nsecs_t getPeriodNsecs() const { return period; } constexpr bool isValid() const { return mFrequency > 0.f; } bool equalsWithMargin(const Fps& other) const { return std::abs(fps - other.fps) < kMargin; } constexpr float getValue() const { return mFrequency; } int getIntValue() const { return static_cast<int>(std::round(mFrequency)); } // DO NOT use for std::sort. Instead use comparesLess(). bool lessThanWithMargin(const Fps& other) const { return fps + kMargin < other.fps; } constexpr nsecs_t getPeriodNsecs() const { return mPeriod; } bool greaterThanWithMargin(const Fps& other) const { return fps > other.fps + kMargin; } private: constexpr Fps(float frequency, nsecs_t period) : mFrequency(frequency), mPeriod(period) {} bool lessThanOrEqualWithMargin(const Fps& other) const { return !greaterThanWithMargin(other); } float mFrequency = 0.f; nsecs_t mPeriod = 0; }; bool greaterThanOrEqualWithMargin(const Fps& other) const { return !lessThanWithMargin(other); } static_assert(std::is_trivially_copyable_v<Fps>); bool isValid() const { return fps > 0.0f; } constexpr Fps operator""_Hz(unsigned long long frequency) { return Fps::fromValue(static_cast<float>(frequency)); } int getIntValue() const { return static_cast<int>(std::round(fps)); } constexpr Fps operator""_Hz(long double frequency) { return Fps::fromValue(static_cast<float>(frequency)); } // Use this comparator for sorting. Using a comparator with margins can // cause std::sort to crash. inline static bool comparesLess(const Fps& left, const Fps& right) { return left.fps < right.fps; inline bool isStrictlyLess(Fps lhs, Fps rhs) { return lhs.getValue() < rhs.getValue(); } // Compares two FPS with margin. // Transitivity is not guaranteed, i.e. a==b and b==c doesn't imply a==c. // DO NOT use with hash maps. Instead use EqualsInBuckets. struct EqualsWithMargin { bool operator()(const Fps& left, const Fps& right) const { return left.equalsWithMargin(right); // Does not satisfy equivalence relation. inline bool isApproxEqual(Fps lhs, Fps rhs) { // TODO(b/185536303): Replace with ULP distance. return std::abs(lhs.getValue() - rhs.getValue()) < 0.001f; } }; // Equals comparator which can be used with hash maps. // It's guaranteed that if two elements are equal, then their hashes are equal. struct EqualsInBuckets { bool operator()(const Fps& left, const Fps& right) const { return left.getBucket() == right.getBucket(); // Does not satisfy strict weak order. inline bool isApproxLess(Fps lhs, Fps rhs) { return isStrictlyLess(lhs, rhs) && !isApproxEqual(lhs, rhs); } }; inline friend std::string to_string(const Fps& fps) { return base::StringPrintf("%.2ffps", fps.fps); namespace fps_approx_ops { inline bool operator==(Fps lhs, Fps rhs) { return isApproxEqual(lhs, rhs); } inline friend std::ostream& operator<<(std::ostream& os, const Fps& fps) { return os << to_string(fps); inline bool operator<(Fps lhs, Fps rhs) { return isApproxLess(lhs, rhs); } private: friend std::hash<android::Fps>; inline bool operator!=(Fps lhs, Fps rhs) { return !isApproxEqual(lhs, rhs); } constexpr Fps(float fps, nsecs_t period) : fps(fps), period(period) {} inline bool operator>(Fps lhs, Fps rhs) { return isApproxLess(rhs, lhs); } float getBucket() const { return std::round(fps / kMargin); } inline bool operator<=(Fps lhs, Fps rhs) { return !isApproxLess(rhs, lhs); } static constexpr float kMargin = 0.001f; float fps = 0; nsecs_t period = 0; }; inline bool operator>=(Fps lhs, Fps rhs) { return !isApproxLess(lhs, rhs); } static_assert(std::is_trivially_copyable_v<Fps>); } // namespace fps_approx_ops } // namespace android struct FpsApproxEqual { bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); } }; namespace std { template <> struct hash<android::Fps> { std::size_t operator()(const android::Fps& fps) const { return std::hash<float>()(fps.getBucket()); inline std::string to_string(Fps fps) { return base::StringPrintf("%.2f Hz", fps.getValue()); } }; } // namespace std No newline at end of file inline std::ostream& operator<<(std::ostream& stream, Fps fps) { return stream << to_string(fps); } } // namespace android services/surfaceflinger/Layer.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -1138,7 +1138,7 @@ bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* tran if (!frameRate.rate.isValid() && frameRate.type != FrameRateCompatibility::NoVote && childrenHaveFrameRate) { *transactionNeeded |= setFrameRateForLayerTree(FrameRate(Fps(0.0f), FrameRateCompatibility::NoVote)); setFrameRateForLayerTree(FrameRate(Fps(), FrameRateCompatibility::NoVote)); } // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for Loading services/surfaceflinger/Scheduler/LayerInfo.cpp +21 −16 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ LayerInfo::LayerInfo(const std::string& name, uid_t ownerUid, : mName(name), mOwnerUid(ownerUid), mDefaultVote(defaultVote), mLayerVote({defaultVote, Fps(0.0f)}), mLayerVote({defaultVote, Fps()}), mRefreshRateHistory(name) {} void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType, Loading Loading @@ -93,10 +93,11 @@ bool LayerInfo::isFrequent(nsecs_t now) const { return false; } using fps_approx_ops::operator>=; // Layer is considered frequent if the average frame rate is higher than the threshold const auto totalTime = mFrameTimes.back().queueTime - it->queueTime; return Fps::fromPeriodNsecs(totalTime / (numFrames - 1)) .greaterThanOrEqualWithMargin(kMinFpsForFrequentLayer); return Fps::fromPeriodNsecs(totalTime / (numFrames - 1)) >= kMinFpsForFrequentLayer; } bool LayerInfo::isAnimating(nsecs_t now) const { Loading Loading @@ -191,17 +192,17 @@ std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible( return std::nullopt; } const auto averageFrameTime = calculateAverageFrameTime(); if (averageFrameTime.has_value()) { if (const auto averageFrameTime = calculateAverageFrameTime()) { const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime); const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now); if (refreshRateConsistent) { const auto knownRefreshRate = refreshRateConfigs.findClosestKnownFrameRate(refreshRate); // To avoid oscillation, use the last calculated refresh rate if it is // close enough using fps_approx_ops::operator!=; // To avoid oscillation, use the last calculated refresh rate if it is close enough. if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) > MARGIN && !mLastRefreshRate.reported.equalsWithMargin(knownRefreshRate)) { mLastRefreshRate.reported != knownRefreshRate) { mLastRefreshRate.calculated = refreshRate; mLastRefreshRate.reported = knownRefreshRate; } Loading @@ -228,15 +229,15 @@ LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateConfigs& ref if (isAnimating(now)) { ALOGV("%s is animating", mName.c_str()); mLastRefreshRate.animatingOrInfrequent = true; return {LayerHistory::LayerVoteType::Max, Fps(0.0f)}; return {LayerHistory::LayerVoteType::Max, Fps()}; } if (!isFrequent(now)) { ALOGV("%s is infrequent", mName.c_str()); mLastRefreshRate.animatingOrInfrequent = true; // Infrequent layers vote for mininal refresh rate for // Infrequent layers vote for minimal refresh rate for // battery saving purposes and also to prevent b/135718869. return {LayerHistory::LayerVoteType::Min, Fps(0.0f)}; return {LayerHistory::LayerVoteType::Min, Fps()}; } // If the layer was previously tagged as animating or infrequent, we clear Loading @@ -253,7 +254,7 @@ LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateConfigs& ref } ALOGV("%s Max (can't resolve refresh rate)", mName.c_str()); return {LayerHistory::LayerVoteType::Max, Fps(0.0f)}; return {LayerHistory::LayerVoteType::Max, Fps()}; } const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const { Loading Loading @@ -300,9 +301,13 @@ bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) { bool LayerInfo::RefreshRateHistory::isConsistent() const { if (mRefreshRates.empty()) return true; const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end()); const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end()); const auto consistent = const auto [min, max] = std::minmax_element(mRefreshRates.begin(), mRefreshRates.end(), [](const auto& lhs, const auto& rhs) { return isStrictlyLess(lhs.refreshRate, rhs.refreshRate); }); const bool consistent = max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS; if (CC_UNLIKELY(sTraceEnabled)) { Loading services/surfaceflinger/Scheduler/LayerInfo.h +12 −20 Original line number Diff line number Diff line Loading @@ -51,7 +51,7 @@ class LayerInfo { // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in // favor of a low refresh rate. static constexpr size_t kFrequentLayerWindowSize = 3; static constexpr Fps kMinFpsForFrequentLayer{10.0f}; static constexpr Fps kMinFpsForFrequentLayer = 10_Hz; static constexpr auto kMaxPeriodForFrequentLayerNs = std::chrono::nanoseconds(kMinFpsForFrequentLayer.getPeriodNsecs()) + 1ms; Loading @@ -62,7 +62,7 @@ public: // Holds information about the layer vote struct LayerVote { LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic; Fps fps{0.0f}; Fps fps; Seamlessness seamlessness = Seamlessness::Default; }; Loading @@ -86,19 +86,17 @@ public: using Seamlessness = scheduler::Seamlessness; Fps rate; FrameRateCompatibility type; Seamlessness seamlessness; FrameRateCompatibility type = FrameRateCompatibility::Default; Seamlessness seamlessness = Seamlessness::Default; FrameRate() = default; FrameRate() : rate(0), type(FrameRateCompatibility::Default), seamlessness(Seamlessness::Default) {} FrameRate(Fps rate, FrameRateCompatibility type, Seamlessness seamlessness = Seamlessness::OnlySeamless) : rate(rate), type(type), seamlessness(getSeamlessness(rate, seamlessness)) {} bool operator==(const FrameRate& other) const { return rate.equalsWithMargin(other.rate) && type == other.type && return isApproxEqual(rate, other.rate) && type == other.type && seamlessness == other.seamlessness; } Loading Loading @@ -151,7 +149,7 @@ public: void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; } // Resets the layer vote to its default. void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(0.0f), Seamlessness::Default}; } void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default}; } std::string getName() const { return mName; } Loading Loading @@ -201,9 +199,9 @@ private: // Holds information about the calculated and reported refresh rate struct RefreshRateHeuristicData { // Rate calculated on the layer Fps calculated{0.0f}; Fps calculated; // Last reported rate for LayerInfo::getRefreshRate() Fps reported{0.0f}; Fps reported; // Whether the last reported rate for LayerInfo::getRefreshRate() // was due to animation or infrequent updates bool animatingOrInfrequent = false; Loading @@ -229,14 +227,8 @@ private: // Holds the refresh rate when it was calculated struct RefreshRateData { Fps refreshRate{0.0f}; Fps refreshRate; nsecs_t timestamp = 0; bool operator<(const RefreshRateData& other) const { // We don't need comparison with margins since we are using // this to find the min and max refresh rates. return refreshRate.getValue() < other.refreshRate.getValue(); } }; // Holds tracing strings Loading Loading @@ -268,7 +260,7 @@ private: // Used for sanitizing the heuristic data. If two frames are less than // this period apart from each other they'll be considered as duplicates. static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(240.f).getPeriodNsecs(); static constexpr nsecs_t kMinPeriodBetweenFrames = (240_Hz).getPeriodNsecs(); // Used for sanitizing the heuristic data. If two frames are more than // this period apart from each other, the interval between them won't be // taken into account when calculating average frame rate. Loading services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +43 −31 Original line number Diff line number Diff line Loading @@ -42,19 +42,18 @@ std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, f } std::vector<Fps> constructKnownFrameRates(const DisplayModes& modes) { std::vector<Fps> knownFrameRates = {Fps(24.0f), Fps(30.0f), Fps(45.0f), Fps(60.0f), Fps(72.0f)}; std::vector<Fps> knownFrameRates = {24_Hz, 30_Hz, 45_Hz, 60_Hz, 72_Hz}; knownFrameRates.reserve(knownFrameRates.size() + modes.size()); // Add all supported refresh rates to the set // Add all supported refresh rates. for (const auto& mode : modes) { const auto refreshRate = Fps::fromPeriodNsecs(mode->getVsyncPeriod()); knownFrameRates.emplace_back(refreshRate); knownFrameRates.push_back(Fps::fromPeriodNsecs(mode->getVsyncPeriod())); } // Sort and remove duplicates std::sort(knownFrameRates.begin(), knownFrameRates.end(), Fps::comparesLess); // Sort and remove duplicates. std::sort(knownFrameRates.begin(), knownFrameRates.end(), isStrictlyLess); knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(), Fps::EqualsWithMargin()), isApproxEqual), knownFrameRates.end()); return knownFrameRates; } Loading @@ -64,6 +63,11 @@ std::vector<Fps> constructKnownFrameRates(const DisplayModes& modes) { using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType; using RefreshRate = RefreshRateConfigs::RefreshRate; bool RefreshRate::inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const { using fps_approx_ops::operator<=; return minRefreshRate <= getFps() && getFps() <= maxRefreshRate; } std::string RefreshRate::toString() const { return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}", getModeId().value(), mode->getHwcId(), getFps().getValue(), Loading Loading @@ -110,14 +114,14 @@ std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPe bool RefreshRateConfigs::isVoteAllowed(const LayerRequirement& layer, const RefreshRate& refreshRate) const { using namespace fps_approx_ops; switch (layer.vote) { case LayerVoteType::ExplicitExactOrMultiple: case LayerVoteType::Heuristic: if (mConfig.frameRateMultipleThreshold != 0 && refreshRate.getFps().greaterThanOrEqualWithMargin( Fps(mConfig.frameRateMultipleThreshold)) && layer.desiredRefreshRate.lessThanWithMargin( Fps(mConfig.frameRateMultipleThreshold / 2))) { refreshRate.getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold) && layer.desiredRefreshRate < Fps::fromValue(mConfig.frameRateMultipleThreshold / 2)) { // Don't vote high refresh rates past the threshold for layers with a low desired // refresh rate. For example, desired 24 fps with 120 Hz threshold means no vote for // 120 Hz, but desired 60 fps should have a vote. Loading Loading @@ -247,7 +251,7 @@ struct RefreshRateScore { }; RefreshRate RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals, GlobalSignals globalSignals, GlobalSignals* outSignalsConsidered) const { std::lock_guard lock(mLock); Loading @@ -269,7 +273,7 @@ RefreshRate RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequir } std::optional<RefreshRate> RefreshRateConfigs::getCachedBestRefreshRate( const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals, const std::vector<LayerRequirement>& layers, GlobalSignals globalSignals, GlobalSignals* outSignalsConsidered) const { const bool sameAsLastCall = lastBestRefreshRateInvocation && lastBestRefreshRateInvocation->layerRequirements == layers && Loading @@ -286,7 +290,7 @@ std::optional<RefreshRate> RefreshRateConfigs::getCachedBestRefreshRate( } RefreshRate RefreshRateConfigs::getBestRefreshRateLocked( const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals, const std::vector<LayerRequirement>& layers, GlobalSignals globalSignals, GlobalSignals* outSignalsConsidered) const { ATRACE_CALL(); ALOGV("getBestRefreshRate %zu layers", layers.size()); Loading Loading @@ -370,7 +374,7 @@ RefreshRate RefreshRateConfigs::getBestRefreshRateLocked( // move out the of range if layers explicitly request a different refresh // rate. const bool primaryRangeIsSingleRate = policy->primaryRange.min.equalsWithMargin(policy->primaryRange.max); isApproxEqual(policy->primaryRange.min, policy->primaryRange.max); if (!globalSignals.touch && globalSignals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) { Loading Loading @@ -498,8 +502,11 @@ RefreshRate RefreshRateConfigs::getBestRefreshRateLocked( return explicitExact == 0; } }(); using fps_approx_ops::operator<; if (globalSignals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact && bestRefreshRate->getFps().lessThanWithMargin(touchRefreshRate.getFps())) { bestRefreshRate->getFps() < touchRefreshRate.getFps()) { setTouchConsidered(); ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str()); return touchRefreshRate; Loading Loading @@ -552,7 +559,8 @@ std::vector<RefreshRateScore> initializeScoresForAllRefreshRates( } RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverrides( const std::vector<LayerRequirement>& layers, Fps displayFrameRate, bool touch) const { const std::vector<LayerRequirement>& layers, Fps displayFrameRate, GlobalSignals globalSignals) const { ATRACE_CALL(); if (!mSupportsFrameRateOverride) return {}; Loading @@ -570,7 +578,7 @@ RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverr return layer->vote == LayerVoteType::ExplicitExactOrMultiple; }); if (touch && hasExplicitExactOrMultiple) { if (globalSignals.touch && hasExplicitExactOrMultiple) { continue; } Loading Loading @@ -798,8 +806,10 @@ bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const { ALOGE("Default mode is not in the primary range."); return false; } return policy.appRequestRange.min.lessThanOrEqualWithMargin(policy.primaryRange.min) && policy.appRequestRange.max.greaterThanOrEqualWithMargin(policy.primaryRange.max); using namespace fps_approx_ops; return policy.appRequestRange.min <= policy.primaryRange.min && policy.appRequestRange.max >= policy.primaryRange.max; } status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) { Loading Loading @@ -925,19 +935,21 @@ void RefreshRateConfigs::constructAvailableRefreshRates() { } Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const { if (frameRate.lessThanOrEqualWithMargin(*mKnownFrameRates.begin())) { return *mKnownFrameRates.begin(); using namespace fps_approx_ops; if (frameRate <= mKnownFrameRates.front()) { return mKnownFrameRates.front(); } if (frameRate.greaterThanOrEqualWithMargin(*std::prev(mKnownFrameRates.end()))) { return *std::prev(mKnownFrameRates.end()); if (frameRate >= mKnownFrameRates.back()) { return mKnownFrameRates.back(); } auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate, Fps::comparesLess); isStrictlyLess); const auto distance1 = std::abs((frameRate.getValue() - lowerBound->getValue())); const auto distance2 = std::abs((frameRate.getValue() - std::prev(lowerBound)->getValue())); const auto distance1 = std::abs(frameRate.getValue() - lowerBound->getValue()); const auto distance2 = std::abs(frameRate.getValue() - std::prev(lowerBound)->getValue()); return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound); } Loading @@ -956,7 +968,7 @@ RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction } if (minByPolicy == maxByPolicy) { // when min primary range in display manager policy is below device min turn on the timer. if (currentPolicy->primaryRange.min.lessThanWithMargin(deviceMin.getFps())) { if (isApproxLess(currentPolicy->primaryRange.min, deviceMin.getFps())) { return RefreshRateConfigs::KernelIdleTimerAction::TurnOn; } return RefreshRateConfigs::KernelIdleTimerAction::TurnOff; Loading @@ -982,14 +994,14 @@ int RefreshRateConfigs::getFrameRateDivider(Fps displayFrameRate, Fps layerFrame } bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) { if (smaller.getValue() > bigger.getValue()) { if (isStrictlyLess(bigger, smaller)) { 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)); return isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier / kCoef)) || isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef)); } void RefreshRateConfigs::dump(std::string& result) const { Loading Loading
services/surfaceflinger/Fps.h +79 −63 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. Loading @@ -19,94 +19,110 @@ #include <cmath> #include <ostream> #include <string> #include <type_traits> #include <android-base/stringprintf.h> #include <utils/Timers.h> namespace android { // Value which represents "frames per second". This class is a wrapper around // float, providing some useful utilities, such as comparisons with tolerance // and converting between period duration and frequency. // Frames per second, stored as floating-point frequency. Provides conversion from/to period in // nanoseconds, and relational operators with precision threshold. // // const Fps fps = 60_Hz; // // using namespace fps_approx_ops; // assert(fps == Fps::fromPeriodNsecs(16'666'667)); // class Fps { public: static constexpr Fps fromPeriodNsecs(nsecs_t period) { return Fps(1e9f / period, period); } constexpr Fps() = default; Fps() = default; explicit constexpr Fps(float fps) : fps(fps), period(fps == 0.0f ? 0 : static_cast<nsecs_t>(1e9f / fps)) {} static constexpr Fps fromValue(float frequency) { return frequency > 0.f ? Fps(frequency, static_cast<nsecs_t>(1e9f / frequency)) : Fps(); } constexpr float getValue() const { return fps; } static constexpr Fps fromPeriodNsecs(nsecs_t period) { return period > 0 ? Fps(1e9f / period, period) : Fps(); } constexpr nsecs_t getPeriodNsecs() const { return period; } constexpr bool isValid() const { return mFrequency > 0.f; } bool equalsWithMargin(const Fps& other) const { return std::abs(fps - other.fps) < kMargin; } constexpr float getValue() const { return mFrequency; } int getIntValue() const { return static_cast<int>(std::round(mFrequency)); } // DO NOT use for std::sort. Instead use comparesLess(). bool lessThanWithMargin(const Fps& other) const { return fps + kMargin < other.fps; } constexpr nsecs_t getPeriodNsecs() const { return mPeriod; } bool greaterThanWithMargin(const Fps& other) const { return fps > other.fps + kMargin; } private: constexpr Fps(float frequency, nsecs_t period) : mFrequency(frequency), mPeriod(period) {} bool lessThanOrEqualWithMargin(const Fps& other) const { return !greaterThanWithMargin(other); } float mFrequency = 0.f; nsecs_t mPeriod = 0; }; bool greaterThanOrEqualWithMargin(const Fps& other) const { return !lessThanWithMargin(other); } static_assert(std::is_trivially_copyable_v<Fps>); bool isValid() const { return fps > 0.0f; } constexpr Fps operator""_Hz(unsigned long long frequency) { return Fps::fromValue(static_cast<float>(frequency)); } int getIntValue() const { return static_cast<int>(std::round(fps)); } constexpr Fps operator""_Hz(long double frequency) { return Fps::fromValue(static_cast<float>(frequency)); } // Use this comparator for sorting. Using a comparator with margins can // cause std::sort to crash. inline static bool comparesLess(const Fps& left, const Fps& right) { return left.fps < right.fps; inline bool isStrictlyLess(Fps lhs, Fps rhs) { return lhs.getValue() < rhs.getValue(); } // Compares two FPS with margin. // Transitivity is not guaranteed, i.e. a==b and b==c doesn't imply a==c. // DO NOT use with hash maps. Instead use EqualsInBuckets. struct EqualsWithMargin { bool operator()(const Fps& left, const Fps& right) const { return left.equalsWithMargin(right); // Does not satisfy equivalence relation. inline bool isApproxEqual(Fps lhs, Fps rhs) { // TODO(b/185536303): Replace with ULP distance. return std::abs(lhs.getValue() - rhs.getValue()) < 0.001f; } }; // Equals comparator which can be used with hash maps. // It's guaranteed that if two elements are equal, then their hashes are equal. struct EqualsInBuckets { bool operator()(const Fps& left, const Fps& right) const { return left.getBucket() == right.getBucket(); // Does not satisfy strict weak order. inline bool isApproxLess(Fps lhs, Fps rhs) { return isStrictlyLess(lhs, rhs) && !isApproxEqual(lhs, rhs); } }; inline friend std::string to_string(const Fps& fps) { return base::StringPrintf("%.2ffps", fps.fps); namespace fps_approx_ops { inline bool operator==(Fps lhs, Fps rhs) { return isApproxEqual(lhs, rhs); } inline friend std::ostream& operator<<(std::ostream& os, const Fps& fps) { return os << to_string(fps); inline bool operator<(Fps lhs, Fps rhs) { return isApproxLess(lhs, rhs); } private: friend std::hash<android::Fps>; inline bool operator!=(Fps lhs, Fps rhs) { return !isApproxEqual(lhs, rhs); } constexpr Fps(float fps, nsecs_t period) : fps(fps), period(period) {} inline bool operator>(Fps lhs, Fps rhs) { return isApproxLess(rhs, lhs); } float getBucket() const { return std::round(fps / kMargin); } inline bool operator<=(Fps lhs, Fps rhs) { return !isApproxLess(rhs, lhs); } static constexpr float kMargin = 0.001f; float fps = 0; nsecs_t period = 0; }; inline bool operator>=(Fps lhs, Fps rhs) { return !isApproxLess(lhs, rhs); } static_assert(std::is_trivially_copyable_v<Fps>); } // namespace fps_approx_ops } // namespace android struct FpsApproxEqual { bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); } }; namespace std { template <> struct hash<android::Fps> { std::size_t operator()(const android::Fps& fps) const { return std::hash<float>()(fps.getBucket()); inline std::string to_string(Fps fps) { return base::StringPrintf("%.2f Hz", fps.getValue()); } }; } // namespace std No newline at end of file inline std::ostream& operator<<(std::ostream& stream, Fps fps) { return stream << to_string(fps); } } // namespace android
services/surfaceflinger/Layer.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -1138,7 +1138,7 @@ bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* tran if (!frameRate.rate.isValid() && frameRate.type != FrameRateCompatibility::NoVote && childrenHaveFrameRate) { *transactionNeeded |= setFrameRateForLayerTree(FrameRate(Fps(0.0f), FrameRateCompatibility::NoVote)); setFrameRateForLayerTree(FrameRate(Fps(), FrameRateCompatibility::NoVote)); } // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for Loading
services/surfaceflinger/Scheduler/LayerInfo.cpp +21 −16 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ LayerInfo::LayerInfo(const std::string& name, uid_t ownerUid, : mName(name), mOwnerUid(ownerUid), mDefaultVote(defaultVote), mLayerVote({defaultVote, Fps(0.0f)}), mLayerVote({defaultVote, Fps()}), mRefreshRateHistory(name) {} void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType, Loading Loading @@ -93,10 +93,11 @@ bool LayerInfo::isFrequent(nsecs_t now) const { return false; } using fps_approx_ops::operator>=; // Layer is considered frequent if the average frame rate is higher than the threshold const auto totalTime = mFrameTimes.back().queueTime - it->queueTime; return Fps::fromPeriodNsecs(totalTime / (numFrames - 1)) .greaterThanOrEqualWithMargin(kMinFpsForFrequentLayer); return Fps::fromPeriodNsecs(totalTime / (numFrames - 1)) >= kMinFpsForFrequentLayer; } bool LayerInfo::isAnimating(nsecs_t now) const { Loading Loading @@ -191,17 +192,17 @@ std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible( return std::nullopt; } const auto averageFrameTime = calculateAverageFrameTime(); if (averageFrameTime.has_value()) { if (const auto averageFrameTime = calculateAverageFrameTime()) { const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime); const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now); if (refreshRateConsistent) { const auto knownRefreshRate = refreshRateConfigs.findClosestKnownFrameRate(refreshRate); // To avoid oscillation, use the last calculated refresh rate if it is // close enough using fps_approx_ops::operator!=; // To avoid oscillation, use the last calculated refresh rate if it is close enough. if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) > MARGIN && !mLastRefreshRate.reported.equalsWithMargin(knownRefreshRate)) { mLastRefreshRate.reported != knownRefreshRate) { mLastRefreshRate.calculated = refreshRate; mLastRefreshRate.reported = knownRefreshRate; } Loading @@ -228,15 +229,15 @@ LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateConfigs& ref if (isAnimating(now)) { ALOGV("%s is animating", mName.c_str()); mLastRefreshRate.animatingOrInfrequent = true; return {LayerHistory::LayerVoteType::Max, Fps(0.0f)}; return {LayerHistory::LayerVoteType::Max, Fps()}; } if (!isFrequent(now)) { ALOGV("%s is infrequent", mName.c_str()); mLastRefreshRate.animatingOrInfrequent = true; // Infrequent layers vote for mininal refresh rate for // Infrequent layers vote for minimal refresh rate for // battery saving purposes and also to prevent b/135718869. return {LayerHistory::LayerVoteType::Min, Fps(0.0f)}; return {LayerHistory::LayerVoteType::Min, Fps()}; } // If the layer was previously tagged as animating or infrequent, we clear Loading @@ -253,7 +254,7 @@ LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateConfigs& ref } ALOGV("%s Max (can't resolve refresh rate)", mName.c_str()); return {LayerHistory::LayerVoteType::Max, Fps(0.0f)}; return {LayerHistory::LayerVoteType::Max, Fps()}; } const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const { Loading Loading @@ -300,9 +301,13 @@ bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) { bool LayerInfo::RefreshRateHistory::isConsistent() const { if (mRefreshRates.empty()) return true; const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end()); const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end()); const auto consistent = const auto [min, max] = std::minmax_element(mRefreshRates.begin(), mRefreshRates.end(), [](const auto& lhs, const auto& rhs) { return isStrictlyLess(lhs.refreshRate, rhs.refreshRate); }); const bool consistent = max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS; if (CC_UNLIKELY(sTraceEnabled)) { Loading
services/surfaceflinger/Scheduler/LayerInfo.h +12 −20 Original line number Diff line number Diff line Loading @@ -51,7 +51,7 @@ class LayerInfo { // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in // favor of a low refresh rate. static constexpr size_t kFrequentLayerWindowSize = 3; static constexpr Fps kMinFpsForFrequentLayer{10.0f}; static constexpr Fps kMinFpsForFrequentLayer = 10_Hz; static constexpr auto kMaxPeriodForFrequentLayerNs = std::chrono::nanoseconds(kMinFpsForFrequentLayer.getPeriodNsecs()) + 1ms; Loading @@ -62,7 +62,7 @@ public: // Holds information about the layer vote struct LayerVote { LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic; Fps fps{0.0f}; Fps fps; Seamlessness seamlessness = Seamlessness::Default; }; Loading @@ -86,19 +86,17 @@ public: using Seamlessness = scheduler::Seamlessness; Fps rate; FrameRateCompatibility type; Seamlessness seamlessness; FrameRateCompatibility type = FrameRateCompatibility::Default; Seamlessness seamlessness = Seamlessness::Default; FrameRate() = default; FrameRate() : rate(0), type(FrameRateCompatibility::Default), seamlessness(Seamlessness::Default) {} FrameRate(Fps rate, FrameRateCompatibility type, Seamlessness seamlessness = Seamlessness::OnlySeamless) : rate(rate), type(type), seamlessness(getSeamlessness(rate, seamlessness)) {} bool operator==(const FrameRate& other) const { return rate.equalsWithMargin(other.rate) && type == other.type && return isApproxEqual(rate, other.rate) && type == other.type && seamlessness == other.seamlessness; } Loading Loading @@ -151,7 +149,7 @@ public: void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; } // Resets the layer vote to its default. void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(0.0f), Seamlessness::Default}; } void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default}; } std::string getName() const { return mName; } Loading Loading @@ -201,9 +199,9 @@ private: // Holds information about the calculated and reported refresh rate struct RefreshRateHeuristicData { // Rate calculated on the layer Fps calculated{0.0f}; Fps calculated; // Last reported rate for LayerInfo::getRefreshRate() Fps reported{0.0f}; Fps reported; // Whether the last reported rate for LayerInfo::getRefreshRate() // was due to animation or infrequent updates bool animatingOrInfrequent = false; Loading @@ -229,14 +227,8 @@ private: // Holds the refresh rate when it was calculated struct RefreshRateData { Fps refreshRate{0.0f}; Fps refreshRate; nsecs_t timestamp = 0; bool operator<(const RefreshRateData& other) const { // We don't need comparison with margins since we are using // this to find the min and max refresh rates. return refreshRate.getValue() < other.refreshRate.getValue(); } }; // Holds tracing strings Loading Loading @@ -268,7 +260,7 @@ private: // Used for sanitizing the heuristic data. If two frames are less than // this period apart from each other they'll be considered as duplicates. static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(240.f).getPeriodNsecs(); static constexpr nsecs_t kMinPeriodBetweenFrames = (240_Hz).getPeriodNsecs(); // Used for sanitizing the heuristic data. If two frames are more than // this period apart from each other, the interval between them won't be // taken into account when calculating average frame rate. Loading
services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +43 −31 Original line number Diff line number Diff line Loading @@ -42,19 +42,18 @@ std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, f } std::vector<Fps> constructKnownFrameRates(const DisplayModes& modes) { std::vector<Fps> knownFrameRates = {Fps(24.0f), Fps(30.0f), Fps(45.0f), Fps(60.0f), Fps(72.0f)}; std::vector<Fps> knownFrameRates = {24_Hz, 30_Hz, 45_Hz, 60_Hz, 72_Hz}; knownFrameRates.reserve(knownFrameRates.size() + modes.size()); // Add all supported refresh rates to the set // Add all supported refresh rates. for (const auto& mode : modes) { const auto refreshRate = Fps::fromPeriodNsecs(mode->getVsyncPeriod()); knownFrameRates.emplace_back(refreshRate); knownFrameRates.push_back(Fps::fromPeriodNsecs(mode->getVsyncPeriod())); } // Sort and remove duplicates std::sort(knownFrameRates.begin(), knownFrameRates.end(), Fps::comparesLess); // Sort and remove duplicates. std::sort(knownFrameRates.begin(), knownFrameRates.end(), isStrictlyLess); knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(), Fps::EqualsWithMargin()), isApproxEqual), knownFrameRates.end()); return knownFrameRates; } Loading @@ -64,6 +63,11 @@ std::vector<Fps> constructKnownFrameRates(const DisplayModes& modes) { using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType; using RefreshRate = RefreshRateConfigs::RefreshRate; bool RefreshRate::inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const { using fps_approx_ops::operator<=; return minRefreshRate <= getFps() && getFps() <= maxRefreshRate; } std::string RefreshRate::toString() const { return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}", getModeId().value(), mode->getHwcId(), getFps().getValue(), Loading Loading @@ -110,14 +114,14 @@ std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPe bool RefreshRateConfigs::isVoteAllowed(const LayerRequirement& layer, const RefreshRate& refreshRate) const { using namespace fps_approx_ops; switch (layer.vote) { case LayerVoteType::ExplicitExactOrMultiple: case LayerVoteType::Heuristic: if (mConfig.frameRateMultipleThreshold != 0 && refreshRate.getFps().greaterThanOrEqualWithMargin( Fps(mConfig.frameRateMultipleThreshold)) && layer.desiredRefreshRate.lessThanWithMargin( Fps(mConfig.frameRateMultipleThreshold / 2))) { refreshRate.getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold) && layer.desiredRefreshRate < Fps::fromValue(mConfig.frameRateMultipleThreshold / 2)) { // Don't vote high refresh rates past the threshold for layers with a low desired // refresh rate. For example, desired 24 fps with 120 Hz threshold means no vote for // 120 Hz, but desired 60 fps should have a vote. Loading Loading @@ -247,7 +251,7 @@ struct RefreshRateScore { }; RefreshRate RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals, GlobalSignals globalSignals, GlobalSignals* outSignalsConsidered) const { std::lock_guard lock(mLock); Loading @@ -269,7 +273,7 @@ RefreshRate RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequir } std::optional<RefreshRate> RefreshRateConfigs::getCachedBestRefreshRate( const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals, const std::vector<LayerRequirement>& layers, GlobalSignals globalSignals, GlobalSignals* outSignalsConsidered) const { const bool sameAsLastCall = lastBestRefreshRateInvocation && lastBestRefreshRateInvocation->layerRequirements == layers && Loading @@ -286,7 +290,7 @@ std::optional<RefreshRate> RefreshRateConfigs::getCachedBestRefreshRate( } RefreshRate RefreshRateConfigs::getBestRefreshRateLocked( const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals, const std::vector<LayerRequirement>& layers, GlobalSignals globalSignals, GlobalSignals* outSignalsConsidered) const { ATRACE_CALL(); ALOGV("getBestRefreshRate %zu layers", layers.size()); Loading Loading @@ -370,7 +374,7 @@ RefreshRate RefreshRateConfigs::getBestRefreshRateLocked( // move out the of range if layers explicitly request a different refresh // rate. const bool primaryRangeIsSingleRate = policy->primaryRange.min.equalsWithMargin(policy->primaryRange.max); isApproxEqual(policy->primaryRange.min, policy->primaryRange.max); if (!globalSignals.touch && globalSignals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) { Loading Loading @@ -498,8 +502,11 @@ RefreshRate RefreshRateConfigs::getBestRefreshRateLocked( return explicitExact == 0; } }(); using fps_approx_ops::operator<; if (globalSignals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact && bestRefreshRate->getFps().lessThanWithMargin(touchRefreshRate.getFps())) { bestRefreshRate->getFps() < touchRefreshRate.getFps()) { setTouchConsidered(); ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str()); return touchRefreshRate; Loading Loading @@ -552,7 +559,8 @@ std::vector<RefreshRateScore> initializeScoresForAllRefreshRates( } RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverrides( const std::vector<LayerRequirement>& layers, Fps displayFrameRate, bool touch) const { const std::vector<LayerRequirement>& layers, Fps displayFrameRate, GlobalSignals globalSignals) const { ATRACE_CALL(); if (!mSupportsFrameRateOverride) return {}; Loading @@ -570,7 +578,7 @@ RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverr return layer->vote == LayerVoteType::ExplicitExactOrMultiple; }); if (touch && hasExplicitExactOrMultiple) { if (globalSignals.touch && hasExplicitExactOrMultiple) { continue; } Loading Loading @@ -798,8 +806,10 @@ bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const { ALOGE("Default mode is not in the primary range."); return false; } return policy.appRequestRange.min.lessThanOrEqualWithMargin(policy.primaryRange.min) && policy.appRequestRange.max.greaterThanOrEqualWithMargin(policy.primaryRange.max); using namespace fps_approx_ops; return policy.appRequestRange.min <= policy.primaryRange.min && policy.appRequestRange.max >= policy.primaryRange.max; } status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) { Loading Loading @@ -925,19 +935,21 @@ void RefreshRateConfigs::constructAvailableRefreshRates() { } Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const { if (frameRate.lessThanOrEqualWithMargin(*mKnownFrameRates.begin())) { return *mKnownFrameRates.begin(); using namespace fps_approx_ops; if (frameRate <= mKnownFrameRates.front()) { return mKnownFrameRates.front(); } if (frameRate.greaterThanOrEqualWithMargin(*std::prev(mKnownFrameRates.end()))) { return *std::prev(mKnownFrameRates.end()); if (frameRate >= mKnownFrameRates.back()) { return mKnownFrameRates.back(); } auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate, Fps::comparesLess); isStrictlyLess); const auto distance1 = std::abs((frameRate.getValue() - lowerBound->getValue())); const auto distance2 = std::abs((frameRate.getValue() - std::prev(lowerBound)->getValue())); const auto distance1 = std::abs(frameRate.getValue() - lowerBound->getValue()); const auto distance2 = std::abs(frameRate.getValue() - std::prev(lowerBound)->getValue()); return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound); } Loading @@ -956,7 +968,7 @@ RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction } if (minByPolicy == maxByPolicy) { // when min primary range in display manager policy is below device min turn on the timer. if (currentPolicy->primaryRange.min.lessThanWithMargin(deviceMin.getFps())) { if (isApproxLess(currentPolicy->primaryRange.min, deviceMin.getFps())) { return RefreshRateConfigs::KernelIdleTimerAction::TurnOn; } return RefreshRateConfigs::KernelIdleTimerAction::TurnOff; Loading @@ -982,14 +994,14 @@ int RefreshRateConfigs::getFrameRateDivider(Fps displayFrameRate, Fps layerFrame } bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) { if (smaller.getValue() > bigger.getValue()) { if (isStrictlyLess(bigger, smaller)) { 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)); return isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier / kCoef)) || isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef)); } void RefreshRateConfigs::dump(std::string& result) const { Loading