1772 lines
74 KiB
C++
1772 lines
74 KiB
C++
|
|
/*
|
||
|
|
* Copyright 2019 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.
|
||
|
|
* You may obtain a copy of the License at
|
||
|
|
*
|
||
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
*
|
||
|
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
* See the License for the specific language governing permissions and
|
||
|
|
* limitations under the License.
|
||
|
|
*/
|
||
|
|
|
||
|
|
// #define LOG_NDEBUG 0
|
||
|
|
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
|
||
|
|
|
||
|
|
// TODO(b/129481165): remove the #pragma below and fix conversion issues
|
||
|
|
#pragma clang diagnostic push
|
||
|
|
#pragma clang diagnostic ignored "-Wextra"
|
||
|
|
|
||
|
|
#include <chrono>
|
||
|
|
#include <cmath>
|
||
|
|
#include <deque>
|
||
|
|
#include <map>
|
||
|
|
|
||
|
|
#include <android-base/properties.h>
|
||
|
|
#include <android-base/stringprintf.h>
|
||
|
|
#include <ftl/enum.h>
|
||
|
|
#include <ftl/fake_guard.h>
|
||
|
|
#include <ftl/match.h>
|
||
|
|
#include <ftl/unit.h>
|
||
|
|
#include <gui/TraceUtils.h>
|
||
|
|
#include <scheduler/FrameRateMode.h>
|
||
|
|
#include <utils/Trace.h>
|
||
|
|
|
||
|
|
#include "../SurfaceFlingerProperties.h"
|
||
|
|
#include "RefreshRateSelector.h"
|
||
|
|
|
||
|
|
#undef LOG_TAG
|
||
|
|
#define LOG_TAG "RefreshRateSelector"
|
||
|
|
|
||
|
|
namespace android::scheduler {
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
struct RefreshRateScore {
|
||
|
|
FrameRateMode frameRateMode;
|
||
|
|
float overallScore;
|
||
|
|
struct {
|
||
|
|
float modeBelowThreshold;
|
||
|
|
float modeAboveThreshold;
|
||
|
|
} fixedRateBelowThresholdLayersScore;
|
||
|
|
};
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
// need this flag to ignore touch active, since touch state will always be active
|
||
|
|
// for touch scroll mode: 1001->120->90->1000
|
||
|
|
static bool sIsRefreshRateControlByAP = false;
|
||
|
|
static bool sShowContentDetectionDetailLog = false;
|
||
|
|
|
||
|
|
static bool isSupportContentDetection() {
|
||
|
|
static bool enable = false;
|
||
|
|
static bool read = false;
|
||
|
|
if (!read) {
|
||
|
|
enable = android::base::GetBoolProperty("debug.sf.use_content_detection_for_refresh_rate", false);
|
||
|
|
sShowContentDetectionDetailLog = android::base::GetBoolProperty("debug.sf.show_content_detection_detail_log", false);
|
||
|
|
read = true;
|
||
|
|
}
|
||
|
|
return enable;
|
||
|
|
}
|
||
|
|
static bool isShowCDDetailLog() {
|
||
|
|
return sShowContentDetectionDetailLog;
|
||
|
|
}
|
||
|
|
static int init __attribute((unused)) = (isSupportContentDetection(), 0);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
constexpr RefreshRateSelector::GlobalSignals kNoSignals;
|
||
|
|
|
||
|
|
std::string formatLayerInfo(const RefreshRateSelector::LayerRequirement& layer, float weight) {
|
||
|
|
return base::StringPrintf("%s (type=%s, weight=%.2f, seamlessness=%s) %s", layer.name.c_str(),
|
||
|
|
ftl::enum_string(layer.vote).c_str(), weight,
|
||
|
|
ftl::enum_string(layer.seamlessness).c_str(),
|
||
|
|
to_string(layer.desiredRefreshRate).c_str());
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<Fps> constructKnownFrameRates(const DisplayModes& modes) {
|
||
|
|
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.
|
||
|
|
for (const auto& [id, mode] : modes) {
|
||
|
|
knownFrameRates.push_back(mode->getFps());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sort and remove duplicates.
|
||
|
|
std::sort(knownFrameRates.begin(), knownFrameRates.end(), isStrictlyLess);
|
||
|
|
knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
|
||
|
|
isApproxEqual),
|
||
|
|
knownFrameRates.end());
|
||
|
|
return knownFrameRates;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes) {
|
||
|
|
std::vector<DisplayModeIterator> sortedModes;
|
||
|
|
sortedModes.reserve(modes.size());
|
||
|
|
for (auto it = modes.begin(); it != modes.end(); ++it) {
|
||
|
|
sortedModes.push_back(it);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::sort(sortedModes.begin(), sortedModes.end(), [](auto it1, auto it2) {
|
||
|
|
const auto& mode1 = it1->second;
|
||
|
|
const auto& mode2 = it2->second;
|
||
|
|
|
||
|
|
if (mode1->getVsyncPeriod() == mode2->getVsyncPeriod()) {
|
||
|
|
return mode1->getGroup() > mode2->getGroup();
|
||
|
|
}
|
||
|
|
|
||
|
|
return mode1->getVsyncPeriod() > mode2->getVsyncPeriod();
|
||
|
|
});
|
||
|
|
|
||
|
|
return sortedModes;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::pair<unsigned, unsigned> divisorRange(Fps fps, FpsRange range,
|
||
|
|
RefreshRateSelector::Config::FrameRateOverride config) {
|
||
|
|
if (config != RefreshRateSelector::Config::FrameRateOverride::Enabled) {
|
||
|
|
return {1, 1};
|
||
|
|
}
|
||
|
|
|
||
|
|
using fps_approx_ops::operator/;
|
||
|
|
// use signed type as `fps / range.max` might be 0
|
||
|
|
const auto start = std::max(1, static_cast<int>(fps / range.max) - 1);
|
||
|
|
const auto end = fps /
|
||
|
|
std::max(range.min, RefreshRateSelector::kMinSupportedFrameRate,
|
||
|
|
fps_approx_ops::operator<);
|
||
|
|
|
||
|
|
return {start, end};
|
||
|
|
}
|
||
|
|
|
||
|
|
bool shouldEnableFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) {
|
||
|
|
for (const auto it1 : sortedModes) {
|
||
|
|
const auto& mode1 = it1->second;
|
||
|
|
for (const auto it2 : sortedModes) {
|
||
|
|
const auto& mode2 = it2->second;
|
||
|
|
|
||
|
|
if (RefreshRateSelector::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string toString(const RefreshRateSelector::PolicyVariant& policy) {
|
||
|
|
using namespace std::string_literals;
|
||
|
|
|
||
|
|
return ftl::match(
|
||
|
|
policy,
|
||
|
|
[](const RefreshRateSelector::DisplayManagerPolicy& policy) {
|
||
|
|
return "DisplayManagerPolicy"s + policy.toString();
|
||
|
|
},
|
||
|
|
[](const RefreshRateSelector::OverridePolicy& policy) {
|
||
|
|
return "OverridePolicy"s + policy.toString();
|
||
|
|
},
|
||
|
|
[](RefreshRateSelector::NoOverridePolicy) { return "NoOverridePolicy"s; });
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
Fps RefreshRateSelector::kMinSupportedFrameRate = 20_Hz;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
auto RefreshRateSelector::createFrameRateModes(
|
||
|
|
std::function<bool(const DisplayMode&)>&& filterModes, const FpsRange& renderRange) const
|
||
|
|
-> std::vector<FrameRateMode> {
|
||
|
|
struct Key {
|
||
|
|
Fps fps;
|
||
|
|
int32_t group;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct KeyLess {
|
||
|
|
bool operator()(const Key& a, const Key& b) const {
|
||
|
|
using namespace fps_approx_ops;
|
||
|
|
if (a.fps != b.fps) {
|
||
|
|
return a.fps < b.fps;
|
||
|
|
}
|
||
|
|
|
||
|
|
// For the same fps the order doesn't really matter, but we still
|
||
|
|
// want the behaviour of a strictly less operator.
|
||
|
|
// We use the group id as the secondary ordering for that.
|
||
|
|
return a.group < b.group;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
std::map<Key, DisplayModeIterator, KeyLess> ratesMap;
|
||
|
|
for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) {
|
||
|
|
const auto& [id, mode] = *it;
|
||
|
|
|
||
|
|
if (!filterModes(*mode)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const auto [start, end] =
|
||
|
|
divisorRange(mode->getFps(), renderRange, mConfig.enableFrameRateOverride);
|
||
|
|
for (auto divisor = start; divisor <= end; divisor++) {
|
||
|
|
const auto fps = mode->getFps() / divisor;
|
||
|
|
using fps_approx_ops::operator<;
|
||
|
|
if (divisor > 1 && fps < kMinSupportedFrameRate) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Enabled &&
|
||
|
|
!renderRange.includes(fps)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mConfig.enableFrameRateOverride ==
|
||
|
|
Config::FrameRateOverride::AppOverrideNativeRefreshRates &&
|
||
|
|
!isNativeRefreshRate(fps)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
// not divide too deep
|
||
|
|
if (kMinSupportedFrameRate.getIntValue() < 20 && divisor > 2) {
|
||
|
|
/*ALOGI("%s: skip %s (%s)", __func__, to_string(fps).c_str(),
|
||
|
|
to_string(mode->getFps()).c_str());*/
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
const auto [existingIter, emplaceHappened] =
|
||
|
|
ratesMap.try_emplace(Key{fps, mode->getGroup()}, it);
|
||
|
|
if (emplaceHappened) {
|
||
|
|
ALOGV("%s: including %s (%s)", __func__, to_string(fps).c_str(),
|
||
|
|
to_string(mode->getFps()).c_str());
|
||
|
|
} else {
|
||
|
|
// We might need to update the map as we found a lower refresh rate
|
||
|
|
if (isStrictlyLess(mode->getFps(), existingIter->second->second->getFps())) {
|
||
|
|
existingIter->second = it;
|
||
|
|
ALOGV("%s: changing %s (%s)", __func__, to_string(fps).c_str(),
|
||
|
|
to_string(mode->getFps()).c_str());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<FrameRateMode> frameRateModes;
|
||
|
|
frameRateModes.reserve(ratesMap.size());
|
||
|
|
for (const auto& [key, mode] : ratesMap) {
|
||
|
|
frameRateModes.emplace_back(FrameRateMode{key.fps, ftl::as_non_null(mode->second)});
|
||
|
|
}
|
||
|
|
|
||
|
|
// We always want that the lowest frame rate will be corresponding to the
|
||
|
|
// lowest mode for power saving.
|
||
|
|
const auto lowestRefreshRateIt =
|
||
|
|
std::min_element(frameRateModes.begin(), frameRateModes.end(),
|
||
|
|
[](const FrameRateMode& lhs, const FrameRateMode& rhs) {
|
||
|
|
return isStrictlyLess(lhs.modePtr->getFps(),
|
||
|
|
rhs.modePtr->getFps());
|
||
|
|
});
|
||
|
|
frameRateModes.erase(frameRateModes.begin(), lowestRefreshRateIt);
|
||
|
|
|
||
|
|
return frameRateModes;
|
||
|
|
}
|
||
|
|
|
||
|
|
struct RefreshRateSelector::RefreshRateScoreComparator {
|
||
|
|
bool operator()(const RefreshRateScore& lhs, const RefreshRateScore& rhs) const {
|
||
|
|
const auto& [frameRateMode, overallScore, _] = lhs;
|
||
|
|
|
||
|
|
std::string name = to_string(frameRateMode);
|
||
|
|
|
||
|
|
ALOGV("%s sorting scores %.2f", name.c_str(), overallScore);
|
||
|
|
|
||
|
|
if (!ScoredFrameRate::scoresEqual(overallScore, rhs.overallScore)) {
|
||
|
|
return overallScore > rhs.overallScore;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (refreshRateOrder == RefreshRateOrder::Descending) {
|
||
|
|
using fps_approx_ops::operator>;
|
||
|
|
return frameRateMode.fps > rhs.frameRateMode.fps;
|
||
|
|
} else {
|
||
|
|
using fps_approx_ops::operator<;
|
||
|
|
return frameRateMode.fps < rhs.frameRateMode.fps;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const RefreshRateOrder refreshRateOrder;
|
||
|
|
};
|
||
|
|
|
||
|
|
std::string RefreshRateSelector::Policy::toString() const {
|
||
|
|
return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s"
|
||
|
|
", primaryRanges=%s, appRequestRanges=%s}",
|
||
|
|
defaultMode.value(), allowGroupSwitching ? "true" : "false",
|
||
|
|
to_string(primaryRanges).c_str(),
|
||
|
|
to_string(appRequestRanges).c_str());
|
||
|
|
}
|
||
|
|
|
||
|
|
std::pair<nsecs_t, nsecs_t> RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod,
|
||
|
|
nsecs_t displayPeriod) const {
|
||
|
|
auto [quotient, remainder] = std::div(layerPeriod, displayPeriod);
|
||
|
|
if (remainder <= MARGIN_FOR_PERIOD_CALCULATION ||
|
||
|
|
std::abs(remainder - displayPeriod) <= MARGIN_FOR_PERIOD_CALCULATION) {
|
||
|
|
quotient++;
|
||
|
|
remainder = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {quotient, remainder};
|
||
|
|
}
|
||
|
|
|
||
|
|
float RefreshRateSelector::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer,
|
||
|
|
Fps refreshRate) const {
|
||
|
|
constexpr float kScoreForFractionalPairs = .8f;
|
||
|
|
|
||
|
|
const auto displayPeriod = refreshRate.getPeriodNsecs();
|
||
|
|
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 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) {
|
||
|
|
const float multiplier = refreshRate.getValue() / layer.desiredRefreshRate.getValue();
|
||
|
|
|
||
|
|
// We only want to score this layer as a fractional pair if the content is not
|
||
|
|
// significantly faster than the display rate, at it would cause a significant frame drop.
|
||
|
|
// It is more appropriate to choose a higher display rate even if
|
||
|
|
// a pull-down will be required.
|
||
|
|
constexpr float kMinMultiplier = 0.75f;
|
||
|
|
if (multiplier >= kMinMultiplier &&
|
||
|
|
isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
|
||
|
|
return kScoreForFractionalPairs;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate how many display vsyncs we need to present a single frame for this
|
||
|
|
// layer
|
||
|
|
const auto [displayFramesQuotient, displayFramesRemainder] =
|
||
|
|
getDisplayFrames(layerPeriod, displayPeriod);
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (displayFramesQuotient == 0) {
|
||
|
|
// Layer desired refresh rate is higher than the display rate.
|
||
|
|
return (static_cast<float>(layerPeriod) / static_cast<float>(displayPeriod)) *
|
||
|
|
(1.0f / (MAX_FRAMES_TO_FIT + 1));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Layer desired refresh rate is lower than the display rate. Check how well it fits
|
||
|
|
// the cadence.
|
||
|
|
auto diff = std::abs(displayFramesRemainder - (displayPeriod - displayFramesRemainder));
|
||
|
|
int iter = 2;
|
||
|
|
while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) {
|
||
|
|
diff = diff - (displayPeriod - diff);
|
||
|
|
iter++;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (1.0f / iter);
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
float RefreshRateSelector::calculateDistanceScoreFromMax(Fps refreshRate) const {
|
||
|
|
const auto& maxFps = mAppRequestFrameRates.back().fps;
|
||
|
|
const float ratio = refreshRate.getValue() / maxFps.getValue();
|
||
|
|
// Use ratio^2 to get a lower score the more we get further from peak
|
||
|
|
return ratio * ratio;
|
||
|
|
}
|
||
|
|
|
||
|
|
float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate,
|
||
|
|
bool isSeamlessSwitch) const {
|
||
|
|
ATRACE_CALL();
|
||
|
|
// 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) {
|
||
|
|
return calculateDistanceScoreFromMax(refreshRate);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (layer.vote == LayerVoteType::ExplicitExact) {
|
||
|
|
const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate);
|
||
|
|
if (supportsAppFrameRateOverrideByContent()) {
|
||
|
|
// Since we support frame rate override, allow refresh rates which are
|
||
|
|
// multiples of the layer's request, as those apps would be throttled
|
||
|
|
// down to run at the desired refresh rate.
|
||
|
|
return divisor > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return divisor == 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If the layer frame rate is a divisor of the refresh rate it should score
|
||
|
|
// the highest score.
|
||
|
|
if (getFrameRateDivisor(refreshRate, layer.desiredRefreshRate) > 0) {
|
||
|
|
return 1.0f * seamlessness;
|
||
|
|
}
|
||
|
|
|
||
|
|
// The layer frame rate is not a divisor 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;
|
||
|
|
}
|
||
|
|
|
||
|
|
auto RefreshRateSelector::getRankedFrameRates(const std::vector<LayerRequirement>& layers,
|
||
|
|
GlobalSignals signals) const -> RankedFrameRates {
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
|
||
|
|
if (mGetRankedFrameRatesCache &&
|
||
|
|
mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) {
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: layers is the same as mGetRankedFrameRatesCache, return cached ones", __func__);
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return mGetRankedFrameRatesCache->result;
|
||
|
|
}
|
||
|
|
|
||
|
|
const auto result = getRankedFrameRatesLocked(layers, signals);
|
||
|
|
mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result};
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
|
||
|
|
GlobalSignals signals) const
|
||
|
|
-> RankedFrameRates {
|
||
|
|
using namespace fps_approx_ops;
|
||
|
|
ATRACE_CALL();
|
||
|
|
ALOGV("%s: %zu layers", __func__, layers.size());
|
||
|
|
|
||
|
|
const auto& activeMode = *getActiveModeLocked().modePtr;
|
||
|
|
|
||
|
|
// Keep the display at max frame rate for the duration of powering on the display.
|
||
|
|
if (signals.powerOnImminent) {
|
||
|
|
ALOGV("Power On Imminent");
|
||
|
|
const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending);
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (Power On Imminent)",
|
||
|
|
to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
return {ranking, GlobalSignals{.powerOnImminent = true}};
|
||
|
|
}
|
||
|
|
|
||
|
|
int noVoteLayers = 0;
|
||
|
|
int minVoteLayers = 0;
|
||
|
|
int maxVoteLayers = 0;
|
||
|
|
int explicitDefaultVoteLayers = 0;
|
||
|
|
int explicitExactOrMultipleVoteLayers = 0;
|
||
|
|
int explicitExact = 0;
|
||
|
|
int seamedFocusedLayers = 0;
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
int noChange = 0;
|
||
|
|
int fpsFloor = 0;
|
||
|
|
int heuristic = 0;
|
||
|
|
int heuristic_fps_max = 0;
|
||
|
|
int explicit_fps_max = 0;
|
||
|
|
int heuristic_infrequent = 0;
|
||
|
|
int needPromoteLayers = 0;
|
||
|
|
std::vector<std::string> vecExplicitPkgName;
|
||
|
|
std::vector<std::string> vecHeuristicName;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
for (const auto& layer : layers) {
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: vote=%s, fps=%.2f, weight=%.2f, focused=%d, isProtected=%d, name=%s",
|
||
|
|
__func__, ftl::enum_string(layer.vote).c_str(), layer.desiredRefreshRate.getValue(),
|
||
|
|
layer.weight, layer.focused, layer.isProtected, layer.name.c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
switch (layer.vote) {
|
||
|
|
case LayerVoteType::NoVote:
|
||
|
|
noVoteLayers++;
|
||
|
|
break;
|
||
|
|
case LayerVoteType::Min:
|
||
|
|
minVoteLayers++;
|
||
|
|
break;
|
||
|
|
case LayerVoteType::Max:
|
||
|
|
maxVoteLayers++;
|
||
|
|
break;
|
||
|
|
case LayerVoteType::ExplicitDefault:
|
||
|
|
explicitDefaultVoteLayers++;
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
explicit_fps_max = std::max(explicit_fps_max, layer.desiredRefreshRate.getIntValue());
|
||
|
|
if (isPromoteLayer(layer)) {
|
||
|
|
needPromoteLayers++;
|
||
|
|
}
|
||
|
|
getPkgName(layer.name, vecExplicitPkgName);
|
||
|
|
#endif
|
||
|
|
break;
|
||
|
|
case LayerVoteType::ExplicitExactOrMultiple:
|
||
|
|
explicitExactOrMultipleVoteLayers++;
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
explicit_fps_max = std::max(explicit_fps_max, layer.desiredRefreshRate.getIntValue());
|
||
|
|
if (isPromoteLayer(layer)) {
|
||
|
|
needPromoteLayers++;
|
||
|
|
}
|
||
|
|
getPkgName(layer.name, vecExplicitPkgName);
|
||
|
|
#endif
|
||
|
|
break;
|
||
|
|
case LayerVoteType::ExplicitExact:
|
||
|
|
explicitExact++;
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
explicit_fps_max = std::max(explicit_fps_max, layer.desiredRefreshRate.getIntValue());
|
||
|
|
if (isPromoteLayer(layer)) {
|
||
|
|
needPromoteLayers++;
|
||
|
|
}
|
||
|
|
getPkgName(layer.name, vecExplicitPkgName);
|
||
|
|
#endif
|
||
|
|
break;
|
||
|
|
case LayerVoteType::Heuristic:
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isSupportContentDetection()) {
|
||
|
|
int fps = layer.desiredRefreshRate.getIntValue();
|
||
|
|
if (fps == REFRESH_RATE_SET_DEFAULT) {
|
||
|
|
sIsRefreshRateControlByAP = false;
|
||
|
|
} else if (fps == REFRESH_RATE_CONTROL_BY_AP) {
|
||
|
|
sIsRefreshRateControlByAP = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (layer.name.find("SurfaceView") != std::string::npos) {
|
||
|
|
fpsFloor = layer.desiredRefreshRate.getIntValue();
|
||
|
|
}
|
||
|
|
|
||
|
|
heuristic++;
|
||
|
|
if (fps < REFRESH_RATE_SET_DEFAULT && fps > heuristic_fps_max) {
|
||
|
|
heuristic_fps_max = fps;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
break;
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
case LayerVoteType::NoChange:
|
||
|
|
noChange++;
|
||
|
|
break;
|
||
|
|
case LayerVoteType::Heuristic_Infrequent:
|
||
|
|
heuristic_infrequent++;
|
||
|
|
vecHeuristicName.push_back(layer.name);
|
||
|
|
break;
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
if (layer.seamlessness == Seamlessness::SeamedAndSeamless && layer.focused) {
|
||
|
|
seamedFocusedLayers++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 ||
|
||
|
|
explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0;
|
||
|
|
|
||
|
|
const Policy* policy = getCurrentPolicyLocked();
|
||
|
|
const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
|
||
|
|
|
||
|
|
// If the default mode group is different from the group of current mode,
|
||
|
|
// this means a layer requesting a seamed mode switch just disappeared and
|
||
|
|
// we should switch back to the default group.
|
||
|
|
// However if a seamed layer is still present we anchor around the group
|
||
|
|
// of the current mode, in order to prevent unnecessary seamed mode switches
|
||
|
|
// (e.g. when pausing a video playback).
|
||
|
|
const auto anchorGroup =
|
||
|
|
seamedFocusedLayers > 0 ? activeMode.getGroup() : defaultMode->getGroup();
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog() && layers.size() > 0) {
|
||
|
|
ALOGI("%s: mActiveMode=%d, noVoteLayers=%d, minVoteLayers=%d, maxVoteLayers=%d, explicitDefaultVoteLayers=%d"
|
||
|
|
", explicitExactOrMultipleVoteLayers=%d, explicitExact=%d, signals.touch=%d, signals.idle=%d"
|
||
|
|
", hasExplicitVoteLayers=%d, sIsRefreshRateControlByAP=%d, noChange=%d, fpsFloor=%d, heuristic=%d"
|
||
|
|
", heuristic_fps_max=%d, heuristic_infrequent=%d, needPromoteLayers=%d",
|
||
|
|
__func__, mActiveModeOpt->modePtr->getFps().getIntValue(),
|
||
|
|
noVoteLayers, minVoteLayers, maxVoteLayers, explicitDefaultVoteLayers,
|
||
|
|
explicitExactOrMultipleVoteLayers, explicitExact, signals.touch, signals.idle,
|
||
|
|
hasExplicitVoteLayers, sIsRefreshRateControlByAP, noChange, fpsFloor, heuristic,
|
||
|
|
heuristic_fps_max, heuristic_infrequent, needPromoteLayers);
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've
|
||
|
|
// selected a refresh rate to see if we should apply touch boost.
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (signals.touch && !hasExplicitVoteLayers && !sIsRefreshRateControlByAP /*&& noChange == 0*/) {
|
||
|
|
#else
|
||
|
|
if (signals.touch && !hasExplicitVoteLayers) {
|
||
|
|
ALOGV("Touch Boost");
|
||
|
|
#endif
|
||
|
|
const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (Touch Boost)",
|
||
|
|
to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: Touch Boost, choose %s", __func__, to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return {ranking, GlobalSignals{.touch = true}};
|
||
|
|
}
|
||
|
|
|
||
|
|
// If the primary range consists of a single refresh rate then we can only
|
||
|
|
// move out the of range if layers explicitly request a different refresh
|
||
|
|
// rate.
|
||
|
|
const bool primaryRangeIsSingleRate =
|
||
|
|
isApproxEqual(policy->primaryRanges.physical.min, policy->primaryRanges.physical.max);
|
||
|
|
|
||
|
|
if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
|
||
|
|
#ifndef MTK_SF_MSYNC_3
|
||
|
|
ALOGV("Idle");
|
||
|
|
#endif
|
||
|
|
const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (Idle)", to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: Idle, choose %s", __func__, to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return {ranking, GlobalSignals{.idle = true}};
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (!isSupportContentDetection() && (layers.empty() || noVoteLayers == layers.size())) {
|
||
|
|
#else
|
||
|
|
if (layers.empty() || noVoteLayers == layers.size()) {
|
||
|
|
ALOGV("No layers with votes");
|
||
|
|
#endif
|
||
|
|
const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (No layers with votes)",
|
||
|
|
to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: No layers with votes, choose %s", __func__, to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return {ranking, kNoSignals};
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
/*if (isSupportContentDetection())*/ {
|
||
|
|
if (noChange > 0 && !hasExplicitVoteLayers/*noChange == layers.size()*/) {
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: noChange, keep current mode - choose %s", __func__, to_string(activeMode.getFps()).c_str());
|
||
|
|
}
|
||
|
|
return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId()),
|
||
|
|
kNoSignals};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isSupportContentDetection()) {
|
||
|
|
if (layers.size() == 0) {
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: no layer, keep current mode - choose %s", __func__, to_string(activeMode.getFps()).c_str());
|
||
|
|
}
|
||
|
|
return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId()), kNoSignals};
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
using fps_approx_ops::operator==;
|
||
|
|
const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup);
|
||
|
|
// not change to 60hz when activeMode is 120hz(max)
|
||
|
|
if (max->getFps() == activeMode.getFps() && heuristic_infrequent == layers.size()) {
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: max now and only have heuristic_infrequent, keep current mode - choose %s", __func__,
|
||
|
|
to_string(activeMode.getFps()).c_str());
|
||
|
|
}
|
||
|
|
return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId()), kNoSignals};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// Only if all layers want Min we should return Min
|
||
|
|
if (noVoteLayers + minVoteLayers == layers.size()) {
|
||
|
|
#ifndef MTK_SF_MSYNC_3
|
||
|
|
ALOGV("All layers Min");
|
||
|
|
#endif
|
||
|
|
const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (All layers Min)",
|
||
|
|
to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: All layers Min, choose %s", __func__, to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return {ranking, kNoSignals};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find the best refresh rate based on score
|
||
|
|
std::vector<RefreshRateScore> scores;
|
||
|
|
scores.reserve(mAppRequestFrameRates.size());
|
||
|
|
|
||
|
|
for (const FrameRateMode& it : mAppRequestFrameRates) {
|
||
|
|
scores.emplace_back(RefreshRateScore{it, 0.0f});
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const auto& layer : layers) {
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: Calculating score for %s (%s, weight %.2f, desired %.2f) ", __func__,
|
||
|
|
layer.name.c_str(), ftl::enum_string(layer.vote).c_str(), layer.weight,
|
||
|
|
layer.desiredRefreshRate.getValue());
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
ALOGV("Calculating score for %s (%s, weight %.2f, desired %.2f) ", layer.name.c_str(),
|
||
|
|
ftl::enum_string(layer.vote).c_str(), layer.weight,
|
||
|
|
layer.desiredRefreshRate.getValue());
|
||
|
|
#endif
|
||
|
|
if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
{
|
||
|
|
if (layer.vote == LayerVoteType::Heuristic_Infrequent && (heuristic > 0 || hasExplicitVoteLayers)) {
|
||
|
|
continue;
|
||
|
|
} else if (layer.vote == LayerVoteType::Heuristic && heuristic >= 2
|
||
|
|
&& layer.desiredRefreshRate.getIntValue() < heuristic_fps_max) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
const auto weight = layer.weight;
|
||
|
|
|
||
|
|
for (auto& [mode, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
|
||
|
|
const auto& [fps, modePtr] = mode;
|
||
|
|
const bool isSeamlessSwitch = modePtr->getGroup() == activeMode.getGroup();
|
||
|
|
|
||
|
|
if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) {
|
||
|
|
ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s",
|
||
|
|
formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
|
||
|
|
to_string(activeMode).c_str());
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (layer.seamlessness == Seamlessness::SeamedAndSeamless && !isSeamlessSwitch &&
|
||
|
|
!layer.focused) {
|
||
|
|
ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed."
|
||
|
|
" Current mode = %s",
|
||
|
|
formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
|
||
|
|
to_string(activeMode).c_str());
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Layers with default seamlessness vote for the current mode group if
|
||
|
|
// there are layers with seamlessness=SeamedAndSeamless and for the default
|
||
|
|
// mode group otherwise. In second case, if the current mode group is different
|
||
|
|
// from the default, this means a layer with seamlessness=SeamedAndSeamless has just
|
||
|
|
// disappeared.
|
||
|
|
const bool isInPolicyForDefault = modePtr->getGroup() == anchorGroup;
|
||
|
|
if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) {
|
||
|
|
ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(),
|
||
|
|
to_string(*modePtr).c_str(), to_string(activeMode).c_str());
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
const bool inPrimaryRange = policy->primaryRanges.render.includes(fps);
|
||
|
|
if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
|
||
|
|
!(layer.focused &&
|
||
|
|
(layer.vote == LayerVoteType::ExplicitDefault ||
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
|
||
|
|
#endif
|
||
|
|
layer.vote == LayerVoteType::ExplicitExact))) {
|
||
|
|
// Only focused layers with ExplicitDefault frame rate settings are allowed to score
|
||
|
|
// refresh rates outside the primary range.
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: primaryRangeIsSingleRate=%d, inPrimaryRange=%d ignores %s", __func__,
|
||
|
|
primaryRangeIsSingleRate, inPrimaryRange, to_string(*modePtr).c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
const float layerScore = calculateLayerScoreLocked(layer, fps, isSeamlessSwitch);
|
||
|
|
const float weightedLayerScore = weight * layerScore;
|
||
|
|
|
||
|
|
// Layer with fixed source has a special consideration which depends on the
|
||
|
|
// mConfig.frameRateMultipleThreshold. We don't want these layers to score
|
||
|
|
// refresh rates above the threshold, but we also don't want to favor the lower
|
||
|
|
// ones by having a greater number of layers scoring them. Instead, we calculate
|
||
|
|
// the score independently for these layers and later decide which
|
||
|
|
// refresh rates to add it. For example, desired 24 fps with 120 Hz threshold should not
|
||
|
|
// score 120 Hz, but desired 60 fps should contribute to the score.
|
||
|
|
const bool fixedSourceLayer = [](LayerVoteType vote) {
|
||
|
|
switch (vote) {
|
||
|
|
case LayerVoteType::ExplicitExactOrMultiple:
|
||
|
|
case LayerVoteType::Heuristic:
|
||
|
|
return true;
|
||
|
|
case LayerVoteType::NoVote:
|
||
|
|
case LayerVoteType::Min:
|
||
|
|
case LayerVoteType::Max:
|
||
|
|
case LayerVoteType::ExplicitDefault:
|
||
|
|
case LayerVoteType::ExplicitExact:
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
case LayerVoteType::NoChange:
|
||
|
|
case LayerVoteType::Heuristic_Infrequent:
|
||
|
|
#endif
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}(layer.vote);
|
||
|
|
const bool layerBelowThreshold = mConfig.frameRateMultipleThreshold != 0 &&
|
||
|
|
layer.desiredRefreshRate <
|
||
|
|
Fps::fromValue(mConfig.frameRateMultipleThreshold / 2);
|
||
|
|
if (fixedSourceLayer && layerBelowThreshold) {
|
||
|
|
const bool modeAboveThreshold =
|
||
|
|
modePtr->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
|
||
|
|
if (modeAboveThreshold) {
|
||
|
|
ALOGV("%s gives %s (%s) fixed source (above threshold) score of %.4f",
|
||
|
|
formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
|
||
|
|
to_string(modePtr->getFps()).c_str(), layerScore);
|
||
|
|
fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore;
|
||
|
|
} else {
|
||
|
|
ALOGV("%s gives %s (%s) fixed source (below threshold) score of %.4f",
|
||
|
|
formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
|
||
|
|
to_string(modePtr->getFps()).c_str(), layerScore);
|
||
|
|
fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: %s gives %s (%s) score of %.4f", __func__, formatLayerInfo(layer, weight).c_str(),
|
||
|
|
to_string(fps).c_str(), to_string(modePtr->getFps()).c_str(), layerScore);
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
ALOGV("%s gives %s (%s) score of %.4f", formatLayerInfo(layer, weight).c_str(),
|
||
|
|
to_string(fps).c_str(), to_string(modePtr->getFps()).c_str(), layerScore);
|
||
|
|
#endif
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
{
|
||
|
|
auto tempWeight = weight;
|
||
|
|
if (layer.vote == LayerVoteType::ExplicitExact && tempWeight < 1.0f) {
|
||
|
|
tempWeight = 1.0f;
|
||
|
|
}
|
||
|
|
overallScore += tempWeight * layerScore;
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
overallScore += weightedLayerScore;
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// We want to find the best refresh rate without the fixed source layers,
|
||
|
|
// so we could know whether we should add the modeAboveThreshold scores or not.
|
||
|
|
// If the best refresh rate is already above the threshold, it means that
|
||
|
|
// some non-fixed source layers already scored it, so we can just add the score
|
||
|
|
// for all fixed source layers, even the ones that are above the threshold.
|
||
|
|
const bool maxScoreAboveThreshold = [&] {
|
||
|
|
if (mConfig.frameRateMultipleThreshold == 0 || scores.empty()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const auto maxScoreIt =
|
||
|
|
std::max_element(scores.begin(), scores.end(),
|
||
|
|
[](RefreshRateScore max, RefreshRateScore current) {
|
||
|
|
return current.overallScore > max.overallScore;
|
||
|
|
});
|
||
|
|
ALOGV("%s (%s) is the best refresh rate without fixed source layers. It is %s the "
|
||
|
|
"threshold for "
|
||
|
|
"refresh rate multiples",
|
||
|
|
to_string(maxScoreIt->frameRateMode.fps).c_str(),
|
||
|
|
to_string(maxScoreIt->frameRateMode.modePtr->getFps()).c_str(),
|
||
|
|
maxScoreAboveThreshold ? "above" : "below");
|
||
|
|
return maxScoreIt->frameRateMode.modePtr->getFps() >=
|
||
|
|
Fps::fromValue(mConfig.frameRateMultipleThreshold);
|
||
|
|
}();
|
||
|
|
|
||
|
|
// Now we can add the fixed rate layers score
|
||
|
|
for (auto& [frameRateMode, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
|
||
|
|
overallScore += fixedRateBelowThresholdLayersScore.modeBelowThreshold;
|
||
|
|
if (maxScoreAboveThreshold) {
|
||
|
|
overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold;
|
||
|
|
}
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: %s (%s) adjusted overallScore is %.4f", __func__, to_string(frameRateMode.fps).c_str(),
|
||
|
|
to_string(frameRateMode.modePtr->getFps()).c_str(), overallScore);
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
ALOGV("%s (%s) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(),
|
||
|
|
to_string(frameRateMode.modePtr->getFps()).c_str(), overallScore);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
// Now that we scored all the refresh rates we need to pick the one that got the highest
|
||
|
|
// overallScore. Sort the scores based on their overallScore in descending order of priority.
|
||
|
|
const RefreshRateOrder refreshRateOrder =
|
||
|
|
maxVoteLayers > 0 ? RefreshRateOrder::Descending : RefreshRateOrder::Ascending;
|
||
|
|
std::sort(scores.begin(), scores.end(),
|
||
|
|
RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder});
|
||
|
|
|
||
|
|
FrameRateRanking ranking;
|
||
|
|
ranking.reserve(scores.size());
|
||
|
|
|
||
|
|
std::transform(scores.begin(), scores.end(), back_inserter(ranking),
|
||
|
|
[](const RefreshRateScore& score) {
|
||
|
|
return ScoredFrameRate{score.frameRateMode, score.overallScore};
|
||
|
|
});
|
||
|
|
|
||
|
|
const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) {
|
||
|
|
return score.overallScore == 0;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (primaryRangeIsSingleRate) {
|
||
|
|
// If we never scored any layers, then choose the rate from the primary
|
||
|
|
// range instead of picking a random score from the app range.
|
||
|
|
if (noLayerScore) {
|
||
|
|
ALOGV("Layers not scored");
|
||
|
|
const auto descending = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (Layers not scored)",
|
||
|
|
to_string(descending.front().frameRateMode.fps).c_str());
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: Layers not scored, primaryRangeIsSingleRate, choose descending %s", __func__,
|
||
|
|
to_string(descending.front().frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return {descending, kNoSignals};
|
||
|
|
} else {
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
|
||
|
|
to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: primaryRangeIsSingleRate, choose ranking %s", __func__,
|
||
|
|
to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return {ranking, kNoSignals};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Consider the touch event if there are no ExplicitDefault layers. ExplicitDefault are mostly
|
||
|
|
// interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit
|
||
|
|
// vote we should not change it if we get a touch event. Only apply touch boost if it will
|
||
|
|
// actually increase the refresh rate over the normal selection.
|
||
|
|
const bool touchBoostForExplicitExact = [&] {
|
||
|
|
if (supportsAppFrameRateOverrideByContent()) {
|
||
|
|
// Enable touch boost if there are other layers besides exact
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
return explicitExact + noVoteLayers + heuristic_infrequent != layers.size();
|
||
|
|
#else
|
||
|
|
return explicitExact + noVoteLayers != layers.size();
|
||
|
|
#endif
|
||
|
|
} else {
|
||
|
|
// Enable touch boost if there are no exact layers
|
||
|
|
return explicitExact == 0;
|
||
|
|
}
|
||
|
|
}();
|
||
|
|
|
||
|
|
const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
|
||
|
|
using fps_approx_ops::operator<;
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (signals.touch /*&& explicitDefaultVoteLayers == 0*/ && touchBoostForExplicitExact &&
|
||
|
|
#else
|
||
|
|
if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
|
||
|
|
#endif
|
||
|
|
scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
|
||
|
|
ALOGV("Touch Boost");
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
|
||
|
|
to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: Touch Boost [late], chosse %s", __func__, to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return {touchRefreshRates, GlobalSignals{.touch = true}};
|
||
|
|
}
|
||
|
|
|
||
|
|
// If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
|
||
|
|
// current config
|
||
|
|
if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
|
||
|
|
const auto ascendingWithPreferred =
|
||
|
|
rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
|
||
|
|
to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: Layers not scored, kepp current, choose %s", __func__, to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return {ascendingWithPreferred, kNoSignals};
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
{
|
||
|
|
const auto& [frameRateMode, _] = ranking.front();
|
||
|
|
const DisplayModePtr& min = getMinRefreshRateByPolicyLocked();
|
||
|
|
const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup);
|
||
|
|
using fps_approx_ops::operator==;
|
||
|
|
if (hasExplicitVoteLayers) {
|
||
|
|
if (explicit_fps_max > max->getFps().getIntValue()) {
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: Explicit fps is higher than ceiling, keep current mode - choose %s", __func__,
|
||
|
|
to_string(activeMode.getFps()).c_str());
|
||
|
|
}
|
||
|
|
return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId()), kNoSignals};
|
||
|
|
} else if (needPromoteLayers > 0 && frameRateMode.fps.getIntValue() < 60) {
|
||
|
|
// for svp, always select max refresh rate
|
||
|
|
const auto maxRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: has promote layers, use max instead - choose %s", __func__, to_string(maxRefreshRates.front().frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
return {maxRefreshRates, GlobalSignals{.touch = true}};
|
||
|
|
} else if (explicit_fps_max < 60) {
|
||
|
|
// if explicit fps is lower than 60fps, we need to consider multi-window case for performance
|
||
|
|
int matchedNum = 0;
|
||
|
|
for (auto pkgName : vecExplicitPkgName) {
|
||
|
|
auto count = std::count_if(vecHeuristicName.begin(), vecHeuristicName.end(),
|
||
|
|
[pkgName](const auto& layerName) {
|
||
|
|
return layerName.find(pkgName) != std::string::npos;
|
||
|
|
});
|
||
|
|
ALOGI("%s: pkgName=%s, count=%d, vecHeuristicName.size()=%zu", __func__,
|
||
|
|
pkgName.c_str(), static_cast<int>(count), vecHeuristicName.size());
|
||
|
|
matchedNum += count;
|
||
|
|
}
|
||
|
|
if (matchedNum > 0 && matchedNum < vecHeuristicName.size()) {
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: multi-window with explicit fps, keep current mode - choose %s", __func__,
|
||
|
|
to_string(activeMode.getFps()).c_str());
|
||
|
|
}
|
||
|
|
return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId()), kNoSignals};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: ranking's front with explicit fps - choose %s", __func__, to_string(frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
} else if (min->getFps().getIntValue() < 60 && min->getFps() == frameRateMode.modePtr->getFps()) {
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: ranking's front is min, keep current mode - choose %s", __func__, to_string(activeMode.getFps()).c_str());
|
||
|
|
}
|
||
|
|
return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId()), kNoSignals};
|
||
|
|
} else if (frameRateMode.modePtr->getFps().getIntValue() < fpsFloor) {
|
||
|
|
const FrameRateMode& floorFrameRateMode = getSpecificRefreshRateByFpsLocked(fpsFloor);
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: ranking's front is lower than floor, use floor instead - choose %s", __func__, to_string(floorFrameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, floorFrameRateMode.modePtr->getId()), kNoSignals};
|
||
|
|
} else if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: ranking's front - choose %s", __func__, to_string(frameRateMode.fps).c_str());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
|
||
|
|
return {ranking, kNoSignals};
|
||
|
|
}
|
||
|
|
|
||
|
|
using LayerRequirementPtrs = std::vector<const RefreshRateSelector::LayerRequirement*>;
|
||
|
|
using PerUidLayerRequirements = std::unordered_map<uid_t, LayerRequirementPtrs>;
|
||
|
|
|
||
|
|
PerUidLayerRequirements groupLayersByUid(
|
||
|
|
const std::vector<RefreshRateSelector::LayerRequirement>& layers) {
|
||
|
|
PerUidLayerRequirements layersByUid;
|
||
|
|
for (const auto& layer : layers) {
|
||
|
|
const auto it = layersByUid.emplace(layer.ownerUid, LayerRequirementPtrs()).first;
|
||
|
|
auto& layersWithSameUid = it->second;
|
||
|
|
layersWithSameUid.push_back(&layer);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove uids that can't have a frame rate override
|
||
|
|
for (auto it = layersByUid.begin(); it != layersByUid.end();) {
|
||
|
|
const auto& layersWithSameUid = it->second;
|
||
|
|
bool skipUid = false;
|
||
|
|
for (const auto& layer : layersWithSameUid) {
|
||
|
|
using LayerVoteType = RefreshRateSelector::LayerVoteType;
|
||
|
|
|
||
|
|
if (layer->vote == LayerVoteType::Max || layer->vote == LayerVoteType::Heuristic) {
|
||
|
|
skipUid = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (skipUid) {
|
||
|
|
it = layersByUid.erase(it);
|
||
|
|
} else {
|
||
|
|
++it;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return layersByUid;
|
||
|
|
}
|
||
|
|
|
||
|
|
auto RefreshRateSelector::getFrameRateOverrides(const std::vector<LayerRequirement>& layers,
|
||
|
|
Fps displayRefreshRate,
|
||
|
|
GlobalSignals globalSignals) const
|
||
|
|
-> UidToFrameRateOverride {
|
||
|
|
ATRACE_CALL();
|
||
|
|
if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Disabled) {
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
ALOGV("%s: %zu layers", __func__, layers.size());
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
|
||
|
|
const auto* policyPtr = getCurrentPolicyLocked();
|
||
|
|
// We don't want to run lower than 30fps
|
||
|
|
const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess);
|
||
|
|
|
||
|
|
using fps_approx_ops::operator/;
|
||
|
|
const unsigned numMultiples = displayRefreshRate / minFrameRate;
|
||
|
|
|
||
|
|
std::vector<std::pair<Fps, float>> scoredFrameRates;
|
||
|
|
scoredFrameRates.reserve(numMultiples);
|
||
|
|
|
||
|
|
for (unsigned n = numMultiples; n > 0; n--) {
|
||
|
|
const Fps divisor = displayRefreshRate / n;
|
||
|
|
if (mConfig.enableFrameRateOverride ==
|
||
|
|
Config::FrameRateOverride::AppOverrideNativeRefreshRates &&
|
||
|
|
!isNativeRefreshRate(divisor)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (policyPtr->appRequestRanges.render.includes(divisor)) {
|
||
|
|
ALOGV("%s: adding %s as a potential frame rate", __func__, to_string(divisor).c_str());
|
||
|
|
scoredFrameRates.emplace_back(divisor, 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const auto layersByUid = groupLayersByUid(layers);
|
||
|
|
UidToFrameRateOverride frameRateOverrides;
|
||
|
|
for (const auto& [uid, layersWithSameUid] : layersByUid) {
|
||
|
|
// Layers with ExplicitExactOrMultiple expect touch boost
|
||
|
|
const bool hasExplicitExactOrMultiple =
|
||
|
|
std::any_of(layersWithSameUid.cbegin(), layersWithSameUid.cend(),
|
||
|
|
[](const auto& layer) {
|
||
|
|
return layer->vote == LayerVoteType::ExplicitExactOrMultiple;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (globalSignals.touch && hasExplicitExactOrMultiple) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (auto& [_, score] : scoredFrameRates) {
|
||
|
|
score = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const auto& layer : layersWithSameUid) {
|
||
|
|
if (layer->vote == LayerVoteType::NoVote || layer->vote == LayerVoteType::Min
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
|| layer->vote == LayerVoteType::NoChange
|
||
|
|
|| layer->vote == LayerVoteType::Heuristic_Infrequent
|
||
|
|
#endif
|
||
|
|
) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
|
||
|
|
layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
|
||
|
|
layer->vote != LayerVoteType::ExplicitExact);
|
||
|
|
for (auto& [fps, score] : scoredFrameRates) {
|
||
|
|
constexpr bool isSeamlessSwitch = true;
|
||
|
|
const auto layerScore = calculateLayerScoreLocked(*layer, fps, isSeamlessSwitch);
|
||
|
|
score += layer->weight * layerScore;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If we never scored any layers, we don't have a preferred frame rate
|
||
|
|
if (std::all_of(scoredFrameRates.begin(), scoredFrameRates.end(),
|
||
|
|
[](const auto& scoredFrameRate) {
|
||
|
|
const auto [_, score] = scoredFrameRate;
|
||
|
|
return score == 0;
|
||
|
|
})) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Now that we scored all the refresh rates we need to pick the lowest refresh rate
|
||
|
|
// that got the highest score.
|
||
|
|
const auto [overrideFps, _] =
|
||
|
|
*std::max_element(scoredFrameRates.begin(), scoredFrameRates.end(),
|
||
|
|
[](const auto& lhsPair, const auto& rhsPair) {
|
||
|
|
const float lhs = lhsPair.second;
|
||
|
|
const float rhs = rhsPair.second;
|
||
|
|
return lhs < rhs && !ScoredFrameRate::scoresEqual(lhs, rhs);
|
||
|
|
});
|
||
|
|
ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid);
|
||
|
|
frameRateOverrides.emplace(uid, overrideFps);
|
||
|
|
}
|
||
|
|
|
||
|
|
return frameRateOverrides;
|
||
|
|
}
|
||
|
|
|
||
|
|
ftl::Optional<FrameRateMode> RefreshRateSelector::onKernelTimerChanged(
|
||
|
|
std::optional<DisplayModeId> desiredActiveModeId, bool timerExpired) const {
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
|
||
|
|
const auto current = [&]() REQUIRES(mLock) -> FrameRateMode {
|
||
|
|
if (desiredActiveModeId) {
|
||
|
|
const auto& modePtr = mDisplayModes.get(*desiredActiveModeId)->get();
|
||
|
|
return FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)};
|
||
|
|
}
|
||
|
|
|
||
|
|
return getActiveModeLocked();
|
||
|
|
}();
|
||
|
|
|
||
|
|
const DisplayModePtr& min = mMinRefreshRateModeIt->second;
|
||
|
|
if (current.modePtr->getId() == min->getId()) {
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
return timerExpired ? FrameRateMode{min->getFps(), ftl::as_non_null(min)} : current;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
const FrameRateMode& RefreshRateSelector::getSpecificRefreshRateByFpsLocked(int fps) const {
|
||
|
|
for (const FrameRateMode& mode : mAppRequestFrameRates) {
|
||
|
|
if (fps == mode.fps.getIntValue()) {
|
||
|
|
return mode;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ALOGE("%s: Can't find refresh rate by fps %d, return max", __func__, fps);
|
||
|
|
return mAppRequestFrameRates.back();
|
||
|
|
}
|
||
|
|
|
||
|
|
void RefreshRateSelector::getPkgName(const std::string& layerName, std::vector<std::string>& vecPkgName) const {
|
||
|
|
std::size_t startPos = layerName.find('[');
|
||
|
|
if (startPos != std::string::npos) {
|
||
|
|
startPos++;
|
||
|
|
} else {
|
||
|
|
startPos = 0;
|
||
|
|
}
|
||
|
|
std::size_t len = layerName.find('/');
|
||
|
|
if (len != std::string::npos) {
|
||
|
|
len = len - startPos;
|
||
|
|
vecPkgName.push_back(layerName.substr(startPos, len));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool RefreshRateSelector::isPromoteLayer(const LayerRequirement& layer) const {
|
||
|
|
return (layer.name.find("com.netflix.mediaclient") != std::string::npos)
|
||
|
|
|| (layer.name.find("com.google.android.youtube") != std::string::npos)
|
||
|
|
|| layer.isProtected;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const {
|
||
|
|
const auto& activeMode = *getActiveModeLocked().modePtr;
|
||
|
|
|
||
|
|
for (const FrameRateMode& mode : mPrimaryFrameRates) {
|
||
|
|
if (activeMode.getGroup() == mode.modePtr->getGroup()) {
|
||
|
|
return mode.modePtr.get();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ALOGE("Can't find min refresh rate by policy with the same mode group as the current mode %s",
|
||
|
|
to_string(activeMode).c_str());
|
||
|
|
|
||
|
|
// Default to the lowest refresh rate.
|
||
|
|
return mPrimaryFrameRates.front().modePtr.get();
|
||
|
|
}
|
||
|
|
|
||
|
|
const DisplayModePtr& RefreshRateSelector::getMaxRefreshRateByPolicyLocked(int anchorGroup) const {
|
||
|
|
const ftl::NonNull<DisplayModePtr>* maxByAnchor = &mPrimaryFrameRates.back().modePtr;
|
||
|
|
const ftl::NonNull<DisplayModePtr>* max = &mPrimaryFrameRates.back().modePtr;
|
||
|
|
|
||
|
|
bool maxByAnchorFound = false;
|
||
|
|
for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) {
|
||
|
|
using namespace fps_approx_ops;
|
||
|
|
if (it->modePtr->getFps() > (*max)->getFps()) {
|
||
|
|
max = &it->modePtr;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (anchorGroup == it->modePtr->getGroup() &&
|
||
|
|
it->modePtr->getFps() >= (*maxByAnchor)->getFps()) {
|
||
|
|
maxByAnchorFound = true;
|
||
|
|
maxByAnchor = &it->modePtr;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (maxByAnchorFound) {
|
||
|
|
return maxByAnchor->get();
|
||
|
|
}
|
||
|
|
|
||
|
|
ALOGE("Can't find max refresh rate by policy with the same group %d", anchorGroup);
|
||
|
|
|
||
|
|
// Default to the highest refresh rate.
|
||
|
|
return max->get();
|
||
|
|
}
|
||
|
|
|
||
|
|
auto RefreshRateSelector::rankFrameRates(std::optional<int> anchorGroupOpt,
|
||
|
|
RefreshRateOrder refreshRateOrder,
|
||
|
|
std::optional<DisplayModeId> preferredDisplayModeOpt) const
|
||
|
|
-> FrameRateRanking {
|
||
|
|
using fps_approx_ops::operator<;
|
||
|
|
const char* const whence = __func__;
|
||
|
|
|
||
|
|
// find the highest frame rate for each display mode
|
||
|
|
ftl::SmallMap<DisplayModeId, Fps, 8> maxRenderRateForMode;
|
||
|
|
const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending);
|
||
|
|
if (ascending) {
|
||
|
|
// TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually
|
||
|
|
// use a lower frame rate when we want Ascending frame rates.
|
||
|
|
for (const auto& frameRateMode : mPrimaryFrameRates) {
|
||
|
|
if (anchorGroupOpt && frameRateMode.modePtr->getGroup() != anchorGroupOpt) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
const auto [iter, _] = maxRenderRateForMode.try_emplace(frameRateMode.modePtr->getId(),
|
||
|
|
frameRateMode.fps);
|
||
|
|
if (iter->second < frameRateMode.fps) {
|
||
|
|
iter->second = frameRateMode.fps;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::deque<ScoredFrameRate> ranking;
|
||
|
|
const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) {
|
||
|
|
const auto& modePtr = frameRateMode.modePtr;
|
||
|
|
if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending);
|
||
|
|
const auto id = modePtr->getId();
|
||
|
|
if (ascending && frameRateMode.fps < *maxRenderRateForMode.get(id)) {
|
||
|
|
// TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually
|
||
|
|
// use a lower frame rate when we want Ascending frame rates.
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
float score = calculateDistanceScoreFromMax(frameRateMode.fps);
|
||
|
|
|
||
|
|
if (ascending) {
|
||
|
|
score = 1.0f / score;
|
||
|
|
}
|
||
|
|
|
||
|
|
constexpr float kScore = std::numeric_limits<float>::max();
|
||
|
|
if (preferredDisplayModeOpt) {
|
||
|
|
if (*preferredDisplayModeOpt == modePtr->getId()) {
|
||
|
|
ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
constexpr float kNonPreferredModePenalty = 0.95f;
|
||
|
|
score *= kNonPreferredModePenalty;
|
||
|
|
} else if (ascending && id == getMinRefreshRateByPolicyLocked()->getId()) {
|
||
|
|
// TODO(b/266481656): Once this bug is fixed, we can remove this workaround
|
||
|
|
// and actually use a lower frame rate when we want Ascending frame rates.
|
||
|
|
ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
|
||
|
|
to_string(frameRateMode.fps).c_str(), to_string(modePtr->getFps()).c_str(), score);
|
||
|
|
ranking.emplace_back(ScoredFrameRate{frameRateMode, score});
|
||
|
|
};
|
||
|
|
|
||
|
|
if (refreshRateOrder == RefreshRateOrder::Ascending) {
|
||
|
|
std::for_each(mPrimaryFrameRates.begin(), mPrimaryFrameRates.end(), rankFrameRate);
|
||
|
|
} else {
|
||
|
|
std::for_each(mPrimaryFrameRates.rbegin(), mPrimaryFrameRates.rend(), rankFrameRate);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!ranking.empty() || !anchorGroupOpt) {
|
||
|
|
return {ranking.begin(), ranking.end()};
|
||
|
|
}
|
||
|
|
|
||
|
|
ALOGW("Can't find %s refresh rate by policy with the same mode group"
|
||
|
|
" as the mode group %d",
|
||
|
|
refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value());
|
||
|
|
|
||
|
|
constexpr std::optional<int> kNoAnchorGroup = std::nullopt;
|
||
|
|
return rankFrameRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
|
||
|
|
}
|
||
|
|
|
||
|
|
FrameRateMode RefreshRateSelector::getActiveMode() const {
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
return getActiveModeLocked();
|
||
|
|
}
|
||
|
|
|
||
|
|
const FrameRateMode& RefreshRateSelector::getActiveModeLocked() const {
|
||
|
|
return *mActiveModeOpt;
|
||
|
|
}
|
||
|
|
|
||
|
|
void RefreshRateSelector::setActiveMode(DisplayModeId modeId, Fps renderFrameRate) {
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
|
||
|
|
// Invalidate the cached invocation to getRankedFrameRates. This forces
|
||
|
|
// the refresh rate to be recomputed on the next call to getRankedFrameRates.
|
||
|
|
mGetRankedFrameRatesCache.reset();
|
||
|
|
|
||
|
|
const auto activeModeOpt = mDisplayModes.get(modeId);
|
||
|
|
LOG_ALWAYS_FATAL_IF(!activeModeOpt);
|
||
|
|
|
||
|
|
mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())});
|
||
|
|
}
|
||
|
|
|
||
|
|
RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId,
|
||
|
|
Config config)
|
||
|
|
: mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) {
|
||
|
|
initializeIdleTimer();
|
||
|
|
FTL_FAKE_GUARD(kMainThreadContext, updateDisplayModes(std::move(modes), activeModeId));
|
||
|
|
}
|
||
|
|
|
||
|
|
void RefreshRateSelector::initializeIdleTimer() {
|
||
|
|
if (mConfig.idleTimerTimeout > 0ms) {
|
||
|
|
mIdleTimer.emplace(
|
||
|
|
"IdleTimer", mConfig.idleTimerTimeout,
|
||
|
|
[this] {
|
||
|
|
std::scoped_lock lock(mIdleTimerCallbacksMutex);
|
||
|
|
if (const auto callbacks = getIdleTimerCallbacks()) {
|
||
|
|
callbacks->onReset();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
[this] {
|
||
|
|
std::scoped_lock lock(mIdleTimerCallbacksMutex);
|
||
|
|
if (const auto callbacks = getIdleTimerCallbacks()) {
|
||
|
|
callbacks->onExpired();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
|
||
|
|
// Invalidate the cached invocation to getRankedFrameRates. This forces
|
||
|
|
// the refresh rate to be recomputed on the next call to getRankedFrameRates.
|
||
|
|
mGetRankedFrameRatesCache.reset();
|
||
|
|
|
||
|
|
mDisplayModes = std::move(modes);
|
||
|
|
const auto activeModeOpt = mDisplayModes.get(activeModeId);
|
||
|
|
LOG_ALWAYS_FATAL_IF(!activeModeOpt);
|
||
|
|
mActiveModeOpt =
|
||
|
|
FrameRateMode{activeModeOpt->get()->getFps(), ftl::as_non_null(activeModeOpt->get())};
|
||
|
|
|
||
|
|
const auto sortedModes = sortByRefreshRate(mDisplayModes);
|
||
|
|
mMinRefreshRateModeIt = sortedModes.front();
|
||
|
|
mMaxRefreshRateModeIt = sortedModes.back();
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
// reset kMinSupportedFrameRate according to the min fps
|
||
|
|
if ((mMinRefreshRateModeIt->second->getFps().getIntValue() / 2) < kMinSupportedFrameRate.getIntValue()) {
|
||
|
|
kMinSupportedFrameRate = Fps::fromValue(mMinRefreshRateModeIt->second->getFps().getValue() / 2);
|
||
|
|
ALOGI("%s: reset kMinSupportedFrameRate as %d", __func__, kMinSupportedFrameRate.getIntValue());
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// Reset the policy because the old one may no longer be valid.
|
||
|
|
mDisplayManagerPolicy = {};
|
||
|
|
mDisplayManagerPolicy.defaultMode = activeModeId;
|
||
|
|
|
||
|
|
mFrameRateOverrideConfig = [&] {
|
||
|
|
switch (mConfig.enableFrameRateOverride) {
|
||
|
|
case Config::FrameRateOverride::Disabled:
|
||
|
|
case Config::FrameRateOverride::AppOverride:
|
||
|
|
case Config::FrameRateOverride::Enabled:
|
||
|
|
return mConfig.enableFrameRateOverride;
|
||
|
|
case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
|
||
|
|
return shouldEnableFrameRateOverride(sortedModes)
|
||
|
|
? Config::FrameRateOverride::AppOverrideNativeRefreshRates
|
||
|
|
: Config::FrameRateOverride::Disabled;
|
||
|
|
}
|
||
|
|
}();
|
||
|
|
|
||
|
|
if (mConfig.enableFrameRateOverride ==
|
||
|
|
Config::FrameRateOverride::AppOverrideNativeRefreshRates) {
|
||
|
|
for (const auto& [_, mode] : mDisplayModes) {
|
||
|
|
mAppOverrideNativeRefreshRates.try_emplace(mode->getFps(), ftl::unit);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
constructAvailableRefreshRates();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const {
|
||
|
|
// defaultMode must be a valid mode, and within the given refresh rate range.
|
||
|
|
if (const auto mode = mDisplayModes.get(policy.defaultMode)) {
|
||
|
|
if (!policy.primaryRanges.physical.includes(mode->get()->getFps())) {
|
||
|
|
ALOGE("Default mode is not in the primary range.");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
ALOGE("Default mode is not found.");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const auto& primaryRanges = policy.primaryRanges;
|
||
|
|
const auto& appRequestRanges = policy.appRequestRanges;
|
||
|
|
ALOGE_IF(!appRequestRanges.physical.includes(primaryRanges.physical),
|
||
|
|
"Physical range is invalid: primary: %s appRequest: %s",
|
||
|
|
to_string(primaryRanges.physical).c_str(),
|
||
|
|
to_string(appRequestRanges.physical).c_str());
|
||
|
|
ALOGE_IF(!appRequestRanges.render.includes(primaryRanges.render),
|
||
|
|
"Render range is invalid: primary: %s appRequest: %s",
|
||
|
|
to_string(primaryRanges.render).c_str(), to_string(appRequestRanges.render).c_str());
|
||
|
|
|
||
|
|
return primaryRanges.valid() && appRequestRanges.valid();
|
||
|
|
}
|
||
|
|
|
||
|
|
auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult {
|
||
|
|
Policy oldPolicy;
|
||
|
|
PhysicalDisplayId displayId;
|
||
|
|
{
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
oldPolicy = *getCurrentPolicyLocked();
|
||
|
|
|
||
|
|
const bool valid = ftl::match(
|
||
|
|
policy,
|
||
|
|
[this](const auto& policy) {
|
||
|
|
ftl::FakeGuard guard(mLock);
|
||
|
|
if (!isPolicyValidLocked(policy)) {
|
||
|
|
ALOGE("Invalid policy: %s", policy.toString().c_str());
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
using T = std::decay_t<decltype(policy)>;
|
||
|
|
|
||
|
|
if constexpr (std::is_same_v<T, DisplayManagerPolicy>) {
|
||
|
|
mDisplayManagerPolicy = policy;
|
||
|
|
} else {
|
||
|
|
static_assert(std::is_same_v<T, OverridePolicy>);
|
||
|
|
mOverridePolicy = policy;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
},
|
||
|
|
[this](NoOverridePolicy) {
|
||
|
|
ftl::FakeGuard guard(mLock);
|
||
|
|
mOverridePolicy.reset();
|
||
|
|
return true;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!valid) {
|
||
|
|
return SetPolicyResult::Invalid;
|
||
|
|
}
|
||
|
|
|
||
|
|
mGetRankedFrameRatesCache.reset();
|
||
|
|
|
||
|
|
if (*getCurrentPolicyLocked() == oldPolicy) {
|
||
|
|
return SetPolicyResult::Unchanged;
|
||
|
|
}
|
||
|
|
constructAvailableRefreshRates();
|
||
|
|
|
||
|
|
displayId = getActiveModeLocked().modePtr->getPhysicalDisplayId();
|
||
|
|
}
|
||
|
|
|
||
|
|
const unsigned numModeChanges = std::exchange(mNumModeSwitchesInPolicy, 0u);
|
||
|
|
|
||
|
|
ALOGI("Display %s policy changed\n"
|
||
|
|
"Previous: %s\n"
|
||
|
|
"Current: %s\n"
|
||
|
|
"%u mode changes were performed under the previous policy",
|
||
|
|
to_string(displayId).c_str(), oldPolicy.toString().c_str(), toString(policy).c_str(),
|
||
|
|
numModeChanges);
|
||
|
|
|
||
|
|
return SetPolicyResult::Changed;
|
||
|
|
}
|
||
|
|
|
||
|
|
auto RefreshRateSelector::getCurrentPolicyLocked() const -> const Policy* {
|
||
|
|
return mOverridePolicy ? &mOverridePolicy.value() : &mDisplayManagerPolicy;
|
||
|
|
}
|
||
|
|
|
||
|
|
auto RefreshRateSelector::getCurrentPolicy() const -> Policy {
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
return *getCurrentPolicyLocked();
|
||
|
|
}
|
||
|
|
|
||
|
|
auto RefreshRateSelector::getDisplayManagerPolicy() const -> Policy {
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
return mDisplayManagerPolicy;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool RefreshRateSelector::isModeAllowed(const FrameRateMode& mode) const {
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
return std::find(mAppRequestFrameRates.begin(), mAppRequestFrameRates.end(), mode) !=
|
||
|
|
mAppRequestFrameRates.end();
|
||
|
|
}
|
||
|
|
|
||
|
|
void RefreshRateSelector::constructAvailableRefreshRates() {
|
||
|
|
// Filter modes based on current policy and sort on refresh rate.
|
||
|
|
const Policy* policy = getCurrentPolicyLocked();
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: %s ", __func__, policy->toString().c_str());
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
ALOGV("%s: %s ", __func__, policy->toString().c_str());
|
||
|
|
#endif
|
||
|
|
|
||
|
|
const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
|
||
|
|
|
||
|
|
const auto filterRefreshRates = [&](const FpsRanges& ranges,
|
||
|
|
const char* rangeName) REQUIRES(mLock) {
|
||
|
|
const auto filterModes = [&](const DisplayMode& mode) {
|
||
|
|
return mode.getResolution() == defaultMode->getResolution() &&
|
||
|
|
mode.getDpi() == defaultMode->getDpi() &&
|
||
|
|
(policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) &&
|
||
|
|
ranges.physical.includes(mode.getFps()) &&
|
||
|
|
(supportsFrameRateOverride() || ranges.render.includes(mode.getFps()));
|
||
|
|
};
|
||
|
|
|
||
|
|
const auto frameRateModes = createFrameRateModes(filterModes, ranges.render);
|
||
|
|
LOG_ALWAYS_FATAL_IF(frameRateModes.empty(),
|
||
|
|
"No matching frame rate modes for %s range. policy: %s", rangeName,
|
||
|
|
policy->toString().c_str());
|
||
|
|
|
||
|
|
const auto stringifyModes = [&] {
|
||
|
|
std::string str;
|
||
|
|
for (const auto& frameRateMode : frameRateModes) {
|
||
|
|
str += to_string(frameRateMode) + " ";
|
||
|
|
}
|
||
|
|
return str;
|
||
|
|
};
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s render rates: %s", rangeName, stringifyModes().c_str());
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str());
|
||
|
|
#endif
|
||
|
|
|
||
|
|
return frameRateModes;
|
||
|
|
};
|
||
|
|
|
||
|
|
mPrimaryFrameRates = filterRefreshRates(policy->primaryRanges, "primary");
|
||
|
|
mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate, bool bCheckDefaultDistance) const {
|
||
|
|
#else
|
||
|
|
Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const {
|
||
|
|
#endif
|
||
|
|
using namespace fps_approx_ops;
|
||
|
|
|
||
|
|
if (frameRate <= mKnownFrameRates.front()) {
|
||
|
|
return mKnownFrameRates.front();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (frameRate >= mKnownFrameRates.back()) {
|
||
|
|
return mKnownFrameRates.back();
|
||
|
|
}
|
||
|
|
|
||
|
|
auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate,
|
||
|
|
isStrictlyLess);
|
||
|
|
|
||
|
|
const auto distance1 = std::abs(frameRate.getValue() - lowerBound->getValue());
|
||
|
|
const auto distance2 = std::abs(frameRate.getValue() - std::prev(lowerBound)->getValue());
|
||
|
|
#ifdef MTK_SF_MSYNC_3
|
||
|
|
if (isSupportContentDetection()) {
|
||
|
|
// mKnownFrameRates: {10, 24_Hz, 30_Hz, 45_Hz, 60_Hz, 72_Hz, 90, 120}
|
||
|
|
// if distance2 < INTERVAL*0.15, then we should select prev(lowerBound) for tolerance
|
||
|
|
// otherwise, always select lowerBound, which is equal or greater than frameRate
|
||
|
|
auto MARGIN = (distance1 + distance2) * 0.15; // 15%
|
||
|
|
auto selected = distance2 < MARGIN ? *std::prev(lowerBound) : *lowerBound;
|
||
|
|
if (bCheckDefaultDistance) {
|
||
|
|
selected = distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
|
||
|
|
}
|
||
|
|
if (isShowCDDetailLog()) {
|
||
|
|
ALOGI("%s: frameRate=%.2f, lowerBound=%.2f, prev=%.2f, distance1=%.2f, distance2=%.2f, selected=%.2f",
|
||
|
|
__func__, frameRate.getValue(), lowerBound->getValue(), std::prev(lowerBound)->getValue(),
|
||
|
|
distance1, distance2, selected.getValue());
|
||
|
|
}
|
||
|
|
return selected;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
|
||
|
|
}
|
||
|
|
|
||
|
|
auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction {
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
|
||
|
|
const Fps deviceMinFps = mMinRefreshRateModeIt->second->getFps();
|
||
|
|
const DisplayModePtr& minByPolicy = getMinRefreshRateByPolicyLocked();
|
||
|
|
|
||
|
|
// Kernel idle timer will set the refresh rate to the device min. If DisplayManager says that
|
||
|
|
// the min allowed refresh rate is higher than the device min, we do not want to enable the
|
||
|
|
// timer.
|
||
|
|
if (isStrictlyLess(deviceMinFps, minByPolicy->getFps())) {
|
||
|
|
return KernelIdleTimerAction::TurnOff;
|
||
|
|
}
|
||
|
|
|
||
|
|
const DisplayModePtr& maxByPolicy =
|
||
|
|
getMaxRefreshRateByPolicyLocked(getActiveModeLocked().modePtr->getGroup());
|
||
|
|
if (minByPolicy == maxByPolicy) {
|
||
|
|
// Turn on the timer when the min of the primary range is below the device min.
|
||
|
|
if (const Policy* currentPolicy = getCurrentPolicyLocked();
|
||
|
|
isApproxLess(currentPolicy->primaryRanges.physical.min, deviceMinFps)) {
|
||
|
|
return KernelIdleTimerAction::TurnOn;
|
||
|
|
}
|
||
|
|
return KernelIdleTimerAction::TurnOff;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Turn on the timer in all other cases.
|
||
|
|
return KernelIdleTimerAction::TurnOn;
|
||
|
|
}
|
||
|
|
|
||
|
|
int RefreshRateSelector::getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate) {
|
||
|
|
// This calculation needs to be in sync with the java code
|
||
|
|
// in DisplayManagerService.getDisplayInfoForFrameRateOverride
|
||
|
|
|
||
|
|
// The threshold must be smaller than 0.001 in order to differentiate
|
||
|
|
// between the fractional pairs (e.g. 59.94 and 60).
|
||
|
|
constexpr float kThreshold = 0.0009f;
|
||
|
|
const auto numPeriods = displayRefreshRate.getValue() / layerFrameRate.getValue();
|
||
|
|
const auto numPeriodsRounded = std::round(numPeriods);
|
||
|
|
if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return static_cast<int>(numPeriodsRounded);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool RefreshRateSelector::isFractionalPairOrMultiple(Fps smaller, Fps bigger) {
|
||
|
|
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 isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier / kCoef)) ||
|
||
|
|
isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef));
|
||
|
|
}
|
||
|
|
|
||
|
|
void RefreshRateSelector::dump(utils::Dumper& dumper) const {
|
||
|
|
using namespace std::string_view_literals;
|
||
|
|
|
||
|
|
std::lock_guard lock(mLock);
|
||
|
|
|
||
|
|
const auto activeMode = getActiveModeLocked();
|
||
|
|
dumper.dump("activeMode"sv, to_string(activeMode));
|
||
|
|
|
||
|
|
dumper.dump("displayModes"sv);
|
||
|
|
{
|
||
|
|
utils::Dumper::Indent indent(dumper);
|
||
|
|
for (const auto& [id, mode] : mDisplayModes) {
|
||
|
|
dumper.dump({}, to_string(*mode));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
dumper.dump("displayManagerPolicy"sv, mDisplayManagerPolicy.toString());
|
||
|
|
|
||
|
|
if (const Policy& currentPolicy = *getCurrentPolicyLocked();
|
||
|
|
mOverridePolicy && currentPolicy != mDisplayManagerPolicy) {
|
||
|
|
dumper.dump("overridePolicy"sv, currentPolicy.toString());
|
||
|
|
}
|
||
|
|
|
||
|
|
dumper.dump("frameRateOverrideConfig"sv, *ftl::enum_name(mFrameRateOverrideConfig));
|
||
|
|
|
||
|
|
dumper.dump("idleTimer"sv);
|
||
|
|
{
|
||
|
|
utils::Dumper::Indent indent(dumper);
|
||
|
|
dumper.dump("interval"sv, mIdleTimer.transform(&OneShotTimer::interval));
|
||
|
|
dumper.dump("controller"sv,
|
||
|
|
mConfig.kernelIdleTimerController
|
||
|
|
.and_then(&ftl::enum_name<KernelIdleTimerController>)
|
||
|
|
.value_or("Platform"sv));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() {
|
||
|
|
return mConfig.idleTimerTimeout;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace android::scheduler
|
||
|
|
|
||
|
|
// TODO(b/129481165): remove the #pragma below and fix conversion issues
|
||
|
|
#pragma clang diagnostic pop // ignored "-Wextra"
|