blob: 2e5b6e96932b242b99c631ab253f79c1a47361fd [file] [log] [blame]
/*
* 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 "VSyncDispatchTimerQueue.h"
#include "VSyncTracker.h"
namespace android::scheduler {
VSyncDispatch::~VSyncDispatch() = default;
VSyncTracker::~VSyncTracker() = default;
TimeKeeper::~TimeKeeper() = default;
VSyncDispatchTimerQueueEntry::VSyncDispatchTimerQueueEntry(std::string const& name,
VSyncDispatch::Callback const& cb,
nsecs_t minVsyncDistance)
: mName(name),
mCallback(cb),
mWorkDuration(0),
mEarliestVsync(0),
mMinVsyncDistance(minVsyncDistance) {}
std::optional<nsecs_t> VSyncDispatchTimerQueueEntry::lastExecutedVsyncTarget() const {
return mLastDispatchTime;
}
std::string_view VSyncDispatchTimerQueueEntry::name() const {
return mName;
}
std::optional<nsecs_t> VSyncDispatchTimerQueueEntry::wakeupTime() const {
if (!mArmedInfo) {
return {};
}
return {mArmedInfo->mActualWakeupTime};
}
ScheduleResult VSyncDispatchTimerQueueEntry::schedule(nsecs_t workDuration, nsecs_t earliestVsync,
VSyncTracker& tracker, nsecs_t now) {
auto nextVsyncTime =
tracker.nextAnticipatedVSyncTimeFrom(std::max(earliestVsync, now + workDuration));
bool const wouldSkipAVsyncTarget =
mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
if (wouldSkipAVsyncTarget) {
return ScheduleResult::Scheduled;
}
bool const alreadyDispatchedForVsync = mLastDispatchTime &&
((*mLastDispatchTime + mMinVsyncDistance) >= nextVsyncTime &&
(*mLastDispatchTime - mMinVsyncDistance) <= nextVsyncTime);
if (alreadyDispatchedForVsync) {
nextVsyncTime =
tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance);
}
auto const nextWakeupTime = nextVsyncTime - workDuration;
mWorkDuration = workDuration;
mEarliestVsync = earliestVsync;
mArmedInfo = {nextWakeupTime, nextVsyncTime};
return ScheduleResult::Scheduled;
}
void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) {
if (!mArmedInfo) {
return;
}
auto const nextVsyncTime =
tracker.nextAnticipatedVSyncTimeFrom(std::max(mEarliestVsync, now + mWorkDuration));
mArmedInfo = {nextVsyncTime - mWorkDuration, nextVsyncTime};
}
void VSyncDispatchTimerQueueEntry::disarm() {
mArmedInfo.reset();
}
nsecs_t VSyncDispatchTimerQueueEntry::executing() {
mLastDispatchTime = mArmedInfo->mActualVsyncTime;
disarm();
return *mLastDispatchTime;
}
void VSyncDispatchTimerQueueEntry::callback(nsecs_t vsyncTimestamp, nsecs_t wakeupTimestamp) {
{
std::lock_guard<std::mutex> lk(mRunningMutex);
mRunning = true;
}
mCallback(vsyncTimestamp, wakeupTimestamp);
std::lock_guard<std::mutex> lk(mRunningMutex);
mRunning = false;
mCv.notify_all();
}
void VSyncDispatchTimerQueueEntry::ensureNotRunning() {
std::unique_lock<std::mutex> lk(mRunningMutex);
mCv.wait(lk, [this]() REQUIRES(mRunningMutex) { return !mRunning; });
}
VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper> tk,
VSyncTracker& tracker, nsecs_t timerSlack,
nsecs_t minVsyncDistance)
: mTimeKeeper(std::move(tk)),
mTracker(tracker),
mTimerSlack(timerSlack),
mMinVsyncDistance(minVsyncDistance) {}
VSyncDispatchTimerQueue::~VSyncDispatchTimerQueue() {
std::lock_guard<decltype(mMutex)> lk(mMutex);
cancelTimer();
}
void VSyncDispatchTimerQueue::cancelTimer() {
mIntendedWakeupTime = kInvalidTime;
mTimeKeeper->alarmCancel();
}
void VSyncDispatchTimerQueue::setTimer(nsecs_t targetTime, nsecs_t now) {
mIntendedWakeupTime = targetTime;
mTimeKeeper->alarmIn(std::bind(&VSyncDispatchTimerQueue::timerCallback, this),
targetTime - now);
}
void VSyncDispatchTimerQueue::rearmTimer(nsecs_t now) {
rearmTimerSkippingUpdateFor(now, mCallbacks.end());
}
void VSyncDispatchTimerQueue::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 VSyncDispatchTimerQueue::timerCallback() {
struct Invocation {
std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;
nsecs_t vsyncTimestamp;
nsecs_t wakeupTimestamp;
};
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(), *wakeupTime});
}
}
mIntendedWakeupTime = kInvalidTime;
rearmTimer(mTimeKeeper->now());
}
for (auto const& invocation : invocations) {
invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp);
}
}
VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback(
Callback const& callbackFn, std::string callbackName) {
std::lock_guard<decltype(mMutex)> lk(mMutex);
return CallbackToken{
mCallbacks
.emplace(++mCallbackToken,
std::make_shared<VSyncDispatchTimerQueueEntry>(callbackName,
callbackFn,
mMinVsyncDistance))
.first->first};
}
void VSyncDispatchTimerQueue::unregisterCallback(CallbackToken token) {
std::shared_ptr<VSyncDispatchTimerQueueEntry> 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 VSyncDispatchTimerQueue::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;
auto const now = mTimeKeeper->now();
result = callback->schedule(workDuration, earliestVsync, mTracker, now);
if (result == ScheduleResult::CannotSchedule) {
return result;
}
if (callback->wakeupTime() < mIntendedWakeupTime - mTimerSlack) {
rearmTimerSkippingUpdateFor(now, it);
}
}
return result;
}
CancelResult VSyncDispatchTimerQueue::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,
VSyncDispatch::Callback 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