diff --git a/.ci/Makefile b/.ci/Makefile index 4ebce27..8e6d641 100644 --- a/.ci/Makefile +++ b/.ci/Makefile @@ -99,6 +99,7 @@ clean: # IStats includes are not available in aospless SKIP_FILES := \ compositor/FlatteningControllerTests.cpp \ + compositor/FlatteningEventAtomReporterDesktop.cpp \ drm/DrmDisplayPipelineTest.cpp \ stats/CompositionStatsTest.cpp \ stats/CompositionStatsAtomReporterDesktop.cpp \ diff --git a/Android.bp b/Android.bp index c762f11..3081a8b 100644 --- a/Android.bp +++ b/Android.bp @@ -280,6 +280,7 @@ cc_test_host { "bufferinfo/BufferInfoGetter.cpp", "compositor/FlatteningController.cpp", "compositor/FlatteningControllerTests.cpp", + "compositor/FlatteningEventAtomReporter.cpp", "drm/DrmAtomicStateManager.cpp", "drm/DrmConnector.cpp", "drm/DrmCrtc.cpp", @@ -328,7 +329,11 @@ soong_config_string_variable { drm_hwcomposer_atom_reporter_library { name: "drm_hwcomposer_atom_reporter", + srcs: [ + "compositor/FlatteningController.cpp", + ], shared_libs: [ + "libbase", "libc++", "libdrm", "libhardware", @@ -338,6 +343,7 @@ drm_hwcomposer_atom_reporter_library { atom_reporter: { desktop: { srcs: [ + "compositor/FlatteningEventAtomReporterDesktop.cpp", "stats/CompositionStatsAtomReporterDesktop.cpp", ], shared_libs: [ @@ -348,6 +354,7 @@ drm_hwcomposer_atom_reporter_library { }, conditions_default: { srcs: [ + "compositor/FlatteningEventAtomReporter.cpp", "stats/CompositionStatsAtomReporter.cpp", ], }, diff --git a/backend/GenericCompositionPlanner.cpp b/backend/GenericCompositionPlanner.cpp index 60d0540..080c457 100644 --- a/backend/GenericCompositionPlanner.cpp +++ b/backend/GenericCompositionPlanner.cpp @@ -21,6 +21,7 @@ #include #include "BackendManager.h" +#include "compositor/FlatteningController.h" #include "compositor/LayerData.h" #include "hwc/HwcDisplay.h" #include "hwc/HwcLayer.h" diff --git a/compositor/FlatteningController.cpp b/compositor/FlatteningController.cpp index 6a9b290..faec95e 100644 --- a/compositor/FlatteningController.cpp +++ b/compositor/FlatteningController.cpp @@ -33,13 +33,25 @@ #include "FlatteningController.h" +#include +#include +#include + +#include + +#include "compositor/FlatteningEventAtomReporter.h" +#include "hwc/HwcDisplay.h" #include "utils/log.h" namespace android::drm_hwcomposer { -FlatteningController::FlatteningController(FlatConCallbacks callbacks, +FlatteningController::FlatteningController(DisplayHandle handle, + FlatConCallbacks callbacks, std::chrono::milliseconds timeout) - : cbks_(std::move(callbacks)), timeout_(timeout) { + : handle_(handle), + reporter_(FlatteningEventAtomReporter::Create()), + cbks_(std::move(callbacks)), + timeout_(timeout) { thread_ = std::thread(&FlatteningController::ThreadFn, this); } @@ -50,20 +62,20 @@ FlatteningController::~FlatteningController() { void FlatteningController::DisableFlattening() { auto lock = std::lock_guard(mutex_); - state_ = State::kDisabled; + SetState(State::kDisabled); } void FlatteningController::NewFrame() { auto lock = std::lock_guard(mutex_); if (state_ == State::kTriggeredCallback) { - state_ = State::kFlattened; + SetState(State::kFlattened); return; } sleep_until_ = std::chrono::system_clock::now() + timeout_; bool was_active = (state_ == State::kActive); - state_ = State::kActive; + SetState(State::kActive); if (!was_active) { cv_.notify_all(); @@ -77,7 +89,7 @@ bool FlatteningController::ShouldFlatten() const { void FlatteningController::StopThread() { auto lock = std::lock_guard(mutex_); - state_ = State::kExitThread; + SetState(State::kExitThread); cv_.notify_all(); } @@ -91,7 +103,7 @@ void FlatteningController::ThreadFn() { if (sleep_until_ <= std::chrono::system_clock::now() && (state_ == State::kActive)) { - state_ = State::kTriggeredCallback; + SetState(State::kTriggeredCallback); ALOGV("Timeout. Sending an event to compositor"); cbks_.trigger(); } @@ -106,4 +118,24 @@ void FlatteningController::ThreadFn() { } } +void FlatteningController::SetState(State state) { + if (state_ != state) { + state_ = state; + + if (reporter_) { + switch (state_) { + case State::kDisabled: + case State::kActive: + case State::kFlattened: + reporter_->PushAtom(handle_, state_); + break; + case State::kTriggeredCallback: + case State::kExitThread: + // Internal states, no need to report. + break; + } + } + } +} + } // namespace android::drm_hwcomposer diff --git a/compositor/FlatteningController.h b/compositor/FlatteningController.h index 253ef7e..0db7b8c 100644 --- a/compositor/FlatteningController.h +++ b/compositor/FlatteningController.h @@ -19,22 +19,27 @@ #include #include #include +#include #include #include +#include "hwc/HwcDisplay.h" + namespace android::drm_hwcomposer { // NOLINTNEXTLINE(misc-unused-using-decls): False positive using std::chrono_literals::operator""s; +class FlatteningEventAtomReporter; + struct FlatConCallbacks { std::function trigger; }; class FlatteningController { public: - FlatteningController(FlatConCallbacks callbacks, + FlatteningController(DisplayHandle handle, FlatConCallbacks callbacks, std::chrono::milliseconds timeout); ~FlatteningController(); @@ -49,16 +54,6 @@ class FlatteningController { // and should be flattened by the compositor. bool ShouldFlatten() const; - private: - // Stop the helper thread - void StopThread(); - - void ThreadFn(); - - std::thread thread_; - mutable std::mutex mutex_; - std::condition_variable cv_; - enum class State { // Thread is not active, should not flatten. kDisabled, @@ -72,6 +67,18 @@ class FlatteningController { kExitThread, }; + private: + // Stop the helper thread + void StopThread(); + + void ThreadFn(); + + void SetState(State state) EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + std::thread thread_; + mutable std::mutex mutex_; + std::condition_variable cv_; + /* Disable the controller by default as it can cause refresh event to be * issued at creation time, even when it is not required. This can fail VTS * tests at teardown that check for this behaviour. See: @@ -79,6 +86,9 @@ class FlatteningController { */ State state_ GUARDED_BY(mutex_) = State::kDisabled; + const DisplayHandle handle_; + const std::unique_ptr reporter_; + // Only accessed from helper thread. const FlatConCallbacks cbks_; decltype(std::chrono::system_clock::now()) sleep_until_{}; diff --git a/compositor/FlatteningControllerTests.cpp b/compositor/FlatteningControllerTests.cpp index 66e4c77..6d7919f 100644 --- a/compositor/FlatteningControllerTests.cpp +++ b/compositor/FlatteningControllerTests.cpp @@ -20,6 +20,7 @@ #include #include "compositor/FlatteningController.h" +#include "hwc/HwcDisplay.h" using ::testing::StrictMock; @@ -28,6 +29,7 @@ namespace android::drm_hwcomposer { namespace { constexpr auto kTestTimeout = std::chrono::milliseconds(100); constexpr auto kTimeoutEpsilon = std::chrono::milliseconds(50); +constexpr DisplayHandle kHandle = {0}; } // namespace class MockFlatConCallbacks { @@ -43,7 +45,8 @@ class FlatteningControllerTest : public ::testing::Test { TEST_F(FlatteningControllerTest, DisabledOnCreation) { FlatConCallbacks cbks = {.trigger = EmptyTrigger}; - auto flat_con = std::make_unique(cbks, kTestTimeout); + auto flat_con = std::make_unique(kHandle, cbks, + kTestTimeout); EXPECT_FALSE(flat_con->ShouldFlatten()); @@ -54,7 +57,8 @@ TEST_F(FlatteningControllerTest, DisabledOnCreation) { TEST_F(FlatteningControllerTest, EnabledAfterNewFrame) { FlatConCallbacks cbks = {.trigger = EmptyTrigger}; - auto flat_con = std::make_unique(cbks, kTestTimeout); + auto flat_con = std::make_unique(kHandle, cbks, + kTestTimeout); flat_con->NewFrame(); EXPECT_FALSE(flat_con->ShouldFlatten()); @@ -66,7 +70,8 @@ TEST_F(FlatteningControllerTest, EnabledAfterNewFrame) { TEST_F(FlatteningControllerTest, DisabledAfterCallingDisable) { FlatConCallbacks cbks = {.trigger = EmptyTrigger}; - auto flat_con = std::make_unique(cbks, kTestTimeout); + auto flat_con = std::make_unique(kHandle, cbks, + kTestTimeout); flat_con->NewFrame(); std::this_thread::sleep_for(kTestTimeout + kTimeoutEpsilon); @@ -82,7 +87,8 @@ TEST_F(FlatteningControllerTest, DisabledAfterCallingDisable) { TEST_F(FlatteningControllerTest, TriggersCallbackAfterTimeout) { StrictMock mock_cb; FlatConCallbacks cbks = {.trigger = [&]() { mock_cb.Trigger(); }}; - auto flat_con = std::make_unique(cbks, kTestTimeout); + auto flat_con = std::make_unique(kHandle, cbks, + kTestTimeout); EXPECT_CALL(mock_cb, Trigger()).Times(1); @@ -94,7 +100,8 @@ TEST_F(FlatteningControllerTest, TriggersCallbackAfterTimeout) { TEST_F(FlatteningControllerTest, ShouldFlattenAfterFirstNewFrame) { StrictMock mock_cb; FlatConCallbacks cbks = {.trigger = [&]() { mock_cb.Trigger(); }}; - auto flat_con = std::make_unique(cbks, kTestTimeout); + auto flat_con = std::make_unique(kHandle, cbks, + kTestTimeout); EXPECT_CALL(mock_cb, Trigger()).Times(1); @@ -112,7 +119,8 @@ TEST_F(FlatteningControllerTest, ShouldFlattenAfterFirstNewFrame) { TEST_F(FlatteningControllerTest, ShouldNotFlattenAfterSecondNewFrame) { StrictMock mock_cb; FlatConCallbacks cbks = {.trigger = [&]() { mock_cb.Trigger(); }}; - auto flat_con = std::make_unique(cbks, kTestTimeout); + auto flat_con = std::make_unique(kHandle, cbks, + kTestTimeout); EXPECT_CALL(mock_cb, Trigger()).Times(1); @@ -145,7 +153,8 @@ TEST_F(FlatteningControllerTest, TriggersCallbackAtCorrectTime) { triggered = true; cv.notify_one(); }}; - auto flat_con = std::make_unique(cbks, kTestTimeout); + auto flat_con = std::make_unique(kHandle, cbks, + kTestTimeout); start_time = std::chrono::steady_clock::now(); flat_con->NewFrame(); @@ -174,7 +183,8 @@ TEST_F(FlatteningControllerTest, ResetsTimeoutOnNewFrame) { triggered = true; cv.notify_one(); }}; - auto flat_con = std::make_unique(cbks, kTestTimeout); + auto flat_con = std::make_unique(kHandle, cbks, + kTestTimeout); flat_con->NewFrame(); std::this_thread::sleep_for(kTestTimeout / 2); diff --git a/compositor/FlatteningEventAtomReporter.cpp b/compositor/FlatteningEventAtomReporter.cpp new file mode 100644 index 0000000..a270ce5 --- /dev/null +++ b/compositor/FlatteningEventAtomReporter.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 "FlatteningEventAtomReporter.h" + +#include "utils/log.h" + +namespace android::drm_hwcomposer { + +std::unique_ptr +FlatteningEventAtomReporter::Create() { + ALOGI("Atom reporting is not enabled."); + return {}; +} + +} // namespace android::drm_hwcomposer diff --git a/compositor/FlatteningEventAtomReporter.h b/compositor/FlatteningEventAtomReporter.h new file mode 100644 index 0000000..30cac79 --- /dev/null +++ b/compositor/FlatteningEventAtomReporter.h @@ -0,0 +1,41 @@ +/* + * 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 + +#include "compositor/FlatteningController.h" + +namespace android::drm_hwcomposer { + +// CompositionStatsAtomReporter is a wrapper around creation of a VendorAtom +// and pushing it to the IStats::reportVendorAtom interface. +class FlatteningEventAtomReporter { + public: + // Returns nullptr if atom reporting is not enabled via soong config variables + // or if there is some error getting the IStats service. + static std::unique_ptr Create(); + + virtual ~FlatteningEventAtomReporter() = default; + + // Pushes a Vendor Atom to IStats::reportVendorAtom. + virtual void PushAtom(int64_t display_handle, + FlatteningController::State state) = 0; +}; + +} // namespace android::drm_hwcomposer diff --git a/compositor/FlatteningEventAtomReporterDesktop.cpp b/compositor/FlatteningEventAtomReporterDesktop.cpp new file mode 100644 index 0000000..4d2196f --- /dev/null +++ b/compositor/FlatteningEventAtomReporterDesktop.cpp @@ -0,0 +1,121 @@ +/* + * 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 "FlatteningEventAtomReporter.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "compositor/FlatteningController.h" +#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 { + +using FlatteningState = FlatteningController::State; + +const std::string kStatsServiceName = std::string(IStats::descriptor) + .append("/default"); + +DesktopAtoms::FlatteningStateChanged::FlatteningState FlatteningStateToAtomType( + FlatteningState state) { + switch (state) { + case FlatteningState::kDisabled: + return DesktopAtoms::FlatteningStateChanged::FlatteningState:: + FLATTENING_STATE_DISABLED; + case FlatteningState::kActive: + return DesktopAtoms::FlatteningStateChanged::FlatteningState:: + FLATTENING_STATE_ACTIVE; + case FlatteningState ::kFlattened: + return DesktopAtoms::FlatteningStateChanged::FlatteningState:: + FLATTENING_STATE_FLATTENED; + case FlatteningState::kTriggeredCallback: + case FlatteningState::kExitThread: + return DesktopAtoms::FlatteningStateChanged::FlatteningState:: + FLATTENING_STATE_UNSPECIFIED; + } + LOG_ALWAYS_FATAL("Unknown FlatteningController::State value=%d", + static_cast(state)); +} + +std::string StateToString(FlatteningState state) { + switch (state) { + case FlatteningState::kDisabled: + return "Disabled"; + case FlatteningState::kActive: + return "Active"; + case FlatteningState::kTriggeredCallback: + return "TriggeredCallback"; + case FlatteningState::kFlattened: + return "Flattened"; + case FlatteningState::kExitThread: + return "ExitThread"; + } + LOG_ALWAYS_FATAL("Unknown FlatteningController::State value=%d", + static_cast(state)); +} + +// Use a private implementation of FlatteningEventAtomReporter to avoid leaking +// the IStats interface through the public api. +class FlatteningEventAtomReporterDesktop : public FlatteningEventAtomReporter { + public: + void PushAtom(int64_t display_handle, FlatteningState state) override { + ALOGV("Sending flattening state change event: display_handle=%" PRId64 + " state=%s", + display_handle, StateToString(state).c_str()); + + const char* kDeprecatedReverseDomainName = ""; + const VendorAtom atom = DesktopAtoms:: + createVendorAtom(DesktopAtoms::FLATTENING_STATE_CHANGED, + kDeprecatedReverseDomainName, display_handle, + FlatteningStateToAtomType(state)); + 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(atom); + ALOGE_IF(!ret.isOk(), "Failed to report stats: %s", + ret.getDescription().c_str()); + } + } +}; + +} // namespace + +std::unique_ptr +FlatteningEventAtomReporter::Create() { + if (!AServiceManager_isDeclared(kStatsServiceName.c_str())) { + ALOGW("Stats service is not declared."); + return nullptr; + } + return std::make_unique(); +} + +} // namespace android::drm_hwcomposer diff --git a/hwc/HwcDisplay.cpp b/hwc/HwcDisplay.cpp index 13df9d3..1d21504 100644 --- a/hwc/HwcDisplay.cpp +++ b/hwc/HwcDisplay.cpp @@ -24,10 +24,12 @@ #include #include +#include #include #include "backend/CompositionPlanner.h" #include "compositor/DisplayInfo.h" +#include "compositor/FlatteningController.h" #include "drm/DrmConnector.h" #include "drm/DrmDisplayPipeline.h" #include "drm/DrmHwc.h" @@ -660,7 +662,7 @@ bool HwcDisplay::Init() { if (!IsInHeadlessMode()) { auto flatcbk = (struct FlatConCallbacks){ .trigger = [this]() { hwc_->SendRefreshEventToClient(handle_); }}; - flatcon_ = std::make_unique(flatcbk, + flatcon_ = std::make_unique(handle_, flatcbk, kFlatteningTimeout); #if HAS_LIBDISPLAY_INFO diff --git a/hwc/HwcDisplay.h b/hwc/HwcDisplay.h index 61cf006..fd1f099 100644 --- a/hwc/HwcDisplay.h +++ b/hwc/HwcDisplay.h @@ -18,19 +18,24 @@ #include -#include - #include "HwcDisplayConfigs.h" #include "HwcLayer.h" #include "backend/CompositionPlanner.h" #include "compositor/DisplayInfo.h" -#include "compositor/FlatteningController.h" #include "compositor/LayerData.h" #include "drm/DrmAtomicStateManager.h" #include "drm/VSyncWorker.h" #include "stats/CompositionStats.h" #include "utils/EdidWrapper.h" +namespace aidl::android::hardware::graphics::common { +enum class Hdr; +} // namespace aidl::android::hardware::graphics::common + +namespace android::ui { +using aidl::android::hardware::graphics::common::Hdr; +} // namespace android::ui + namespace android::drm_hwcomposer { using DisplayHandle = int64_t; @@ -38,6 +43,7 @@ using EdidWrapperUnique = std::unique_ptr; class CompositionPlanner; class DrmHwc; +class FlatteningController; class FrontendDisplayBase { public: diff --git a/hwc2_device/hwc2_device.cpp b/hwc2_device/hwc2_device.cpp index f9d7c8f..fffe7aa 100644 --- a/hwc2_device/hwc2_device.cpp +++ b/hwc2_device/hwc2_device.cpp @@ -17,8 +17,6 @@ // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) // #define LOG_NDEBUG 0 // Uncomment to see HWC2 API calls in logcat -#include "hardware/hwcomposer2.h" -#include "system/graphics-base-v1.1.h" #define LOG_TAG "drmhwc" #include @@ -27,11 +25,14 @@ #include #include +#include #include "DrmHwcTwo.h" #include "backend/CompositionPlanner.h" #include "compositor/DisplayInfo.h" +#include "hardware/hwcomposer2.h" #include "hwc/HwcLayer.h" +#include "system/graphics-base-v1.1.h" #include "utils/log.h" namespace android::drm_hwcomposer { diff --git a/meson.build b/meson.build index 21c5348..453f456 100644 --- a/meson.build +++ b/meson.build @@ -12,6 +12,7 @@ inc_include = [include_directories('.')] src_common = files( 'compositor/DrmKmsPlan.cpp', 'compositor/FlatteningController.cpp', + 'compositor/FlatteningEventAtomReporter.cpp', 'backend/BackendManager.cpp', 'backend/GenericCompositionPlanner.cpp', 'backend/ClientBackend.cpp', diff --git a/utils/EdidWrapper.h b/utils/EdidWrapper.h index 7a512f7..381f8d5 100644 --- a/utils/EdidWrapper.h +++ b/utils/EdidWrapper.h @@ -26,9 +26,13 @@ extern "C" { #include "compositor/DisplayInfo.h" #include "drm/DrmUnique.h" -namespace ui { +namespace aidl::android::hardware::graphics::common { enum class Hdr; -} // namespace ui +} // namespace aidl::android::hardware::graphics::common + +namespace android::ui { +using aidl::android::hardware::graphics::common::Hdr; +} // namespace android::ui namespace android::drm_hwcomposer {