SurfaceFlinger: more aggressive infrequent layer detection
Change the algorithm that chooses the refresh rate to treat layers as infrequent unless proven otherwise. This change helps with multiple switches during scenarios of blinking cursor where the detection of infrequent layer is too long. The down side on this change is that animations will be considered infrequent as well for the first few frames. However the touch boost is a good mitigation of this. Test: Typing in Messages and observe refresh rate Test: Settings->About->up time and observe refresh rate Bug: 155062712 Bug: 156654519 Change-Id: I317c69bd063df5d70f2d5705163cf61c1c9b1fff
This commit is contained in:
parent
b209e7b56b
commit
1adbb72759
7 changed files with 77 additions and 96 deletions
|
@ -90,7 +90,7 @@ LayerHistoryV2::~LayerHistoryV2() = default;
|
|||
void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
|
||||
LayerVoteType type) {
|
||||
const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
|
||||
auto info = std::make_unique<LayerInfoV2>(highRefreshRatePeriod, type);
|
||||
auto info = std::make_unique<LayerInfoV2>(layer->getName(), highRefreshRatePeriod, type);
|
||||
std::lock_guard lock(mLock);
|
||||
mLayerInfos.emplace_back(layer, std::move(info));
|
||||
}
|
||||
|
|
|
@ -27,8 +27,10 @@
|
|||
|
||||
namespace android::scheduler {
|
||||
|
||||
LayerInfoV2::LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote)
|
||||
: mHighRefreshRatePeriod(highRefreshRatePeriod),
|
||||
LayerInfoV2::LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
|
||||
LayerHistory::LayerVoteType defaultVote)
|
||||
: mName(name),
|
||||
mHighRefreshRatePeriod(highRefreshRatePeriod),
|
||||
mDefaultVote(defaultVote),
|
||||
mLayerVote({defaultVote, 0.0f}) {}
|
||||
|
||||
|
@ -45,42 +47,23 @@ void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
|
|||
}
|
||||
}
|
||||
|
||||
bool LayerInfoV2::isFrameTimeValid(const FrameTimeData& frameTime) const {
|
||||
return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
mFrameTimeValidSince.time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
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;
|
||||
for (auto it = mFrameTimes.crbegin(); it != mFrameTimes.crend(); ++it) {
|
||||
if (now - it->queueTime >= MAX_FREQUENT_LAYER_PERIOD_NS.count()) {
|
||||
ALOGV("%s infrequent (last frame is %.2fms ago", mName.c_str(),
|
||||
(now - mFrameTimes.back().queueTime) / 1e6f);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto numFrames = std::distance(mFrameTimes.crbegin(), it + 1);
|
||||
if (numFrames >= FREQUENT_LAYER_WINDOW_SIZE) {
|
||||
ALOGV("%s frequent (burst of %zu frames", mName.c_str(), numFrames);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we know nothing about this layer we consider it as frequent as it might be the start
|
||||
// of an animation.
|
||||
if (std::distance(it, mFrameTimes.end()) < FREQUENT_LAYER_WINDOW_SIZE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the first active frame
|
||||
for (; it != mFrameTimes.end(); ++it) {
|
||||
if (it->queueTime >= getActiveLayerThreshold(now)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto numFrames = std::distance(it, mFrameTimes.end());
|
||||
if (numFrames < FREQUENT_LAYER_WINDOW_SIZE) {
|
||||
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 - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
|
||||
ALOGV("%s infrequent (not enough frames %zu)", mName.c_str(), mFrameTimes.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayerInfoV2::hasEnoughDataForHeuristic() const {
|
||||
|
@ -89,10 +72,6 @@ bool LayerInfoV2::hasEnoughDataForHeuristic() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!isFrameTimeValid(mFrameTimes.front())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mFrameTimes.size() < HISTORY_SIZE &&
|
||||
mFrameTimes.back().queueTime - mFrameTimes.front().queueTime < HISTORY_TIME.count()) {
|
||||
return false;
|
||||
|
@ -167,18 +146,22 @@ std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
|
|||
|
||||
std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
|
||||
if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
|
||||
ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
|
||||
return {mLayerVote.type, mLayerVote.fps};
|
||||
}
|
||||
|
||||
if (!isFrequent(now)) {
|
||||
ALOGV("%s is infrequent", mName.c_str());
|
||||
return {LayerHistory::LayerVoteType::Min, 0};
|
||||
}
|
||||
|
||||
auto refreshRate = calculateRefreshRateIfPossible();
|
||||
if (refreshRate.has_value()) {
|
||||
ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
|
||||
return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
|
||||
}
|
||||
|
||||
ALOGV("%s Max (can't resolve refresh rate", mName.c_str());
|
||||
return {LayerHistory::LayerVoteType::Max, 0};
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,8 @@ class LayerInfoV2 {
|
|||
friend class LayerHistoryTestV2;
|
||||
|
||||
public:
|
||||
LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote);
|
||||
LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
|
||||
LayerHistory::LayerVoteType defaultVote);
|
||||
|
||||
LayerInfoV2(const LayerInfo&) = delete;
|
||||
LayerInfoV2& operator=(const LayerInfoV2&) = delete;
|
||||
|
@ -83,11 +84,7 @@ public:
|
|||
nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
|
||||
|
||||
void clearHistory() {
|
||||
// Mark mFrameTimeValidSince to now to ignore all previous frame times.
|
||||
// We are not deleting the old frame to keep track of whether we should treat the first
|
||||
// buffer as Max as we don't know anything about this layer or Min as this layer is
|
||||
// posting infrequent updates.
|
||||
mFrameTimeValidSince = std::chrono::steady_clock::now();
|
||||
mFrameTimes.clear();
|
||||
mLastReportedRefreshRate = 0.0f;
|
||||
}
|
||||
|
||||
|
@ -101,7 +98,8 @@ private:
|
|||
bool isFrequent(nsecs_t now) const;
|
||||
bool hasEnoughDataForHeuristic() const;
|
||||
std::optional<float> calculateRefreshRateIfPossible();
|
||||
bool isFrameTimeValid(const FrameTimeData&) const;
|
||||
|
||||
const std::string mName;
|
||||
|
||||
// Used for sanitizing the heuristic data
|
||||
const nsecs_t mHighRefreshRatePeriod;
|
||||
|
@ -118,8 +116,6 @@ private:
|
|||
} mLayerVote;
|
||||
|
||||
std::deque<FrameTimeData> mFrameTimes;
|
||||
std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
|
||||
std::chrono::steady_clock::now();
|
||||
static constexpr size_t HISTORY_SIZE = 90;
|
||||
static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
|
||||
};
|
||||
|
|
|
@ -103,7 +103,7 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
|
|||
ATRACE_CALL();
|
||||
ALOGV("getRefreshRateForContent %zu layers", layers.size());
|
||||
|
||||
*touchConsidered = false;
|
||||
if (touchConsidered) *touchConsidered = false;
|
||||
std::lock_guard lock(mLock);
|
||||
|
||||
int noVoteLayers = 0;
|
||||
|
@ -131,7 +131,8 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
|
|||
// 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;
|
||||
ALOGV("TouchBoost - choose %s", getMaxRefreshRateByPolicyLocked().getName().c_str());
|
||||
if (touchConsidered) *touchConsidered = true;
|
||||
return getMaxRefreshRateByPolicyLocked();
|
||||
}
|
||||
|
||||
|
@ -145,6 +146,7 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
|
|||
|
||||
// Only if all layers want Min we should return Min
|
||||
if (noVoteLayers + minVoteLayers == layers.size()) {
|
||||
ALOGV("all layers Min - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
|
||||
return getMinRefreshRateByPolicyLocked();
|
||||
}
|
||||
|
||||
|
@ -243,9 +245,11 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
|
|||
|
||||
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);
|
||||
ALOGV("%s (%s, weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(),
|
||||
layer.vote == LayerVoteType::ExplicitExactOrMultiple
|
||||
? "ExplicitExactOrMultiple"
|
||||
: "Heuristic",
|
||||
weight, 1e9f / layerPeriod, scores[i].first->name.c_str(), layerScore);
|
||||
scores[i].second += weight * layerScore;
|
||||
continue;
|
||||
}
|
||||
|
@ -266,7 +270,8 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
|
|||
const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();
|
||||
if (touchActive && explicitDefaultVoteLayers == 0 &&
|
||||
bestRefreshRate->fps < touchRefreshRate.fps) {
|
||||
*touchConsidered = true;
|
||||
if (touchConsidered) *touchConsidered = true;
|
||||
ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
|
||||
return touchRefreshRate;
|
||||
}
|
||||
|
||||
|
|
|
@ -526,7 +526,9 @@ void Scheduler::idleTimerCallback(TimerState state) {
|
|||
|
||||
void Scheduler::touchTimerCallback(TimerState state) {
|
||||
const TouchState touch = state == TimerState::Reset ? TouchState::Active : TouchState::Inactive;
|
||||
handleTimerStateChanged(&mFeatures.touch, touch, true /* eventOnContentDetection */);
|
||||
if (handleTimerStateChanged(&mFeatures.touch, touch, true /* eventOnContentDetection */)) {
|
||||
mLayerHistory->clear();
|
||||
}
|
||||
ATRACE_INT("TouchState", static_cast<int>(touch));
|
||||
}
|
||||
|
||||
|
@ -549,18 +551,19 @@ void Scheduler::dump(std::string& result) const {
|
|||
}
|
||||
|
||||
template <class T>
|
||||
void Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection) {
|
||||
bool Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection) {
|
||||
ConfigEvent event = ConfigEvent::None;
|
||||
HwcConfigIndexType newConfigId;
|
||||
bool touchConsidered = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mFeatureStateLock);
|
||||
if (*currentState == newState) {
|
||||
return;
|
||||
return touchConsidered;
|
||||
}
|
||||
*currentState = newState;
|
||||
newConfigId = calculateRefreshRateConfigIndexType();
|
||||
newConfigId = calculateRefreshRateConfigIndexType(&touchConsidered);
|
||||
if (mFeatures.configId == newConfigId) {
|
||||
return;
|
||||
return touchConsidered;
|
||||
}
|
||||
mFeatures.configId = newConfigId;
|
||||
if (eventOnContentDetection && !mFeatures.contentRequirements.empty()) {
|
||||
|
@ -569,10 +572,12 @@ void Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventO
|
|||
}
|
||||
const RefreshRate& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
|
||||
mSchedulerCallback.changeRefreshRate(newRefreshRate, event);
|
||||
return touchConsidered;
|
||||
}
|
||||
|
||||
HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType() {
|
||||
HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType(bool* touchConsidered) {
|
||||
ATRACE_CALL();
|
||||
if (touchConsidered) *touchConsidered = false;
|
||||
|
||||
// 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.
|
||||
|
@ -607,18 +612,9 @@ HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType() {
|
|||
.getConfigId();
|
||||
}
|
||||
|
||||
bool touchConsidered;
|
||||
const auto& ret = mRefreshRateConfigs
|
||||
.getBestRefreshRate(mFeatures.contentRequirements, touchActive, idle,
|
||||
&touchConsidered)
|
||||
.getConfigId();
|
||||
if (touchConsidered) {
|
||||
// Clear layer history if refresh rate was selected based on touch to allow
|
||||
// the hueristic to pick up with the new rate.
|
||||
mLayerHistory->clear();
|
||||
}
|
||||
|
||||
return ret;
|
||||
return mRefreshRateConfigs
|
||||
.getBestRefreshRate(mFeatures.contentRequirements, touchActive, idle, touchConsidered)
|
||||
.getConfigId();
|
||||
}
|
||||
|
||||
std::optional<HwcConfigIndexType> Scheduler::getPreferredConfigId() {
|
||||
|
|
|
@ -177,14 +177,15 @@ private:
|
|||
|
||||
// handles various timer features to change the refresh rate.
|
||||
template <class T>
|
||||
void handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection);
|
||||
bool handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection);
|
||||
|
||||
void setVsyncPeriod(nsecs_t period);
|
||||
|
||||
// This function checks whether individual features that are affecting the refresh rate
|
||||
// selection were initialized, prioritizes them, and calculates the HwcConfigIndexType
|
||||
// for the suggested refresh rate.
|
||||
HwcConfigIndexType calculateRefreshRateConfigIndexType() REQUIRES(mFeatureStateLock);
|
||||
HwcConfigIndexType calculateRefreshRateConfigIndexType(bool* touchConsidered = nullptr)
|
||||
REQUIRES(mFeatureStateLock);
|
||||
|
||||
// Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
|
||||
struct Connection {
|
||||
|
|
|
@ -103,12 +103,22 @@ TEST_F(LayerHistoryTestV2, oneLayer) {
|
|||
EXPECT_TRUE(history().summarize(time).empty());
|
||||
EXPECT_EQ(0, activeLayerCount());
|
||||
|
||||
// The first few updates are considered infrequent
|
||||
for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
|
||||
history().record(layer.get(), 0, time);
|
||||
ASSERT_EQ(1, history().summarize(time).size());
|
||||
EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
|
||||
EXPECT_EQ(1, activeLayerCount());
|
||||
EXPECT_EQ(0, frequentLayerCount(time));
|
||||
}
|
||||
|
||||
// Max returned if active layers have insufficient history.
|
||||
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
|
||||
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
|
||||
history().record(layer.get(), 0, time);
|
||||
ASSERT_EQ(1, history().summarize(time).size());
|
||||
EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
|
||||
EXPECT_EQ(1, activeLayerCount());
|
||||
EXPECT_EQ(1, frequentLayerCount(time));
|
||||
}
|
||||
|
||||
// Max is returned since we have enough history but there is no timestamp votes.
|
||||
|
@ -117,6 +127,7 @@ TEST_F(LayerHistoryTestV2, oneLayer) {
|
|||
ASSERT_EQ(1, history().summarize(time).size());
|
||||
EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
|
||||
EXPECT_EQ(1, activeLayerCount());
|
||||
EXPECT_EQ(1, frequentLayerCount(time));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +145,7 @@ TEST_F(LayerHistoryTestV2, oneInvisibleLayer) {
|
|||
auto summary = history().summarize(time);
|
||||
ASSERT_EQ(1, history().summarize(time).size());
|
||||
// Layer is still considered inactive so we expect to get Min
|
||||
EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
|
||||
EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
|
||||
EXPECT_EQ(1, activeLayerCount());
|
||||
|
||||
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
|
||||
|
@ -464,28 +475,15 @@ TEST_F(LayerHistoryTestV2, inactiveLayers) {
|
|||
|
||||
nsecs_t time = systemTime();
|
||||
|
||||
// the very first updates makes the layer frequent
|
||||
// The first few updates are considered infrequent
|
||||
for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
|
||||
history().record(layer.get(), time, time);
|
||||
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
|
||||
|
||||
EXPECT_EQ(1, layerCount());
|
||||
history().record(layer.get(), 0, time);
|
||||
ASSERT_EQ(1, history().summarize(time).size());
|
||||
EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
|
||||
EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
|
||||
EXPECT_EQ(1, activeLayerCount());
|
||||
EXPECT_EQ(1, frequentLayerCount(time));
|
||||
EXPECT_EQ(0, frequentLayerCount(time));
|
||||
}
|
||||
|
||||
// the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
|
||||
history().record(layer.get(), time, time);
|
||||
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
|
||||
|
||||
EXPECT_EQ(1, layerCount());
|
||||
ASSERT_EQ(1, history().summarize(time).size());
|
||||
EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
|
||||
EXPECT_EQ(1, activeLayerCount());
|
||||
EXPECT_EQ(0, frequentLayerCount(time));
|
||||
|
||||
// advance the time for the previous frame to be inactive
|
||||
time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
|
||||
|
||||
|
@ -528,9 +526,11 @@ TEST_F(LayerHistoryTestV2, invisibleExplicitLayer) {
|
|||
|
||||
nsecs_t time = systemTime();
|
||||
|
||||
// Post a buffer to the layers to make them active
|
||||
history().record(explicitVisiblelayer.get(), time, time);
|
||||
history().record(explicitInvisiblelayer.get(), time, time);
|
||||
// Post a few buffers to the layers to make them active
|
||||
for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
|
||||
history().record(explicitVisiblelayer.get(), time, time);
|
||||
history().record(explicitInvisiblelayer.get(), time, time);
|
||||
}
|
||||
|
||||
EXPECT_EQ(2, layerCount());
|
||||
ASSERT_EQ(1, history().summarize(time).size());
|
||||
|
|
Loading…
Reference in a new issue