SurfaceFlinger: allow switching when layers vote to refresh rate
This CL is a refinement of the refresh rate switching algorithm
to allow refresh rate switching even if some of the layers voted
explicitly for a refresh rate.
Test: Run ExoPlayer demo app and scroll the list while playing a video
Bug: 147516364
Change-Id: Id01ff8477804bba9e859545e20b05eeb1ec0d319
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index b313777..bc0111b 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -97,6 +97,7 @@
}
LayerHistory::Summary LayerHistory::summarize(nsecs_t now) {
+ ATRACE_CALL();
std::lock_guard lock(mLock);
partitionLayers(now);
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
index 345b8f9..b755798 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
@@ -61,21 +61,35 @@
}
bool LayerInfoV2::isFrequent(nsecs_t now) const {
+ // Find the first valid frame time
+ auto it = mFrameTimes.begin();
+ for (; it != mFrameTimes.end(); ++it) {
+ if (isFrameTimeValid(*it)) {
+ break;
+ }
+ }
+
// If we know nothing about this layer we consider it as frequent as it might be the start
// of an animation.
- if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
+ if (std::distance(it, mFrameTimes.end()) < FREQUENT_LAYER_WINDOW_SIZE) {
return true;
}
- // Layer is frequent if the earliest value in the window of most recent present times is
- // within threshold.
- const auto it = mFrameTimes.end() - FREQUENT_LAYER_WINDOW_SIZE;
- if (!isFrameTimeValid(*it)) {
- return true;
+ // Find the first active frame
+ for (; it != mFrameTimes.end(); ++it) {
+ if (it->queueTime >= getActiveLayerThreshold(now)) {
+ break;
+ }
}
- const nsecs_t threshold = now - MAX_FREQUENT_LAYER_PERIOD_NS.count();
- return it->queueTime >= threshold;
+ const auto numFrames = std::distance(it, mFrameTimes.end()) - 1;
+ if (numFrames <= 0) {
+ return false;
+ }
+
+ // Layer is considered frequent if the average frame rate is higher than the threshold
+ const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
+ return (1e9f * numFrames) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
}
bool LayerInfoV2::hasEnoughDataForHeuristic() const {
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
index 90f6310..25fb95a 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.h
@@ -47,7 +47,9 @@
// is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
// favor of a low refresh rate.
static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
- static constexpr std::chrono::nanoseconds MAX_FREQUENT_LAYER_PERIOD_NS = 250ms;
+ static constexpr float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
+ static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
+ std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
friend class LayerHistoryTestV2;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index d1de737..b876ccd 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -23,8 +23,6 @@
#include <chrono>
#include <cmath>
-using namespace std::chrono_literals;
-
namespace android::scheduler {
using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
@@ -84,14 +82,31 @@
return *bestSoFar;
}
+std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
+ nsecs_t displayPeriod) const {
+ auto [displayFramesQuot, displayFramesRem] = std::div(layerPeriod, displayPeriod);
+ if (displayFramesRem <= MARGIN_FOR_PERIOD_CALCULATION ||
+ std::abs(displayFramesRem - displayPeriod) <= MARGIN_FOR_PERIOD_CALCULATION) {
+ displayFramesQuot++;
+ displayFramesRem = 0;
+ }
+
+ return {displayFramesQuot, displayFramesRem};
+}
+
const RefreshRate& RefreshRateConfigs::getRefreshRateForContentV2(
- const std::vector<LayerRequirement>& layers) const {
- constexpr nsecs_t MARGIN = std::chrono::nanoseconds(800us).count();
+ const std::vector<LayerRequirement>& layers, bool touchActive) const {
ATRACE_CALL();
ALOGV("getRefreshRateForContent %zu layers", layers.size());
std::lock_guard lock(mLock);
+ // For now if the touch is active return the peak refresh rate
+ // This should be optimized to consider other layers as well.
+ if (touchActive) {
+ return *mAvailableRefreshRates.back();
+ }
+
int noVoteLayers = 0;
int minVoteLayers = 0;
int maxVoteLayers = 0;
@@ -115,11 +130,6 @@
return *mAvailableRefreshRates.front();
}
- // If we have some Max layers and no Explicit we should return Max
- if (maxVoteLayers > 0 && explicitDefaultVoteLayers + explicitExactOrMultipleVoteLayers == 0) {
- return *mAvailableRefreshRates.back();
- }
-
// Find the best refresh rate based on score
std::vector<std::pair<const RefreshRate*, float>> scores;
scores.reserve(mAvailableRefreshRates.size());
@@ -130,67 +140,85 @@
for (const auto& layer : layers) {
ALOGV("Calculating score for %s (type: %d)", layer.name.c_str(), layer.vote);
- if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min ||
- layer.vote == LayerVoteType::Max) {
+ if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {
continue;
}
- // Adjust the weight in case we have explicit layers. The priority is:
- // - ExplicitExactOrMultiple
- // - ExplicitDefault
- // - Heuristic
auto weight = layer.weight;
- if (explicitExactOrMultipleVoteLayers + explicitDefaultVoteLayers > 0) {
- if (layer.vote == LayerVoteType::Heuristic) {
- weight /= 2.f;
- }
- }
- if (explicitExactOrMultipleVoteLayers > 0) {
- if (layer.vote == LayerVoteType::Heuristic ||
- layer.vote == LayerVoteType::ExplicitDefault) {
- weight /= 2.f;
+ for (auto i = 0u; i < scores.size(); i++) {
+ // 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;
+ // use ratio^2 to get a lower score the more we get further from peak
+ const auto layerScore = ratio * ratio;
+ ALOGV("%s (Max, weight %.2f) gives %s score of %.2f", layer.name.c_str(), weight,
+ scores[i].first->name.c_str(), layerScore);
+ scores[i].second += weight * layerScore;
+ continue;
}
- }
- for (auto& [refreshRate, overallScore] : scores) {
- const auto displayPeriod = refreshRate->vsyncPeriod;
+ const auto displayPeriod = scores[i].first->vsyncPeriod;
const auto layerPeriod = round<nsecs_t>(1e9f / layer.desiredRefreshRate);
+ if (layer.vote == LayerVoteType::ExplicitDefault) {
+ const auto layerScore = [&]() {
+ const auto [displayFramesQuot, displayFramesRem] =
+ getDisplayFrames(layerPeriod, displayPeriod);
+ if (displayFramesQuot == 0) {
+ // Layer desired refresh rate is higher the display rate.
+ return static_cast<float>(layerPeriod) / static_cast<float>(displayPeriod);
+ }
- // Calculate how many display vsyncs we need to present a single frame for this layer
- auto [displayFramesQuot, displayFramesRem] = std::div(layerPeriod, displayPeriod);
- if (displayFramesRem <= MARGIN ||
- std::abs(displayFramesRem - displayPeriod) <= MARGIN) {
- displayFramesQuot++;
- displayFramesRem = 0;
+ return 1.0f -
+ (static_cast<float>(displayFramesRem) /
+ static_cast<float>(layerPeriod));
+ }();
+
+ ALOGV("%s (ExplicitDefault, weight %.2f) %.2fHz gives %s score of %.2f",
+ layer.name.c_str(), weight, 1e9f / layerPeriod, scores[i].first->name.c_str(),
+ layerScore);
+ scores[i].second += weight * layerScore;
+ continue;
}
- float layerScore;
- static constexpr size_t MAX_FRAMES_TO_FIT = 10; // Stop calculating when score < 0.1
- if (displayFramesRem == 0) {
- // Layer desired refresh rate matches the display rate.
- layerScore = weight * 1.0f;
- } else if (displayFramesQuot == 0) {
- // Layer desired refresh rate is higher the display rate.
- layerScore = weight *
- (static_cast<float>(layerPeriod) / static_cast<float>(displayPeriod)) *
- (1.0f / (MAX_FRAMES_TO_FIT + 1));
- } else {
- // Layer desired refresh rate is lower the display rate. Check how well it fits the
- // cadence
- auto diff = std::abs(displayFramesRem - (displayPeriod - displayFramesRem));
- int iter = 2;
- while (diff > MARGIN && iter < MAX_FRAMES_TO_FIT) {
- diff = diff - (displayPeriod - diff);
- iter++;
- }
+ if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
+ layer.vote == LayerVoteType::Heuristic) {
+ const auto layerScore = [&]() {
+ // Calculate how many display vsyncs we need to present a single frame for this
+ // layer
+ const auto [displayFramesQuot, displayFramesRem] =
+ getDisplayFrames(layerPeriod, displayPeriod);
+ static constexpr size_t MAX_FRAMES_TO_FIT =
+ 10; // Stop calculating when score < 0.1
+ if (displayFramesRem == 0) {
+ // Layer desired refresh rate matches the display rate.
+ return 1.0f;
+ }
- layerScore = weight * (1.0f / iter);
+ if (displayFramesQuot == 0) {
+ // Layer desired refresh rate is higher 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 the display rate. Check how well it fits
+ // the cadence
+ auto diff = std::abs(displayFramesRem - (displayPeriod - displayFramesRem));
+ int iter = 2;
+ while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) {
+ diff = diff - (displayPeriod - diff);
+ iter++;
+ }
+
+ return 1.0f / iter;
+ }();
+ ALOGV("%s (ExplicitExactOrMultiple, weight %.2f) %.2fHz gives %s score of %.2f",
+ layer.name.c_str(), weight, 1e9f / layerPeriod, scores[i].first->name.c_str(),
+ layerScore);
+ scores[i].second += weight * layerScore;
+ continue;
}
-
- ALOGV("%s (weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(), weight,
- 1e9f / layerPeriod, refreshRate->name.c_str(), layerScore);
- overallScore += layerScore;
}
}
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 1132a8c..0b5c73c 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -28,6 +28,7 @@
#include "Scheduler/StrongTyping.h"
namespace android::scheduler {
+using namespace std::chrono_literals;
enum class RefreshRateConfigEvent : unsigned { None = 0b0, Changed = 0b1 };
@@ -43,6 +44,10 @@
*/
class RefreshRateConfigs {
public:
+ // Margin used when matching refresh rates to the content desired ones.
+ static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION =
+ std::chrono::nanoseconds(800us).count();
+
struct RefreshRate {
// The tolerance within which we consider FPS approximately equals.
static constexpr float FPS_EPSILON = 0.001f;
@@ -123,13 +128,15 @@
bool operator!=(const LayerRequirement& other) const { return !(*this == other); }
};
- // Returns all available refresh rates according to the current policy.
+ // Returns the refresh rate that fits best to the given layers.
const RefreshRate& getRefreshRateForContent(const std::vector<LayerRequirement>& layers) const
EXCLUDES(mLock);
- // Returns all available refresh rates according to the current policy.
- const RefreshRate& getRefreshRateForContentV2(const std::vector<LayerRequirement>& layers) const
- EXCLUDES(mLock);
+ // Returns the refresh rate that fits best to the given layers. This function also gets a
+ // boolean flag that indicates whether user touched the screen recently to be factored in when
+ // choosing the refresh rate.
+ const RefreshRate& getRefreshRateForContentV2(const std::vector<LayerRequirement>& layers,
+ bool touchActive) const EXCLUDES(mLock);
// Returns all the refresh rates supported by the device. This won't change at runtime.
const AllRefreshRatesMapType& getAllRefreshRates() const EXCLUDES(mLock);
@@ -188,6 +195,10 @@
template <typename Iter>
const RefreshRate* getBestRefreshRate(Iter begin, Iter end) const;
+ // Returns number of display frames and remainder when dividing the layer refresh period by
+ // display refresh period.
+ std::pair<nsecs_t, nsecs_t> getDisplayFrames(nsecs_t layerPeriod, nsecs_t displayPeriod) const;
+
// The list of refresh rates, indexed by display config ID. This must not change after this
// object is initialized.
AllRefreshRatesMapType mRefreshRates;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 71ac90e..3a44332 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -439,7 +439,7 @@
return;
}
mFeatures.contentRequirements = summary;
- mFeatures.contentDetection =
+ mFeatures.contentDetectionV1 =
!summary.empty() ? ContentDetectionState::On : ContentDetectionState::Off;
newConfigId = calculateRefreshRateConfigIndexType();
@@ -466,7 +466,7 @@
// NOTE: Instead of checking all the layers, we should be checking the layer
// that is currently on top. b/142507166 will give us this capability.
std::lock_guard<std::mutex> lock(mFeatureStateLock);
- if (mLayerHistory && !layerHistoryHasClientSpecifiedFrameRate()) {
+ if (mLayerHistory) {
mLayerHistory->clear();
mTouchTimer->reset();
@@ -556,7 +556,7 @@
return;
}
mFeatures.configId = newConfigId;
- if (eventOnContentDetection && mFeatures.contentDetection == ContentDetectionState::On) {
+ if (eventOnContentDetection && !mFeatures.contentRequirements.empty()) {
event = ConfigEvent::Changed;
}
}
@@ -564,33 +564,10 @@
mSchedulerCallback.changeRefreshRate(newRefreshRate, event);
}
-bool Scheduler::layerHistoryHasClientSpecifiedFrameRate() {
- // Traverse all the layers to see if any of them requested frame rate.
- for (const auto& layer : mFeatures.contentRequirements) {
- if (layer.vote == scheduler::RefreshRateConfigs::LayerVoteType::ExplicitDefault ||
- layer.vote == scheduler::RefreshRateConfigs::LayerVoteType::ExplicitExactOrMultiple) {
- return true;
- }
- }
-
- return false;
-}
-
HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType() {
- // This block of the code checks whether any layers used the SetFrameRate API. If they have,
- // their request should be honored depending on other active layers.
- if (layerHistoryHasClientSpecifiedFrameRate()) {
- if (!mUseContentDetectionV2) {
- return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements)
- .configId;
- } else {
- return mRefreshRateConfigs.getRefreshRateForContentV2(mFeatures.contentRequirements)
- .configId;
- }
- }
+ ATRACE_CALL();
- // If the layer history doesn't have the frame rate specified, check for other features and
- // honor them. NOTE: If we remove the kernel idle timer, and use our internal idle timer, this
+ // 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.
@@ -600,9 +577,11 @@
return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
}
- // As long as touch is active we want to be in performance mode.
- if (mTouchTimer && mFeatures.touch == TouchState::Active) {
- return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
+ if (!mUseContentDetectionV2) {
+ // As long as touch is active we want to be in performance mode.
+ if (mTouchTimer && mFeatures.touch == TouchState::Active) {
+ return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
+ }
}
// If timer has expired as it means there is no new content on the screen.
@@ -612,7 +591,7 @@
if (!mUseContentDetectionV2) {
// If content detection is off we choose performance as we don't know the content fps.
- if (mFeatures.contentDetection == ContentDetectionState::Off) {
+ if (mFeatures.contentDetectionV1 == ContentDetectionState::Off) {
// NOTE: V1 always calls this, but this is not a default behavior for V2.
return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
}
@@ -621,14 +600,10 @@
return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements).configId;
}
- // Content detection is on, find the appropriate refresh rate with minimal error
- if (mFeatures.contentDetection == ContentDetectionState::On) {
- return mRefreshRateConfigs.getRefreshRateForContentV2(mFeatures.contentRequirements)
- .configId;
- }
-
- // There are no signals for refresh rate, just leave it as is.
- return mRefreshRateConfigs.getCurrentRefreshRateByPolicy().configId;
+ return mRefreshRateConfigs
+ .getRefreshRateForContentV2(mFeatures.contentRequirements,
+ mTouchTimer && mFeatures.touch == TouchState::Active)
+ .configId;
}
std::optional<HwcConfigIndexType> Scheduler::getPreferredConfigId() {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 81051be..46d1a5e 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -185,8 +185,6 @@
// for the suggested refresh rate.
HwcConfigIndexType calculateRefreshRateConfigIndexType() REQUIRES(mFeatureStateLock);
- bool layerHistoryHasClientSpecifiedFrameRate() REQUIRES(mFeatureStateLock);
-
// Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
struct Connection {
sp<EventThreadConnection> connection;
@@ -229,7 +227,7 @@
std::mutex mFeatureStateLock;
struct {
- ContentDetectionState contentDetection = ContentDetectionState::Off;
+ ContentDetectionState contentDetectionV1 = ContentDetectionState::Off;
TimerState idleTimer = TimerState::Reset;
TouchState touch = TouchState::Inactive;
TimerState displayPowerTimer = TimerState::Expired;