- Initiating a VSync Request
An app triggers a VSync request via Binder IPC, ultimately invoking EventThread::requestNextVsync(). This method updates the associated EventThreadConnection’s internal request state and signals the event thread:
void EventThread::requestNextVsync(const sp<EventThreadConnection>& conn) {
std::lock_guard<std::mutex> lock(mMutex);
if (conn->vsyncRequest == VSyncRequest::None) {
conn->vsyncRequest = VSyncRequest::Single;
mCondition.notify_all(); // Wake up threadMain loop
} else if (conn->vsyncRequest == VSyncRequest::SingleSuppressCallback) {
conn->vsyncRequest = VSyncRequest::Single;
}
}
The VSyncRequest enum governs dispatch behavior:
None: No active scheduling or delivery.Single: Triggers two consecutive VSync deliveries (first as full event, second with suppressed callback).SingleSuppressCallback: Final delivery; resets toNoneafter consumption.
- Scheduling Logic and Timer Setup
Inside EventThread::threadMain(), the loop detects pending requests and transitions in to State::VSync. This activates the underlying mVSyncSource, which delegates timing to VSyncDispatchTimerQueue:
void CallbackRepeater::start(std::chrono::nanoseconds work,
std::chrono::nanoseconds ready) {
mStarted = true;
mWorkDuration = work;
mReadyDuration = ready;
mRegistration.schedule({
.workDuration = work.count(),
.readyDuration = ready.count(),
.earliestVsync = mLastCallTime.count()
});
}
The scheduling flow proceeds as follows:
-
Timing calculation:
VSyncDispatchTimerQueueEntry::schedule()usesVSyncTrackerto project the next software-synchronized timestamp, subtractingworkDurationandreadyDurationto determine the optimal wakeup time. -
Timer registration: The computed absolute nanosecond deadline is passed to
Timer::alarmAt(), which configures atimerfdusingtimerfd_settime(). -
Event loop integration: The
timerfdis registered with anepollinstance inTimer::dispatch(). When the timer fires,epoll_wait()returns, and the storedmCallbackis invoked. -
Callback Chain and Event Delivery
The callback path forms a tightly coupled chain:
Timer::alarmAt()→ invokesVSyncDispatchTimerQueue::timerCallback()- → calls
VSyncDispatchTimerQueueEntry::callback() - → forwards to
CallbackRepeater::callback() - → delegates to
DispSyncSource::onVsyncCallback() - → routes to
EventThread::onVSyncEvent()
In onVSyncEvent(), a new DisplayEventReceiver::Event is enqueued into mPendingEvents and mCondition is signaled:
void EventThread::onVSyncEvent(nsecs_t ts, VSyncData data) {
mPendingEvents.push_back(makeVSync(mVSyncState->displayId, ts,
++mVSyncState->count, data.expectedPresentationTime,
data.deadlineTimestamp));
mCondition.notify_all();
}
Back in threadMain(), the loop consumes the front of mPendingEvents, filters eligible connections via shouldConsumeEvent(), and dispatches via dispatchEvent() — which writes the event over a Unix domain socket to the client’s DisplayEventReceiver.
- Request Termination and State Cleanup
VSync termination occurs implicitly when no connection has an active VSyncRequest. In threadMain(), vsyncRequested becomes false after shouldConsumeEvent() downgrades SingleSuppressCallback to None. This causes nextState to shift from State::VSync to State::Idle, triggering:
mVSyncSource->setVSyncEnabled(false), which cascades toCallbackRepeater::stop()stop()setsmStarted = falseand callsmRegistration.cancel()to disarm the timer- Subsequent invocations of
CallbackRepeater::callback()exit early due to the!mStartedguard
This ensures no further timer rescheduling, cleanly halting the VSync pipeline without race conditions or resource leaks.