diff --git a/.ci/Makefile b/.ci/Makefile index 8e6d641..4bb2857 100644 --- a/.ci/Makefile +++ b/.ci/Makefile @@ -103,6 +103,7 @@ SKIP_FILES := \ drm/DrmDisplayPipelineTest.cpp \ stats/CompositionStatsTest.cpp \ stats/CompositionStatsAtomReporterDesktop.cpp \ + stats/DisplayRefreshRatesChangedAtomReporterDesktop.cpp \ BUILD_FILES_AUTO := $(shell find -L $(SRC_DIR) -not -path '*/\.*' -not -path '*/tests/test_include/*' -path '*.cpp') SKIP_FILES_path := $(foreach file,$(SKIP_FILES),$(SRC_DIR)/$(file)) diff --git a/Android.bp b/Android.bp index 72c8ebe..6e88481 100644 --- a/Android.bp +++ b/Android.bp @@ -345,6 +345,7 @@ drm_hwcomposer_atom_reporter_library { srcs: [ "compositor/FlatteningEventAtomReporterDesktop.cpp", "stats/CompositionStatsAtomReporterDesktop.cpp", + "stats/DisplayRefreshRatesChangedAtomReporterDesktop.cpp", ], shared_libs: [ "android.frameworks.stats-V2-ndk", @@ -356,6 +357,7 @@ drm_hwcomposer_atom_reporter_library { srcs: [ "compositor/FlatteningEventAtomReporter.cpp", "stats/CompositionStatsAtomReporter.cpp", + "stats/DisplayRefreshRatesChangedAtomReporter.cpp", ], }, }, diff --git a/drm/DrmHwc.cpp b/drm/DrmHwc.cpp index b675148..a7371fe 100644 --- a/drm/DrmHwc.cpp +++ b/drm/DrmHwc.cpp @@ -72,7 +72,11 @@ std::string DumpDisplayStats(const HwcDisplay *display, } } // namespace -DrmHwc::DrmHwc() : resource_manager_(this), dump_stats_tracker_(this) {}; +DrmHwc::DrmHwc() + : resource_manager_(this), + dump_stats_tracker_(this), + refresh_rates_reporter_( + DisplayRefreshRatesChangedAtomReporter::Create()) {}; /* Must be called after every display attach/detach cycle */ void DrmHwc::FinalizeDisplayBinding() { @@ -274,4 +278,18 @@ void DrmHwc::DeinitDisplays() { } } +void DrmHwc::LogRefreshRateChanges() { + std::vector refresh_rates; + refresh_rates.reserve(displays_.size()); + for (const auto &[_, display] : displays_) { + if (const HwcDisplayConfig *config = display->GetCurrentConfig(); config) { + refresh_rates.push_back( + static_cast(lround(config->mode.GetVRefresh()))); + } + } + + if (refresh_rates_reporter_) + refresh_rates_reporter_->UpdateRefreshRates(refresh_rates); +} + } // namespace android::drm_hwcomposer diff --git a/drm/DrmHwc.h b/drm/DrmHwc.h index 9fa0ffb..d07eaf1 100644 --- a/drm/DrmHwc.h +++ b/drm/DrmHwc.h @@ -19,6 +19,7 @@ #include "drm/ResourceManager.h" #include "hwc/HwcDisplay.h" #include "stats/CompositionStats.h" +#include "stats/DisplayRefreshRatesChangedAtomReporter.h" namespace android::drm_hwcomposer { @@ -85,6 +86,9 @@ class DrmHwc : public PipelineToFrontendBindingInterface, void NotifyDisplayLinkStatus( std::shared_ptr pipeline) override; + // Should be done for all successful modesets (full and seamless). + void LogRefreshRateChanges(); + protected: auto &Displays() { return displays_; @@ -100,6 +104,9 @@ class DrmHwc : public PipelineToFrontendBindingInterface, DisplayHandle last_display_handle_ = kPrimaryDisplay; CompositionStatsTracker dump_stats_tracker_; + + std::unique_ptr + refresh_rates_reporter_; }; } // namespace android::drm_hwcomposer diff --git a/hwc3/ComposerClient.cpp b/hwc3/ComposerClient.cpp index 5e6614f..3b3bc01 100644 --- a/hwc3/ComposerClient.cpp +++ b/hwc3/ComposerClient.cpp @@ -1318,6 +1318,7 @@ ndk::ScopedAStatus ComposerClient::setActiveConfigWithConstraints( return ToBinderStatus(hwc3::Error::kBadConfig); #endif case HwcDisplay::ConfigError::kNone: + hwc_->LogRefreshRateChanges(); return ndk::ScopedAStatus::ok(); } } diff --git a/meson.build b/meson.build index a7935e1..a9257e6 100644 --- a/meson.build +++ b/meson.build @@ -24,6 +24,7 @@ src_common = files( 'stats/CompositionStats.cpp', 'stats/CompositionStatsAtomReporter.cpp', 'stats/CompositionStatsPoller.cpp', + 'stats/DisplayRefreshRatesChangedAtomReporter.cpp', 'utils/fd.cpp', 'utils/LibdisplayEdidWrapper.cpp', 'utils/properties.cpp', diff --git a/stats/DisplayRefreshRatesChangedAtomReporter.cpp b/stats/DisplayRefreshRatesChangedAtomReporter.cpp new file mode 100644 index 0000000..c0d1084 --- /dev/null +++ b/stats/DisplayRefreshRatesChangedAtomReporter.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2025 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 LOG_TAG "drmhwc" + +#include "DisplayRefreshRatesChangedAtomReporter.h" + +#include "utils/log.h" + +namespace android::drm_hwcomposer { + +std::unique_ptr +DisplayRefreshRatesChangedAtomReporter::Create() { + ALOGI("Atom reporting is not enabled."); + return {}; +} + +} // namespace android::drm_hwcomposer diff --git a/stats/DisplayRefreshRatesChangedAtomReporter.h b/stats/DisplayRefreshRatesChangedAtomReporter.h new file mode 100644 index 0000000..05b800a --- /dev/null +++ b/stats/DisplayRefreshRatesChangedAtomReporter.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2025 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. + */ + +#pragma once + +#include +#include + +namespace android::drm_hwcomposer { +class DisplayRefreshRatesChangedAtomReporter { + public: + static std::unique_ptr Create(); + + virtual void UpdateRefreshRates(std::vector refresh_rates) = 0; + virtual ~DisplayRefreshRatesChangedAtomReporter() = default; +}; + +} // namespace android::drm_hwcomposer diff --git a/stats/DisplayRefreshRatesChangedAtomReporterDesktop.cpp b/stats/DisplayRefreshRatesChangedAtomReporterDesktop.cpp new file mode 100644 index 0000000..864b737 --- /dev/null +++ b/stats/DisplayRefreshRatesChangedAtomReporterDesktop.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2025 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 LOG_TAG "drmhwc" +// #define NLOG_DEBUG 0 + +#include "DisplayRefreshRatesChangedAtomReporter.h" + +#include +#include + +#include +#include + +#include "desktopatoms.h" +#include "utils/log.h" + +using aidl::android::frameworks::stats::IStats; +using aidl::android::frameworks::stats::VendorAtom; +namespace DesktopAtoms = android::vendor::google::desktop::stats::DesktopAtoms; + +namespace android::drm_hwcomposer { +namespace { + +const std::string kStatsServiceName = std::string(IStats::descriptor) + .append("/default"); + +// Use a private implementation of DisplayRefreshRatesChangedAtomReporter to +// avoid leaking the IStats interface through the public api. +class DisplayRefreshRatesChangedAtomReporterDesktop + : public DisplayRefreshRatesChangedAtomReporter { + public: + void UpdateRefreshRates(std::vector refresh_rates) override { + if (refresh_rates == last_refresh_rates_) { + return; + } + + last_refresh_rates_ = refresh_rates; + + // The order of the arguments to createVendorAtom is determined by the + // proto definition in libdesktopatoms. + const char* deprecated_reverse_domain_name = ""; + const VendorAtom vendor_atom = DesktopAtoms:: + createVendorAtom(DesktopAtoms::DISPLAY_REFRESH_RATES_CHANGED, + deprecated_reverse_domain_name, refresh_rates); + + auto stats_service = IStats::fromBinder(ndk::SpAIBinder( + AServiceManager_checkService(kStatsServiceName.c_str()))); + ALOGE_IF(stats_service == nullptr, "Failed to get IStats service"); + if (stats_service) { + const ndk::ScopedAStatus ret = stats_service->reportVendorAtom( + vendor_atom); + } + } + + private: + std::vector last_refresh_rates_; +}; +} // namespace + +std::unique_ptr +DisplayRefreshRatesChangedAtomReporter::Create() { + if (!AServiceManager_isDeclared(kStatsServiceName.c_str())) { + ALOGW("Stats service is not declared."); + return nullptr; + } + return std::make_unique(); +} + +} // namespace android::drm_hwcomposer