| /* |
| * Copyright 2024 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. |
| */ |
| |
| #define ATRACE_TAG ATRACE_TAG_GRAPHICS |
| |
| #include "Display/DisplayModeController.h" |
| #include "Display/DisplaySnapshot.h" |
| #include "DisplayHardware/HWComposer.h" |
| |
| #include <android-base/properties.h> |
| #include <common/FlagManager.h> |
| #include <common/trace.h> |
| #include <ftl/concat.h> |
| #include <ftl/expected.h> |
| #include <ftl/fake_guard.h> |
| #include <log/log.h> |
| #include <utils/Errors.h> |
| |
| namespace android::display { |
| |
| template <size_t N> |
| inline std::string DisplayModeController::Display::concatId(const char (&str)[N]) const { |
| return std::string(ftl::Concat(str, ' ', snapshot.get().displayId().value).str()); |
| } |
| |
| DisplayModeController::Display::Display(DisplaySnapshotRef snapshot, |
| RefreshRateSelectorPtr selectorPtr) |
| : snapshot(snapshot), |
| selectorPtr(std::move(selectorPtr)), |
| pendingModeFpsTrace(concatId("PendingModeFps")), |
| activeModeFpsTrace(concatId("ActiveModeFps")), |
| renderRateFpsTrace(concatId("RenderRateFps")), |
| hasDesiredModeTrace(concatId("HasDesiredMode"), false) {} |
| |
| DisplayModeController::DisplayModeController() { |
| using namespace std::string_literals; |
| mSupportsHdcp = base::GetBoolProperty("debug.sf.hdcp_support"s, false); |
| } |
| |
| void DisplayModeController::registerDisplay(PhysicalDisplayId displayId, |
| DisplaySnapshotRef snapshotRef, |
| RefreshRateSelectorPtr selectorPtr) { |
| DisplayPtr displayPtr = std::make_unique<Display>(snapshotRef, selectorPtr); |
| // TODO: b/349703362 - Remove first condition when HDCP aidl APIs are enforced |
| displayPtr->setSecure(!supportsHdcp() || |
| snapshotRef.get().connectionType() == |
| ui::DisplayConnectionType::Internal); |
| std::lock_guard lock(mDisplayLock); |
| mDisplays.emplace_or_replace(displayId, std::move(displayPtr)); |
| } |
| |
| void DisplayModeController::registerDisplay(DisplaySnapshotRef snapshotRef, |
| DisplayModeId activeModeId, |
| scheduler::RefreshRateSelector::Config config) { |
| const auto& snapshot = snapshotRef.get(); |
| const auto displayId = snapshot.displayId(); |
| DisplayPtr displayPtr = |
| std::make_unique<Display>(snapshotRef, snapshot.displayModes(), activeModeId, config); |
| // TODO: b/349703362 - Remove first condition when HDCP aidl APIs are enforced |
| displayPtr->setSecure(!supportsHdcp() || |
| snapshotRef.get().connectionType() == |
| ui::DisplayConnectionType::Internal); |
| std::lock_guard lock(mDisplayLock); |
| mDisplays.emplace_or_replace(displayId, std::move(displayPtr)); |
| } |
| |
| void DisplayModeController::unregisterDisplay(PhysicalDisplayId displayId) { |
| std::lock_guard lock(mDisplayLock); |
| const bool ok = mDisplays.erase(displayId); |
| ALOGE_IF(!ok, "%s: Unknown display %s", __func__, to_string(displayId).c_str()); |
| } |
| |
| auto DisplayModeController::selectorPtrFor(PhysicalDisplayId displayId) const |
| -> RefreshRateSelectorPtr { |
| std::lock_guard lock(mDisplayLock); |
| return mDisplays.get(displayId) |
| .transform([](const DisplayPtr& displayPtr) { return displayPtr->selectorPtr; }) |
| .value_or(nullptr); |
| } |
| |
| auto DisplayModeController::setDesiredMode(PhysicalDisplayId displayId, |
| DisplayModeRequest&& desiredMode) -> DesiredModeAction { |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = |
| FTL_EXPECT(mDisplays.get(displayId).ok_or(DesiredModeAction::None)).get(); |
| |
| { |
| SFTRACE_NAME(displayPtr->concatId(__func__).c_str()); |
| ALOGD("%s %s", displayPtr->concatId(__func__).c_str(), to_string(desiredMode).c_str()); |
| |
| std::scoped_lock lock(displayPtr->desiredModeLock); |
| |
| if (auto& desiredModeOpt = displayPtr->desiredModeOpt) { |
| if (!FlagManager::getInstance().modeset_state_machine() || |
| desiredMode.mode.matchesResolution(desiredModeOpt->mode)) { |
| // A mode transition was already scheduled, so just override the desired mode. |
| const bool emitEvent = desiredModeOpt->emitEvent; |
| const bool force = desiredModeOpt->force; |
| desiredModeOpt = std::move(desiredMode); |
| desiredModeOpt->emitEvent |= emitEvent; |
| desiredModeOpt->force |= force; |
| return DesiredModeAction::None; |
| } |
| } |
| |
| // If the desired mode is already active... |
| const auto activeMode = displayPtr->selectorPtr->getActiveMode(); |
| if (const auto& desiredModePtr = desiredMode.mode.modePtr; |
| !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) { |
| ftl::FakeGuard guard(kMainThreadContext); |
| |
| // ...and there is no pending resolution change... |
| if (!FlagManager::getInstance().modeset_state_machine() || |
| (!displayPtr->pendingModeOpt && desiredMode.mode.matchesResolution(activeMode))) { |
| if (activeMode == desiredMode.mode) { |
| return DesiredModeAction::None; |
| } |
| |
| // ...but the render rate changed: |
| setActiveModeLocked(displayId, desiredModePtr->getId(), |
| desiredModePtr->getVsyncRate(), desiredMode.mode.fps); |
| return DesiredModeAction::InitiateRenderRateSwitch; |
| } |
| } |
| |
| // Restore peak render rate to schedule the next frame as soon as possible. |
| setActiveModeLocked(displayId, activeMode.modePtr->getId(), |
| activeMode.modePtr->getVsyncRate(), activeMode.modePtr->getPeakFps()); |
| |
| // Initiate a mode change. |
| displayPtr->desiredModeOpt = std::move(desiredMode); |
| displayPtr->hasDesiredModeTrace = true; |
| } |
| |
| return DesiredModeAction::InitiateDisplayModeSwitch; |
| } |
| |
| auto DisplayModeController::getDesiredMode(PhysicalDisplayId displayId) const |
| -> DisplayModeRequestOpt { |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = |
| FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get(); |
| |
| { |
| std::scoped_lock lock(displayPtr->desiredModeLock); |
| return displayPtr->desiredModeOpt; |
| } |
| } |
| |
| auto DisplayModeController::getPendingMode(PhysicalDisplayId displayId) const |
| -> DisplayModeRequestOpt { |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = |
| FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get(); |
| |
| if (FlagManager::getInstance().modeset_state_machine()) { |
| return displayPtr->pendingModeOpt; |
| } |
| |
| { |
| std::scoped_lock lock(displayPtr->desiredModeLock); |
| return displayPtr->pendingModeOpt; |
| } |
| } |
| |
| bool DisplayModeController::isModeSetPending(PhysicalDisplayId displayId) const { |
| if (FlagManager::getInstance().modeset_state_machine()) { |
| return getPendingMode(displayId).has_value(); |
| } |
| |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get(); |
| |
| { |
| std::scoped_lock lock(displayPtr->desiredModeLock); |
| return displayPtr->isModeSetPending; |
| } |
| } |
| |
| scheduler::FrameRateMode DisplayModeController::getActiveMode(PhysicalDisplayId displayId) const { |
| return selectorPtrFor(displayId)->getActiveMode(); |
| } |
| |
| auto DisplayModeController::takeDesiredModeIfMatches(PhysicalDisplayId displayId, |
| ui::Size expectedResolution) |
| -> DisplayModeRequestOpt { |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = |
| FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get(); |
| |
| DisplayModeRequestOpt desiredModeOpt; |
| { |
| std::scoped_lock lock(displayPtr->desiredModeLock); |
| |
| if (FlagManager::getInstance().synced_resolution_switch()) { |
| if (const auto modeOpt = displayPtr->desiredModeOpt.transform( |
| [](const auto& request) { return request.mode; })) { |
| const bool resolutionMatch = |
| displayPtr->selectorPtr->getActiveMode().matchesResolution(*modeOpt); |
| |
| if (!resolutionMatch) { |
| if (expectedResolution == modeOpt->modePtr->getResolution()) { |
| displayPtr->desiredModeOpt->force = true; |
| } else { |
| // When initiating a resolution change, wait until the commit that resizes |
| // the display. |
| return std::nullopt; |
| } |
| } |
| } |
| } |
| |
| std::swap(displayPtr->desiredModeOpt, desiredModeOpt); |
| displayPtr->hasDesiredModeTrace = false; |
| } |
| return desiredModeOpt; |
| } |
| |
| void DisplayModeController::clearDesiredMode(PhysicalDisplayId displayId) { |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get(); |
| |
| { |
| std::scoped_lock lock(displayPtr->desiredModeLock); |
| displayPtr->desiredModeOpt.reset(); |
| displayPtr->hasDesiredModeTrace = false; |
| } |
| } |
| |
| auto DisplayModeController::initiateModeChange( |
| PhysicalDisplayId displayId, DisplayModeRequest&& desiredMode, |
| const hal::VsyncPeriodChangeConstraints& constraints, |
| hal::VsyncPeriodChangeTimeline& outTimeline) -> ModeChangeResult { |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = |
| FTL_EXPECT(mDisplays.get(displayId).ok_or(ModeChangeResult::Aborted)).get(); |
| |
| if (!FlagManager::getInstance().modeset_state_machine()) { |
| displayPtr->isModeSetPending = true; |
| |
| // For now, `desiredMode` and `desiredModeOpt` are one and the same, but the latter is not |
| // cleared until the next `SF::initiateDisplayModeChanges`. However, the desired mode has |
| // been consumed at this point, so clear the `force` flag to prevent an endless loop of |
| // `initiateModeChange`. |
| std::scoped_lock lock(displayPtr->desiredModeLock); |
| if (displayPtr->desiredModeOpt) { |
| displayPtr->desiredModeOpt->force = false; |
| } |
| } |
| |
| ALOGD("%s %s", displayPtr->concatId(__func__).c_str(), to_string(desiredMode).c_str()); |
| displayPtr->pendingModeOpt = std::move(desiredMode); |
| |
| const auto& mode = *displayPtr->pendingModeOpt->mode.modePtr; |
| |
| const auto error = mComposerPtr->setActiveModeWithConstraints(displayId, mode.getHwcId(), |
| constraints, &outTimeline); |
| switch (error) { |
| case FAILED_TRANSACTION: |
| return ModeChangeResult::Rejected; |
| case OK: |
| SFTRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue()); |
| return ModeChangeResult::Changed; |
| default: |
| return ModeChangeResult::Aborted; |
| } |
| } |
| |
| void DisplayModeController::finalizeModeChange(PhysicalDisplayId displayId, DisplayModeId modeId, |
| Fps vsyncRate, Fps renderFps) { |
| std::lock_guard lock(mDisplayLock); |
| setActiveModeLocked(displayId, modeId, vsyncRate, renderFps); |
| |
| const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get(); |
| displayPtr->isModeSetPending = false; |
| } |
| |
| auto DisplayModeController::finalizeModeChange(PhysicalDisplayId displayId) -> ModeChange { |
| std::lock_guard lock(mDisplayLock); |
| |
| const auto& displayPtr = |
| FTL_EXPECT(mDisplays.get(displayId).ok_or(NoModeChange{"Unknown display"})).get(); |
| if (!displayPtr->pendingModeOpt) return NoModeChange{"No pending mode"}; |
| |
| auto pendingMode = *std::exchange(displayPtr->pendingModeOpt, std::nullopt); |
| auto& pendingModePtr = pendingMode.mode.modePtr; |
| |
| if (!displayPtr->selectorPtr->displayModes().contains(pendingModePtr->getId())) { |
| return NoModeChange{"Unknown pending mode"}; |
| } |
| |
| const bool resolutionMatch = |
| displayPtr->selectorPtr->getActiveMode().matchesResolution(pendingMode.mode); |
| |
| setActiveModeLocked(displayId, pendingModePtr->getId(), pendingModePtr->getVsyncRate(), |
| pendingMode.mode.fps); |
| |
| if (resolutionMatch) { |
| return RefreshRateChange{std::move(pendingMode)}; |
| } else { |
| return ResolutionChange{std::move(pendingMode)}; |
| } |
| } |
| |
| void DisplayModeController::setActiveMode(PhysicalDisplayId displayId, DisplayModeId modeId, |
| Fps vsyncRate, Fps renderFps) { |
| std::lock_guard lock(mDisplayLock); |
| setActiveModeLocked(displayId, modeId, vsyncRate, renderFps); |
| } |
| |
| void DisplayModeController::setActiveModeLocked(PhysicalDisplayId displayId, DisplayModeId modeId, |
| Fps vsyncRate, Fps renderFps) { |
| const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get(); |
| |
| SFTRACE_INT(displayPtr->activeModeFpsTrace.c_str(), vsyncRate.getIntValue()); |
| SFTRACE_INT(displayPtr->renderRateFpsTrace.c_str(), renderFps.getIntValue()); |
| |
| displayPtr->selectorPtr->setActiveMode(modeId, renderFps); |
| |
| if (mActiveModeListener) { |
| mActiveModeListener(displayId, vsyncRate, renderFps); |
| } |
| } |
| |
| void DisplayModeController::updateKernelIdleTimer(PhysicalDisplayId displayId) { |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get(); |
| |
| const auto controllerOpt = displayPtr->selectorPtr->kernelIdleTimerController(); |
| if (!controllerOpt) return; |
| |
| using KernelIdleTimerAction = scheduler::RefreshRateSelector::KernelIdleTimerAction; |
| |
| switch (displayPtr->selectorPtr->getIdleTimerAction()) { |
| case KernelIdleTimerAction::TurnOff: |
| if (displayPtr->isKernelIdleTimerEnabled) { |
| SFTRACE_INT("KernelIdleTimer", 0); |
| updateKernelIdleTimer(displayId, std::chrono::milliseconds::zero(), *controllerOpt); |
| displayPtr->isKernelIdleTimerEnabled = false; |
| } |
| break; |
| case KernelIdleTimerAction::TurnOn: |
| if (!displayPtr->isKernelIdleTimerEnabled) { |
| SFTRACE_INT("KernelIdleTimer", 1); |
| const auto timeout = displayPtr->selectorPtr->getIdleTimerTimeout(); |
| updateKernelIdleTimer(displayId, timeout, *controllerOpt); |
| displayPtr->isKernelIdleTimerEnabled = true; |
| } |
| break; |
| } |
| } |
| |
| void DisplayModeController::updateKernelIdleTimer(PhysicalDisplayId displayId, |
| std::chrono::milliseconds timeout, |
| KernelIdleTimerController controller) { |
| switch (controller) { |
| case KernelIdleTimerController::HwcApi: |
| mComposerPtr->setIdleTimerEnabled(displayId, timeout); |
| break; |
| |
| case KernelIdleTimerController::Sysprop: |
| using namespace std::string_literals; |
| base::SetProperty("graphics.display.kernel_idle_timer.enabled"s, |
| timeout > std::chrono::milliseconds::zero() ? "true"s : "false"s); |
| break; |
| } |
| } |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wunused-value" // b/369277774 |
| auto DisplayModeController::getKernelIdleTimerState(PhysicalDisplayId displayId) const |
| -> KernelIdleTimerState { |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = |
| FTL_EXPECT(mDisplays.get(displayId).ok_or(KernelIdleTimerState())).get(); |
| |
| const auto desiredModeIdOpt = |
| (std::scoped_lock(displayPtr->desiredModeLock), displayPtr->desiredModeOpt) |
| .transform([](const display::DisplayModeRequest& request) { |
| return request.mode.modePtr->getId(); |
| }); |
| |
| return {desiredModeIdOpt, displayPtr->isKernelIdleTimerEnabled}; |
| } |
| |
| bool DisplayModeController::supportsHdcp() const { |
| return mSupportsHdcp && FlagManager::getInstance().hdcp_level_hal() && |
| FlagManager::getInstance().hdcp_negotiation(); |
| } |
| |
| void DisplayModeController::startHdcpNegotiation(PhysicalDisplayId displayId) { |
| using aidl::android::hardware::drm::HdcpLevel; |
| using aidl::android::hardware::drm::HdcpLevels; |
| constexpr HdcpLevels kLevels = {.connectedLevel = HdcpLevel::HDCP_V2_1, |
| .maxLevel = HdcpLevel::HDCP_V2_3}; |
| |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get(); |
| if (displayPtr->hdcpState == HdcpState::Desired) { |
| const auto status = mComposerPtr->startHdcpNegotiation(displayId, kLevels); |
| displayPtr->hdcpState = (status == NO_ERROR) ? HdcpState::Enabled : HdcpState::Undesired; |
| } |
| } |
| |
| void DisplayModeController::setSecure(PhysicalDisplayId displayId, bool secure) { |
| std::lock_guard lock(mDisplayLock); |
| const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get(); |
| displayPtr->setSecure(secure); |
| } |
| |
| #pragma clang diagnostic pop |
| } // namespace android::display |