SF: Suppress frame rate when small area updating
The small area is defined as the area that user is hard to obvious the
content updating such as small icon animtion or progress bar.
When content detect frame rate feature is enabled, and no explict
refresh vote is set to the layer, it will heuristically calculate the
predict frame rate by counting the average frame time of recent frames.
This CL will enhance the contect detection by checking the small dirty
frames and supress the predict frame rate that could prevent the app
always running at max frame rate cause unexcpected battery consumption.
This will cover 2 use cases:
1. When a layer contains a textureview, it will enter the heuristic
calcuation, and this would skip the small dirty frames and supress
the calculated frame rate not always to be max.
2. When an app has the separated video surface and ui surface, and the
video had set an explicit rate by the content, this will ignore the
heristic calculation for ui layer when it detected the small dirty
frames, so the final refresh rate selection could mainly rely on the
weight of the video layer.
Bug: 281720315
Test: atest LayerHistoryTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:65705849c4f6339ade986bd0745ea5470ba76ebb)
Merged-In: I9731de8adf8e68b72326b195fa720d51e02bae74
Change-Id: I9731de8adf8e68b72326b195fa720d51e02bae74
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 750803b..e4df494 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -65,7 +65,8 @@
case LayerUpdateType::Buffer:
FrameTimeData frameTime = {.presentTime = lastPresentTime,
.queueTime = mLastUpdatedTime,
- .pendingModeChange = pendingModeChange};
+ .pendingModeChange = pendingModeChange,
+ .isSmallDirty = props.isSmallDirty};
mFrameTimes.push_back(frameTime);
if (mFrameTimes.size() > HISTORY_SIZE) {
mFrameTimes.pop_front();
@@ -101,11 +102,15 @@
// classification.
bool isFrequent = true;
bool isInfrequent = true;
+ int32_t smallDirtyCount = 0;
const auto n = mFrameTimes.size() - 1;
for (size_t i = 0; i < kFrequentLayerWindowSize - 1; i++) {
if (mFrameTimes[n - i].queueTime - mFrameTimes[n - i - 1].queueTime <
kMaxPeriodForFrequentLayerNs.count()) {
isInfrequent = false;
+ if (mFrameTimes[n - i].presentTime == 0 && mFrameTimes[n - i].isSmallDirty) {
+ smallDirtyCount++;
+ }
} else {
isFrequent = false;
}
@@ -115,7 +120,8 @@
// If the layer was previously inconclusive, we clear
// the history as indeterminate layers changed to frequent,
// and we should not look at the stale data.
- return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true};
+ return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true,
+ /* isSmallDirty */ smallDirtyCount >= kNumSmallDirtyThreshold};
}
// If we can't determine whether the layer is frequent or not, we return
@@ -204,6 +210,7 @@
nsecs_t totalDeltas = 0;
int numDeltas = 0;
+ int32_t smallDirtyCount = 0;
auto prevFrame = mFrameTimes.begin();
for (auto it = mFrameTimes.begin() + 1; it != mFrameTimes.end(); ++it) {
const auto currDelta = getFrameTime(*it) - getFrameTime(*prevFrame);
@@ -212,6 +219,13 @@
continue;
}
+ // If this is a small area update, we don't want to consider it for calculating the average
+ // frame time. Instead, we let the bigger frame updates to drive the calculation.
+ if (it->isSmallDirty && currDelta < kMinPeriodBetweenSmallDirtyFrames) {
+ smallDirtyCount++;
+ continue;
+ }
+
prevFrame = it;
if (currDelta > kMaxPeriodBetweenFrames) {
@@ -223,6 +237,10 @@
numDeltas++;
}
+ if (smallDirtyCount > 0) {
+ ATRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
+ }
+
if (numDeltas == 0) {
return std::nullopt;
}
@@ -313,6 +331,14 @@
clearHistory(now);
}
+ // Return no vote if the latest frames are small dirty.
+ if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) {
+ ATRACE_FORMAT_INSTANT("NoVote (small dirty)");
+ ALOGV("%s is small dirty", mName.c_str());
+ votes.push_back({LayerHistory::LayerVoteType::NoVote, Fps()});
+ return votes;
+ }
+
auto refreshRate = calculateRefreshRateIfPossible(selector, now);
if (refreshRate.has_value()) {
ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());