blob: 1492707ad2c6c02a206763df3b09ce6d1c1352ed [file] [log] [blame]
/*
* 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 "PredictorTest"
#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 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;
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));
}
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());
}
};
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,
};
LayerFECompositionState layerFECompositionStateOne;
layerFECompositionStateOne.alpha = sAlphaOne;
layerFECompositionStateOne.colorTransformIsIdentity = true;
layerFECompositionStateOne.blendMode = hal::BlendMode::NONE;
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,
};
LayerFECompositionState layerFECompositionStateTwo;
layerFECompositionStateTwo.alpha = sAlphaTwo;
layerFECompositionStateTwo.colorTransformIsIdentity = false;
layerFECompositionStateTwo.colorTransform = sMat4One;
layerFECompositionStateTwo.blendMode = hal::BlendMode::PREMULTIPLIED;
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,
};
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,
};
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}));
}
struct PredictionTest : public testing::Test {
PredictionTest() {
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());
}
~PredictionTest() {
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());
}
};
TEST_F(LayerStackTest, reorderingChangesNonBufferHash) {
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);
NonBufferHash hash = getNonBufferHash({&layerStateOne, &layerStateTwo});
NonBufferHash hashReverse = getNonBufferHash({&layerStateTwo, &layerStateOne});
EXPECT_NE(hash, hashReverse);
}
TEST_F(PredictionTest, constructPrediction) {
Plan plan;
plan.addLayerType(hal::Composition::DEVICE);
Prediction prediction({}, plan);
EXPECT_EQ(plan, prediction.getPlan());
// check that dump doesn't crash
std::string result;
prediction.dump(result);
}
TEST_F(PredictionTest, recordHits) {
Prediction prediction({}, {});
const constexpr uint32_t kExactMatches = 2;
for (uint32_t i = 0; i < kExactMatches; i++) {
prediction.recordHit(Prediction::Type::Exact);
}
const constexpr uint32_t kApproximateMatches = 3;
for (uint32_t i = 0; i < kApproximateMatches; i++) {
prediction.recordHit(Prediction::Type::Approximate);
}
EXPECT_EQ(kExactMatches, prediction.getHitCount(Prediction::Type::Exact));
EXPECT_EQ(kApproximateMatches, prediction.getHitCount(Prediction::Type::Approximate));
EXPECT_EQ(kExactMatches + kApproximateMatches, prediction.getHitCount(Prediction::Type::Total));
}
TEST_F(PredictionTest, recordMisses) {
Prediction prediction({}, {});
const constexpr uint32_t kExactMatches = 2;
for (uint32_t i = 0; i < kExactMatches; i++) {
prediction.recordMiss(Prediction::Type::Exact);
}
const constexpr uint32_t kApproximateMatches = 3;
for (uint32_t i = 0; i < kApproximateMatches; i++) {
prediction.recordMiss(Prediction::Type::Approximate);
}
EXPECT_EQ(kExactMatches, prediction.getMissCount(Prediction::Type::Exact));
EXPECT_EQ(kApproximateMatches, prediction.getMissCount(Prediction::Type::Approximate));
EXPECT_EQ(kExactMatches + kApproximateMatches,
prediction.getMissCount(Prediction::Type::Total));
}
struct PredictorTest : public testing::Test {
PredictorTest() {
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());
}
~PredictorTest() {
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());
}
};
TEST_F(PredictorTest, getPredictedPlan_emptyLayersWithoutExactMatch_returnsNullopt) {
Predictor predictor;
EXPECT_FALSE(predictor.getPredictedPlan({}, 0));
}
TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveExactMatch) {
mock::OutputLayer outputLayerOne;
mock::LayerFE layerFEOne;
OutputLayerCompositionState outputLayerCompositionStateOne;
LayerFECompositionState layerFECompositionStateOne;
layerFECompositionStateOne.compositionType = hal::Composition::DEVICE;
setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
layerFECompositionStateOne);
LayerState layerStateOne(&outputLayerOne);
Plan plan;
plan.addLayerType(hal::Composition::DEVICE);
Predictor predictor;
NonBufferHash hash = getNonBufferHash({&layerStateOne});
predictor.recordResult(std::nullopt, hash, {&layerStateOne}, false, plan);
auto predictedPlan = predictor.getPredictedPlan({}, hash);
EXPECT_TRUE(predictedPlan);
Predictor::PredictedPlan expectedPlan{hash, plan, Prediction::Type::Exact};
EXPECT_EQ(expectedPlan, predictedPlan);
}
TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatch) {
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);
Plan plan;
plan.addLayerType(hal::Composition::DEVICE);
Predictor predictor;
NonBufferHash hashOne = getNonBufferHash({&layerStateOne});
NonBufferHash hashTwo = getNonBufferHash({&layerStateTwo});
predictor.recordResult(std::nullopt, hashOne, {&layerStateOne}, false, plan);
auto predictedPlan = predictor.getPredictedPlan({&layerStateTwo}, hashTwo);
EXPECT_TRUE(predictedPlan);
Predictor::PredictedPlan expectedPlan{hashOne, plan, Prediction::Type::Approximate};
EXPECT_EQ(expectedPlan, predictedPlan);
}
TEST_F(PredictorTest, recordMissedPlan_skipsApproximateMatch) {
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);
Plan plan;
plan.addLayerType(hal::Composition::DEVICE);
Predictor predictor;
NonBufferHash hashOne = getNonBufferHash({&layerStateOne});
NonBufferHash hashTwo = getNonBufferHash({&layerStateTwo});
predictor.recordResult(std::nullopt, hashOne, {&layerStateOne}, false, plan);
auto predictedPlan = predictor.getPredictedPlan({&layerStateTwo}, hashTwo);
ASSERT_TRUE(predictedPlan);
EXPECT_EQ(Prediction::Type::Approximate, predictedPlan->type);
Plan planTwo;
planTwo.addLayerType(hal::Composition::CLIENT);
predictor.recordResult(predictedPlan, hashTwo, {&layerStateTwo}, false, planTwo);
// Now trying to retrieve the predicted plan again returns a nullopt instead.
// TODO(b/158790260): Even though this is enforced in this test, we might want to reassess this.
// One of the implications around this implementation is that if we miss a prediction then we
// can never actually correct our mistake if we see the same layer stack again, which doesn't
// seem robust.
auto predictedPlanTwo = predictor.getPredictedPlan({&layerStateTwo}, hashTwo);
EXPECT_FALSE(predictedPlanTwo);
}
} // namespace
} // namespace android::compositionengine::impl::planner