Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ead2fee4 authored by Alec Mouri's avatar Alec Mouri Committed by Android (Google) Code Review
Browse files

Merge "Add additional predictor unit tests" into sc-dev

parents 732ff442 f2b3cde7
Loading
Loading
Loading
Loading
+33 −2
Original line number Diff line number Diff line
@@ -120,6 +120,10 @@ public:
    }
    friend bool operator!=(const Plan& lhs, const Plan& rhs) { return !(lhs == rhs); }

    friend std::ostream& operator<<(std::ostream& os, const Plan& plan) {
        return os << to_string(plan);
    }

private:
    std::vector<hardware::graphics::composer::hal::Composition> mLayerTypes;
};
@@ -158,6 +162,10 @@ public:
        }
    }

    friend std::ostream& operator<<(std::ostream& os, const Type& type) {
        return os << to_string(type);
    }

    Prediction(const std::vector<const LayerState*>& layers, Plan plan)
          : mExampleLayerStack(layers), mPlan(std::move(plan)) {}

@@ -217,11 +225,25 @@ public:
        NonBufferHash hash;
        Plan plan;
        Prediction::Type type;

        friend bool operator==(const PredictedPlan& lhs, const PredictedPlan& rhs) {
            return lhs.hash == rhs.hash && lhs.plan == rhs.plan && lhs.type == rhs.type;
        }
    };

    std::optional<PredictedPlan> getPredictedPlan(const std::vector<const LayerState*>&,
                                                  NonBufferHash) const;
    // Retrieves the predicted plan based on a layer stack alongside its hash.
    //
    // If the exact layer stack has previously been seen by the predictor, then report the plan used
    // for that layer stack.
    //
    // Otherwise, try to match to the best approximate stack to retireve the most likely plan.
    std::optional<PredictedPlan> getPredictedPlan(const std::vector<const LayerState*>& layers,
                                                  NonBufferHash hash) const;

    // Records a comparison between the predicted plan and the resulting plan, alongside the layer
    // stack we used.
    //
    // This method is intended to help with scoring how effective the prediction engine is.
    void recordResult(std::optional<PredictedPlan> predictedPlan, NonBufferHash flattenedHash,
                      const std::vector<const LayerState*>&, bool hasSkippedLayers, Plan result);

@@ -287,4 +309,13 @@ private:
    mutable size_t mMissCount = 0;
};

// Defining PrintTo helps with Google Tests.
inline void PrintTo(Predictor::PredictedPlan plan, ::std::ostream* os) {
    *os << "PredictedPlan {";
    *os << "\n    .hash = " << plan.hash;
    *os << "\n    .plan = " << plan.plan;
    *os << "\n    .type = " << plan.type;
    *os << "\n}";
}

} // namespace android::compositionengine::impl::planner
+203 −11
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@

#include "DisplayHardware/Hal.h"
#undef LOG_TAG
#define LOG_TAG "LayerStateTest"
#define LOG_TAG "PredictorTest"

#include <compositionengine/impl/planner/Predictor.h>
#include <compositionengine/mock/LayerFE.h>
@@ -45,6 +45,16 @@ 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 =
@@ -57,16 +67,6 @@ struct LayerStackTest : public testing::Test {
                ::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) {
@@ -332,5 +332,197 @@ TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) {
    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(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
 No newline at end of file