SurfaceFlinger: define a known frame rates list
Keep a list of known frame rates that would be used when we calculated
heuristically the frame rate of a layer. This keeps the signal to the
algorithm that chooses the refresh rate steady and avoid strange
frame rates like 57.2 due to inconsistent presentation timestamps.
Bug: 157540021
Test: adb shell /data/nativetest64/libsurfaceflinger_unittest/libsurfaceflinger_unittest
Change-Id: I97a24b74605256646e9b8444bd9f3818fe0a4a2a
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index acd76b0..228b8a0 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -136,7 +136,7 @@
class LayerHistoryV2 : public android::scheduler::LayerHistory {
public:
- LayerHistoryV2();
+ LayerHistoryV2(const scheduler::RefreshRateConfigs&);
virtual ~LayerHistoryV2();
// Layers are unregistered when the weak reference expires.
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
index 316000c..6216f6e 100644
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
@@ -87,8 +87,11 @@
}
} // namespace
-LayerHistoryV2::LayerHistoryV2()
- : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
+LayerHistoryV2::LayerHistoryV2(const scheduler::RefreshRateConfigs& refreshRateConfigs)
+ : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
+ LayerInfoV2::setRefreshRateConfigs(refreshRateConfigs);
+}
+
LayerHistoryV2::~LayerHistoryV2() = default;
void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
index 82da007..f4bbc37 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
@@ -27,6 +27,8 @@
namespace android::scheduler {
+const RefreshRateConfigs* LayerInfoV2::sRefreshRateConfigs = nullptr;
+
LayerInfoV2::LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
LayerHistory::LayerVoteType defaultVote)
: mName(name),
@@ -168,7 +170,6 @@
std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
static constexpr float MARGIN = 1.0f; // 1Hz
-
if (!hasEnoughDataForHeuristic()) {
ALOGV("Not enough data");
return std::nullopt;
@@ -177,7 +178,7 @@
const auto [averageFrameTime, missingPresentTime] = calculateAverageFrameTime();
// If there are no presentation timestamps provided we can't calculate the refresh rate
- if (missingPresentTime && mLastReportedRefreshRate == 0) {
+ if (missingPresentTime && mLastRefreshRate.reported == 0) {
return std::nullopt;
}
@@ -186,12 +187,16 @@
}
const auto refreshRate = 1e9f / averageFrameTime;
- if (std::abs(refreshRate - mLastReportedRefreshRate) > MARGIN) {
- mLastReportedRefreshRate = refreshRate;
+ const auto knownRefreshRate = sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
+ if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
+ mLastRefreshRate.reported != knownRefreshRate) {
+ mLastRefreshRate.calculated = refreshRate;
+ mLastRefreshRate.reported = knownRefreshRate;
}
- ALOGV("Refresh rate: %.2f", mLastReportedRefreshRate);
- return mLastReportedRefreshRate;
+ ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(), refreshRate,
+ mLastRefreshRate.reported);
+ return mLastRefreshRate.reported;
}
std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
index 9e6783f..c434963 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.h
@@ -56,6 +56,10 @@
friend class LayerHistoryTestV2;
public:
+ static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
+ sRefreshRateConfigs = &refreshRateConfigs;
+ }
+
LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
LayerHistory::LayerVoteType defaultVote);
@@ -93,7 +97,7 @@
// posting infrequent updates.
const auto timePoint = std::chrono::nanoseconds(now);
mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
- mLastReportedRefreshRate = 0.0f;
+ mLastRefreshRate = {};
}
void clearHistory(nsecs_t now) {
@@ -127,7 +131,13 @@
nsecs_t mLastAnimationTime = 0;
- float mLastReportedRefreshRate = 0.0f;
+ // Holds information about the calculated and reported refresh rate
+ struct RefreshRateHeuristicData {
+ float calculated = 0.0f; // Rate calculated on the layer
+ float reported = 0.0f; // Last reported rate for LayerInfoV2::getRefreshRate()
+ };
+
+ RefreshRateHeuristicData mLastRefreshRate;
// Holds information about the layer vote
struct {
@@ -140,6 +150,9 @@
std::chrono::steady_clock::now();
static constexpr size_t HISTORY_SIZE = 90;
static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
+
+ // Shared for all LayerInfo instances
+ static const RefreshRateConfigs* sRefreshRateConfigs;
};
} // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 3c8bd68..ea27955 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -242,7 +242,7 @@
if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
layer.vote == LayerVoteType::Heuristic) {
- const auto layerScore = [&]() {
+ const auto layerScore = [&] {
// Calculate how many display vsyncs we need to present a single frame for this
// layer
const auto [displayFramesQuot, displayFramesRem] =
@@ -384,7 +384,8 @@
RefreshRateConfigs::RefreshRateConfigs(
const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
- HwcConfigIndexType currentConfigId) {
+ HwcConfigIndexType currentConfigId)
+ : mKnownFrameRates(constructKnownFrameRates(configs)) {
LOG_ALWAYS_FATAL_IF(configs.empty());
LOG_ALWAYS_FATAL_IF(currentConfigId.value() >= configs.size());
@@ -544,4 +545,40 @@
&mAppRequestRefreshRates);
}
+std::vector<float> RefreshRateConfigs::constructKnownFrameRates(
+ const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
+ std::vector<float> knownFrameRates = {24.0f, 30.0f, 45.0f, 60.0f, 72.0f};
+ knownFrameRates.reserve(knownFrameRates.size() + configs.size());
+
+ // Add all supported refresh rates to the set
+ for (const auto& config : configs) {
+ const auto refreshRate = 1e9f / config->getVsyncPeriod();
+ knownFrameRates.emplace_back(refreshRate);
+ }
+
+ // Sort and remove duplicates
+ const auto frameRatesEqual = [](float a, float b) { return std::abs(a - b) <= 0.01f; };
+ std::sort(knownFrameRates.begin(), knownFrameRates.end());
+ knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
+ frameRatesEqual),
+ knownFrameRates.end());
+ return knownFrameRates;
+}
+
+float RefreshRateConfigs::findClosestKnownFrameRate(float frameRate) const {
+ if (frameRate <= *mKnownFrameRates.begin()) {
+ return *mKnownFrameRates.begin();
+ }
+
+ if (frameRate >= *std::prev(mKnownFrameRates.end())) {
+ return *std::prev(mKnownFrameRates.end());
+ }
+
+ auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate);
+
+ const auto distance1 = std::abs(frameRate - *lowerBound);
+ const auto distance2 = std::abs(frameRate - *std::prev(lowerBound));
+ return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
+}
+
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index ff1eabd..88e4eb5 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -29,7 +29,6 @@
#include "Scheduler/StrongTyping.h"
namespace android::scheduler {
-class RefreshRateConfigsTest;
using namespace std::chrono_literals;
@@ -87,7 +86,7 @@
private:
friend RefreshRateConfigs;
- friend RefreshRateConfigsTest;
+ friend class RefreshRateConfigsTest;
// The tolerance within which we consider FPS approximately equals.
static constexpr float FPS_EPSILON = 0.001f;
@@ -258,11 +257,18 @@
// Returns a string that represents the layer vote type
static std::string layerVoteTypeString(LayerVoteType vote);
+ // Returns a known frame rate that is the closest to frameRate
+ float findClosestKnownFrameRate(float frameRate) const;
+
RefreshRateConfigs(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
HwcConfigIndexType currentConfigId);
private:
+ friend class RefreshRateConfigsTest;
+
void constructAvailableRefreshRates() REQUIRES(mLock);
+ static std::vector<float> constructKnownFrameRates(
+ const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs);
void getSortedRefreshRateList(
const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
@@ -320,6 +326,10 @@
const RefreshRate* mMaxSupportedRefreshRate;
mutable std::mutex mLock;
+
+ // A sorted list of known frame rates that a Heuristic layer will choose
+ // from based on the closest value.
+ const std::vector<float> mKnownFrameRates;
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index e250bbf..bfa7493 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -111,7 +111,7 @@
using namespace sysprop;
if (mUseContentDetectionV2) {
- mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+ mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(refreshRateConfig);
} else {
mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
}
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
index f376b4a..2d3b01f 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -84,6 +84,22 @@
return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
}
+ void recordFramesAndExpect(const sp<mock::MockLayer>& layer, float frameRate,
+ float desiredRefreshRate) {
+ nsecs_t time = systemTime();
+ const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
+
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += framePeriod;
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(desiredRefreshRate, history().summarize(time)[0].desiredRefreshRate)
+ << "Frame rate is " << frameRate;
+ }
+
Hwc2::mock::Display mDisplay;
RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
.setVsyncPeriod(int32_t(LO_FPS_PERIOD))
@@ -600,6 +616,28 @@
EXPECT_EQ(1, animatingLayerCount(time));
}
+TEST_F(LayerHistoryTestV2, heuristicLayer60Hz) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
+ recordFramesAndExpect(layer, fps, 60.0f);
+ }
+}
+
+TEST_F(LayerHistoryTestV2, heuristicLayerNotOscillating) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ recordFramesAndExpect(layer, 27.10f, 30.0f);
+ recordFramesAndExpect(layer, 26.50f, 30.0f);
+ recordFramesAndExpect(layer, 25.90f, 24.0f);
+ recordFramesAndExpect(layer, 26.50f, 24.0f);
+ recordFramesAndExpect(layer, 27.10f, 30.0f);
+}
+
class LayerHistoryTestV2Parameterized
: public LayerHistoryTestV2,
public testing::WithParamInterface<std::chrono::nanoseconds> {};
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index c919e93..4fa8455 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -43,6 +43,14 @@
RefreshRateConfigsTest();
~RefreshRateConfigsTest();
+ float findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, float frameRate) {
+ return refreshRateConfigs.findClosestKnownFrameRate(frameRate);
+ }
+
+ std::vector<float> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
+ return refreshRateConfigs.mKnownFrameRates;
+ }
+
// Test config IDs
static inline const HwcConfigIndexType HWC_CONFIG_ID_60 = HwcConfigIndexType(0);
static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(1);
@@ -902,8 +910,7 @@
const auto& refreshRate =
refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
/*idle*/ false, &ignored);
- printf("%.2fHz chooses %s\n", fps, refreshRate.getName().c_str());
- EXPECT_EQ(mExpected60Config, refreshRate);
+ EXPECT_EQ(mExpected60Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
}
}
@@ -989,8 +996,7 @@
const auto& refreshRate =
refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
/*idle*/ false, &ignored);
- printf("%.2fHz chooses %s\n", fps, refreshRate.getName().c_str());
- EXPECT_EQ(mExpected90Config, refreshRate);
+ EXPECT_EQ(mExpected90Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
}
}
@@ -1456,6 +1462,73 @@
.getConfigId());
}
+TEST_F(RefreshRateConfigsTest, findClosestKnownFrameRate) {
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m60_90Device,
+ /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+ for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
+ const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, fps);
+ float expectedFrameRate;
+ if (fps < 26.91f) {
+ expectedFrameRate = 24.0f;
+ } else if (fps < 37.51f) {
+ expectedFrameRate = 30.0f;
+ } else if (fps < 52.51f) {
+ expectedFrameRate = 45.0f;
+ } else if (fps < 66.01f) {
+ expectedFrameRate = 60.0f;
+ } else if (fps < 81.01f) {
+ expectedFrameRate = 72.0f;
+ } else {
+ expectedFrameRate = 90.0f;
+ }
+ EXPECT_FLOAT_EQ(expectedFrameRate, knownFrameRate)
+ << "findClosestKnownFrameRate(" << fps << ") = " << knownFrameRate;
+ }
+}
+
+TEST_F(RefreshRateConfigsTest, getBestRefreshRate_KnownFrameRate) {
+ bool ignored;
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m60_90Device,
+ /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+ struct ExpectedRate {
+ float rate;
+ const RefreshRate& expected;
+ };
+
+ /* clang-format off */
+ std::vector<ExpectedRate> knownFrameRatesExpectations = {
+ {24.0f, mExpected60Config},
+ {30.0f, mExpected60Config},
+ {45.0f, mExpected90Config},
+ {60.0f, mExpected60Config},
+ {72.0f, mExpected90Config},
+ {90.0f, mExpected90Config},
+ };
+ /* clang-format on */
+
+ // Make sure the test tests all the known frame rate
+ const auto knownFrameRateList = getKnownFrameRate(*refreshRateConfigs);
+ const auto equal = std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
+ knownFrameRatesExpectations.begin(),
+ [](float a, const ExpectedRate& b) { return a == b.rate; });
+ EXPECT_TRUE(equal);
+
+ auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+ auto& layer = layers[0];
+ layer.vote = LayerVoteType::Heuristic;
+ for (const auto& expectedRate : knownFrameRatesExpectations) {
+ layer.desiredRefreshRate = expectedRate.rate;
+ const auto& refreshRate = refreshRateConfigs->getBestRefreshRate(layers,
+ /*touchActive*/ false,
+ /*idle*/ false, &ignored);
+ EXPECT_EQ(expectedRate.expected, refreshRate);
+ }
+}
+
} // namespace
} // namespace scheduler
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 41b5d49..d5ecae8 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -31,7 +31,7 @@
TestableScheduler(const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
: Scheduler([](bool) {}, configs, *this, useContentDetectionV2, true) {
if (mUseContentDetectionV2) {
- mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+ mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
} else {
mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
}
@@ -43,7 +43,7 @@
: Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this,
useContentDetectionV2, true) {
if (mUseContentDetectionV2) {
- mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+ mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
} else {
mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
}