/* * 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. */ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" #include "DisplayTransactionTestHelpers.h" #include "mock/DisplayHardware/MockDisplayMode.h" #include namespace android { namespace { using android::hardware::graphics::composer::V2_4::Error; using android::hardware::graphics::composer::V2_4::VsyncPeriodChangeTimeline; class DisplayModeSwitchingTest : public DisplayTransactionTest { public: void SetUp() override { injectFakeBufferQueueFactory(); injectFakeNativeWindowSurfaceFactory(); PrimaryDisplayVariant::setupHwcHotplugCallExpectations(this); PrimaryDisplayVariant::setupFramebufferConsumerBufferQueueCallExpectations(this); PrimaryDisplayVariant::setupFramebufferProducerBufferQueueCallExpectations(this); PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this); PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this); mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED); mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) .setDisplayModes(kModes, kModeId60) .inject(); setupScheduler(mDisplay->holdRefreshRateConfigs()); // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy // will call setActiveConfig instead of setActiveConfigWithConstraints. ON_CALL(*mComposer, isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching)) .WillByDefault(Return(true)); } protected: void setupScheduler(std::shared_ptr); sp mDisplay; mock::EventThread* mAppEventThread; static constexpr DisplayModeId kModeId60{0}; static constexpr DisplayModeId kModeId90{1}; static constexpr DisplayModeId kModeId120{2}; static constexpr DisplayModeId kModeId90_4K{3}; static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz, 0); static inline const DisplayModePtr kMode90 = createDisplayMode(kModeId90, 90_Hz, 1); static inline const DisplayModePtr kMode120 = createDisplayMode(kModeId120, 120_Hz, 2); static constexpr ui::Size kResolution4K{3840, 2160}; static inline const DisplayModePtr kMode90_4K = createDisplayMode(kModeId90_4K, 90_Hz, 3, kResolution4K); static inline const DisplayModes kModes = makeModes(kMode60, kMode90, kMode120, kMode90_4K); }; void DisplayModeSwitchingTest::setupScheduler( std::shared_ptr configs) { auto eventThread = std::make_unique(); mAppEventThread = eventThread.get(); auto sfEventThread = std::make_unique(); EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); EXPECT_CALL(*eventThread, createEventConnection(_, _)) .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0, ResyncCallback()))); EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0, ResyncCallback()))); auto vsyncController = std::make_unique(); auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) .WillRepeatedly( Return(TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread), std::move(sfEventThread), TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, std::move(configs)); } TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRequired) { ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); mFlinger.onActiveDisplayChanged(mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(), false, 0.f, 120.f, 0.f, 120.f); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; EXPECT_CALL(*mComposer, setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, hal::HWConfigId(kModeId90.value()), _, _)) .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); mFlinger.commit(); Mock::VerifyAndClearExpectations(mComposer); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); // Verify that the next commit will complete the mode change and send // a onModeChanged event to the framework. EXPECT_CALL(*mAppEventThread, onModeChanged(kMode90)); mFlinger.commit(); Mock::VerifyAndClearExpectations(mAppEventThread); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90); } TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefreshRequired) { ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); mFlinger.onActiveDisplayChanged(mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(), true, 0.f, 120.f, 0.f, 120.f); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. const VsyncPeriodChangeTimeline timeline{.refreshRequired = false}; EXPECT_CALL(*mComposer, setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, hal::HWConfigId(kModeId90.value()), _, _)) .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); EXPECT_CALL(*mAppEventThread, onModeChanged(kMode90)); mFlinger.commit(); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90); } TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { // Test that if we call setDesiredDisplayModeSpecs while a previous mode change // is still being processed the later call will be respected. ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); mFlinger.onActiveDisplayChanged(mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(), false, 0.f, 120.f, 0.f, 120.f); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; EXPECT_CALL(*mComposer, setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, hal::HWConfigId(kModeId90.value()), _, _)) .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); mFlinger.commit(); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId120.value(), false, 0.f, 180.f, 0.f, 180.f); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120); EXPECT_CALL(*mComposer, setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, hal::HWConfigId(kModeId120.value()), _, _)) .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); mFlinger.commit(); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120); mFlinger.commit(); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId120); } TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefreshRequired) { ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); mFlinger.onActiveDisplayChanged(mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90_4K.value(), false, 0.f, 120.f, 0.f, 120.f); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90_4K); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. const VsyncPeriodChangeTimeline timeline{.refreshRequired = false}; EXPECT_CALL(*mComposer, setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, hal::HWConfigId(kModeId90_4K.value()), _, _)) .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplay->getPhysicalId(), true)); // Misc expecations. We don't need to enforce these method calls, but since the helper methods // already set expectations we should add new ones here, otherwise the test will fail. EXPECT_CALL(*mConsumer, setDefaultBufferSize(static_cast(kResolution4K.getWidth()), static_cast(kResolution4K.getHeight()))) .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*mConsumer, consumerConnect(_, false)).WillOnce(Return(NO_ERROR)); EXPECT_CALL(*mComposer, setClientTargetSlotCount(_)).WillOnce(Return(hal::Error::NONE)); // Create a new native surface to be used by the recreated display. mNativeWindowSurface = nullptr; injectFakeNativeWindowSurfaceFactory(); PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this); const auto displayToken = mDisplay->getDisplayToken().promote(); mFlinger.commit(); // The DisplayDevice will be destroyed and recreated, // so we need to update with the new instance. mDisplay = mFlinger.getDisplay(displayToken); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90_4K); } TEST_F(DisplayModeSwitchingTest, multiDisplay) { constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID; constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1; constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u); constexpr bool kIsPrimary = false; TestableSurfaceFlinger::FakeHwcDisplayInjector(kOuterDisplayId, hal::DisplayType::PHYSICAL, kIsPrimary) .setHwcDisplayId(kOuterDisplayHwcId) .inject(&mFlinger, mComposer); const auto outerDisplay = mFakeDisplayInjector.injectInternalDisplay( [&](FakeDisplayDeviceInjector& injector) { injector.setDisplayModes(mock::cloneForDisplay(kOuterDisplayId, kModes), kModeId120); }, {.displayId = kOuterDisplayId, .hwcDisplayId = kOuterDisplayHwcId, .isPrimary = kIsPrimary}); const auto& innerDisplay = mDisplay; EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); EXPECT_EQ(innerDisplay->getActiveMode()->getId(), kModeId60); EXPECT_EQ(outerDisplay->getActiveMode()->getId(), kModeId120); mFlinger.onActiveDisplayChanged(innerDisplay); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), kModeId90.value(), false, 0.f, 120.f, 0.f, 120.f)); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(), kModeId60.value(), false, 0.f, 120.f, 0.f, 120.f)); // Transition on the inner display. ASSERT_TRUE(innerDisplay->getDesiredActiveMode()); EXPECT_EQ(innerDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); // No transition on the outer display. EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; EXPECT_CALL(*mComposer, setActiveConfigWithConstraints(kInnerDisplayHwcId, hal::HWConfigId(kModeId90.value()), _, _)) .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); mFlinger.commit(); // Transition on the inner display. ASSERT_TRUE(innerDisplay->getDesiredActiveMode()); EXPECT_EQ(innerDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); // No transition on the outer display. EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); mFlinger.commit(); // Transition on the inner display. EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); EXPECT_EQ(innerDisplay->getActiveMode()->getId(), kModeId90); // No transition on the outer display. EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); EXPECT_EQ(outerDisplay->getActiveMode()->getId(), kModeId120); mFlinger.onActiveDisplayChanged(outerDisplay); // No transition on the inner display. EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); // Transition on the outer display. ASSERT_TRUE(outerDisplay->getDesiredActiveMode()); EXPECT_EQ(outerDisplay->getDesiredActiveMode()->mode->getId(), kModeId60); EXPECT_CALL(*mComposer, setActiveConfigWithConstraints(kOuterDisplayHwcId, hal::HWConfigId(kModeId60.value()), _, _)) .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); mFlinger.commit(); // No transition on the inner display. EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); // Transition on the outer display. ASSERT_TRUE(outerDisplay->getDesiredActiveMode()); EXPECT_EQ(outerDisplay->getDesiredActiveMode()->mode->getId(), kModeId60); mFlinger.commit(); // No transition on the inner display. EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); EXPECT_EQ(innerDisplay->getActiveMode()->getId(), kModeId90); // Transition on the outer display. EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); EXPECT_EQ(outerDisplay->getActiveMode()->getId(), kModeId60); } } // namespace } // namespace android