SF: Improve LayerInfo::calculateAverageFrameTime
This refactors and improves LayerInfo::calculateAverageFrameTime.
The behaviour is changed in two ways:
* if two consecutive frames are too close to each other we count
them as one frame and consider the delta between them in
the total. This gives a better estimation for the average
refresh rate. See CalculateAverageFrameTimeTest::ignoresSmallPeriods
which was failing with the previous implementation.
* if two consecutive frames are too far apart we discard the delta
between them. This is covered by the test "ignoresLargePeriods".
Fixes: 170476958
Test: atest CalculateAverageFrameTimeTest
Change-Id: If98199bb8198f74c93e93c9996107c021f1bc7ba
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 0fa71f1..4324855 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -54,7 +54,7 @@
break;
case LayerUpdateType::SetFrameRate:
case LayerUpdateType::Buffer:
- FrameTimeData frameTime = {.presetTime = lastPresentTime,
+ FrameTimeData frameTime = {.presentTime = lastPresentTime,
.queueTime = mLastUpdatedTime,
.pendingConfigChange = pendingConfigChange};
mFrameTimes.push_back(frameTime);
@@ -74,7 +74,7 @@
bool LayerInfo::isFrequent(nsecs_t now) const {
// 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 (mFrameTimes.size() < kFrequentLayerWindowSize) {
return true;
}
@@ -87,14 +87,14 @@
}
const auto numFrames = std::distance(it, mFrameTimes.end());
- if (numFrames < FREQUENT_LAYER_WINDOW_SIZE) {
+ if (numFrames < kFrequentLayerWindowSize) {
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 Fps::fromPeriodNsecs(totalTime / (numFrames - 1))
- .greaterThanOrEqualWithMargin(MIN_FPS_FOR_FREQUENT_LAYER);
+ .greaterThanOrEqualWithMargin(kMinFpsForFrequentLayer);
}
bool LayerInfo::isAnimating(nsecs_t now) const {
@@ -124,32 +124,21 @@
}
std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
- nsecs_t totalPresentTimeDeltas = 0;
- nsecs_t totalQueueTimeDeltas = 0;
- bool missingPresentTime = false;
- int numFrames = 0;
- for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
- // Ignore frames captured during a config change
- if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
- return std::nullopt;
- }
+ // Ignore frames captured during a config change
+ const bool isDuringConfigChange =
+ std::any_of(mFrameTimes.begin(), mFrameTimes.end(),
+ [](auto frame) { return frame.pendingConfigChange; });
+ if (isDuringConfigChange) {
+ return std::nullopt;
+ }
- totalQueueTimeDeltas +=
- std::max(((it + 1)->queueTime - it->queueTime), kMinPeriodBetweenFrames);
- numFrames++;
-
- if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
- missingPresentTime = true;
- // If there are no presentation timestamps and we haven't calculated
- // one in the past then we can't calculate the refresh rate
- if (!mLastRefreshRate.reported.isValid()) {
- return std::nullopt;
- }
- continue;
- }
-
- totalPresentTimeDeltas +=
- std::max(((it + 1)->presetTime - it->presetTime), kMinPeriodBetweenFrames);
+ const bool isMissingPresentTime =
+ std::any_of(mFrameTimes.begin(), mFrameTimes.end(),
+ [](auto frame) { return frame.presentTime == 0; });
+ if (isMissingPresentTime && !mLastRefreshRate.reported.isValid()) {
+ // If there are no presentation timestamps and we haven't calculated
+ // one in the past then we can't calculate the refresh rate
+ return std::nullopt;
}
// Calculate the average frame time based on presentation timestamps. If those
@@ -160,9 +149,35 @@
// presentation timestamps we look at the queue time to see if the current refresh rate still
// matches the content.
- const auto averageFrameTime =
- static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
- numFrames;
+ auto getFrameTime = isMissingPresentTime ? [](FrameTimeData data) { return data.queueTime; }
+ : [](FrameTimeData data) { return data.presentTime; };
+
+ nsecs_t totalDeltas = 0;
+ int numDeltas = 0;
+ auto prevFrame = mFrameTimes.begin();
+ for (auto it = mFrameTimes.begin() + 1; it != mFrameTimes.end(); ++it) {
+ const auto currDelta = getFrameTime(*it) - getFrameTime(*prevFrame);
+ if (currDelta < kMinPeriodBetweenFrames) {
+ // Skip this frame, but count the delta into the next frame
+ continue;
+ }
+
+ prevFrame = it;
+
+ if (currDelta > kMaxPeriodBetweenFrames) {
+ // Skip this frame and the current delta.
+ continue;
+ }
+
+ totalDeltas += currDelta;
+ numDeltas++;
+ }
+
+ if (numDeltas == 0) {
+ return std::nullopt;
+ }
+
+ const auto averageFrameTime = static_cast<double>(totalDeltas) / static_cast<double>(numDeltas);
return static_cast<nsecs_t>(averageFrameTime);
}
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 427cc9e..e32ba09 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -49,12 +49,13 @@
// Layer is considered frequent if the earliest value in the window of most recent present times
// 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 Fps MIN_FPS_FOR_FREQUENT_LAYER{10.0f};
- static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
- std::chrono::nanoseconds(MIN_FPS_FOR_FREQUENT_LAYER.getPeriodNsecs()) + 1ms;
+ static constexpr size_t kFrequentLayerWindowSize = 3;
+ static constexpr Fps kMinFpsForFrequentLayer{10.0f};
+ static constexpr auto kMaxPeriodForFrequentLayerNs =
+ std::chrono::nanoseconds(kMinFpsForFrequentLayer.getPeriodNsecs()) + 1ms;
friend class LayerHistoryTest;
+ friend class LayerInfoTest;
public:
// Holds information about the layer vote
@@ -121,7 +122,7 @@
private:
// Used to store the layer timestamps
struct FrameTimeData {
- nsecs_t presetTime; // desiredPresentTime, if provided
+ nsecs_t presentTime; // desiredPresentTime, if provided
nsecs_t queueTime; // buffer queue time
bool pendingConfigChange;
};
@@ -196,6 +197,10 @@
// Used for sanitizing the heuristic data. If two frames are less than
// this period apart from each other they'll be considered as duplicates.
static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(120.f).getPeriodNsecs();
+ // Used for sanitizing the heuristic data. If two frames are more than
+ // this period apart from each other, the interval between them won't be
+ // taken into account when calculating average frame rate.
+ static constexpr nsecs_t kMaxPeriodBetweenFrames = kMinFpsForFrequentLayer.getPeriodNsecs();
LayerHistory::LayerVoteType mDefaultVote;
LayerVote mLayerVote;
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 13c7c8b..f2fe3cc 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -49,6 +49,7 @@
"HWComposerTest.cpp",
"OneShotTimerTest.cpp",
"LayerHistoryTest.cpp",
+ "LayerInfoTest.cpp",
"LayerMetadataTest.cpp",
"MessageQueueTest.cpp",
"SurfaceFlinger_CreateDisplayTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 2ee9c64..612ed19 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -43,8 +43,8 @@
class LayerHistoryTest : public testing::Test {
protected:
static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::HISTORY_SIZE;
- static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::MAX_FREQUENT_LAYER_PERIOD_NS;
- static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::FREQUENT_LAYER_WINDOW_SIZE;
+ static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::kMaxPeriodForFrequentLayerNs;
+ static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::kFrequentLayerWindowSize;
static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfo::HISTORY_DURATION;
static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
LayerInfo::RefreshRateHistory::HISTORY_DURATION;
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
new file mode 100644
index 0000000..d5c9b57
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerInfoTest"
+
+#include <gtest/gtest.h>
+
+#include "Fps.h"
+#include "Scheduler/LayerHistory.h"
+#include "Scheduler/LayerInfo.h"
+
+namespace android::scheduler {
+
+class LayerInfoTest : public testing::Test {
+protected:
+ using FrameTimeData = LayerInfo::FrameTimeData;
+
+ void setFrameTimes(const std::deque<FrameTimeData>& frameTimes) {
+ layerInfo.mFrameTimes = frameTimes;
+ }
+
+ void setLastRefreshRate(Fps fps) {
+ layerInfo.mLastRefreshRate.reported = fps;
+ layerInfo.mLastRefreshRate.calculated = fps;
+ }
+
+ auto calculateAverageFrameTime() { return layerInfo.calculateAverageFrameTime(); }
+
+ LayerInfo layerInfo{"TestLayerInfo", LayerHistory::LayerVoteType::Heuristic};
+};
+
+namespace {
+
+TEST_F(LayerInfoTest, prefersPresentTime) {
+ std::deque<FrameTimeData> frameTimes;
+ constexpr auto kExpectedFps = Fps(50.0f);
+ constexpr auto kPeriod = kExpectedFps.getPeriodNsecs();
+ constexpr int kNumFrames = 10;
+ for (int i = 1; i <= kNumFrames; i++) {
+ frameTimes.push_back(FrameTimeData{.presentTime = kPeriod * i,
+ .queueTime = 0,
+ .pendingConfigChange = false});
+ }
+ setFrameTimes(frameTimes);
+ const auto averageFrameTime = calculateAverageFrameTime();
+ ASSERT_TRUE(averageFrameTime.has_value());
+ const auto averageFps = Fps::fromPeriodNsecs(*averageFrameTime);
+ ASSERT_TRUE(kExpectedFps.equalsWithMargin(averageFps))
+ << "Expected " << averageFps << " to be equal to " << kExpectedFps;
+}
+
+TEST_F(LayerInfoTest, fallbacksToQueueTimeIfNoPresentTime) {
+ std::deque<FrameTimeData> frameTimes;
+ constexpr auto kExpectedFps = Fps(50.0f);
+ constexpr auto kPeriod = kExpectedFps.getPeriodNsecs();
+ constexpr int kNumFrames = 10;
+ for (int i = 1; i <= kNumFrames; i++) {
+ frameTimes.push_back(FrameTimeData{.presentTime = 0,
+ .queueTime = kPeriod * i,
+ .pendingConfigChange = false});
+ }
+ setFrameTimes(frameTimes);
+ setLastRefreshRate(Fps(20.0f)); // Set to some valid value
+ const auto averageFrameTime = calculateAverageFrameTime();
+ ASSERT_TRUE(averageFrameTime.has_value());
+ const auto averageFps = Fps::fromPeriodNsecs(*averageFrameTime);
+ ASSERT_TRUE(kExpectedFps.equalsWithMargin(averageFps))
+ << "Expected " << averageFps << " to be equal to " << kExpectedFps;
+}
+
+TEST_F(LayerInfoTest, returnsNulloptIfThereWasConfigChange) {
+ std::deque<FrameTimeData> frameTimesWithoutConfigChange;
+ const auto period = Fps(50.0f).getPeriodNsecs();
+ constexpr int kNumFrames = 10;
+ for (int i = 1; i <= kNumFrames; i++) {
+ frameTimesWithoutConfigChange.push_back(FrameTimeData{.presentTime = period * i,
+ .queueTime = period * i,
+ .pendingConfigChange = false});
+ }
+
+ setFrameTimes(frameTimesWithoutConfigChange);
+ ASSERT_TRUE(calculateAverageFrameTime().has_value());
+
+ {
+ // Config change in the first record
+ auto frameTimes = frameTimesWithoutConfigChange;
+ frameTimes[0].pendingConfigChange = true;
+ setFrameTimes(frameTimes);
+ ASSERT_FALSE(calculateAverageFrameTime().has_value());
+ }
+
+ {
+ // Config change in the last record
+ auto frameTimes = frameTimesWithoutConfigChange;
+ frameTimes[frameTimes.size() - 1].pendingConfigChange = true;
+ setFrameTimes(frameTimes);
+ ASSERT_FALSE(calculateAverageFrameTime().has_value());
+ }
+
+ {
+ // Config change in the middle
+ auto frameTimes = frameTimesWithoutConfigChange;
+ frameTimes[frameTimes.size() / 2].pendingConfigChange = true;
+ setFrameTimes(frameTimes);
+ ASSERT_FALSE(calculateAverageFrameTime().has_value());
+ }
+}
+
+// A frame can be recorded twice with very close presentation or queue times.
+// Make sure that this doesn't influence the calculated average FPS.
+TEST_F(LayerInfoTest, ignoresSmallPeriods) {
+ std::deque<FrameTimeData> frameTimes;
+ constexpr auto kExpectedFps = Fps(50.0f);
+ constexpr auto kExpectedPeriod = kExpectedFps.getPeriodNsecs();
+ constexpr auto kSmallPeriod = Fps(150.0f).getPeriodNsecs();
+ constexpr int kNumIterations = 10;
+ for (int i = 1; i <= kNumIterations; i++) {
+ frameTimes.push_back(FrameTimeData{.presentTime = kExpectedPeriod * i,
+ .queueTime = 0,
+ .pendingConfigChange = false});
+
+ // A duplicate frame
+ frameTimes.push_back(FrameTimeData{.presentTime = kExpectedPeriod * i + kSmallPeriod,
+ .queueTime = 0,
+ .pendingConfigChange = false});
+ }
+ setFrameTimes(frameTimes);
+ const auto averageFrameTime = calculateAverageFrameTime();
+ ASSERT_TRUE(averageFrameTime.has_value());
+ const auto averageFps = Fps::fromPeriodNsecs(*averageFrameTime);
+ ASSERT_TRUE(kExpectedFps.equalsWithMargin(averageFps))
+ << "Expected " << averageFps << " to be equal to " << kExpectedFps;
+}
+
+// There may be a big period of time between two frames. Make sure that
+// this doesn't influence the calculated average FPS.
+TEST_F(LayerInfoTest, ignoresLargePeriods) {
+ std::deque<FrameTimeData> frameTimes;
+ constexpr auto kExpectedFps = Fps(50.0f);
+ constexpr auto kExpectedPeriod = kExpectedFps.getPeriodNsecs();
+ constexpr auto kLargePeriod = Fps(9.0f).getPeriodNsecs();
+
+ auto record = [&](nsecs_t time) {
+ frameTimes.push_back(
+ FrameTimeData{.presentTime = time, .queueTime = 0, .pendingConfigChange = false});
+ };
+
+ auto time = kExpectedPeriod; // Start with non-zero time.
+ record(time);
+ time += kLargePeriod;
+ record(time);
+ constexpr int kNumIterations = 10;
+ for (int i = 1; i <= kNumIterations; i++) {
+ time += kExpectedPeriod;
+ record(time);
+ }
+
+ setFrameTimes(frameTimes);
+ const auto averageFrameTime = calculateAverageFrameTime();
+ ASSERT_TRUE(averageFrameTime.has_value());
+ const auto averageFps = Fps::fromPeriodNsecs(*averageFrameTime);
+ ASSERT_TRUE(kExpectedFps.equalsWithMargin(averageFps))
+ << "Expected " << averageFps << " to be equal to " << kExpectedFps;
+}
+
+} // namespace
+} // namespace android::scheduler