Add tests for LayerStack::ApproximateMatch
Also:
* Fix a bug in approximate match calculation where the number of
differing fields was always zero.
* Add some documentation.
Bug: 180669092
Test: libcompositionengine_test
Change-Id: I89d6d3f3298615e6b964b053d12127143e2dc060
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index d297d74..08147ed 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -106,6 +106,7 @@
"tests/planner/CachedSetTest.cpp",
"tests/planner/FlattenerTest.cpp",
"tests/planner/LayerStateTest.cpp",
+ "tests/planner/PredictorTest.cpp",
"tests/CompositionEngineTest.cpp",
"tests/DisplayColorProfileTest.cpp",
"tests/DisplayTest.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
index 422af77..55d2a60 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
@@ -24,16 +24,28 @@
public:
LayerStack(const std::vector<const LayerState*>& layers) : mLayers(copyLayers(layers)) {}
+ // Describes an approximate match between two layer stacks
struct ApproximateMatch {
bool operator==(const ApproximateMatch& other) const {
return differingIndex == other.differingIndex &&
differingFields == other.differingFields;
}
+ // The index of the single differing layer between the two stacks.
+ // This implies that only one layer is allowed to differ in an approximate match.
size_t differingIndex;
+ // Set of fields that differ for the differing layer in the approximate match.
Flags<LayerStateField> differingFields;
};
+ // Returns an approximate match when comparing this layer stack with the provided list of
+ // layers, for the purposes of scoring how closely the two layer stacks will match composition
+ // strategies.
+ //
+ // If the two layer stacks are identical, then an approximate match is still returned, but the
+ // differing fields will be empty to represent an exact match.
+ //
+ // If the two layer stacks differ by too much, then an empty optional is returned.
std::optional<ApproximateMatch> getApproximateMatch(
const std::vector<const LayerState*>& other) const;
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
index ba5e64d..07920b8 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
@@ -50,8 +50,8 @@
return std::nullopt;
}
- // If layers are not identical, but we already have a prior approximate match,
- // the LayerStacks differ by too much, so return nothing
+ // If layers are not identical, but we already detected a prior approximate match for a
+ // previous layer, the LayerStacks differ by too much, so return nothing
if (approximateMatch) {
return std::nullopt;
}
@@ -72,6 +72,10 @@
}
}
+ if (approximateMatch) {
+ return approximateMatch;
+ }
+
// If we make it through the layer-by-layer comparison without an approximate match,
// it means that all layers were either identical or had client-composited layers in common,
// which don't affect the composition strategy, so return a successful result with
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
new file mode 100644
index 0000000..abd64d4
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#include "DisplayHardware/Hal.h"
+#undef LOG_TAG
+#define LOG_TAG "LayerStateTest"
+
+#include <compositionengine/impl/planner/Predictor.h>
+#include <compositionengine/mock/LayerFE.h>
+#include <compositionengine/mock/OutputLayer.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+namespace android::compositionengine::impl::planner {
+namespace {
+
+const FloatRect sFloatRectOne = FloatRect(100.f, 200.f, 300.f, 400.f);
+const FloatRect sFloatRectTwo = FloatRect(400.f, 300.f, 200.f, 100.f);
+const Rect sRectOne = Rect(1, 2, 3, 4);
+const Rect sRectTwo = Rect(4, 3, 2, 1);
+const constexpr int32_t sZOne = 100;
+const constexpr int32_t sZTwo = 101;
+const constexpr float sAlphaOne = 0.25f;
+const constexpr float sAlphaTwo = 0.5f;
+const Region sRegionOne = Region(sRectOne);
+const Region sRegionTwo = Region(sRectTwo);
+const mat4 sMat4One = mat4::scale(vec4(2.f, 3.f, 1.f, 1.f));
+
+using testing::Return;
+using testing::ReturnRef;
+
+const std::string sDebugName = std::string("Test LayerFE");
+const constexpr int32_t sSequenceId = 12345;
+
+struct LayerStackTest : public testing::Test {
+ LayerStackTest() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+ }
+
+ ~LayerStackTest() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+ }
+
+ void setupMocksForLayer(mock::OutputLayer& layer, mock::LayerFE& layerFE,
+ const OutputLayerCompositionState& outputLayerState,
+ const LayerFECompositionState& layerFEState) {
+ EXPECT_CALL(layer, getLayerFE()).WillRepeatedly(ReturnRef(layerFE));
+ EXPECT_CALL(layer, getState()).WillRepeatedly(ReturnRef(outputLayerState));
+ EXPECT_CALL(layerFE, getSequence()).WillRepeatedly(Return(sSequenceId));
+ EXPECT_CALL(layerFE, getDebugName()).WillRepeatedly(Return(sDebugName.c_str()));
+ EXPECT_CALL(layerFE, getCompositionState()).WillRepeatedly(Return(&layerFEState));
+ }
+};
+
+TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchSizeDifferences) {
+ mock::OutputLayer outputLayerOne;
+ mock::LayerFE layerFEOne;
+ OutputLayerCompositionState outputLayerCompositionStateOne;
+ LayerFECompositionState layerFECompositionStateOne;
+ setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+ layerFECompositionStateOne);
+ LayerState layerStateOne(&outputLayerOne);
+
+ mock::OutputLayer outputLayerTwo;
+ mock::LayerFE layerFETwo;
+ OutputLayerCompositionState outputLayerCompositionStateTwo;
+ LayerFECompositionState layerFECompositionStateTwo;
+ setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+ layerFECompositionStateTwo);
+ LayerState layerStateTwo(&outputLayerTwo);
+
+ mock::OutputLayer outputLayerThree;
+ mock::LayerFE layerFEThree;
+ OutputLayerCompositionState outputLayerCompositionStateThree;
+ LayerFECompositionState layerFECompositionStateThree;
+ setupMocksForLayer(outputLayerThree, layerFEThree, outputLayerCompositionStateThree,
+ layerFECompositionStateThree);
+ LayerState layerStateThree(&outputLayerThree);
+
+ LayerStack stack({&layerStateOne});
+
+ EXPECT_FALSE(stack.getApproximateMatch({}));
+ EXPECT_FALSE(stack.getApproximateMatch({&layerStateOne, &layerStateThree}));
+}
+
+TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchDifferentCompositionTypes) {
+ mock::OutputLayer outputLayerOne;
+ mock::LayerFE layerFEOne;
+ OutputLayerCompositionState outputLayerCompositionStateOne;
+ LayerFECompositionState layerFECompositionStateOne;
+ layerFECompositionStateOne.compositionType = hal::Composition::DEVICE;
+ setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+ layerFECompositionStateOne);
+ LayerState layerStateOne(&outputLayerOne);
+
+ mock::OutputLayer outputLayerTwo;
+ mock::LayerFE layerFETwo;
+ OutputLayerCompositionState outputLayerCompositionStateTwo;
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.compositionType = hal::Composition::SOLID_COLOR;
+ setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+ layerFECompositionStateTwo);
+ LayerState layerStateTwo(&outputLayerTwo);
+
+ LayerStack stack({&layerStateOne});
+
+ EXPECT_FALSE(stack.getApproximateMatch({&layerStateTwo}));
+}
+
+TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) {
+ mock::OutputLayer outputLayerOne;
+ mock::LayerFE layerFEOne;
+ OutputLayerCompositionState outputLayerCompositionStateOne{
+ .sourceCrop = sFloatRectOne,
+ };
+ LayerFECompositionState layerFECompositionStateOne;
+ setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+ layerFECompositionStateOne);
+ LayerState layerStateOne(&outputLayerOne);
+
+ mock::OutputLayer outputLayerTwo;
+ mock::LayerFE layerFETwo;
+ OutputLayerCompositionState outputLayerCompositionStateTwo{
+ .sourceCrop = sFloatRectTwo,
+ };
+ LayerFECompositionState layerFECompositionStateTwo;
+ setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+ layerFECompositionStateTwo);
+ LayerState layerStateTwo(&outputLayerTwo);
+
+ LayerStack stack({&layerStateOne});
+
+ const auto match = stack.getApproximateMatch({&layerStateTwo});
+ EXPECT_TRUE(match);
+ LayerStack::ApproximateMatch expectedMatch;
+ expectedMatch.differingIndex = 0;
+ expectedMatch.differingFields = LayerStateField::SourceCrop;
+ EXPECT_EQ(expectedMatch, *match);
+}
+
+TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInMultiLayerStack) {
+ mock::OutputLayer outputLayerOne;
+ mock::LayerFE layerFEOne;
+ OutputLayerCompositionState outputLayerCompositionStateOne{
+ .sourceCrop = sFloatRectOne,
+ };
+ LayerFECompositionState layerFECompositionStateOne;
+ setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+ layerFECompositionStateOne);
+ LayerState layerStateOne(&outputLayerOne);
+
+ mock::OutputLayer outputLayerTwo;
+ mock::LayerFE layerFETwo;
+ OutputLayerCompositionState outputLayerCompositionStateTwo{
+ .sourceCrop = sFloatRectTwo,
+ };
+ LayerFECompositionState layerFECompositionStateTwo;
+ setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+ layerFECompositionStateTwo);
+ LayerState layerStateTwo(&outputLayerTwo);
+
+ LayerStack stack({&layerStateOne, &layerStateOne});
+
+ const auto match = stack.getApproximateMatch({&layerStateOne, &layerStateTwo});
+ EXPECT_TRUE(match);
+ LayerStack::ApproximateMatch expectedMatch;
+ expectedMatch.differingIndex = 1;
+ expectedMatch.differingFields = LayerStateField::SourceCrop;
+ EXPECT_EQ(expectedMatch, *match);
+}
+
+TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchManyDifferences) {
+ mock::OutputLayer outputLayerOne;
+ mock::LayerFE layerFEOne;
+ OutputLayerCompositionState outputLayerCompositionStateOne{
+ .visibleRegion = sRegionOne,
+ .displayFrame = sRectOne,
+ .sourceCrop = sFloatRectOne,
+ .dataspace = ui::Dataspace::SRGB,
+ .z = sZOne,
+ };
+ LayerFECompositionState layerFECompositionStateOne;
+ layerFECompositionStateOne.alpha = sAlphaOne;
+ layerFECompositionStateOne.colorTransformIsIdentity = true;
+ setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+ layerFECompositionStateOne);
+ LayerState layerStateOne(&outputLayerOne);
+
+ mock::OutputLayer outputLayerTwo;
+ mock::LayerFE layerFETwo;
+ OutputLayerCompositionState outputLayerCompositionStateTwo{
+ .visibleRegion = sRegionTwo,
+ .displayFrame = sRectTwo,
+ .sourceCrop = sFloatRectTwo,
+ .dataspace = ui::Dataspace::DISPLAY_P3,
+ .z = sZTwo,
+ };
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.alpha = sAlphaTwo;
+ layerFECompositionStateTwo.colorTransformIsIdentity = false;
+ layerFECompositionStateTwo.colorTransform = sMat4One;
+ setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+ layerFECompositionStateTwo);
+ LayerState layerStateTwo(&outputLayerTwo);
+
+ LayerStack stack({&layerStateOne});
+
+ EXPECT_FALSE(stack.getApproximateMatch({&layerStateTwo}));
+}
+
+TEST_F(LayerStackTest, getApproximateMatch_exactMatchesSameBuffer) {
+ sp<GraphicBuffer> buffer = new GraphicBuffer();
+ mock::OutputLayer outputLayerOne;
+ mock::LayerFE layerFEOne;
+ OutputLayerCompositionState outputLayerCompositionStateOne;
+ LayerFECompositionState layerFECompositionStateOne;
+ layerFECompositionStateOne.buffer = buffer;
+ setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+ layerFECompositionStateOne);
+ LayerState layerStateOne(&outputLayerOne);
+
+ mock::OutputLayer outputLayerTwo;
+ mock::LayerFE layerFETwo;
+ OutputLayerCompositionState outputLayerCompositionStateTwo;
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.buffer = buffer;
+ setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+ layerFECompositionStateTwo);
+ LayerState layerStateTwo(&outputLayerTwo);
+
+ LayerStack stack({&layerStateOne});
+
+ const auto match = stack.getApproximateMatch({&layerStateTwo});
+ EXPECT_TRUE(match);
+ LayerStack::ApproximateMatch expectedMatch;
+ expectedMatch.differingIndex = 0;
+ expectedMatch.differingFields = LayerStateField::None;
+ EXPECT_EQ(expectedMatch, *match);
+}
+
+TEST_F(LayerStackTest, getApproximateMatch_alwaysMatchesClientComposition) {
+ mock::OutputLayer outputLayerOne;
+ mock::LayerFE layerFEOne;
+ OutputLayerCompositionState outputLayerCompositionStateOne{
+ .visibleRegion = sRegionOne,
+ .forceClientComposition = true,
+ .displayFrame = sRectOne,
+ .sourceCrop = sFloatRectOne,
+ .dataspace = ui::Dataspace::SRGB,
+ .z = sZOne,
+ };
+ LayerFECompositionState layerFECompositionStateOne;
+ layerFECompositionStateOne.buffer = new GraphicBuffer();
+ layerFECompositionStateOne.alpha = sAlphaOne;
+ layerFECompositionStateOne.colorTransformIsIdentity = true;
+ setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+ layerFECompositionStateOne);
+ LayerState layerStateOne(&outputLayerOne);
+
+ mock::OutputLayer outputLayerTwo;
+ mock::LayerFE layerFETwo;
+ OutputLayerCompositionState outputLayerCompositionStateTwo{
+ .visibleRegion = sRegionTwo,
+ .forceClientComposition = true,
+ .displayFrame = sRectTwo,
+ .sourceCrop = sFloatRectTwo,
+ .dataspace = ui::Dataspace::DISPLAY_P3,
+ .z = sZTwo,
+ };
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.buffer = new GraphicBuffer();
+ layerFECompositionStateTwo.alpha = sAlphaTwo;
+ layerFECompositionStateTwo.colorTransformIsIdentity = false;
+ layerFECompositionStateTwo.colorTransform = sMat4One;
+ setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+ layerFECompositionStateTwo);
+ LayerState layerStateTwo(&outputLayerTwo);
+
+ LayerStack stack({&layerStateOne});
+
+ const auto match = stack.getApproximateMatch({&layerStateTwo});
+ EXPECT_TRUE(match);
+ LayerStack::ApproximateMatch expectedMatch;
+ expectedMatch.differingIndex = 0;
+ expectedMatch.differingFields = LayerStateField::None;
+ EXPECT_EQ(expectedMatch, *match);
+}
+
+TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) {
+ mock::OutputLayer outputLayerOne;
+ mock::LayerFE layerFEOne;
+ OutputLayerCompositionState outputLayerCompositionStateOne{
+ .sourceCrop = sFloatRectOne,
+ };
+ LayerFECompositionState layerFECompositionStateOne;
+ layerFECompositionStateOne.buffer = new GraphicBuffer();
+ setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+ layerFECompositionStateOne);
+ LayerState layerStateOne(&outputLayerOne);
+
+ mock::OutputLayer outputLayerTwo;
+ mock::LayerFE layerFETwo;
+ OutputLayerCompositionState outputLayerCompositionStateTwo{
+ .sourceCrop = sFloatRectTwo,
+ };
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.buffer = new GraphicBuffer();
+ setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+ layerFECompositionStateTwo);
+ LayerState layerStateTwo(&outputLayerTwo);
+
+ EXPECT_TRUE(LayerStack({&layerStateOne}).getApproximateMatch({&layerStateTwo}));
+
+ LayerStack stack({&layerStateOne, &layerStateOne});
+ EXPECT_FALSE(stack.getApproximateMatch({&layerStateTwo, &layerStateTwo}));
+}
+
+} // namespace
+} // namespace android::compositionengine::impl::planner
\ No newline at end of file