blob: 049b0923a627e738a55bc819ae8513094fbe6c73 [file] [log] [blame]
/*
* 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 <common/test/FlagUtils.h>
#include <ftl/fake_guard.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
#include <mutex>
#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 <FrontEnd/LayerHierarchy.h>
#include <com_android_graphics_surfaceflinger_flags.h>
#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 SchedulerTest : public testing::Test {
protected:
class MockEventThreadConnection : public android::EventThreadConnection {
public:
explicit MockEventThreadConnection(EventThread* eventThread)
: EventThreadConnection(eventThread, /*callingUid*/ static_cast<uid_t>(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 PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u);
static inline const ftl::NonNull<DisplayModePtr> kDisplay1Mode60 =
ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz));
static inline const ftl::NonNull<DisplayModePtr> kDisplay1Mode120 =
ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz));
static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120);
static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u);
static inline const ftl::NonNull<DisplayModePtr> kDisplay2Mode60 =
ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz));
static inline const ftl::NonNull<DisplayModePtr> 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<DisplayModePtr> kDisplay3Mode60 =
ftl::as_non_null(createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz));
static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60);
std::shared_ptr<RefreshRateSelector> mSelector =
std::make_shared<RefreshRateSelector>(makeModes(kDisplay1Mode60),
kDisplay1Mode60->getId());
mock::SchedulerCallback mSchedulerCallback;
TestableSurfaceFlinger mFlinger;
TestableScheduler* mScheduler = new TestableScheduler{mSelector, mFlinger, mSchedulerCallback};
surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
MockEventThread* mEventThread;
sp<MockEventThreadConnection> mEventThreadConnection;
};
SchedulerTest::SchedulerTest() {
auto eventThread = std::make_unique<MockEventThread>();
mEventThread = eventThread.get();
EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0));
mEventThreadConnection = sp<MockEventThreadConnection>::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));
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<RefreshRateSelector>(kDisplay1Modes,
kDisplay1Mode60->getId()));
// TODO(b/241285191): Restore once VsyncSchedule::getPendingHardwareVsyncState is called by
// Scheduler::setDisplayPowerMode rather than SF::setPowerModeInternal.
#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<RefreshRateSelector>(kDisplay2Modes,
kDisplay2Mode60->getId()));
mScheduler->registerDisplay(kDisplayId3,
std::make_shared<RefreshRateSelector>(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<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
// recordLayerHistory should be a noop
ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 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<MockLayer> layer = sp<MockLayer>::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<RefreshRateSelector>(kDisplay1Modes,
kDisplay1Mode60->getId()));
ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, 0,
LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(1u, mScheduler->getNumActiveLayers());
}
TEST_F(SchedulerTest, dispatchCachedReportedMode) {
mScheduler->clearCachedReportedMode();
EXPECT_CALL(*mEventThread, onModeChanged(_)).Times(0);
EXPECT_NO_FATAL_FAILURE(mScheduler->dispatchCachedReportedMode());
}
TEST_F(SchedulerTest, calculateMaxAcquiredBufferCount) {
EXPECT_EQ(1, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 30ms));
EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(90_Hz, 30ms));
EXPECT_EQ(3, mFlinger.calculateMaxAcquiredBufferCount(120_Hz, 30ms));
EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 40ms));
EXPECT_EQ(1, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 10ms));
const auto savedMinAcquiredBuffers = mFlinger.mutableMinAcquiredBuffers();
mFlinger.mutableMinAcquiredBuffers() = 2;
EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 10ms));
mFlinger.mutableMinAcquiredBuffers() = savedMinAcquiredBuffers;
}
MATCHER(Is120Hz, "") {
return isApproxEqual(arg.front().mode.fps, 120_Hz);
}
TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) {
mScheduler->registerDisplay(kDisplayId1,
std::make_shared<RefreshRateSelector>(kDisplay1Modes,
kDisplay1Mode60->getId()));
const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true));
mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 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, chooseDisplayModesSingleDisplay) {
mScheduler->registerDisplay(kDisplayId1,
std::make_shared<RefreshRateSelector>(kDisplay1Modes,
kDisplay1Mode60->getId()));
std::vector<RefreshRateSelector::LayerRequirement> layers =
std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
mScheduler->setContentRequirements(layers);
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, chooseDisplayModesSingleDisplayHighHintTouchSignal) {
mScheduler->registerDisplay(kDisplayId1,
std::make_shared<RefreshRateSelector>(kDisplay1Modes,
kDisplay1Mode60->getId()));
using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
std::vector<RefreshRateSelector::LayerRequirement> layers =
std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
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) {
mScheduler->registerDisplay(kDisplayId1,
std::make_shared<RefreshRateSelector>(kDisplay1Modes,
kDisplay1Mode60->getId()));
mScheduler->registerDisplay(kDisplayId2,
std::make_shared<RefreshRateSelector>(kDisplay2Modes,
kDisplay2Mode60->getId()));
using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
TestableScheduler::DisplayModeChoiceMap expectedChoices;
{
const GlobalSignals globalSignals = {.idle = true};
expectedChoices =
ftl::init::map<const PhysicalDisplayId&,
DisplayModeChoice>(kDisplayId1,
FrameRateMode{60_Hz, kDisplay1Mode60},
globalSignals)(kDisplayId2,
FrameRateMode{60_Hz,
kDisplay2Mode60},
globalSignals);
std::vector<RefreshRateSelector::LayerRequirement> layers = {{.weight = 1.f},
{.weight = 1.f}};
mScheduler->setContentRequirements(layers);
mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
const auto actualChoices = mScheduler->chooseDisplayModes();
EXPECT_EQ(expectedChoices, actualChoices);
}
{
const GlobalSignals globalSignals = {.idle = false};
expectedChoices =
ftl::init::map<const PhysicalDisplayId&,
DisplayModeChoice>(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<const PhysicalDisplayId&,
DisplayModeChoice>(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<RefreshRateSelector>(kDisplay3Modes,
kDisplay3Mode60->getId()));
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) {
mScheduler->registerDisplay(kDisplayId1,
std::make_shared<RefreshRateSelector>(kDisplay1Modes,
kDisplay1Mode60->getId()));
mScheduler->registerDisplay(kDisplayId2,
std::make_shared<RefreshRateSelector>(kDisplay2Modes,
kDisplay2Mode60->getId()));
using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
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<DisplayModePtr> kMode = ftl::as_non_null(
createVrrDisplayMode(DisplayModeId(0), refreshRate,
hal::VrrConfig{.minFrameIntervalNs = static_cast<int32_t>(
frameRate.getPeriodNsecs())}));
std::shared_ptr<VSyncPredictor> vrrTracker =
std::make_shared<VSyncPredictor>(std::make_unique<SystemClock>(), kMode, kHistorySize,
kMinimumSamplesForPrediction,
kOutlierTolerancePercent);
std::shared_ptr<RefreshRateSelector> vrrSelectorPtr =
std::make_shared<RefreshRateSelector>(makeModes(kMode), kMode->getId());
TestableScheduler scheduler{std::make_unique<android::mock::VsyncController>(),
vrrTracker,
vrrSelectorPtr,
mFlinger.getFactory(),
mFlinger.getTimeStats(),
mSchedulerCallback};
scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
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
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);
// Set 2000 as vsync seq #0
vrrTracker->nextAnticipatedVSyncTimeFrom(1700);
EXPECT_EQ(Fps::fromPeriodNsecs(2000),
scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
TimePoint::fromNs(2000)));
EXPECT_EQ(Fps::fromPeriodNsecs(2000),
scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
TimePoint::fromNs(4000)));
}
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<RefreshRateSelector>(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) {
SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
// 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<RefreshRateSelector>(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);
}
TEST_F(SchedulerTest, resyncAllLegacyAppliesToOffDisplays) FTL_FAKE_GUARD(kMainThreadContext) {
SET_FLAG_FOR_TEST(flags::multithreaded_present, false);
// In the legacy code, prior to the flag, resync applied to OFF displays.
EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, true)).Times(1);
EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId2, true)).Times(1);
mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
mScheduler->registerDisplay(kDisplayId2,
std::make_shared<RefreshRateSelector>(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<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
const sp<IDisplayEventConnection> 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<MockLayer> layer = sp<MockLayer>::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<MockEventThreadConnection>::make(mEventThread);
const auto mockConnection2 = sp<MockEventThreadConnection>::make(mEventThread);
EXPECT_CALL(*mEventThread, createEventConnection(_, _))
.WillOnce(Return(mockConnection1))
.WillOnce(Return(mockConnection2));
const sp<IDisplayEventConnection> connection1 =
mScheduler->createDisplayEventConnection(Cycle::Render, {}, handle);
const sp<IDisplayEventConnection> 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<MockLayer> layer1 = sp<MockLayer>::make(mFlinger.flinger());
const sp<MockLayer> layer2 = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached).Times(2);
const sp<IDisplayEventConnection> connection1 =
mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer1->getHandle());
const sp<IDisplayEventConnection> 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<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
sp<IDisplayEventConnection> 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<MockEventThreadConnection> mockConnection =
sp<MockEventThreadConnection>::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<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
const sp<IDisplayEventConnection> connection =
mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle());
layer.clear();
mFlinger.mutableLayersPendingRemoval().clear();
EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
}
void AttachedChoreographerTest::frameRateTestScenario(Fps layerFps, int8_t frameRateCompatibility,
Fps displayFps,
Fps expectedChoreographerFps) {
const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
sp<IDisplayEventConnection> 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<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
sp<IDisplayEventConnection> 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<MockLayer> layer1 = sp<MockLayer>::make(mFlinger.flinger());
const sp<MockLayer> layer2 = sp<MockLayer>::make(mFlinger.flinger());
const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
sp<IDisplayEventConnection> 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<MockLayer> layer1 = sp<MockLayer>::make(mFlinger.flinger());
const sp<MockLayer> layer2 = sp<MockLayer>::make(mFlinger.flinger());
const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
sp<IDisplayEventConnection> 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<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
sp<IDisplayEventConnection> 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<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
sp<IDisplayEventConnection> 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