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:
Ady Abraham 2020-05-15 11:51:48 -07:00
parent b209e7b56b
commit 1adbb72759
7 changed files with 77 additions and 96 deletions

View file

@ -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));
}

View file

@ -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};
}

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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() {

View file

@ -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 {

View file

@ -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());