Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0b738740 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "end2end: Generate Hwc3 vsync events [5/N]" into main

parents e9acb433 7a9735b3
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -34,12 +34,18 @@ cc_test {
        "main.cpp",
        "test_framework/core/EdidBuilder.cpp",
        "test_framework/core/TestService.cpp",
        "test_framework/hwc3/DisplayVSyncEventService.cpp",
        "test_framework/hwc3/FakeComposer.cpp",
        "test_framework/hwc3/Hwc3Controller.cpp",
        "test_framework/hwc3/MultiDisplayRefreshEventGenerator.cpp",
        "test_framework/hwc3/SingleDisplayRefreshEventGenerator.cpp",
        "test_framework/hwc3/SingleDisplayRefreshSchedule.cpp",
        "test_framework/hwc3/TimeKeeperThread.cpp",
        "test_framework/surfaceflinger/SFController.cpp",

        // Internal tests
        "tests/internal/AsyncFunction_test.cpp",
        "tests/internal/DisplayRefresh_test.cpp",
        "tests/internal/EdidBuilder_test.cpp",

        // SurfaceFlinger tests
+51 −0
Original line number Diff line number Diff line
/*
 * Copyright 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 <chrono>
#include <string>

#include <fmt/chrono.h>  // NOLINT(misc-include-cleaner)
#include <fmt/format.h>

namespace android::surfaceflinger::tests::end2end::test_framework::core {

// Represents a span of time between two absolute points in time.
// The interval is half-open, including the beginning point, but not the end point.
// Note: "interval" is used to be consistent with the mathematical definition of the term.
// NOLINTBEGIN(misc-non-private-member-variables-in-classes)
struct TimeInterval final {
    using TimePoint = std::chrono::steady_clock::time_point;

    // The half-open interval [begin, end), where the end time is not included.
    TimePoint begin;
    TimePoint end;

    [[nodiscard]] auto empty() const -> bool { return begin == end; }
    friend auto operator==(const TimeInterval&, const TimeInterval&) -> bool = default;
};
// NOLINTEND(misc-non-private-member-variables-in-classes)

inline auto toString(const TimeInterval& value) -> std::string {
    return fmt::format("[{}, {})", value.begin.time_since_epoch(), value.end.time_since_epoch());
}

inline auto format_as(const TimeInterval& event) -> std::string {
    return toString(event);
}

}  // namespace android::surfaceflinger::tests::end2end::test_framework::core
+106 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.
 */

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <android-base/expected.h>
#include <android-base/logging.h>
#include <ftl/ignore.h>

#include "test_framework/core/GuardedSharedState.h"
#include "test_framework/hwc3/DisplayVSyncEventService.h"
#include "test_framework/hwc3/MultiDisplayRefreshEventGenerator.h"
#include "test_framework/hwc3/SingleDisplayRefreshSchedule.h"
#include "test_framework/hwc3/TimeKeeperThread.h"
#include "test_framework/hwc3/events/VSync.h"

namespace android::surfaceflinger::tests::end2end::test_framework::hwc3 {

struct DisplayVSyncEventService::Passkey final {};

auto DisplayVSyncEventService::make()
        -> base::expected<std::shared_ptr<DisplayVSyncEventService>, std::string> {
    using namespace std::string_literals;

    auto service = std::make_unique<DisplayVSyncEventService>(Passkey{});
    if (service == nullptr) {
        return base::unexpected("Failed to construct the DisplayVSyncEventService"s);
    }
    if (auto result = service->init(); !result) {
        return base::unexpected("Failed to init the DisplayVSyncEventService: "s + result.error());
    }
    return service;
}

DisplayVSyncEventService::DisplayVSyncEventService(Passkey passkey) {
    ftl::ignore(passkey);
}

auto DisplayVSyncEventService::init() -> base::expected<void, std::string> {
    auto timekeeperMake = TimeKeeperThread::make();
    if (!timekeeperMake) {
        return base::unexpected(std::move(timekeeperMake.error()));
    }

    mTimeKeeper = *std::move(timekeeperMake);
    mTimeKeeper->editCallbacks().onWakeup.set(
            [this](TimeInterval sleeping) -> TimePoint { return onWakeup(sleeping); })();
    return {};
}

auto DisplayVSyncEventService::editCallbacks() -> Callbacks& {
    return mCallbacks;
}

void DisplayVSyncEventService::addDisplay(DisplayId displayId,
                                          SingleDisplayRefreshSchedule schedule) {
    mGenerator.withExclusiveLock([displayId, schedule](auto& generator) {
        LOG(VERBOSE) << "addDisplay: " << displayId << " schedule: " << toString(schedule);
        generator.addDisplay(displayId, schedule);
    });

    mTimeKeeper->wakeNow();
}

void DisplayVSyncEventService::removeDisplay(DisplayId displayId) {
    mGenerator.withExclusiveLock([displayId](auto& generator) {
        LOG(VERBOSE) << "remove " << displayId;
        generator.removeDisplay(displayId);
    });
}

auto DisplayVSyncEventService::onWakeup(TimeInterval elapsed) -> TimePoint {
    // Obtain all the events to emit.
    auto result = mGenerator.withExclusiveLock(
            [elapsed](auto& generator) { return generator.generateEventsFor(elapsed); });

    // Emit them via the connector interface.
    LOG(VERBOSE) << "emitting " << result.events.size() << " events";
    for (const auto& event : result.events) {
        LOG(VERBOSE) << "displayId: " << event.displayId
                     << " timestamp: " << event.expectedAt.time_since_epoch()
                     << " period: " << event.expectedPeriod;
        mCallbacks.onVSync(event);
    }

    LOG(VERBOSE) << "next wakeup " << result.nextRefreshAt.time_since_epoch();
    return result.nextRefreshAt;
}

}  // namespace android::surfaceflinger::tests::end2end::test_framework::hwc3
+74 −0
Original line number Diff line number Diff line
/*
 * Copyright 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 <chrono>
#include <memory>
#include <string>

#include <android-base/expected.h>

#include "test_framework/core/DisplayConfiguration.h"
#include "test_framework/core/GuardedSharedState.h"
#include "test_framework/core/TimeInterval.h"
#include "test_framework/hwc3/MultiDisplayRefreshEventGenerator.h"
#include "test_framework/hwc3/SingleDisplayRefreshSchedule.h"
#include "test_framework/hwc3/events/VSync.h"

namespace android::surfaceflinger::tests::end2end::test_framework::hwc3 {

class TimeKeeperThread;

class DisplayVSyncEventService final {
    struct Passkey;  // Uses the passkey idiom to restrict construction.

  public:
    struct Callbacks final {
        events::VSync::AsyncConnector onVSync;
    };

    using DisplayId = core::DisplayConfiguration::Id;

    [[nodiscard]] static auto make()
            -> base::expected<std::shared_ptr<DisplayVSyncEventService>, std::string>;

    explicit DisplayVSyncEventService(Passkey passkey);

    [[nodiscard]] auto editCallbacks() -> Callbacks&;

    void addDisplay(DisplayId displayId, SingleDisplayRefreshSchedule schedule);

    void removeDisplay(DisplayId displayId);

  private:
    using TimePoint = std::chrono::steady_clock::time_point;
    using Duration = std::chrono::steady_clock::duration;
    using TimeInterval = core::TimeInterval;

    auto init() -> base::expected<void, std::string>;
    auto onWakeup(TimeInterval elapsed) -> TimePoint;

    core::GuardedSharedState<MultiDisplayRefreshEventGenerator> mGenerator;

    // No mutex needed.
    Callbacks mCallbacks;

    // Note: The time keeper thread should be destroyed first, so is declared last.
    std::unique_ptr<TimeKeeperThread> mTimeKeeper;
};

}  // namespace android::surfaceflinger::tests::end2end::test_framework::hwc3
+79 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@
#include <initializer_list>
#include <memory>
#include <optional>
#include <ratio>
#include <source_location>
#include <string>
#include <string_view>
@@ -58,10 +59,13 @@
#include "test_framework/core/DisplayConfiguration.h"
#include "test_framework/core/EdidBuilder.h"
#include "test_framework/core/GuardedSharedState.h"
#include "test_framework/hwc3/DisplayVSyncEventService.h"
#include "test_framework/hwc3/FakeComposer.h"
#include "test_framework/hwc3/FakeDisplayConfiguration.h"
#include "test_framework/hwc3/SingleDisplayRefreshSchedule.h"
#include "test_framework/hwc3/delegators/Composer.h"
#include "test_framework/hwc3/delegators/ComposerClient.h"
#include "test_framework/hwc3/events/VSync.h"

namespace android::surfaceflinger::tests::end2end::test_framework::hwc3 {

@@ -248,6 +252,16 @@ struct ClientImpl final : public DefaultComposerClientImpl {
        });
    }

    void sendVSync(events::VSync event) {
        mState.withSharedLock([event](const auto& state) {
            state.composerCallbacks->onVsync(
                    event.displayId, event.expectedAt.time_since_epoch().count(),
                    std::chrono::duration_cast<std::chrono::duration<int32_t, std::nano>>(
                            event.expectedPeriod)
                            .count());
        });
    }

    // begin IComposerClient overrides
    // NOLINTBEGIN(bugprone-easily-swappable-parameters)

@@ -569,6 +583,19 @@ struct ClientImpl final : public DefaultComposerClientImpl {
};

struct FakeComposer::ComposerImpl final : public DefaultComposerImpl {
    auto init() -> base::expected<void, std::string> {
        auto displayVsyncEventServiceResult = DisplayVSyncEventService::make();
        if (!displayVsyncEventServiceResult) {
            return base::unexpected(std::move(displayVsyncEventServiceResult).error());
        }

        mDisplayVSyncEventService = *std::move(displayVsyncEventServiceResult);
        mDisplayVSyncEventService->editCallbacks().onVSync.set(
                [this](auto event) { onVsyncEventGenerated(event); })();

        return {};
    }

    auto getComposer() -> std::shared_ptr<IComposer> { return ref<ComposerImpl>(); }

    void addDisplay(const core::DisplayConfiguration& display) {
@@ -603,6 +630,8 @@ struct FakeComposer::ComposerImpl final : public DefaultComposerImpl {
    }

    void removeDisplay(core::DisplayConfiguration::Id displayId) {
        mDisplayVSyncEventService->removeDisplay(displayId);

        auto client = mState.withExclusiveLock([displayId](auto& state) {
            if (state.initialDisplayId == displayId) {
                state.initialDisplayId.reset();
@@ -687,6 +716,51 @@ struct FakeComposer::ComposerImpl final : public DefaultComposerImpl {
        });
    }

    void onVsyncEventGenerated(events::VSync event) {
        if (auto client = mState.withSharedLock([](const auto& state) { return state.client; })) {
            client->sendVSync(event);
        }
    }

    void onVsyncEnabledChanged(int64_t displayId, bool enable) const {
        LOG(VERBOSE) << __func__;
        mState.withSharedLock([this, displayId, enable](const auto& state) {
            LOG(INFO) << "displayId: " << displayId << " enable: " << enable;
            if (!enable) {
                mDisplayVSyncEventService->removeDisplay(displayId);
                return;
            }

            auto found = state.configuredDisplays.find(displayId);
            if (found == state.configuredDisplays.end()) {
                LOG(WARNING) << "No display " << displayId << " to configure vsync generation.";
                return;
            }
            const auto& display = found->second;
            auto foundConfig = std::ranges::find_if(
                    display.displayConfigs, [configId = display.activeConfig](const auto& config) {
                        return config.configId == configId;
                    });
            if (foundConfig == display.displayConfigs.end()) {
                LOG(WARNING) << "No config " << display.activeConfig << " for display " << displayId
                             << " to configure vsync generation.";
                return;
            }
            const auto& config = *foundConfig;

            using TimePoint = std::chrono::steady_clock::time_point;
            using Duration = std::chrono::steady_clock::duration;

            LOG(WARNING) << "Assuming a base of zero for vsync timing!";
            const TimePoint base{Duration(0)};
            const auto period = Duration(config.vsyncPeriod);
            mDisplayVSyncEventService->addDisplay(
                    displayId, SingleDisplayRefreshSchedule{.base = base, .period = period});
        });
    }

    std::shared_ptr<DisplayVSyncEventService> mDisplayVSyncEventService;

    struct State {
        std::shared_ptr<ClientImpl> client;
        std::optional<int64_t> initialDisplayId;
@@ -725,7 +799,11 @@ auto FakeComposer::init() -> base::expected<void, std::string> {

    auto impl = ndk::SharedRefBase::make<ComposerImpl>();
    if (!impl) {
        return base::unexpected("Failed to construct the Hwc3ComposerImpl instance."s);
        return base::unexpected("Failed to construct the ComposerImpl instance."s);
    }

    if (auto result = impl->init(); !result) {
        return base::unexpected("Failed to init the ComposerImpl instance: "s + result.error());
    }

    mImpl = std::move(impl);
Loading