Split refresh rate range into two ranges
To prevent low-priority refresh rate considerations from overriding the
app frame rate as specified via the new setFrameRate() api, split the
display refresh rate range into "primary" and "app request" ranges. The
primary range includes the low priority considerations, while the app
request range removes two lower priority considerations.
In general, surface flinger will keep the display refresh rate within
the primary range, but layers with frame rate settings via the
setFrameRate() api may cause surface flinger to pick a refresh rate
outside the primary range. Surface flinger will never choose a refresh
rate outside the app request range specified by display manager.
Bug: 148978562
Test: - Added a new unit test to DisplayModeDirectorTest to verify that
display manager strips lower priority considerations when
deciding the app request range.
- Added a new unit test to RefreshRateConfigsTest to verify
RefreshRateConfigs handles the primary vs app request range
correctly.
- Manual test: Confirmed that with the "force 90Hz refresh rate" option
turned on, we don't switch to 60Hz when playing a 60Hz video.
- Manual test: Confirmed that with the "force 90Hz refresh rate" option
turned on, when an app calls setFrameRate(60), we stay at 60Hz.
- Manual test: Modified a Pixel 4 XL to prefer 60Hz in low brightness,
entered low brightness, and confirmed we don't touch boost to 90Hz.
- Manual test: Confirmed that Maps stays at 60Hz on Pixel 4.
- Manual test: Turned on verbose logs in RefreshRateConfigs.cpp,
confirmed they look good.
- Manual test: Inspected dumpsys output, confirmed the primary and
app request refresh rate ranges are printed correctly.
Change-Id: Ib16cc9b6158efa575cdbfbb7a0ad014008a3e5af
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 5634adb..43e67c2 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -58,7 +58,7 @@
ATRACE_INT("ContentFPS", contentFramerate);
// Find the appropriate refresh rate with minimal error
- auto iter = min_element(mAvailableRefreshRates.cbegin(), mAvailableRefreshRates.cend(),
+ auto iter = min_element(mPrimaryRefreshRates.cbegin(), mPrimaryRefreshRates.cend(),
[contentFramerate](const auto& lhs, const auto& rhs) -> bool {
return std::abs(lhs->fps - contentFramerate) <
std::abs(rhs->fps - contentFramerate);
@@ -71,7 +71,7 @@
constexpr float MARGIN = 0.05f;
float ratio = (*iter)->fps / contentFramerate;
if (std::abs(std::round(ratio) - ratio) > MARGIN) {
- while (iter != mAvailableRefreshRates.cend()) {
+ while (iter != mPrimaryRefreshRates.cend()) {
ratio = (*iter)->fps / contentFramerate;
if (std::abs(std::round(ratio) - ratio) <= MARGIN) {
@@ -110,7 +110,8 @@
// refresh rate.
if (layers.empty()) {
*touchConsidered = touchActive;
- return touchActive ? *mAvailableRefreshRates.back() : getCurrentRefreshRateByPolicyLocked();
+ return touchActive ? getMaxRefreshRateByPolicyLocked()
+ : getCurrentRefreshRateByPolicyLocked();
}
int noVoteLayers = 0;
@@ -135,25 +136,25 @@
}
}
- // 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 get get a touch event.
- if (touchActive && explicitDefaultVoteLayers == 0) {
+ // 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.
+ if (touchActive && explicitDefaultVoteLayers == 0 && explicitExactOrMultipleVoteLayers == 0) {
*touchConsidered = true;
- return *mAvailableRefreshRates.back();
+ return getMaxRefreshRateByPolicyLocked();
}
// Only if all layers want Min we should return Min
if (noVoteLayers + minVoteLayers == layers.size()) {
- return *mAvailableRefreshRates.front();
+ return getMinRefreshRateByPolicyLocked();
}
+ const Policy* policy = getCurrentPolicyLocked();
+
// Find the best refresh rate based on score
std::vector<std::pair<const RefreshRate*, float>> scores;
- scores.reserve(mAvailableRefreshRates.size());
+ scores.reserve(mAppRequestRefreshRates.size());
- for (const auto refreshRate : mAvailableRefreshRates) {
+ for (const auto refreshRate : mAppRequestRefreshRates) {
scores.emplace_back(refreshRate, 0.0f);
}
@@ -166,6 +167,15 @@
auto weight = layer.weight;
for (auto i = 0u; i < scores.size(); i++) {
+ bool inPrimaryRange =
+ scores[i].first->inPolicy(policy->primaryRange.min, policy->primaryRange.max);
+ if (!inPrimaryRange && layer.vote != LayerVoteType::ExplicitDefault &&
+ layer.vote != LayerVoteType::ExplicitExactOrMultiple) {
+ // Only layers with explicit frame rate settings are allowed to score refresh rates
+ // outside the primary range.
+ continue;
+ }
+
// If the layer wants Max, give higher score to the higher refresh rate
if (layer.vote == LayerVoteType::Max) {
const auto ratio = scores[i].first->fps / scores.back().first->fps;
@@ -249,6 +259,17 @@
? getBestRefreshRate(scores.rbegin(), scores.rend())
: getBestRefreshRate(scores.begin(), scores.end());
+ // 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 RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();
+ if (touchActive && explicitDefaultVoteLayers == 0 &&
+ bestRefreshRate->fps < touchRefreshRate.fps) {
+ *touchConsidered = true;
+ return touchRefreshRate;
+ }
+
return *bestRefreshRate;
}
@@ -278,12 +299,20 @@
const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicy() const {
std::lock_guard lock(mLock);
- return *mAvailableRefreshRates.front();
+ return getMinRefreshRateByPolicyLocked();
+}
+
+const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const {
+ return *mPrimaryRefreshRates.front();
}
const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
std::lock_guard lock(mLock);
- return *mAvailableRefreshRates.back();
+ return getMaxRefreshRateByPolicyLocked();
+}
+
+const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked() const {
+ return *mPrimaryRefreshRates.back();
}
const RefreshRate& RefreshRateConfigs::getCurrentRefreshRate() const {
@@ -297,8 +326,8 @@
}
const RefreshRate& RefreshRateConfigs::getCurrentRefreshRateByPolicyLocked() const {
- if (std::find(mAvailableRefreshRates.begin(), mAvailableRefreshRates.end(),
- mCurrentRefreshRate) != mAvailableRefreshRates.end()) {
+ if (std::find(mAppRequestRefreshRates.begin(), mAppRequestRefreshRates.end(),
+ mCurrentRefreshRate) != mAppRequestRefreshRates.end()) {
return *mCurrentRefreshRate;
}
return *mRefreshRates.at(getCurrentPolicyLocked()->defaultConfig);
@@ -320,7 +349,7 @@
const float fps = 1e9f / config->getVsyncPeriod();
mRefreshRates.emplace(configId,
std::make_unique<RefreshRate>(configId, config,
- base::StringPrintf("%2.ffps", fps), fps,
+ base::StringPrintf("%.0ffps", fps), fps,
RefreshRate::ConstructorTag(0)));
if (configId == currentConfigId) {
mCurrentRefreshRate = mRefreshRates.at(configId).get();
@@ -342,10 +371,11 @@
return false;
}
const RefreshRate& refreshRate = *iter->second;
- if (!refreshRate.inPolicy(policy.minRefreshRate, policy.maxRefreshRate)) {
+ if (!refreshRate.inPolicy(policy.primaryRange.min, policy.primaryRange.max)) {
return false;
}
- return true;
+ return policy.appRequestRange.min <= policy.primaryRange.min &&
+ policy.appRequestRange.max >= policy.primaryRange.max;
}
status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) {
@@ -392,7 +422,7 @@
bool RefreshRateConfigs::isConfigAllowed(HwcConfigIndexType config) const {
std::lock_guard lock(mLock);
- for (const RefreshRate* refreshRate : mAvailableRefreshRates) {
+ for (const RefreshRate* refreshRate : mAppRequestRefreshRates) {
if (refreshRate->configId == config) {
return true;
}
@@ -430,33 +460,44 @@
// Filter configs based on current policy and sort based on vsync period
const Policy* policy = getCurrentPolicyLocked();
const auto& defaultConfig = mRefreshRates.at(policy->defaultConfig)->hwcConfig;
- ALOGV("constructAvailableRefreshRates: default %d group %d min %.2f max %.2f",
- policy->defaultConfig.value(), defaultConfig->getConfigGroup(), policy->minRefreshRate,
- policy->maxRefreshRate);
- getSortedRefreshRateList(
- [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
- const auto& hwcConfig = refreshRate.hwcConfig;
+ ALOGV("constructAvailableRefreshRates: default %d group %d primaryRange=[%.2f %.2f]"
+ " appRequestRange=[%.2f %.2f]",
+ policy->defaultConfig.value(), defaultConfig->getConfigGroup(), policy->primaryRange.min,
+ policy->primaryRange.max, policy->appRequestRange.min, policy->appRequestRange.max);
- return hwcConfig->getHeight() == defaultConfig->getHeight() &&
- hwcConfig->getWidth() == defaultConfig->getWidth() &&
- hwcConfig->getDpiX() == defaultConfig->getDpiX() &&
- hwcConfig->getDpiY() == defaultConfig->getDpiY() &&
- (policy->allowGroupSwitching ||
- hwcConfig->getConfigGroup() == defaultConfig->getConfigGroup()) &&
- refreshRate.inPolicy(policy->minRefreshRate, policy->maxRefreshRate);
- },
- &mAvailableRefreshRates);
+ auto filterRefreshRates = [&](float min, float max, const char* listName,
+ std::vector<const RefreshRate*>* outRefreshRates) {
+ getSortedRefreshRateList(
+ [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
+ const auto& hwcConfig = refreshRate.hwcConfig;
- std::string availableRefreshRates;
- for (const auto& refreshRate : mAvailableRefreshRates) {
- base::StringAppendF(&availableRefreshRates, "%s ", refreshRate->name.c_str());
- }
+ return hwcConfig->getHeight() == defaultConfig->getHeight() &&
+ hwcConfig->getWidth() == defaultConfig->getWidth() &&
+ hwcConfig->getDpiX() == defaultConfig->getDpiX() &&
+ hwcConfig->getDpiY() == defaultConfig->getDpiY() &&
+ (policy->allowGroupSwitching ||
+ hwcConfig->getConfigGroup() == defaultConfig->getConfigGroup()) &&
+ refreshRate.inPolicy(min, max);
+ },
+ outRefreshRates);
- ALOGV("Available refresh rates: %s", availableRefreshRates.c_str());
- LOG_ALWAYS_FATAL_IF(mAvailableRefreshRates.empty(),
- "No compatible display configs for default=%d min=%.0f max=%.0f",
- policy->defaultConfig.value(), policy->minRefreshRate,
- policy->maxRefreshRate);
+ LOG_ALWAYS_FATAL_IF(outRefreshRates->empty(),
+ "No matching configs for %s range: min=%.0f max=%.0f", listName, min,
+ max);
+ auto stringifyRefreshRates = [&]() -> std::string {
+ std::string str;
+ for (auto refreshRate : *outRefreshRates) {
+ base::StringAppendF(&str, "%s ", refreshRate->name.c_str());
+ }
+ return str;
+ };
+ ALOGV("%s refresh rates: %s", listName, stringifyRefreshRates().c_str());
+ };
+
+ filterRefreshRates(policy->primaryRange.min, policy->primaryRange.max, "primary",
+ &mPrimaryRefreshRates);
+ filterRefreshRates(policy->appRequestRange.min, policy->appRequestRange.max, "app request",
+ &mAppRequestRefreshRates);
}
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index dea7e90..e8a7bef 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -107,18 +107,46 @@
std::unordered_map<HwcConfigIndexType, std::unique_ptr<const RefreshRate>>;
struct Policy {
+ struct Range {
+ float min = 0;
+ float max = std::numeric_limits<float>::max();
+
+ bool operator==(const Range& other) const {
+ return min == other.min && max == other.max;
+ }
+
+ bool operator!=(const Range& other) const { return !(*this == other); }
+ };
+
// The default config, used to ensure we only initiate display config switches within the
// same config group as defaultConfigId's group.
HwcConfigIndexType defaultConfig;
- // The min and max FPS allowed by the policy.
- float minRefreshRate = 0;
- float maxRefreshRate = std::numeric_limits<float>::max();
+ // The primary refresh rate range represents display manager's general guidance on the
+ // display configs we'll consider when switching refresh rates. Unless we get an explicit
+ // signal from an app, we should stay within this range.
+ Range primaryRange;
+ // The app request refresh rate range allows us to consider more display configs when
+ // switching refresh rates. Although we should generally stay within the primary range,
+ // specific considerations, such as layer frame rate settings specified via the
+ // setFrameRate() api, may cause us to go outside the primary range. We never go outside the
+ // app request range. The app request range will be greater than or equal to the primary
+ // refresh rate range, never smaller.
+ Range appRequestRange;
// Whether or not we switch config groups to get the best frame rate. Only used by tests.
bool allowGroupSwitching = false;
+ Policy() = default;
+ Policy(HwcConfigIndexType defaultConfig, const Range& range)
+ : Policy(defaultConfig, range, range) {}
+ Policy(HwcConfigIndexType defaultConfig, const Range& primaryRange,
+ const Range& appRequestRange)
+ : defaultConfig(defaultConfig),
+ primaryRange(primaryRange),
+ appRequestRange(appRequestRange) {}
+
bool operator==(const Policy& other) const {
- return defaultConfig == other.defaultConfig && minRefreshRate == other.minRefreshRate &&
- maxRefreshRate == other.maxRefreshRate &&
+ return defaultConfig == other.defaultConfig && primaryRange == other.primaryRange &&
+ appRequestRange == other.appRequestRange &&
allowGroupSwitching == other.allowGroupSwitching;
}
@@ -198,13 +226,15 @@
// Returns the lowest refresh rate supported by the device. This won't change at runtime.
const RefreshRate& getMinRefreshRate() const { return *mMinSupportedRefreshRate; }
- // Returns the lowest refresh rate according to the current policy. May change in runtime.
+ // Returns the lowest refresh rate according to the current policy. May change at runtime. Only
+ // uses the primary range, not the app request range.
const RefreshRate& getMinRefreshRateByPolicy() const EXCLUDES(mLock);
// Returns the highest refresh rate supported by the device. This won't change at runtime.
const RefreshRate& getMaxRefreshRate() const { return *mMaxSupportedRefreshRate; }
- // Returns the highest refresh rate according to the current policy. May change in runtime.
+ // Returns the highest refresh rate according to the current policy. May change at runtime. Only
+ // uses the primary range, not the app request range.
const RefreshRate& getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
// Returns the current refresh rate
@@ -243,6 +273,14 @@
// display refresh period.
std::pair<nsecs_t, nsecs_t> getDisplayFrames(nsecs_t layerPeriod, nsecs_t displayPeriod) const;
+ // Returns the lowest refresh rate according to the current policy. May change at runtime. Only
+ // uses the primary range, not the app request range.
+ const RefreshRate& getMinRefreshRateByPolicyLocked() const REQUIRES(mLock);
+
+ // Returns the highest refresh rate according to the current policy. May change at runtime. Only
+ // uses the primary range, not the app request range.
+ const RefreshRate& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock);
+
// Returns the current refresh rate, if allowed. Otherwise the default that is allowed by
// the policy.
const RefreshRate& getCurrentRefreshRateByPolicyLocked() const REQUIRES(mLock);
@@ -254,9 +292,13 @@
// object is initialized.
AllRefreshRatesMapType mRefreshRates;
- // The list of refresh rates which are available in the current policy, ordered by vsyncPeriod
- // (the first element is the lowest refresh rate)
- std::vector<const RefreshRate*> mAvailableRefreshRates GUARDED_BY(mLock);
+ // The list of refresh rates in the primary range of the current policy, ordered by vsyncPeriod
+ // (the first element is the lowest refresh rate).
+ std::vector<const RefreshRate*> mPrimaryRefreshRates GUARDED_BY(mLock);
+
+ // The list of refresh rates in the app request range of the current policy, ordered by
+ // vsyncPeriod (the first element is the lowest refresh rate).
+ std::vector<const RefreshRate*> mAppRequestRefreshRates GUARDED_BY(mLock);
// The current config. This will change at runtime. This is set by SurfaceFlinger on
// the main thread, and read by the Scheduler (and other objects) on other threads.
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 86bb6eb..217c777 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -574,10 +574,8 @@
HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType() {
ATRACE_CALL();
- // NOTE: If we remove the kernel idle timer, and use our internal idle timer, this
- // code will have to be refactored. If Display Power is not in normal operation we want to be in
- // performance mode. When coming back to normal mode, a grace period is given with
- // DisplayPowerTimer.
+ // If Display Power is not in normal operation we want to be in performance mode. When coming
+ // back to normal mode, a grace period is given with DisplayPowerTimer.
if (mDisplayPowerTimer &&
(!mFeatures.isDisplayPowerStateNormal ||
mFeatures.displayPowerTimer == TimerState::Reset)) {