/* * Copyright 2019 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 #include #include #include #include #include #include "Scheduler/EventThread.h" #include "Scheduler/RefreshRateSelector.h" #include "Scheduler/VSyncPredictor.h" #include "Scheduler/VSyncReactor.h" #include "TestableScheduler.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockDisplayMode.h" #include "mock/MockEventThread.h" #include "mock/MockLayer.h" #include "mock/MockSchedulerCallback.h" #include #include #include #include "FpsOps.h" using namespace com::android::graphics::surfaceflinger; namespace android::scheduler { using android::mock::createDisplayMode; using android::mock::createVrrDisplayMode; using testing::_; using testing::Return; namespace { using MockEventThread = android::mock::EventThread; using MockLayer = android::mock::MockLayer; using LayerHierarchy = surfaceflinger::frontend::LayerHierarchy; using LayerHierarchyBuilder = surfaceflinger::frontend::LayerHierarchyBuilder; using RequestedLayerState = surfaceflinger::frontend::RequestedLayerState; class ZeroClock : public Clock { public: nsecs_t now() const override { return 0; } }; class SchedulerTest : public testing::Test { protected: class MockEventThreadConnection : public android::EventThreadConnection { public: explicit MockEventThreadConnection(EventThread* eventThread) : EventThreadConnection(eventThread, /*callingUid*/ static_cast(0)) {} ~MockEventThreadConnection() = default; MOCK_METHOD1(stealReceiveChannel, binder::Status(gui::BitTube* outChannel)); MOCK_METHOD1(setVsyncRate, binder::Status(int count)); MOCK_METHOD0(requestNextVsync, binder::Status()); }; SchedulerTest(); static constexpr RefreshRateSelector::LayerRequirement kLayer = {.weight = 1.f}; static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u); static inline const ftl::NonNull kDisplay1Mode60 = ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz)); static inline const ftl::NonNull kDisplay1Mode120 = ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz)); static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120); static inline FrameRateMode kDisplay1Mode60_60{60_Hz, kDisplay1Mode60}; static inline FrameRateMode kDisplay1Mode120_120{120_Hz, kDisplay1Mode120}; static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u); static inline const ftl::NonNull kDisplay2Mode60 = ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz)); static inline const ftl::NonNull kDisplay2Mode120 = ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(1), 120_Hz)); static inline const DisplayModes kDisplay2Modes = makeModes(kDisplay2Mode60, kDisplay2Mode120); static constexpr PhysicalDisplayId kDisplayId3 = PhysicalDisplayId::fromPort(253u); static inline const ftl::NonNull kDisplay3Mode60 = ftl::as_non_null(createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz)); static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60); std::shared_ptr mSelector = std::make_shared(makeModes(kDisplay1Mode60), kDisplay1Mode60->getId()); mock::SchedulerCallback mSchedulerCallback; TestableSurfaceFlinger mFlinger; TestableScheduler* mScheduler = new TestableScheduler{mSelector, mFlinger, mSchedulerCallback}; surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder; MockEventThread* mEventThread; sp mEventThreadConnection; }; SchedulerTest::SchedulerTest() { auto eventThread = std::make_unique(); mEventThread = eventThread.get(); EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0)); mEventThreadConnection = sp::make(mEventThread); // createConnection call to scheduler makes a createEventConnection call to EventThread. Make // sure that call gets executed and returns an EventThread::Connection object. EXPECT_CALL(*mEventThread, createEventConnection(_)) .WillRepeatedly(Return(mEventThreadConnection)); mScheduler->setEventThread(Cycle::Render, std::move(eventThread)); mScheduler->setEventThread(Cycle::LastComposite, std::make_unique()); mFlinger.resetScheduler(mScheduler); } } // namespace TEST_F(SchedulerTest, registerDisplay) FTL_FAKE_GUARD(kMainThreadContext) { // Hardware VSYNC should not change if the display is already registered. EXPECT_CALL(mSchedulerCallback, requestHardwareVsync(kDisplayId1, false)).Times(0); mScheduler->registerDisplay(kDisplayId1, std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId())); // TODO(b/241285191): Restore once VsyncSchedule::getPendingHardwareVsyncState is called by // Scheduler::setDisplayPowerMode rather than SF::setPhysicalDisplayPowerMode. #if 0 // Hardware VSYNC should be disabled for newly registered displays. EXPECT_CALL(mSchedulerCallback, requestHardwareVsync(kDisplayId2, false)).Times(1); EXPECT_CALL(mSchedulerCallback, requestHardwareVsync(kDisplayId3, false)).Times(1); #endif mScheduler->registerDisplay(kDisplayId2, std::make_shared(kDisplay2Modes, kDisplay2Mode60->getId())); mScheduler->registerDisplay(kDisplayId3, std::make_shared(kDisplay3Modes, kDisplay3Mode60->getId())); EXPECT_FALSE(mScheduler->getVsyncSchedule(kDisplayId1)->getPendingHardwareVsyncState()); EXPECT_FALSE(mScheduler->getVsyncSchedule(kDisplayId2)->getPendingHardwareVsyncState()); EXPECT_FALSE(mScheduler->getVsyncSchedule(kDisplayId3)->getPendingHardwareVsyncState()); } TEST_F(SchedulerTest, chooseRefreshRateForContentIsNoopWhenModeSwitchingIsNotSupported) { // The layer is registered at creation time and deregistered at destruction time. sp layer = sp::make(mFlinger.flinger()); // recordLayerHistory should be a noop ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); scheduler::LayerProps layerProps = { .visible = true, .bounds = {0, 0, 100, 100}, .transform = {}, .setFrameRateVote = {}, .frameRateSelectionPriority = Layer::PRIORITY_UNSET, .isSmallDirty = false, .isFrontBuffered = false, }; mScheduler->recordLayerHistory(layer->getSequence(), layerProps, 0, 0, LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON; FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn)); constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); EXPECT_CALL(mSchedulerCallback, requestDisplayModes(_)).Times(0); mScheduler->chooseRefreshRateForContent(/*LayerHierarchy*/ nullptr, /*updateAttachedChoreographer*/ false); } TEST_F(SchedulerTest, updateDisplayModes) { ASSERT_EQ(0u, mScheduler->layerHistorySize()); sp layer = sp::make(mFlinger.flinger()); ASSERT_EQ(1u, mScheduler->layerHistorySize()); // Replace `mSelector` with a new `RefreshRateSelector` that has different display modes. mScheduler->registerDisplay(kDisplayId1, std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId())); ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); scheduler::LayerProps layerProps = { .visible = true, .bounds = {0, 0, 100, 100}, .transform = {}, .setFrameRateVote = {}, .frameRateSelectionPriority = Layer::PRIORITY_UNSET, .isSmallDirty = false, .isFrontBuffered = false, }; mScheduler->recordLayerHistory(layer->getSequence(), layerProps, 0, 0, LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1u, mScheduler->getNumActiveLayers()); } TEST_F(SchedulerTest, emitModeChangeEvent) { const auto selectorPtr = std::make_shared(kDisplay1Modes, kDisplay1Mode120->getId()); mScheduler->registerDisplay(kDisplayId1, selectorPtr); mScheduler->onDisplayModeChanged(kDisplayId1, kDisplay1Mode120_120, true); mScheduler->setContentRequirements({kLayer}); // No event is emitted in response to idle. EXPECT_CALL(*mEventThread, onModeChanged(_)).Times(0); using TimerState = TestableScheduler::TimerState; mScheduler->idleTimerCallback(TimerState::Expired); selectorPtr->setActiveMode(kDisplay1Mode60->getId(), 60_Hz); auto layer = kLayer; layer.vote = RefreshRateSelector::LayerVoteType::ExplicitExact; layer.desiredRefreshRate = 60_Hz; mScheduler->setContentRequirements({layer}); // An event is emitted implicitly despite choosing the same mode as when idle. EXPECT_CALL(*mEventThread, onModeChanged(kDisplay1Mode60_60)).Times(1); mScheduler->idleTimerCallback(TimerState::Reset); mScheduler->setContentRequirements({kLayer}); // An event is emitted explicitly for the mode change. EXPECT_CALL(*mEventThread, onModeChanged(kDisplay1Mode120_120)).Times(1); mScheduler->touchTimerCallback(TimerState::Reset); mScheduler->onDisplayModeChanged(kDisplayId1, kDisplay1Mode120_120, true); } TEST_F(SchedulerTest, calculateMaxAcquiredBufferCount) { struct TestCase { Fps refreshRate; std::chrono::nanoseconds presentLatency; int expectedBufferCount; }; const auto verifyTestCases = [&](std::vector tests) { for (const auto testCase : tests) { EXPECT_EQ(testCase.expectedBufferCount, mFlinger.calculateMaxAcquiredBufferCount(testCase.refreshRate, testCase.presentLatency)); } }; std::vector testCases{{60_Hz, 30ms, 1}, {90_Hz, 30ms, 2}, {120_Hz, 30ms, 3}, {60_Hz, 40ms, 2}, {60_Hz, 10ms, 1}}; verifyTestCases(testCases); const auto savedMinAcquiredBuffers = mFlinger.mutableMinAcquiredBuffers(); mFlinger.mutableMinAcquiredBuffers() = 2; verifyTestCases({{60_Hz, 10ms, 2}}); mFlinger.mutableMinAcquiredBuffers() = savedMinAcquiredBuffers; const auto savedMaxAcquiredBuffers = mFlinger.mutableMaxAcquiredBuffers(); mFlinger.mutableMaxAcquiredBuffers() = 2; testCases = {{60_Hz, 30ms, 1}, {90_Hz, 30ms, 2}, {120_Hz, 30ms, 2}, // max buffers allowed is 2 {60_Hz, 40ms, 2}, {60_Hz, 10ms, 1}}; verifyTestCases(testCases); mFlinger.mutableMaxAcquiredBuffers() = 3; // max buffers allowed is 3 verifyTestCases({{120_Hz, 30ms, 3}}); mFlinger.mutableMaxAcquiredBuffers() = savedMaxAcquiredBuffers; } MATCHER(Is120Hz, "") { return isApproxEqual(arg.front().mode.fps, 120_Hz); } TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { mScheduler->registerDisplay(kDisplayId1, std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId())); const sp layer = sp::make(mFlinger.flinger()); scheduler::LayerProps layerProps = { .visible = true, .bounds = {0, 0, 0, 0}, .transform = {}, .setFrameRateVote = {}, .frameRateSelectionPriority = Layer::PRIORITY_UNSET, .isSmallDirty = false, .isFrontBuffered = false, }; mScheduler->recordLayerHistory(layer->getSequence(), layerProps, 0, systemTime(), LayerHistory::LayerUpdateType::Buffer); constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON; FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn)); constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); EXPECT_CALL(mSchedulerCallback, requestDisplayModes(Is120Hz())).Times(1); mScheduler->chooseRefreshRateForContent(/*LayerHierarchy*/ nullptr, /*updateAttachedChoreographer*/ false); // No-op if layer requirements have not changed. EXPECT_CALL(mSchedulerCallback, requestDisplayModes(_)).Times(0); mScheduler->chooseRefreshRateForContent(/*LayerHierarchy*/ nullptr, /*updateAttachedChoreographer*/ false); } TEST_F(SchedulerTest, chooseDisplayModes) { mScheduler->registerDisplay(kDisplayId1, std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId())); mScheduler->setContentRequirements({kLayer, kLayer}); GlobalSignals globalSignals = {.idle = true}; mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); using DisplayModeChoice = TestableScheduler::DisplayModeChoice; auto modeChoices = mScheduler->chooseDisplayModes(); ASSERT_EQ(1u, modeChoices.size()); auto choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); EXPECT_EQ(choice->get(), DisplayModeChoice({60_Hz, kDisplay1Mode60}, globalSignals)); globalSignals = {.idle = false}; mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); modeChoices = mScheduler->chooseDisplayModes(); ASSERT_EQ(1u, modeChoices.size()); choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals)); globalSignals = {.touch = true}; mScheduler->replaceTouchTimer(10); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); modeChoices = mScheduler->chooseDisplayModes(); ASSERT_EQ(1u, modeChoices.size()); choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals)); } TEST_F(SchedulerTest, chooseDisplayModesHighHintTouchSignal) { mScheduler->registerDisplay(kDisplayId1, std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId())); using DisplayModeChoice = TestableScheduler::DisplayModeChoice; std::vector layers = {kLayer, kLayer}; auto& lr1 = layers[0]; auto& lr2 = layers[1]; // Scenario that is similar to game. Expects no touch boost. lr1.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory; lr1.frameRateCategory = FrameRateCategory::HighHint; lr1.name = "ExplicitCategory HighHint"; lr2.vote = RefreshRateSelector::LayerVoteType::ExplicitDefault; lr2.desiredRefreshRate = 30_Hz; lr2.name = "30Hz ExplicitDefault"; mScheduler->setContentRequirements(layers); auto modeChoices = mScheduler->chooseDisplayModes(); ASSERT_EQ(1u, modeChoices.size()); auto choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); EXPECT_EQ(choice->get(), DisplayModeChoice({60_Hz, kDisplay1Mode60}, {.touch = false})); // Scenario that is similar to video playback and interaction. Expects touch boost. lr1.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory; lr1.frameRateCategory = FrameRateCategory::HighHint; lr1.name = "ExplicitCategory HighHint"; lr2.vote = RefreshRateSelector::LayerVoteType::ExplicitExactOrMultiple; lr2.desiredRefreshRate = 30_Hz; lr2.name = "30Hz ExplicitExactOrMultiple"; mScheduler->setContentRequirements(layers); modeChoices = mScheduler->chooseDisplayModes(); ASSERT_EQ(1u, modeChoices.size()); choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, {.touch = true})); // Scenario with explicit category and HighHint. Expects touch boost. lr1.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory; lr1.frameRateCategory = FrameRateCategory::HighHint; lr1.name = "ExplicitCategory HighHint"; lr2.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory; lr2.frameRateCategory = FrameRateCategory::Low; lr2.name = "ExplicitCategory Low"; mScheduler->setContentRequirements(layers); modeChoices = mScheduler->chooseDisplayModes(); ASSERT_EQ(1u, modeChoices.size()); choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, {.touch = true})); } TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1; mScheduler->registerDisplay(kDisplayId1, std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId()), kActiveDisplayId); mScheduler->registerDisplay(kDisplayId2, std::make_shared(kDisplay2Modes, kDisplay2Mode60->getId()), kActiveDisplayId); mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON); mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON); using DisplayModeChoice = TestableScheduler::DisplayModeChoice; TestableScheduler::DisplayModeChoiceMap expectedChoices; { const GlobalSignals globalSignals = {.idle = true}; expectedChoices = ftl::init::map(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60}, globalSignals)(kDisplayId2, FrameRateMode{60_Hz, kDisplay2Mode60}, GlobalSignals{}); mScheduler->setContentRequirements({kLayer, kLayer}); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); } { const GlobalSignals globalSignals = {.idle = false}; expectedChoices = ftl::init::map(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120}, globalSignals)(kDisplayId2, FrameRateMode{120_Hz, kDisplay2Mode120}, GlobalSignals{}); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); } { const GlobalSignals globalSignals = {.touch = true}; mScheduler->replaceTouchTimer(10); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); expectedChoices = ftl::init::map(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120}, globalSignals)(kDisplayId2, FrameRateMode{120_Hz, kDisplay2Mode120}, GlobalSignals{}); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); } { // The kDisplayId3 does not support 120Hz, The pacesetter display rate is chosen to be 120 // Hz. In this case only the display kDisplayId3 choose 60Hz as it does not support 120Hz. mScheduler->registerDisplay(kDisplayId3, std::make_shared(kDisplay3Modes, kDisplay3Mode60->getId()), kActiveDisplayId); mScheduler->setDisplayPowerMode(kDisplayId3, hal::PowerMode::ON); const GlobalSignals globalSignals = {.touch = true}; mScheduler->replaceTouchTimer(10); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); expectedChoices = ftl::init::map< const PhysicalDisplayId&, DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120}, globalSignals)(kDisplayId2, FrameRateMode{120_Hz, kDisplay2Mode120}, GlobalSignals{})(kDisplayId3, FrameRateMode{60_Hz, kDisplay3Mode60}, GlobalSignals{}); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); } { // We should choose 60Hz despite the touch signal as pacesetter only supports 60Hz mScheduler->setPacesetterDisplay(kDisplayId3); const GlobalSignals globalSignals = {.touch = true}; mScheduler->replaceTouchTimer(10); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); expectedChoices = ftl::init::map< const PhysicalDisplayId&, DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60}, GlobalSignals{})(kDisplayId2, FrameRateMode{60_Hz, kDisplay2Mode60}, GlobalSignals{})(kDisplayId3, FrameRateMode{60_Hz, kDisplay3Mode60}, globalSignals); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); } } TEST_F(SchedulerTest, onFrameSignalMultipleDisplays) { constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1; mScheduler->registerDisplay(kDisplayId1, std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId()), kActiveDisplayId); mScheduler->registerDisplay(kDisplayId2, std::make_shared(kDisplay2Modes, kDisplay2Mode60->getId()), kActiveDisplayId); using VsyncIds = std::vector>; struct Compositor final : ICompositor { explicit Compositor(TestableScheduler& scheduler) : scheduler(scheduler) {} TestableScheduler& scheduler; struct { PhysicalDisplayId commit; PhysicalDisplayId composite; } pacesetterIds; struct { VsyncIds commit; VsyncIds composite; } vsyncIds; bool committed = true; bool changePacesetter = false; void configure() override {} bool commit(PhysicalDisplayId pacesetterId, const scheduler::FrameTargets& targets) override { pacesetterIds.commit = pacesetterId; vsyncIds.commit.clear(); vsyncIds.composite.clear(); for (const auto& [id, target] : targets) { vsyncIds.commit.emplace_back(id, target->vsyncId()); } if (changePacesetter) { scheduler.setPacesetterDisplay(kDisplayId2); } return committed; } CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId, const scheduler::FrameTargeters& targeters) override { pacesetterIds.composite = pacesetterId; CompositeResultsPerDisplay results; for (const auto& [id, targeter] : targeters) { vsyncIds.composite.emplace_back(id, targeter->target().vsyncId()); results.try_emplace(id, CompositeResult{.compositionCoverage = CompositionCoverage::Hwc}); } return results; } void sample() override {} void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {} } compositor(*mScheduler); mScheduler->doFrameSignal(compositor, VsyncId(42)); const auto makeVsyncIds = [](VsyncId vsyncId, bool swap = false) -> VsyncIds { if (swap) { return {{kDisplayId2, vsyncId}, {kDisplayId1, vsyncId}}; } else { return {{kDisplayId1, vsyncId}, {kDisplayId2, vsyncId}}; } }; EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.commit); EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.composite); EXPECT_EQ(makeVsyncIds(VsyncId(42)), compositor.vsyncIds.commit); EXPECT_EQ(makeVsyncIds(VsyncId(42)), compositor.vsyncIds.composite); // FrameTargets should be updated despite the skipped commit. compositor.committed = false; mScheduler->doFrameSignal(compositor, VsyncId(43)); EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.commit); EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.composite); EXPECT_EQ(makeVsyncIds(VsyncId(43)), compositor.vsyncIds.commit); EXPECT_TRUE(compositor.vsyncIds.composite.empty()); // The pacesetter may change during commit. compositor.committed = true; compositor.changePacesetter = true; mScheduler->doFrameSignal(compositor, VsyncId(44)); EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.commit); EXPECT_EQ(kDisplayId2, compositor.pacesetterIds.composite); EXPECT_EQ(makeVsyncIds(VsyncId(44)), compositor.vsyncIds.commit); EXPECT_EQ(makeVsyncIds(VsyncId(44), true), compositor.vsyncIds.composite); } TEST_F(SchedulerTest, nextFrameIntervalTest) { SET_FLAG_FOR_TEST(flags::vrr_config, true); static constexpr size_t kHistorySize = 10; static constexpr size_t kMinimumSamplesForPrediction = 6; static constexpr size_t kOutlierTolerancePercent = 25; const auto refreshRate = Fps::fromPeriodNsecs(500); auto frameRate = Fps::fromPeriodNsecs(1000); const ftl::NonNull kMode = ftl::as_non_null( createVrrDisplayMode(DisplayModeId(0), refreshRate, hal::VrrConfig{.minFrameIntervalNs = static_cast( frameRate.getPeriodNsecs())})); std::shared_ptr vrrTracker = std::make_shared(std::make_unique(), kMode, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent); std::shared_ptr vrrSelectorPtr = std::make_shared(makeModes(kMode), kMode->getId()); TestableScheduler scheduler{std::make_unique(), vrrTracker, vrrSelectorPtr, mFlinger.getFactory(), mFlinger.getTimeStats(), mSchedulerCallback}; scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, std::nullopt, vrrTracker); vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false); vrrTracker->addVsyncTimestamp(0); // Set 1000 as vsync seq #0 vrrTracker->nextAnticipatedVSyncTimeFrom(700); EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(1000))); EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2000))); // Not crossing the min frame period vrrTracker->onFrameBegin(TimePoint::fromNs(2000), {TimePoint::fromNs(1500), TimePoint::fromNs(1500)}); EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2500))); // Change render rate frameRate = Fps::fromPeriodNsecs(2000); vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false); EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(5500))); EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(7500))); } TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) { // resyncAllToHardwareVsync will result in requesting hardware VSYNC on both displays, since // they are both on. EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, true)).Times(1); EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId2, true)).Times(1); mScheduler->registerDisplay(kDisplayId2, std::make_shared(kDisplay2Modes, kDisplay2Mode60->getId())); mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON); mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON); static constexpr bool kDisallow = true; mScheduler->disableHardwareVsync(kDisplayId1, kDisallow); mScheduler->disableHardwareVsync(kDisplayId2, kDisallow); static constexpr bool kAllowToEnable = true; mScheduler->resyncAllToHardwareVsync(kAllowToEnable); } TEST_F(SchedulerTest, resyncAllDoNotAllow) FTL_FAKE_GUARD(kMainThreadContext) { // Without setting allowToEnable to true, resyncAllToHardwareVsync does not // result in requesting hardware VSYNC. EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, _)).Times(0); mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON); static constexpr bool kDisallow = true; mScheduler->disableHardwareVsync(kDisplayId1, kDisallow); static constexpr bool kAllowToEnable = false; mScheduler->resyncAllToHardwareVsync(kAllowToEnable); } TEST_F(SchedulerTest, resyncAllSkipsOffDisplays) FTL_FAKE_GUARD(kMainThreadContext) { // resyncAllToHardwareVsync will result in requesting hardware VSYNC on display 1, which is on, // but not on display 2, which is off. EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, true)).Times(1); EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId2, _)).Times(0); mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON); mScheduler->registerDisplay(kDisplayId2, std::make_shared(kDisplay2Modes, kDisplay2Mode60->getId())); ASSERT_EQ(hal::PowerMode::OFF, mScheduler->getDisplayPowerMode(kDisplayId2)); static constexpr bool kDisallow = true; mScheduler->disableHardwareVsync(kDisplayId1, kDisallow); mScheduler->disableHardwareVsync(kDisplayId2, kDisallow); static constexpr bool kAllowToEnable = true; mScheduler->resyncAllToHardwareVsync(kAllowToEnable); } class AttachedChoreographerTest : public SchedulerTest { protected: void frameRateTestScenario(Fps layerFps, int8_t frameRateCompatibility, Fps displayFps, Fps expectedChoreographerFps); }; TEST_F(AttachedChoreographerTest, registerSingle) { EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty()); const sp layer = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached); const sp connection = mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle()); EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers().size()); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence())); EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.size()); EXPECT_FALSE( mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate.isValid()); } TEST_F(AttachedChoreographerTest, registerMultipleOnSameLayer) { EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty()); const sp layer = sp::make(mFlinger.flinger()); const auto handle = layer->getHandle(); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached).Times(2); EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)) .WillOnce(Return(0)) .WillOnce(Return(0)); const auto mockConnection1 = sp::make(mEventThread); const auto mockConnection2 = sp::make(mEventThread); EXPECT_CALL(*mEventThread, createEventConnection(_)) .WillOnce(Return(mockConnection1)) .WillOnce(Return(mockConnection2)); const sp connection1 = mScheduler->createDisplayEventConnection(Cycle::Render, {}, handle); const sp connection2 = mScheduler->createDisplayEventConnection(Cycle::Render, {}, handle); EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers().size()); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence())); EXPECT_EQ(2u, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.size()); EXPECT_FALSE( mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate.isValid()); } TEST_F(AttachedChoreographerTest, registerMultipleOnDifferentLayers) { EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty()); const sp layer1 = sp::make(mFlinger.flinger()); const sp layer2 = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached).Times(2); const sp connection1 = mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer1->getHandle()); const sp connection2 = mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer2->getHandle()); EXPECT_EQ(2u, mScheduler->mutableAttachedChoreographers().size()); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer1->getSequence())); EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers()[layer1->getSequence()] .connections.size()); EXPECT_FALSE( mScheduler->mutableAttachedChoreographers()[layer1->getSequence()].frameRate.isValid()); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer2->getSequence())); EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers()[layer2->getSequence()] .connections.size()); EXPECT_FALSE( mScheduler->mutableAttachedChoreographers()[layer2->getSequence()].frameRate.isValid()); } TEST_F(AttachedChoreographerTest, removedWhenConnectionIsGone) { EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty()); const sp layer = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached); sp connection = mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle()); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence())); EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.size()); // The connection is used all over this test, so it is quite hard to release it from here. // Instead, we just do a small shortcut. { EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0)); sp mockConnection = sp::make(mEventThread); mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.clear(); mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.emplace( mockConnection); } RequestedLayerState layerState(LayerCreationArgs(layer->getSequence())); LayerHierarchy hierarchy(&layerState); mScheduler->updateAttachedChoreographers(hierarchy, 60_Hz); EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty()); } TEST_F(AttachedChoreographerTest, removedWhenLayerIsGone) { EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty()); sp layer = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached); const sp connection = mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle()); layer.clear(); EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty()); } void AttachedChoreographerTest::frameRateTestScenario(Fps layerFps, int8_t frameRateCompatibility, Fps displayFps, Fps expectedChoreographerFps) { const sp layer = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached); sp connection = mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle()); RequestedLayerState layerState(LayerCreationArgs(layer->getSequence())); LayerHierarchy hierarchy(&layerState); layerState.frameRate = layerFps.getValue(); layerState.frameRateCompatibility = frameRateCompatibility; mScheduler->updateAttachedChoreographers(hierarchy, displayFps); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence())); EXPECT_EQ(expectedChoreographerFps, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate); EXPECT_EQ(expectedChoreographerFps, mEventThreadConnection->frameRate); } TEST_F(AttachedChoreographerTest, setsFrameRateDefault) { Fps layerFps = 30_Hz; int8_t frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT; Fps displayFps = 60_Hz; Fps expectedChoreographerFps = 30_Hz; frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps); layerFps = Fps::fromValue(32.7f); frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps); } TEST_F(AttachedChoreographerTest, setsFrameRateExact) { Fps layerFps = 30_Hz; int8_t frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_EXACT; Fps displayFps = 60_Hz; Fps expectedChoreographerFps = 30_Hz; frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps); layerFps = Fps::fromValue(32.7f); expectedChoreographerFps = {}; frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps); } TEST_F(AttachedChoreographerTest, setsFrameRateExactOrMultiple) { Fps layerFps = 30_Hz; int8_t frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; Fps displayFps = 60_Hz; Fps expectedChoreographerFps = 30_Hz; frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps); layerFps = Fps::fromValue(32.7f); expectedChoreographerFps = {}; frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps); } TEST_F(AttachedChoreographerTest, setsFrameRateParent) { const sp layer = sp::make(mFlinger.flinger()); const sp parent = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached); sp connection = mScheduler->createDisplayEventConnection(Cycle::Render, {}, parent->getHandle()); RequestedLayerState parentState(LayerCreationArgs(parent->getSequence())); LayerHierarchy parentHierarchy(&parentState); RequestedLayerState layerState(LayerCreationArgs(layer->getSequence())); LayerHierarchy hierarchy(&layerState); parentHierarchy.mChildren.push_back( std::make_pair(&hierarchy, LayerHierarchy::Variant::Attached)); layerState.frameRate = (30_Hz).getValue(); layerState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT; mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(parent->getSequence())); EXPECT_EQ(30_Hz, mScheduler->mutableAttachedChoreographers()[parent->getSequence()].frameRate); } TEST_F(AttachedChoreographerTest, setsFrameRateParent2Children) { const sp layer1 = sp::make(mFlinger.flinger()); const sp layer2 = sp::make(mFlinger.flinger()); const sp parent = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached); sp connection = mScheduler->createDisplayEventConnection(Cycle::Render, {}, parent->getHandle()); RequestedLayerState parentState(LayerCreationArgs(parent->getSequence())); LayerHierarchy parentHierarchy(&parentState); RequestedLayerState layer1State(LayerCreationArgs(layer1->getSequence())); LayerHierarchy layer1Hierarchy(&layer1State); parentHierarchy.mChildren.push_back( std::make_pair(&layer1Hierarchy, LayerHierarchy::Variant::Attached)); RequestedLayerState layer2State(LayerCreationArgs(layer1->getSequence())); LayerHierarchy layer2Hierarchy(&layer2State); parentHierarchy.mChildren.push_back( std::make_pair(&layer2Hierarchy, LayerHierarchy::Variant::Attached)); layer1State.frameRate = (30_Hz).getValue(); layer1State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT; layer2State.frameRate = (20_Hz).getValue(); layer2State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT; mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(parent->getSequence())); EXPECT_EQ(60_Hz, mScheduler->mutableAttachedChoreographers()[parent->getSequence()].frameRate); } TEST_F(AttachedChoreographerTest, setsFrameRateParentConflictingChildren) { const sp layer1 = sp::make(mFlinger.flinger()); const sp layer2 = sp::make(mFlinger.flinger()); const sp parent = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached); sp connection = mScheduler->createDisplayEventConnection(Cycle::Render, {}, parent->getHandle()); RequestedLayerState parentState(LayerCreationArgs(parent->getSequence())); LayerHierarchy parentHierarchy(&parentState); RequestedLayerState layer1State(LayerCreationArgs(layer1->getSequence())); LayerHierarchy layer1Hierarchy(&layer1State); parentHierarchy.mChildren.push_back( std::make_pair(&layer1Hierarchy, LayerHierarchy::Variant::Attached)); RequestedLayerState layer2State(LayerCreationArgs(layer1->getSequence())); LayerHierarchy layer2Hierarchy(&layer2State); parentHierarchy.mChildren.push_back( std::make_pair(&layer2Hierarchy, LayerHierarchy::Variant::Attached)); layer1State.frameRate = (30_Hz).getValue(); layer1State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT; layer2State.frameRate = (25_Hz).getValue(); layer2State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT; mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(parent->getSequence())); EXPECT_EQ(Fps(), mScheduler->mutableAttachedChoreographers()[parent->getSequence()].frameRate); } TEST_F(AttachedChoreographerTest, setsFrameRateChild) { const sp layer = sp::make(mFlinger.flinger()); const sp parent = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached); sp connection = mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle()); RequestedLayerState parentState(LayerCreationArgs(parent->getSequence())); LayerHierarchy parentHierarchy(&parentState); RequestedLayerState layerState(LayerCreationArgs(layer->getSequence())); LayerHierarchy hierarchy(&layerState); parentHierarchy.mChildren.push_back( std::make_pair(&hierarchy, LayerHierarchy::Variant::Attached)); parentState.frameRate = (30_Hz).getValue(); parentState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT; mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence())); EXPECT_EQ(30_Hz, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate); } TEST_F(AttachedChoreographerTest, setsFrameRateChildNotOverriddenByParent) { const sp layer = sp::make(mFlinger.flinger()); const sp parent = sp::make(mFlinger.flinger()); EXPECT_CALL(mSchedulerCallback, onChoreographerAttached); sp connection = mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle()); RequestedLayerState parentState(LayerCreationArgs(parent->getSequence())); LayerHierarchy parentHierarchy(&parentState); RequestedLayerState layerState(LayerCreationArgs(layer->getSequence())); LayerHierarchy hierarchy(&layerState); parentHierarchy.mChildren.push_back( std::make_pair(&hierarchy, LayerHierarchy::Variant::Attached)); parentState.frameRate = (30_Hz).getValue(); parentState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT; layerState.frameRate = (60_Hz).getValue(); layerState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT; mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz); ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence())); EXPECT_EQ(60_Hz, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate); } } // namespace android::scheduler