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/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