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

Commit 70d79c01 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "SF: add VSyncDispatch"

parents bed73cb0 305bef16
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -165,6 +165,7 @@ filegroup {
        "Scheduler/PhaseOffsets.cpp",
        "Scheduler/Scheduler.cpp",
        "Scheduler/SchedulerUtils.cpp",
        "Scheduler/VSyncDispatch.cpp",
        "Scheduler/VSyncModulator.cpp",
        "StartPropertySetThread.cpp",
        "SurfaceFlinger.cpp",
+53 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#pragma once

#include <utils/Timers.h>
#include <functional>

namespace android::scheduler {

/*
 * TimeKeeper is the interface for a single-shot timer primitive.
 */
class TimeKeeper {
public:
    virtual ~TimeKeeper();

    /*
     * Arms callback to fired in time nanoseconds.
     * There is only one timer, and subsequent calls will reset the callback function and the time.
     */
    virtual void alarmIn(std::function<void()> const& callback, nsecs_t time) = 0;

    /*
     * Cancels an existing pending callback
     */
    virtual void alarmCancel() = 0;

    /*
     * Returns the SYSTEM_TIME_MONOTONIC, used by testing infra to stub time.
     */
    virtual nsecs_t now() const = 0;

protected:
    TimeKeeper(TimeKeeper const&) = delete;
    TimeKeeper& operator=(TimeKeeper const&) = delete;
    TimeKeeper() = default;
};

} // namespace android::scheduler
+285 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include <utils/Trace.h>
#include <vector>

#include "TimeKeeper.h"
#include "VSyncDispatch.h"
#include "VSyncTracker.h"

namespace android::scheduler {

VSyncTracker::~VSyncTracker() = default;
TimeKeeper::~TimeKeeper() = default;

impl::VSyncDispatchEntry::VSyncDispatchEntry(std::string const& name,
                                             std::function<void(nsecs_t)> const& cb)
      : mName(name), mCallback(cb), mWorkDuration(0), mEarliestVsync(0) {}

std::optional<nsecs_t> impl::VSyncDispatchEntry::lastExecutedVsyncTarget() const {
    return mLastDispatchTime;
}

std::string_view impl::VSyncDispatchEntry::name() const {
    return mName;
}

std::optional<nsecs_t> impl::VSyncDispatchEntry::wakeupTime() const {
    if (!mArmedInfo) {
        return {};
    }
    return {mArmedInfo->mActualWakeupTime};
}

nsecs_t impl::VSyncDispatchEntry::schedule(nsecs_t workDuration, nsecs_t earliestVsync,
                                           VSyncTracker& tracker, nsecs_t now) {
    mWorkDuration = workDuration;
    mEarliestVsync = earliestVsync;
    arm(tracker, now);
    return mArmedInfo->mActualWakeupTime;
}

void impl::VSyncDispatchEntry::update(VSyncTracker& tracker, nsecs_t now) {
    if (!mArmedInfo) {
        return;
    }
    arm(tracker, now);
}

void impl::VSyncDispatchEntry::arm(VSyncTracker& tracker, nsecs_t now) {
    auto const nextVsyncTime =
            tracker.nextAnticipatedVSyncTimeFrom(std::max(mEarliestVsync, now + mWorkDuration));
    mArmedInfo = {nextVsyncTime - mWorkDuration, nextVsyncTime};
}

void impl::VSyncDispatchEntry::disarm() {
    mArmedInfo.reset();
}

nsecs_t impl::VSyncDispatchEntry::executing() {
    mLastDispatchTime = mArmedInfo->mActualVsyncTime;
    disarm();
    return *mLastDispatchTime;
}

void impl::VSyncDispatchEntry::callback(nsecs_t t) {
    {
        std::lock_guard<std::mutex> lk(mRunningMutex);
        mRunning = true;
    }

    mCallback(t);

    std::lock_guard<std::mutex> lk(mRunningMutex);
    mRunning = false;
    mCv.notify_all();
}

void impl::VSyncDispatchEntry::ensureNotRunning() {
    std::unique_lock<std::mutex> lk(mRunningMutex);
    mCv.wait(lk, [this]() REQUIRES(mRunningMutex) { return !mRunning; });
}

VSyncDispatch::VSyncDispatch(std::unique_ptr<TimeKeeper> tk, VSyncTracker& tracker,
                             nsecs_t timerSlack)
      : mTimeKeeper(std::move(tk)), mTracker(tracker), mTimerSlack(timerSlack) {}

VSyncDispatch::~VSyncDispatch() {
    std::lock_guard<decltype(mMutex)> lk(mMutex);
    cancelTimer();
}

void VSyncDispatch::cancelTimer() {
    mIntendedWakeupTime = kInvalidTime;
    mTimeKeeper->alarmCancel();
}

void VSyncDispatch::setTimer(nsecs_t targetTime, nsecs_t now) {
    mIntendedWakeupTime = targetTime;
    mTimeKeeper->alarmIn(std::bind(&VSyncDispatch::timerCallback, this), targetTime - now);
}

void VSyncDispatch::rearmTimer(nsecs_t now) {
    rearmTimerSkippingUpdateFor(now, mCallbacks.end());
}

void VSyncDispatch::rearmTimerSkippingUpdateFor(nsecs_t now,
                                                CallbackMap::iterator const& skipUpdateIt) {
    std::optional<nsecs_t> min;
    for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
        auto& callback = it->second;
        if (!callback->wakeupTime()) {
            continue;
        }

        if (it != skipUpdateIt) {
            callback->update(mTracker, now);
        }
        auto const wakeupTime = *callback->wakeupTime();
        if (!min || (min && *min > wakeupTime)) {
            min = wakeupTime;
        }
    }

    if (min && (min < mIntendedWakeupTime)) {
        setTimer(*min, now);
    } else {
        cancelTimer();
    }
}

void VSyncDispatch::timerCallback() {
    struct Invocation {
        std::shared_ptr<impl::VSyncDispatchEntry> callback;
        nsecs_t timestamp;
    };
    std::vector<Invocation> invocations;
    {
        std::lock_guard<decltype(mMutex)> lk(mMutex);
        for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
            auto& callback = it->second;
            auto const wakeupTime = callback->wakeupTime();
            if (!wakeupTime) {
                continue;
            }

            if (*wakeupTime < mIntendedWakeupTime + mTimerSlack) {
                callback->executing();
                invocations.emplace_back(
                        Invocation{callback, *callback->lastExecutedVsyncTarget()});
            }
        }

        mIntendedWakeupTime = kInvalidTime;
        rearmTimer(mTimeKeeper->now());
    }

    for (auto const& invocation : invocations) {
        invocation.callback->callback(invocation.timestamp);
    }
}

VSyncDispatch::CallbackToken VSyncDispatch::registerCallback(
        std::function<void(nsecs_t)> const& callbackFn, std::string callbackName) {
    std::lock_guard<decltype(mMutex)> lk(mMutex);
    return CallbackToken{
            mCallbacks
                    .emplace(++mCallbackToken,
                             std::make_shared<impl::VSyncDispatchEntry>(callbackName, callbackFn))
                    .first->first};
}

void VSyncDispatch::unregisterCallback(CallbackToken token) {
    std::shared_ptr<impl::VSyncDispatchEntry> entry = nullptr;
    {
        std::lock_guard<decltype(mMutex)> lk(mMutex);
        auto it = mCallbacks.find(token);
        if (it != mCallbacks.end()) {
            entry = it->second;
            mCallbacks.erase(it);
        }
    }

    if (entry) {
        entry->ensureNotRunning();
    }
}

ScheduleResult VSyncDispatch::schedule(CallbackToken token, nsecs_t workDuration,
                                       nsecs_t earliestVsync) {
    auto result = ScheduleResult::Error;
    {
        std::lock_guard<decltype(mMutex)> lk(mMutex);

        auto it = mCallbacks.find(token);
        if (it == mCallbacks.end()) {
            return result;
        }
        auto& callback = it->second;
        result = callback->wakeupTime() ? ScheduleResult::ReScheduled : ScheduleResult::Scheduled;

        auto const now = mTimeKeeper->now();
        auto const wakeupTime = callback->schedule(workDuration, earliestVsync, mTracker, now);

        if (wakeupTime < now - mTimerSlack || callback->lastExecutedVsyncTarget() > wakeupTime) {
            return ScheduleResult::CannotSchedule;
        }

        if (wakeupTime < mIntendedWakeupTime - mTimerSlack) {
            rearmTimerSkippingUpdateFor(now, it);
        }
    }

    return result;
}

CancelResult VSyncDispatch::cancel(CallbackToken token) {
    std::lock_guard<decltype(mMutex)> lk(mMutex);

    auto it = mCallbacks.find(token);
    if (it == mCallbacks.end()) {
        return CancelResult::Error;
    }
    auto& callback = it->second;

    if (callback->wakeupTime()) {
        callback->disarm();
        mIntendedWakeupTime = kInvalidTime;
        rearmTimer(mTimeKeeper->now());
        return CancelResult::Cancelled;
    }
    return CancelResult::TooLate;
}

VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,
                                                     std::function<void(nsecs_t)> const& callbackFn,
                                                     std::string const& callbackName)
      : mDispatch(dispatch),
        mToken(dispatch.registerCallback(callbackFn, callbackName)),
        mValidToken(true) {}

VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncCallbackRegistration&& other)
      : mDispatch(other.mDispatch),
        mToken(std::move(other.mToken)),
        mValidToken(std::move(other.mValidToken)) {
    other.mValidToken = false;
}

VSyncCallbackRegistration& VSyncCallbackRegistration::operator=(VSyncCallbackRegistration&& other) {
    mDispatch = std::move(other.mDispatch);
    mToken = std::move(other.mToken);
    mValidToken = std::move(other.mValidToken);
    other.mValidToken = false;
    return *this;
}

VSyncCallbackRegistration::~VSyncCallbackRegistration() {
    if (mValidToken) mDispatch.get().unregisterCallback(mToken);
}

ScheduleResult VSyncCallbackRegistration::schedule(nsecs_t workDuration, nsecs_t earliestVsync) {
    if (!mValidToken) return ScheduleResult::Error;
    return mDispatch.get().schedule(mToken, workDuration, earliestVsync);
}

CancelResult VSyncCallbackRegistration::cancel() {
    if (!mValidToken) return CancelResult::Error;
    return mDispatch.get().cancel(mToken);
}

} // namespace android::scheduler
+230 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#pragma once

#include <android-base/thread_annotations.h>
#include <utils/Timers.h>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <unordered_map>

#include "StrongTyping.h"

namespace android::scheduler {
class TimeKeeper;
class VSyncTracker;

enum class ScheduleResult { Scheduled, ReScheduled, CannotSchedule, Error };
enum class CancelResult { Cancelled, TooLate, Error };

namespace impl {

// VSyncDispatchEntry is a helper class representing internal state for each entry in VSyncDispatch
// hoisted to public for unit testing.
class VSyncDispatchEntry {
public:
    // This is the state of the entry. There are 3 states, armed, running, disarmed.
    // Valid transition: disarmed -> armed ( when scheduled )
    // Valid transition: armed -> running -> disarmed ( when timer is called)
    // Valid transition: armed -> disarmed ( when cancelled )
    VSyncDispatchEntry(std::string const& name, std::function<void(nsecs_t)> const& fn);
    std::string_view name() const;

    // Start: functions that are not threadsafe.
    // Return the last vsync time this callback was invoked.
    std::optional<nsecs_t> lastExecutedVsyncTarget() const;

    // This moves the state from disarmed->armed and will calculate the wakeupTime.
    nsecs_t schedule(nsecs_t workDuration, nsecs_t earliestVsync, VSyncTracker& tracker,
                     nsecs_t now);
    // This will update armed entries with the latest vsync information. Entry remains armed.
    void update(VSyncTracker& tracker, nsecs_t now);

    // This will return empty if not armed, or the next calculated wakeup time if armed.
    // It will not update the wakeupTime.
    std::optional<nsecs_t> wakeupTime() const;

    // This moves state from armed->disarmed.
    void disarm();

    // This moves the state from armed->running.
    // Store the timestamp that this was intended for as the last called timestamp.
    nsecs_t executing();
    // End: functions that are not threadsafe.

    // Invoke the callback with the timestamp, moving the state from running->disarmed.
    void callback(nsecs_t timestamp);
    // Block calling thread while the callback is executing.
    void ensureNotRunning();

private:
    void arm(VSyncTracker& tracker, nsecs_t now);
    std::string const mName;
    std::function<void(nsecs_t)> const mCallback;

    nsecs_t mWorkDuration;
    nsecs_t mEarliestVsync;

    struct ArmingInfo {
        nsecs_t mActualWakeupTime;
        nsecs_t mActualVsyncTime;
    };
    std::optional<ArmingInfo> mArmedInfo;
    std::optional<nsecs_t> mLastDispatchTime;

    std::mutex mRunningMutex;
    std::condition_variable mCv;
    bool mRunning GUARDED_BY(mRunningMutex) = false;
};

} // namespace impl

/*
 * VSyncDispatch is a class that will dispatch callbacks relative to system vsync events.
 */
class VSyncDispatch {
public:
    using CallbackToken = StrongTyping<size_t, class CallbackTokenTag, Compare>;

    /* creates a VsyncDispatch.
     * \param [in]  a timekeeper object for dispatching events.
     * \param [in]  a tracker object that is monitoring expected vsync events.
     * \param [in]  a tunable in nanoseconds that indicates when events that fall close together
     *              should be dispatched in one timer wakeup.
     */
    explicit VSyncDispatch(std::unique_ptr<TimeKeeper> tk, VSyncTracker& tracker,
                           nsecs_t timerSlack);
    ~VSyncDispatch();

    /*
     * Registers a callback that will be called at designated points on the vsync timeline.
     * The callback can be scheduled, rescheduled targeting vsync times, or cancelled.
     * The token returned must be cleaned up via unregisterCallback.
     *
     * \param [in] callbackFn   A function to schedule for callback. The resources needed to invoke
     *                          callbackFn must have lifetimes encompassing the lifetime of the
     *                          CallbackToken returned.
     * \param [in] callbackName A human-readable, unique name to identify the callback.
     * \return                  A token that can be used to schedule, reschedule, or cancel the
     *                          invocation of callbackFn.
     *
     */
    CallbackToken registerCallback(std::function<void(nsecs_t)> const& callbackFn,
                                   std::string callbackName);

    /*
     * Unregisters a callback.
     *
     * \param [in] token        The callback to unregister.
     *
     */
    void unregisterCallback(CallbackToken token);

    /*
     * Schedules the registered callback to be dispatched.
     *
     * The callback will be dispatched at 'workDuration' nanoseconds before a vsync event.
     *
     * The caller designates the earliest vsync event that should be targeted by the earliestVsync
     * parameter.
     * The callback will be scheduled at (workDuration - predictedVsync), where predictedVsync
     * is the first vsync event time where ( predictedVsync >= earliestVsync ).
     *
     * If (workDuration - earliestVsync) is in the past, or if a callback has already been
     * dispatched for the predictedVsync, an error will be returned.
     *
     * It is valid to reschedule a callback to a different time.
     *
     * \param [in] token           The callback to schedule.
     * \param [in] workDuration    The time before the actual vsync time to invoke the callback
     *                             associated with token.
     * \param [in] earliestVsync   The targeted display time. This will be snapped to the closest
     *                             predicted vsync time after earliestVsync.
     * \return                     A ScheduleResult::Scheduled if callback was scheduled.
     *                             A ScheduleResult::ReScheduled if callback was rescheduled.
     *                             A ScheduleResult::CannotSchedule
     *                             if (workDuration - earliestVsync) is in the past, or
     *                             if a callback was dispatched for the predictedVsync already.
     *                             A ScheduleResult::Error if there was another error.
     */
    ScheduleResult schedule(CallbackToken token, nsecs_t workDuration, nsecs_t earliestVsync);

    /* Cancels a scheduled callback, if possible.
     *
     * \param [in] token    The callback to cancel.
     * \return              A CancelResult::TooLate if the callback was already dispatched.
     *                      A CancelResult::Cancelled if the callback was successfully cancelled.
     *                      A CancelResult::Error if there was an pre-condition violation.
     */
    CancelResult cancel(CallbackToken token);

private:
    VSyncDispatch(VSyncDispatch const&) = delete;
    VSyncDispatch& operator=(VSyncDispatch const&) = delete;

    using CallbackMap = std::unordered_map<size_t, std::shared_ptr<impl::VSyncDispatchEntry>>;

    void timerCallback();
    void setTimer(nsecs_t, nsecs_t) REQUIRES(mMutex);
    void rearmTimer(nsecs_t now) REQUIRES(mMutex);
    void rearmTimerSkippingUpdateFor(nsecs_t now, CallbackMap::iterator const& skipUpdate)
            REQUIRES(mMutex);
    void cancelTimer() REQUIRES(mMutex);

    static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max();
    std::unique_ptr<TimeKeeper> const mTimeKeeper;
    VSyncTracker& mTracker;
    nsecs_t const mTimerSlack;

    std::mutex mutable mMutex;
    size_t mCallbackToken GUARDED_BY(mMutex) = 0;

    CallbackMap mCallbacks GUARDED_BY(mMutex);
    nsecs_t mIntendedWakeupTime GUARDED_BY(mMutex) = kInvalidTime;
};

/*
 * Helper class to operate on registered callbacks. It is up to user of the class to ensure
 * that VsyncDispatch lifetime exceeds the lifetime of VSyncCallbackRegistation.
 */
class VSyncCallbackRegistration {
public:
    VSyncCallbackRegistration(VSyncDispatch&, std::function<void(nsecs_t)> const& callbackFn,
                              std::string const& callbackName);
    VSyncCallbackRegistration(VSyncCallbackRegistration&&);
    VSyncCallbackRegistration& operator=(VSyncCallbackRegistration&&);
    ~VSyncCallbackRegistration();

    // See documentation for VSyncDispatch::schedule.
    ScheduleResult schedule(nsecs_t workDuration, nsecs_t earliestVsync);

    // See documentation for VSyncDispatch::cancel.
    CancelResult cancel();

private:
    VSyncCallbackRegistration(VSyncCallbackRegistration const&) = delete;
    VSyncCallbackRegistration& operator=(VSyncCallbackRegistration const&) = delete;

    std::reference_wrapper<VSyncDispatch> mDispatch;
    VSyncDispatch::CallbackToken mToken;
    bool mValidToken;
};

} // namespace android::scheduler
+56 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#pragma once

#include <utils/Timers.h>
#include "VSyncDispatch.h"

namespace android::scheduler {
/*
 * VSyncTracker is an interface for providing estimates on future Vsync signal times based on
 * historical vsync timing data.
 */
class VSyncTracker {
public:
    virtual ~VSyncTracker();

    /*
     * Adds a known timestamp from a vsync timing source (HWVsync signal, present fence)
     * to the model.
     *
     * \param [in] timestamp    The timestamp when the vsync signal was.
     */
    virtual void addVsyncTimestamp(nsecs_t timestamp) = 0;

    /*
     * Access the next anticipated vsync time such that the anticipated time >= timePoint.
     * This will always give the best accurate at the time of calling; multiple
     * calls with the same timePoint might give differing values if the internal model
     * is updated.
     *
     * \param [in] timePoint    The point in time after which to estimate a vsync event.
     * \return                  A prediction of the timestamp of a vsync event.
     */
    virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const = 0;

protected:
    VSyncTracker(VSyncTracker const&) = delete;
    VSyncTracker& operator=(VSyncTracker const&) = delete;
    VSyncTracker() = default;
};

} // namespace android::scheduler
Loading