| /* |
| * 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 <common/include/common/test/FlagUtils.h> |
| #include "com_android_graphics_surfaceflinger_flags.h" |
| |
| #include <compositionengine/impl/OutputCompositionState.h> |
| #include <compositionengine/impl/planner/CachedSet.h> |
| #include <compositionengine/impl/planner/Flattener.h> |
| #include <compositionengine/impl/planner/LayerState.h> |
| #include <compositionengine/mock/LayerFE.h> |
| #include <compositionengine/mock/OutputLayer.h> |
| #include <gtest/gtest.h> |
| #include <renderengine/ExternalTexture.h> |
| #include <renderengine/LayerSettings.h> |
| #include <renderengine/impl/ExternalTexture.h> |
| #include <renderengine/mock/RenderEngine.h> |
| #include <chrono> |
| |
| namespace android::compositionengine { |
| using namespace std::chrono_literals; |
| using impl::planner::CachedSet; |
| using impl::planner::Flattener; |
| using impl::planner::LayerState; |
| using impl::planner::NonBufferHash; |
| |
| using testing::_; |
| using testing::ByMove; |
| using testing::ByRef; |
| using testing::DoAll; |
| using testing::Invoke; |
| using testing::Return; |
| using testing::ReturnRef; |
| using testing::Sequence; |
| using testing::SetArgPointee; |
| |
| namespace { |
| |
| class TestableFlattener : public Flattener { |
| public: |
| TestableFlattener(renderengine::RenderEngine& renderEngine, const Tunables& tunables) |
| : Flattener(renderEngine, tunables) {} |
| const std::optional<CachedSet>& getNewCachedSetForTesting() const { return mNewCachedSet; } |
| }; |
| |
| class FlattenerTest : public testing::Test { |
| public: |
| FlattenerTest() |
| : FlattenerTest(Flattener::Tunables{ |
| .mActiveLayerTimeout = 100ms, |
| .mRenderScheduling = std::nullopt, |
| .mEnableHolePunch = true, |
| }) {} |
| void SetUp() override; |
| |
| protected: |
| FlattenerTest(const Flattener::Tunables& tunables) |
| : mFlattener(std::make_unique<TestableFlattener>(mRenderEngine, tunables)) {} |
| void initializeOverrideBuffer(const std::vector<const LayerState*>& layers); |
| void initializeFlattener(const std::vector<const LayerState*>& layers); |
| void expectAllLayersFlattened(const std::vector<const LayerState*>& layers); |
| |
| // mRenderEngine is held as a reference in mFlattener, so explicitly destroy mFlattener first. |
| renderengine::mock::RenderEngine mRenderEngine; |
| std::unique_ptr<TestableFlattener> mFlattener; |
| |
| const std::chrono::steady_clock::time_point kStartTime = std::chrono::steady_clock::now(); |
| std::chrono::steady_clock::time_point mTime = kStartTime; |
| |
| struct TestLayer { |
| std::string name; |
| mock::OutputLayer outputLayer; |
| impl::OutputLayerCompositionState outputLayerCompositionState; |
| // LayerFE inherits from RefBase and must be held by an sp<> |
| sp<mock::LayerFE> layerFE; |
| LayerFECompositionState layerFECompositionState; |
| |
| std::unique_ptr<LayerState> layerState; |
| }; |
| |
| static constexpr size_t kNumLayers = 5; |
| std::vector<std::unique_ptr<TestLayer>> mTestLayers; |
| impl::OutputCompositionState mOutputState; |
| }; |
| |
| void FlattenerTest::SetUp() { |
| mFlattener->setDisplaySize({1, 1}); |
| for (size_t i = 0; i < kNumLayers; i++) { |
| auto testLayer = std::make_unique<TestLayer>(); |
| auto pos = static_cast<int32_t>(i); |
| std::stringstream ss; |
| ss << "testLayer" << i; |
| testLayer->name = ss.str(); |
| |
| testLayer->outputLayerCompositionState.displayFrame = Rect(pos, pos, pos + 1, pos + 1); |
| testLayer->outputLayerCompositionState.visibleRegion = |
| Region(Rect(pos + 1, pos + 1, pos + 2, pos + 2)); |
| |
| const auto kUsageFlags = |
| static_cast<uint64_t>(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN | |
| GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE); |
| testLayer->layerFECompositionState.buffer = |
| sp<GraphicBuffer>::make(100u, 100u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, |
| "output"); |
| |
| testLayer->layerFE = sp<mock::LayerFE>::make(); |
| |
| EXPECT_CALL(*testLayer->layerFE, getSequence) |
| .WillRepeatedly(Return(static_cast<int32_t>(i))); |
| EXPECT_CALL(*testLayer->layerFE, getDebugName) |
| .WillRepeatedly(Return(testLayer->name.c_str())); |
| EXPECT_CALL(*testLayer->layerFE, getCompositionState) |
| .WillRepeatedly(Return(&testLayer->layerFECompositionState)); |
| |
| std::optional<LayerFE::LayerSettings> clientComposition; |
| clientComposition.emplace(); |
| |
| EXPECT_CALL(*testLayer->layerFE, prepareClientComposition) |
| .WillRepeatedly(Return(clientComposition)); |
| EXPECT_CALL(testLayer->outputLayer, getLayerFE) |
| .WillRepeatedly(ReturnRef(*testLayer->layerFE)); |
| EXPECT_CALL(testLayer->outputLayer, getState) |
| .WillRepeatedly(ReturnRef(testLayer->outputLayerCompositionState)); |
| EXPECT_CALL(testLayer->outputLayer, editState) |
| .WillRepeatedly(ReturnRef(testLayer->outputLayerCompositionState)); |
| |
| testLayer->layerState = std::make_unique<LayerState>(&testLayer->outputLayer); |
| testLayer->layerState->incrementFramesSinceBufferUpdate(); |
| |
| mTestLayers.emplace_back(std::move(testLayer)); |
| |
| // set up minimium params needed for rendering |
| mOutputState.dataspace = ui::Dataspace::SRGB; |
| mOutputState.framebufferSpace = ProjectionSpace(ui::Size(10, 20), Rect(10, 5)); |
| mOutputState.framebufferSpace.setOrientation(ui::ROTATION_90); |
| } |
| } |
| |
| void FlattenerTest::initializeOverrideBuffer(const std::vector<const LayerState*>& layers) { |
| for (const auto layer : layers) { |
| layer->getOutputLayer()->editState().overrideInfo = {}; |
| } |
| } |
| |
| void FlattenerTest::initializeFlattener(const std::vector<const LayerState*>& layers) { |
| // layer stack is unknown, reset current geomentry |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // same geometry, update the internal layer stack |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| } |
| |
| void FlattenerTest::expectAllLayersFlattened(const std::vector<const LayerState*>& layers) { |
| // layers would be flattened but the buffer would not be overridden |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| for (const auto layer : layers) { |
| EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); |
| } |
| |
| // the new flattened layer is replaced |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| const auto buffer = layers[0]->getOutputLayer()->getState().overrideInfo.buffer; |
| EXPECT_NE(nullptr, buffer); |
| for (const auto layer : layers) { |
| EXPECT_EQ(buffer, layer->getOutputLayer()->getState().overrideInfo.buffer); |
| } |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_NewLayerStack) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| auto& layerState2 = mTestLayers[1]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| initializeFlattener(layers); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_ActiveLayersAreNotFlattened) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| auto& layerState2 = mTestLayers[1]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // layers cannot be flattened yet, since they are still active |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| auto& layerState2 = mTestLayers[1]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| mTestLayers[0]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold / 2; |
| mTestLayers[1]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold; |
| |
| expectAllLayersFlattened(layers); |
| } |
| |
| TEST_F(FlattenerTest, unflattenLayers_onlySourceCropMoved) { |
| SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: |
| cache_when_source_crop_layer_only_moved, |
| true); |
| |
| auto& layerState1 = mTestLayers[0]->layerState; |
| auto& layerState2 = mTestLayers[1]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| mTestLayers[0]->outputLayerCompositionState.sourceCrop = FloatRect{0.f, 0.f, 100.f, 100.f}; |
| mTestLayers[1]->outputLayerCompositionState.sourceCrop = FloatRect{8.f, 16.f, 108.f, 116.f}; |
| |
| // only source crop is moved, so no flatten |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_basicFlatten) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| auto& layerState2 = mTestLayers[1]->layerState; |
| auto& layerState3 = mTestLayers[2]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // make all layers inactive |
| mTime += 200ms; |
| expectAllLayersFlattened(layers); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_FlattenedLayersStayFlattenWhenNoUpdate) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // make all layers inactive |
| mTime += 200ms; |
| expectAllLayersFlattened(layers); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(overrideBuffer2, overrideBuffer3); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_FlattenedLayersSetsProjectionSpace) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideDisplaySpace = |
| layerState1->getOutputLayer()->getState().overrideInfo.displaySpace; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // make all layers inactive |
| mTime += 200ms; |
| expectAllLayersFlattened(layers); |
| |
| EXPECT_EQ(overrideDisplaySpace, mOutputState.framebufferSpace); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_FlattenedLayersSetsDamageRegions) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideDamageRegion = |
| layerState1->getOutputLayer()->getState().overrideInfo.damageRegion; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // make all layers inactive |
| mTime += 200ms; |
| expectAllLayersFlattened(layers); |
| EXPECT_TRUE(overrideDamageRegion.isRect() && |
| overrideDamageRegion.bounds() == Rect::INVALID_RECT); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| EXPECT_TRUE(overrideDamageRegion.isRect() && overrideDamageRegion.bounds() == Rect::EMPTY_RECT); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_FlattenedLayersSetsVisibleRegion) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideVisibleRegion = |
| layerState1->getOutputLayer()->getState().overrideInfo.visibleRegion; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // make all layers inactive |
| mTime += 200ms; |
| expectAllLayersFlattened(layers); |
| Region expectedRegion; |
| expectedRegion.orSelf(Rect(1, 1, 2, 2)); |
| expectedRegion.orSelf(Rect(2, 2, 3, 3)); |
| EXPECT_TRUE(overrideVisibleRegion.hasSameRects(expectedRegion)); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_addLayerToFlattenedCauseReset) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| |
| initializeFlattener(layers); |
| // make all layers inactive |
| mTime += 200ms; |
| |
| initializeOverrideBuffer(layers); |
| expectAllLayersFlattened(layers); |
| |
| // add a new layer to the stack, this will cause all the flatenner to reset |
| layers.push_back(layerState3.get()); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // make all layers inactive |
| mTime += 200ms; |
| expectAllLayersFlattened(layers); |
| |
| // Layer 1 posted a buffer update, layers would be decomposed, and a new drawFrame would be |
| // caleed for Layer2 and Layer3 |
| layerState1->resetFramesSinceBufferUpdate(); |
| |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_NE(nullptr, overrideBuffer2); |
| EXPECT_EQ(overrideBuffer2, overrideBuffer3); |
| |
| layerState1->incrementFramesSinceBufferUpdate(); |
| mTime += 200ms; |
| |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_NE(nullptr, overrideBuffer2); |
| EXPECT_EQ(overrideBuffer2, overrideBuffer3); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(overrideBuffer2, overrideBuffer3); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState4 = mTestLayers[3]->layerState; |
| const auto& overrideBuffer4 = layerState4->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState5 = mTestLayers[4]->layerState; |
| const auto& overrideBuffer5 = layerState5->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), layerState2.get(), layerState3.get(), |
| layerState4.get(), layerState5.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // make all layers inactive |
| mTime += 200ms; |
| expectAllLayersFlattened(layers); |
| |
| // Layer 3 posted a buffer update, layers would be decomposed, and a new drawFrame would be |
| // called for Layer1 and Layer2 |
| layerState3->resetFramesSinceBufferUpdate(); |
| |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| EXPECT_EQ(nullptr, overrideBuffer4); |
| EXPECT_EQ(nullptr, overrideBuffer5); |
| |
| // Layers 1 and 2 will be flattened a new drawFrame would be called for Layer4 and Layer5 |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mOutputState.framebufferSpace.setOrientation(ui::ROTATION_90); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| EXPECT_EQ(nullptr, overrideBuffer4); |
| EXPECT_EQ(nullptr, overrideBuffer5); |
| |
| // Layers 4 and 5 will be flattened |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mOutputState.framebufferSpace.setOrientation(ui::ROTATION_180); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| EXPECT_NE(nullptr, overrideBuffer4); |
| EXPECT_EQ(overrideBuffer4, overrideBuffer5); |
| |
| layerState3->incrementFramesSinceBufferUpdate(); |
| mTime += 200ms; |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| EXPECT_NE(nullptr, overrideBuffer4); |
| EXPECT_EQ(overrideBuffer4, overrideBuffer5); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mOutputState.framebufferSpace.setOrientation(ui::ROTATION_270); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(overrideBuffer2, overrideBuffer3); |
| EXPECT_EQ(overrideBuffer3, overrideBuffer4); |
| EXPECT_EQ(overrideBuffer4, overrideBuffer5); |
| } |
| |
| // Tests for a PIP |
| TEST_F(FlattenerTest, flattenLayers_pipRequiresRoundedCorners) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // 3 has a buffer update, so it will not be merged, but it has no round |
| // corners, so it is not a PIP. |
| mTime += 200ms; |
| layerState3->resetFramesSinceBufferUpdate(); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| |
| // This time we merge the CachedSet in, so we have a new hash, and we should |
| // only have two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_pip) { |
| mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5); |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| mTestLayers[2]->layerFECompositionState.blendMode = hal::BlendMode::NONE; |
| |
| EXPECT_CALL(*mTestLayers[2]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); |
| |
| std::optional<LayerFE::LayerSettings> clientComposition; |
| clientComposition.emplace(); |
| clientComposition->source.buffer.buffer = std::make_shared< |
| renderengine::impl::ExternalTexture>(mTestLayers[2]->layerFECompositionState.buffer, |
| mRenderEngine, |
| renderengine::impl::ExternalTexture::Usage:: |
| READABLE); |
| EXPECT_CALL(*mTestLayers[2]->layerFE, prepareClientComposition(_)) |
| .WillOnce(Return(clientComposition)); |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // 3 has a buffer update, so it will not be merged, and it has round |
| // corners, so it is a PIP. |
| mTime += 200ms; |
| layerState3->resetFramesSinceBufferUpdate(); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| |
| // This time we merge the CachedSet in, so we have a new hash, and we should |
| // only have two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| |
| const auto* peekThroughLayer1 = |
| layerState1->getOutputLayer()->getState().overrideInfo.peekThroughLayer; |
| const auto* peekThroughLayer2 = |
| layerState2->getOutputLayer()->getState().overrideInfo.peekThroughLayer; |
| EXPECT_EQ(&mTestLayers[2]->outputLayer, peekThroughLayer1); |
| EXPECT_EQ(peekThroughLayer1, peekThroughLayer2); |
| } |
| |
| // A test that verifies the hole puch optimization can be done on a single layer. |
| TEST_F(FlattenerTest, flattenLayers_holePunchSingleLayer) { |
| mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5); |
| |
| // An opaque static background |
| auto& layerState0 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer0 = layerState0->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| // a rounded updating layer |
| auto& layerState1 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| mTestLayers[1]->layerFECompositionState.blendMode = hal::BlendMode::NONE; |
| |
| EXPECT_CALL(*mTestLayers[1]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); |
| |
| std::optional<LayerFE::LayerSettings> clientComposition; |
| clientComposition.emplace(); |
| clientComposition->source.buffer.buffer = std::make_shared< |
| renderengine::impl::ExternalTexture>(mTestLayers[1]->layerFECompositionState.buffer, |
| mRenderEngine, |
| renderengine::impl::ExternalTexture::Usage:: |
| READABLE); |
| EXPECT_CALL(*mTestLayers[1]->layerFE, prepareClientComposition(_)) |
| .WillOnce(Return(clientComposition)); |
| |
| const std::vector<const LayerState*> layers = { |
| layerState0.get(), |
| layerState1.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // layer 1 satisfies every condition in CachedSet::requiresHolePunch() |
| mTime += 200ms; |
| layerState1->resetFramesSinceBufferUpdate(); // it is updating |
| |
| initializeOverrideBuffer(layers); |
| // Expect no cache invalidation the first time (there's no cache yet) |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the |
| // exception that there would be a hole punch above it. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer0); |
| |
| // This time we merge the CachedSet in and we should still have only two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer0); // got overridden |
| EXPECT_EQ(nullptr, overrideBuffer1); // did not |
| |
| // expect 0's peek though layer to be 1's output layer |
| const auto* peekThroughLayer0 = |
| layerState0->getOutputLayer()->getState().overrideInfo.peekThroughLayer; |
| const auto* peekThroughLayer1 = |
| layerState1->getOutputLayer()->getState().overrideInfo.peekThroughLayer; |
| EXPECT_EQ(&mTestLayers[1]->outputLayer, peekThroughLayer0); |
| EXPECT_EQ(nullptr, peekThroughLayer1); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_holePunchSingleColorLayer) { |
| mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5); |
| mTestLayers[0]->layerFECompositionState.color = half4(255.f, 0.f, 0.f, 255.f); |
| mTestLayers[0]->layerFECompositionState.buffer = nullptr; |
| |
| // An opaque static background |
| auto& layerState0 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer0 = layerState0->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| // a rounded updating layer |
| auto& layerState1 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| mTestLayers[1]->layerFECompositionState.blendMode = hal::BlendMode::NONE; |
| |
| EXPECT_CALL(*mTestLayers[1]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); |
| |
| std::optional<LayerFE::LayerSettings> clientComposition; |
| clientComposition.emplace(); |
| clientComposition->source.buffer.buffer = std::make_shared< |
| renderengine::impl::ExternalTexture>(mTestLayers[1]->layerFECompositionState.buffer, |
| mRenderEngine, |
| renderengine::impl::ExternalTexture::Usage:: |
| READABLE); |
| EXPECT_CALL(*mTestLayers[1]->layerFE, prepareClientComposition(_)) |
| .WillOnce(Return(clientComposition)); |
| |
| const std::vector<const LayerState*> layers = { |
| layerState0.get(), |
| layerState1.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // layer 1 satisfies every condition in CachedSet::requiresHolePunch() |
| mTime += 200ms; |
| layerState1->resetFramesSinceBufferUpdate(); // it is updating |
| |
| initializeOverrideBuffer(layers); |
| // Expect no cache invalidation the first time (there's no cache yet) |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the |
| // exception that there would be a hole punch above it. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer0); |
| |
| // This time we merge the CachedSet in and we should still have only two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer0); // got overridden |
| EXPECT_EQ(nullptr, overrideBuffer1); // did not |
| |
| // expect 0's peek though layer to be 1's output layer |
| const auto* peekThroughLayer0 = |
| layerState0->getOutputLayer()->getState().overrideInfo.peekThroughLayer; |
| const auto* peekThroughLayer1 = |
| layerState1->getOutputLayer()->getState().overrideInfo.peekThroughLayer; |
| EXPECT_EQ(&mTestLayers[1]->outputLayer, peekThroughLayer0); |
| EXPECT_EQ(nullptr, peekThroughLayer1); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1; |
| layerState2->update(&mTestLayers[1]->outputLayer); |
| |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // Mark the first two layers inactive, which contain the blur behind |
| mTime += 200ms; |
| layerState3->resetFramesSinceBufferUpdate(); |
| |
| // layers would be flattened but the buffer would not be overridden |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| for (const auto layer : layers) { |
| EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); |
| } |
| |
| // the new flattened layer is replaced |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_doesNotFlattenBlurBehindRun) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1; |
| layerState2->update(&mTestLayers[1]->outputLayer); |
| |
| auto& layerState3 = mTestLayers[2]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // Mark the last two layers inactive, which contains the blur layer, but does not contain the |
| // first layer |
| mTime += 200ms; |
| layerState1->resetFramesSinceBufferUpdate(); |
| |
| // layers would be flattened but the buffer would not be overridden |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillRepeatedly(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| for (const auto layer : layers) { |
| EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); |
| } |
| |
| // nothing is flattened because the last two frames cannot be cached due to containing a blur |
| // layer |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| for (const auto layer : layers) { |
| EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); |
| } |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_flattenSkipsLayerWithBlurBehind) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| |
| auto& layerStateWithBlurBehind = mTestLayers[1]->layerState; |
| mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1; |
| layerStateWithBlurBehind->update(&mTestLayers[1]->outputLayer); |
| |
| auto& layerState3 = mTestLayers[2]->layerState; |
| auto& layerState4 = mTestLayers[3]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| const auto& blurOverrideBuffer = |
| layerStateWithBlurBehind->getOutputLayer()->getState().overrideInfo.buffer; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| const auto& overrideBuffer4 = layerState4->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerStateWithBlurBehind.get(), |
| layerState3.get(), |
| layerState4.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // Mark the last three layers inactive, which contains the blur layer, but does not contain the |
| // first layer |
| mTime += 200ms; |
| layerState1->resetFramesSinceBufferUpdate(); |
| |
| // layers would be flattened but the buffer would not be overridden |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| for (const auto layer : layers) { |
| EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); |
| } |
| |
| // the new flattened layer is replaced |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, blurOverrideBuffer); |
| EXPECT_NE(nullptr, overrideBuffer3); |
| EXPECT_EQ(overrideBuffer3, overrideBuffer4); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_whenBlurLayerIsChanging_appliesBlurToInactiveBehindLayers) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| auto& layerState2 = mTestLayers[1]->layerState; |
| |
| auto& layerStateWithBlurBehind = mTestLayers[2]->layerState; |
| mTestLayers[2]->layerFECompositionState.backgroundBlurRadius = 1; |
| layerStateWithBlurBehind->update(&mTestLayers[2]->outputLayer); |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| const auto& blurOverrideBuffer = |
| layerStateWithBlurBehind->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerStateWithBlurBehind.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // Mark the first two layers inactive, but update the blur layer |
| mTime += 200ms; |
| layerStateWithBlurBehind->resetFramesSinceBufferUpdate(); |
| |
| // layers would be flattened but the buffer would not be overridden |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| const auto& cachedSet = mFlattener->getNewCachedSetForTesting(); |
| ASSERT_NE(std::nullopt, cachedSet); |
| EXPECT_EQ(&mTestLayers[2]->outputLayer, cachedSet->getBlurLayer()); |
| |
| for (const auto layer : layers) { |
| EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); |
| } |
| |
| // the new flattened layer is replaced |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer2, overrideBuffer1); |
| EXPECT_EQ(nullptr, blurOverrideBuffer); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_renderCachedSets_doesNotRenderTwice) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // Mark the layers inactive |
| mTime += 200ms; |
| // layers would be flattened but the buffer would not be overridden |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| |
| // Simulate attempting to render prior to merging the new cached set with the layer stack. |
| // Here we should not try to re-render. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We provide the override buffer now that it's rendered |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer2, overrideBuffer1); |
| } |
| |
| const constexpr std::chrono::nanoseconds kCachedSetRenderDuration = 0ms; |
| const constexpr size_t kMaxDeferRenderAttempts = 2; |
| |
| class FlattenerRenderSchedulingTest : public FlattenerTest { |
| public: |
| FlattenerRenderSchedulingTest() |
| : FlattenerTest( |
| Flattener::Tunables{.mActiveLayerTimeout = 100ms, |
| .mRenderScheduling = Flattener::Tunables:: |
| RenderScheduling{.cachedSetRenderDuration = |
| kCachedSetRenderDuration, |
| .maxDeferRenderAttempts = |
| kMaxDeferRenderAttempts}, |
| .mEnableHolePunch = true}) {} |
| }; |
| |
| TEST_F(FlattenerRenderSchedulingTest, flattenLayers_renderCachedSets_defersUpToMaxAttempts) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| auto& layerState2 = mTestLayers[1]->layerState; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| // Mark the layers inactive |
| mTime += 200ms; |
| |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| for (size_t i = 0; i < kMaxDeferRenderAttempts; i++) { |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| mFlattener->renderCachedSets(mOutputState, |
| std::chrono::steady_clock::now() - |
| (kCachedSetRenderDuration + 10ms), |
| true); |
| } |
| |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, |
| std::chrono::steady_clock::now() - |
| (kCachedSetRenderDuration + 10ms), |
| true); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_skipsLayersDisabledFromCaching) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| // The third layer has a CachingHint that prevents caching from running |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| mTestLayers[2]->layerFECompositionState.cachingHint = gui::CachingHint::Disabled; |
| mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| mTime += 200ms; |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| |
| // This time we merge the CachedSet in, so we have a new hash, and we should |
| // only have two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| // The third layer uses a dataspace that will not be flattened due to |
| // possible mismatch with DPU rendering. |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| mTestLayers[2]->outputLayerCompositionState.dataspace = ui::Dataspace::STANDARD_BT601_625; |
| mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| mTime += 200ms; |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| |
| // This time we merge the CachedSet in, so we have a new hash, and we should |
| // only have two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_skipsHDR) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| // The third layer uses a dataspace that will not be flattened due to |
| // possible mismatch with DPU rendering. |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| mTestLayers[2]->outputLayerCompositionState.dataspace = ui::Dataspace::BT2020_ITU_HLG; |
| mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| mTime += 200ms; |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| |
| // This time we merge the CachedSet in, so we have a new hash, and we should |
| // only have two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_skipsHDR2) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| // The third layer uses a dataspace that will not be flattened due to |
| // possible mismatch with DPU rendering. |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| mTestLayers[2]->outputLayerCompositionState.dataspace = ui::Dataspace::BT2020_PQ; |
| mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| mTime += 200ms; |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| |
| // This time we merge the CachedSet in, so we have a new hash, and we should |
| // only have two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| auto& layerState4 = mTestLayers[3]->layerState; |
| const auto& overrideBuffer4 = layerState4->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| // Rewrite the first two layers to just be a solid color. |
| mTestLayers[0]->layerFECompositionState.color = half4(255.f, 0.f, 0.f, 255.f); |
| mTestLayers[0]->layerFECompositionState.buffer = nullptr; |
| mTestLayers[1]->layerFECompositionState.color = half4(0.f, 255.f, 0.f, 255.f); |
| mTestLayers[1]->layerFECompositionState.buffer = nullptr; |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| layerState4.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| mTime += 200ms; |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| EXPECT_EQ(nullptr, overrideBuffer4); |
| |
| // This time we merge the CachedSet in, so we have a new hash, and we should |
| // only have two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(overrideBuffer3, overrideBuffer4); |
| EXPECT_NE(nullptr, overrideBuffer4); |
| } |
| |
| TEST_F(FlattenerTest, flattenLayers_includes_DISPLAY_DECORATION) { |
| auto& layerState1 = mTestLayers[0]->layerState; |
| const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| auto& layerState2 = mTestLayers[1]->layerState; |
| const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; |
| |
| // The third layer uses DISPLAY_DECORATION, which should be cached. |
| auto& layerState3 = mTestLayers[2]->layerState; |
| const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; |
| mTestLayers[2]->layerFECompositionState.compositionType = |
| aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION; |
| mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); |
| |
| const std::vector<const LayerState*> layers = { |
| layerState1.get(), |
| layerState2.get(), |
| layerState3.get(), |
| }; |
| |
| initializeFlattener(layers); |
| |
| mTime += 200ms; |
| initializeOverrideBuffer(layers); |
| EXPECT_EQ(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| |
| // This will render a CachedSet. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) |
| .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| // We've rendered a CachedSet, but we haven't merged it in. |
| EXPECT_EQ(nullptr, overrideBuffer1); |
| EXPECT_EQ(nullptr, overrideBuffer2); |
| EXPECT_EQ(nullptr, overrideBuffer3); |
| |
| // This time we merge the CachedSet in, so we have a new hash, and we should |
| // only have two sets. |
| EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); |
| initializeOverrideBuffer(layers); |
| EXPECT_NE(getNonBufferHash(layers), |
| mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); |
| mFlattener->renderCachedSets(mOutputState, std::nullopt, true); |
| |
| EXPECT_NE(nullptr, overrideBuffer1); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer2); |
| EXPECT_EQ(overrideBuffer1, overrideBuffer3); |
| } |
| |
| } // namespace |
| } // namespace android::compositionengine |