Analyze AOSP vsync model
This article based on
AOSP
9.0.
The Android
UI rendering pipeline has following five stages:
- Application’s UI thread process input events, calls app’s callbacks, and updates the View hierarchy’s list of recorded drawing commands.
- Application’s
RenderThread
issues the recorded commands to the GPU.- GPU draws the frame.
SurfaceFlinger
, which is the system service in charge of displaying the different application windows on the screen, composes the screen and submits the frame to the display HAL.- Display presents the frame.
The entire pipeline is controlled by the
Android
Choreographer
. TheChoreographer
is based on the display vertical synchronization (vsync
) events, which indicate the time the display start to scanout the image and update the display pixels.
So the vsync
events are the base model for Android
UI rendering pipeline. This article is to analyze the AOSP
vsync
model to show how the Android
UI rendering pipeline works.
vsync
events source
The SurfaceFlinger
uses the HWC
to generate vsync events, and the HWC
sends the events to SurfaceFlinger
. It is the start of vsync events.
In SurfaceFlinger::init()
, the SurfaceFlinger
will register itself as callback for HWC
to receive the vsync
events from HWC
.
SurfaceFlinger.cpp
getBE().mHwc->registerCallback(this, getBE().mComposerSequenceId);
HWC2.cpp
void Device::registerCallback(ComposerCallback* callback, int32_t sequenceId) {
if (mRegisteredCallback) {
ALOGW("Callback already registered. Ignored extra registration "
"attempt.");
return;
}
mRegisteredCallback = true;
sp<ComposerCallbackBridge> callbackBridge(
new ComposerCallbackBridge(callback, sequenceId));
mComposer->registerCallback(callbackBridge);
}
When the HWC
generated the vsync
event, it will invoke SurfaceFlinger::onVsyncReceived
to notify the vsync
event.
void SurfaceFlinger::onVsyncReceived(int32_t sequenceId, hwc2_display_t displayId, int64_t timestamp) {
Mutex::Autolock lock(mStateLock);
// Ignore any vsyncs from a previous hardware composer.
if (sequenceId != getBE().mComposerSequenceId) {
return;
}
int32_t type;
if (!getBE().mHwc->onVsync(displayId, timestamp, &type)) {
return;
}
bool needsHwVsync = false;
{ // Scope for the lock
Mutex::Autolock _l(mHWVsyncLock);
if (type == DisplayDevice::DISPLAY_PRIMARY && mPrimaryHWVsyncEnabled) {
needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);
}
}
if (needsHwVsync) {
enableHardwareVsync();
} else {
disableHardwareVsync(false);
}
}
The SurfaceFlinger
will invoke mPrimaryDispSync
(type is DispSync
)’s addResyncSample
to notify the vsync
event. If the DispSync
thinks its internal model matches the HWC
vsync
events, it will return false
to notify the SurfaceFlinger
to disable HWC
vsync
events generation. Otherwise, it will return true to notify the SurfaceFlinger
to receive HWC
vsync
events again, and use later vsync
events to correct its internal model. We will analyze it later.
DispSync
The DispSync
is a very important utility class.
It maintains a model of the periodic hardware-based
vsync
events of a display and uses that model to execute period callbacks at specific phase offsets from the hardwarevsync
events. The model is constructed by feeding consecutive hardware event timestamps to theDispSync
object via theaddResyncSample
method. The model is validated using timestaps fromFence
objects that are passed to theDispSync
object via theaddPresentFence
method. These fence timestamps should correspond to a hardwarevsync
event, but they need not be consecutive hardwarevsync
times. If this method determines that the current model accurately represents the hardwarevsync
event times it will returnfalse
to indicate that a resynchronization (vaiaddResyncSample
) is not needed.
The above description of DispSync
is copied from DispSync.h
comments. It illustrates the function of DispSync
. It uses the hardware-base vsync
events to construct its internal model, and then notifying callback the vsync
event periodic based on its internal model. Also the SurfaceFlinger
will check DispSync
’s internal model’s accuracy, and use addResyncSample
to re-construct its internal model if SurfaceFlinger
thinks DispSync
’s internal model’s accuracy is not enough.
In other word, the DispSync
is the middle level of real hardware-based vsync
events, and received vsync
events of callback. It will feed hardware-based vsync
events, and simulate it in software. Also there is a mechanism to correct its simulated model.
Internal model
There are three important fields for internal model:
mPeriod
: ThemPeriod
is the refresh interval orvsync
events interval, and the value is got fromHWC
. For example, themPeriod
is about 16.66ms for 60HZ refresh rate. TheDispSync
will recalculate it based on the real occasion.mPhase
: ThemPhase
is the phase offset of the modeledvsync
events. It is the number of nanoseconds from time 0 to the firstvsync
event. The firstvsync
event in this sentence is the first resync event added byaddResyncSample
.mReferenceTime
: ThemReferenceTime
is the reference time of the modeledvsync
events. It is the nanosecond timestamp of the firstvsync
event after a resync.
The DispSync
also has a internal thread class called DispSyncThread
to use calculated model fields to notify the callbacks the vsync
event periodic.
When the SurfaceFlinger
calls DispSync::addResyncSample
to feed hardware-based vsync
event to DispSync
, it will use them to simulate its internal model.
bool DispSync::addResyncSample(nsecs_t timestamp) {
Mutex::Autolock lock(mMutex);
size_t idx = (mFirstResyncSample + mNumResyncSamples) % MAX_RESYNC_SAMPLES;
mResyncSamples[idx] = timestamp;
if (mNumResyncSamples == 0) {
mPhase = 0;
mReferenceTime = timestamp;
mThread->updateModel(mPeriod, mPhase, mReferenceTime);
}
if (mNumResyncSamples < MAX_RESYNC_SAMPLES) {
mNumResyncSamples++;
} else {
mFirstResyncSample = (mFirstResyncSample + 1) % MAX_RESYNC_SAMPLES;
}
updateModelLocked();
if (mNumResyncSamplesSincePresent++ > MAX_RESYNC_SAMPLES_WITHOUT_PRESENT) {
resetErrorLocked();
}
if (mIgnorePresentFences) {
// If we don't have the sync framework we will never have
// addPresentFence called. This means we have no way to know whether
// or not we're synchronized with the HW vsyncs, so we just request
// that the HW vsync events be turned on whenever we need to generate
// SW vsync events.
return mThread->hasAnyEventListeners();
}
// Check against kErrorThreshold / 2 to add some hysteresis before having to
// resync again
bool modelLocked = mModelUpdated && mError < (kErrorThreshold / 2);
return !modelLocked;
}
If the added resync sample is the first resync sample, it will reset the mPhase
, mReferenceTime
, and pass them to DispSyncThread
to update its model fields. Otherwise, it will save the resync sample, and in updateModelLocked
, it will use MIN_RESYNC_SAMPLES_FOR_UPDATE
saved consecutive resync samples to calculate the internal model fields, and update it to DispSyncThread
.
void DispSync::updateModelLocked() {
if (mNumResyncSamples >= MIN_RESYNC_SAMPLES_FOR_UPDATE) {
nsecs_t durationSum = 0;
nsecs_t minDuration = INT64_MAX;
nsecs_t maxDuration = 0;
for (size_t i = 1; i < mNumResyncSamples; i++) {
size_t idx = (mFirstResyncSample + i) % MAX_RESYNC_SAMPLES;
size_t prev = (idx + MAX_RESYNC_SAMPLES - 1) % MAX_RESYNC_SAMPLES;
nsecs_t duration = mResyncSamples[idx] - mResyncSamples[prev];
durationSum += duration;
minDuration = min(minDuration, duration);
maxDuration = max(maxDuration, duration);
}
// Exclude the min and max from the average
durationSum -= minDuration + maxDuration;
mPeriod = durationSum / (mNumResyncSamples - 3);
double sampleAvgX = 0;
double sampleAvgY = 0;
double scale = 2.0 * M_PI / double(mPeriod);
// Intentionally skip the first sample
for (size_t i = 1; i < mNumResyncSamples; i++) {
size_t idx = (mFirstResyncSample + i) % MAX_RESYNC_SAMPLES;
nsecs_t sample = mResyncSamples[idx] - mReferenceTime;
double samplePhase = double(sample % mPeriod) * scale;
sampleAvgX += cos(samplePhase);
sampleAvgY += sin(samplePhase);
}
sampleAvgX /= double(mNumResyncSamples - 1);
sampleAvgY /= double(mNumResyncSamples - 1);
mPhase = nsecs_t(atan2(sampleAvgY, sampleAvgX) / scale);
if (mPhase < -(mPeriod / 2)) {
mPhase += mPeriod;
}
// Artificially inflate the period if requested.
mPeriod += mPeriod * mRefreshSkipCount;
mThread->updateModel(mPeriod, mPhase, mReferenceTime);
mModelUpdated = true;
}
}
The updateModelLocked
will use the average revsync sample interval to calculate the more accurate mPeriod
. For example, the 60fps refresh rate’s ideal vsync
event interval is 16.66
ms, if we consider the latencies, the real interval may be 17.**
ms or 16.**
ms or 15.**
ms. Because it uses the average value of MIN_RESYNC_SAMPLES_FOR_UPDATE
- 3(MIN_RESYNC_SAMPLES_FOR_UPDATE
is 6), so the calculated mPeriod
can represent the real state.
And then the updateModelLocked
will use formula mean of angles to calculate the more accurate mPhase
based on the average revsync sample reference time difference.
After calculating, the updateModelLocked
will update calculated fields to DispSyncThread
and set mModelUpdated
to true
. The mModelUpdated
is true
will trigger SurfaceFlinger
to disable HWC
to generate vsync
events.
SurfaceFlinger::onVsyncReceived
if (needsHwVsync) {
enableHardwareVsync();
} else {
disableHardwareVsync(false);
}
When the SurfaceFlinger
calls DispSync::addPresentFence
to check the internal model accuracy, the DipsSync::addPresentFence
will call DispSync::updateErrorLocked
to calculate the error square sum:
void DispSync::updateErrorLocked() {
if (!mModelUpdated) {
return;
}
// Need to compare present fences against the un-adjusted refresh period,
// since they might arrive between two events.
nsecs_t period = mPeriod / (1 + mRefreshSkipCount);
int numErrSamples = 0;
nsecs_t sqErrSum = 0;
for (size_t i = 0; i < NUM_PRESENT_SAMPLES; i++) {
// Only check for the cached value of signal time to avoid unecessary
// syscalls. It is the responsibility of the DispSync owner to
// call getSignalTime() periodically so the cache is updated when the
// fence signals.
nsecs_t time = mPresentFences[i]->getCachedSignalTime();
if (time == Fence::SIGNAL_TIME_PENDING || time == Fence::SIGNAL_TIME_INVALID) {
continue;
}
nsecs_t sample = time - mReferenceTime;
if (sample <= mPhase) {
continue;
}
nsecs_t sampleErr = (sample - mPhase) % period;
if (sampleErr > period / 2) {
sampleErr -= period;
}
sqErrSum += sampleErr * sampleErr;
numErrSamples++;
}
if (numErrSamples > 0) {
mError = sqErrSum / numErrSamples;
mZeroErrSamplesCount = 0;
} else {
mError = 0;
// Use mod ACCEPTABLE_ZERO_ERR_SAMPLES_COUNT to avoid log spam.
mZeroErrSamplesCount++;
}
}
If the square sum is larger than the kErrorThreadshold
(static const nsecs_t kErrorThreshold = 160000000000
), the DispSync::addPresentFence
will trigger SurfaceFlinger::enableHardwareVsync()
to use hardware-base vsync
to correct DispSync
internal model.
DispSyncThread
DispSyncThread
is defined in DispSync.cpp
, and it is a normal thread. Its threadLoop
method will loop to find the next target time to send the vsync
event to callback. If the target time is not now, it will sleep to wait for it. If there is any callback, it will sleep to wait the new callback.
virtual bool threadLoop() {
status_t err;
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
while (true) {
Vector<CallbackInvocation> callbackInvocations;
nsecs_t targetTime = 0;
{ // Scope for lock
Mutex::Autolock lock(mMutex);
// Other code to check state
targetTime = computeNextEventTimeLocked(now);
bool isWakeup = false;
if (now < targetTime) {
if (targetTime == INT64_MAX) {
err = mCond.wait(mMutex);
} else {
err = mCond.waitRelative(mMutex, targetTime - now);
}
if (err == TIMED_OUT) {
isWakeup = true;
} else if (err != NO_ERROR) {
return false;
}
}
now = systemTime(SYSTEM_TIME_MONOTONIC);
// Don't correct by more than 1.5 ms
static const nsecs_t kMaxWakeupLatency = us2ns(1500);
if (isWakeup) {
mWakeupLatency = ((mWakeupLatency * 63) + (now - targetTime)) / 64;
mWakeupLatency = min(mWakeupLatency, kMaxWakeupLatency);
}
callbackInvocations = gatherCallbackInvocationsLocked(now);
}
if (callbackInvocations.size() > 0) {
fireCallbackInvocations(callbackInvocations);
}
}
return false;
}
The DispSyncThread::computeNextEventTimeLocked
is to find the nearest next target time of all callbacks, and check the target time to select whether to sleep to wait. If it sleeps to wait and wakeups with timeout, it will add wakeup latency to the later calculation. And then it will use gatherCallbackInvocationsLocked
to collect callbacks that next target time is less than now now. Lastly, the DispSyncThread::threadLoop
will use fireCallbackInvocations
to notify the vsync
event to callbacks. And then, the threadLoop
enters to next loop.
nsecs_t computeListenerNextEventTimeLocked(const EventListener& listener, nsecs_t baseTime) {
nsecs_t lastEventTime = listener.mLastEventTime + mWakeupLatency;
if (baseTime < lastEventTime) {
baseTime = lastEventTime;
}
baseTime -= mReferenceTime;
nsecs_t phase = mPhase + listener.mPhase;
baseTime -= phase;
// If our previous time is before the reference (because the reference
// has since been updated), the division by mPeriod will truncate
// towards zero instead of computing the floor. Since in all cases
// before the reference we want the next time to be effectively now, we
// set baseTime to -mPeriod so that numPeriods will be -1.
// When we add 1 and the phase, we will be at the correct event time for
// this period.
if (baseTime < 0) {
baseTime = -mPeriod;
}
nsecs_t numPeriods = baseTime / mPeriod;
nsecs_t t = (numPeriods + 1) * mPeriod + phase;
t += mReferenceTime;
// Check that it's been slightly more than half a period since the last
// event so that we don't accidentally fall into double-rate vsyncs
if (t - listener.mLastEventTime < (3 * mPeriod / 5)) {
t += mPeriod;
}
t -= mWakeupLatency;
return t;
}
The method to calculate the callback’s next target time is DispSyncThread::computeListenerNextEventTimeLocked
, and it will use mPeriod
,mPhase
and mReferenceTime
passed from DispSync
to calculate the callback’s next target time. The final phase is the plus of mPhase
and listener.mPhase
. The listener.mPhase
has two possibly values in current code base: SurfaceFlinger::vsyncPhaseOffsetNs
for app, and SurfaceFlinger::sfVsyncPhaseOffsetNs
for SurfaceFlinger
. We will discuss it in later part.
The DispSyncThread::gatherCallbackInvocationsLocked
compares current time with all callbacks next target time, and collects next target time is less than current time. It’s very simple.
The DispSyncThread::fireCallbackInvocations
will call callback’s onDispSyncEvent
to notify the vsync
event.
void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) {
for (size_t i = 0; i < callbacks.size(); i++) {
callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime);
}
}
VsyncSource
and EventThread
The DispSyncThread
will call callback’s onDispSyncEvent
to notify the new vsync
event. The callback is DispVsyncSource
defined in SurfaceFlinger.cpp
. In DispVsyncSource::setVsyncEnabled
method, it will add itself as callback to mDispSync
, which is mPrimaryDispSync
in SurfaceFlinger.cpp
with type DispSync
.
void setVSyncEnabled(bool enable) override {
Mutex::Autolock lock(mVsyncMutex);
if (enable) {
status_t err = mDispSync->addEventListener(mName, mPhaseOffset,
static_cast<DispSync::Callback*>(this));
if (err != NO_ERROR) {
ALOGE("error registering vsync callback: %s (%d)",
strerror(-err), err);
}
} else {
status_t err = mDispSync->removeEventListener(
static_cast<DispSync::Callback*>(this));
if (err != NO_ERROR) {
ALOGE("error unregistering vsync callback: %s (%d)",
strerror(-err), err);
}
}
mEnabled = enable;
}
virtual void onDispSyncEvent(nsecs_t when) {
VSyncSource::Callback* callback;
{
Mutex::Autolock lock(mCallbackMutex);
callback = mCallback;
if (mTraceVsync) {
mValue = (mValue + 1) % 2;
}
}
if (callback != nullptr) {
callback->onVSyncEvent(when);
}
}
The DispVysncSource
is a middle level between DispSync
and the real callback.
In SurfaceFlinger::init()
, the SurfaceFlinger
will create two DispVsyncSource
instances:
mEventThreadSource =
std::make_unique<DispSyncSource>(&mPrimaryDispSync, SurfaceFlinger::vsyncPhaseOffsetNs,
true, "app");
mEventThread = std::make_unique<impl::EventThread>(mEventThreadSource.get(),
[this]() { resyncWithRateLimit(); },
impl::EventThread::InterceptVSyncsCallback(),
"appEventThread");
mSfEventThreadSource =
std::make_unique<DispSyncSource>(&mPrimaryDispSync,
SurfaceFlinger::sfVsyncPhaseOffsetNs, true, "sf");
mSFEventThread =
std::make_unique<impl::EventThread>(mSfEventThreadSource.get(),
[this]() { resyncWithRateLimit(); },
[this](nsecs_t timestamp) {
mInterceptor->saveVSyncEvent(timestamp);
},
"sfEventThread");
The mEventThreadSource
is for app, and mSFEventThreadSource
is for SurfaceFlinger
. They will use SurfaceFlinger::vsyncPhaseOffsetNs
and SurfaceFlinger::sfVsyncPhaseOffsetNs
as the input value for mPhase
of DispVsyncSource
, that will set to listener’s mPhase
, what we saw before.
From the code, we can know DispVsyncSource
will passed to create EventThread
, and in EventThread::enableVSyncLocked
, the EventThread
will set itself as DispVsyncSource
’s callback:
void EventThread::enableVSyncLocked() {
if (!mUseSoftwareVSync) {
// never enable h/w VSYNC when screen is off
if (!mVsyncEnabled) {
mVsyncEnabled = true;
mVSyncSource->setCallback(this);
mVSyncSource->setVSyncEnabled(true);
}
}
mDebugVsyncEnabled = true;
}
So the real callback is EventThread
. We will discuss when to call enableVsyncLocked
later, so we will focus on the logic to response to vsync
event.
void EventThread::onVSyncEvent(nsecs_t timestamp) {
std::lock_guard<std::mutex> lock(mMutex);
mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
mVSyncEvent[0].header.id = 0;
mVSyncEvent[0].header.timestamp = timestamp;
mVSyncEvent[0].vsync.count++;
mCondition.notify_all();
}
EventThread::onVsyncEvent
just keeps the vsync
event timestamp, and notify its internal condition object. EventThread
is not a thread, but it contains a thread instance, mThread
. And EventThread::threadMain
is the loop method of mThread
. In EventThread
’s constructor, it initializes the mThread
and starts it to run its threadMain
method.
EventThread::EventThread(VSyncSource* src, ResyncWithRateLimitCallback resyncWithRateLimitCallback,
InterceptVSyncsCallback interceptVSyncsCallback, const char* threadName)
: mVSyncSource(src),
mResyncWithRateLimitCallback(resyncWithRateLimitCallback),
mInterceptVSyncsCallback(interceptVSyncsCallback),
mThreadName(threadName) {
// Other initialization code
mThread = std::thread(&EventThread::threadMain, this);
pthread_setname_np(mThread.native_handle(), threadName);
// Other thread initialization code
}
EventThread::threadMain
is very simple, it will use EventThread::waitForEventLocked
to wait for event, and post events to the remote listener when it receives the new vsync
event.
void EventThread::threadMain() NO_THREAD_SAFETY_ANALYSIS {
std::unique_lock<std::mutex> lock(mMutex);
while (mKeepRunning) {
DisplayEventReceiver::Event event;
Vector<sp<EventThread::Connection> > signalConnections;
signalConnections = waitForEventLocked(&lock, &event);
// dispatch events to listeners...
const size_t count = signalConnections.size();
for (size_t i = 0; i < count; i++) {
const sp<Connection>& conn(signalConnections[i]);
// now see if we still need to report this event
status_t err = conn->postEvent(event);
if (err == -EAGAIN || err == -EWOULDBLOCK) {
} else if (err < 0) {
// handle any other error on the pipe as fatal. the only
// reasonable thing to do is to clean-up this connection.
// The most common error we'll get here is -EPIPE.
removeDisplayEventConnectionLocked(signalConnections[i]);
}
}
}
}
In EventThread::waitForEventLocked
will check the current mVsyncEvent
timestamp, that updated by EventThread::onVsyncEvent
.
- If it is valid and there are listeners, it will add listener to returned
signalConnections
. - If the timestamp is not valid, and there are no listeners, it will call
EventThread::disableVsyncLocked()
to disable the receivingvsync
events fromDispVsyncSource
. - If it is not valid and but there are listeners, it will call
EventThread::enableVsyncLocked
to ensure itself can receive nextvsync
event fromDispVyncSource
. And it will sleep to waitvsync
event.
We have saw there are two EventThread
instances in SurfaceFlinger
, mEventThread
and mSfEventThread
. The first is used to notify the vsync
events to remote listeners for app, and the second is used to notify the vsync
events to SurfaceFlinger
. After the DispSyncSource
receives the vsync
event, it will send it to its responding EventThread
to manage. So one DispSync
manages multiple DispVsyncSource
s, and one DispVyncSource
manage one EventThread
. One EventThread
manages multiple remote listeners called Connection
.
Connection
and BitTube
In EventThread::threadMain
, we know the EventThread
use Connection
to post vsync
event to remote listener. The Connection
is defined EventThread.h
. The Connection
is also a BnDisplayEventConnection
type, so its some methods can be called by binder.
In EventThread::Connection::onFirstRef
it will call EventThread::registerDisplayEventConnection
to add itself to EventThread
for later postEvent
:
void EventThread::Connection::onFirstRef() {
// NOTE: mEventThread doesn't hold a strong reference on us
mEventThread->registerDisplayEventConnection(this);
}
Before we analyze EventThread::Connection::postEvent
, we should introduce the BitTube
. The BitTube
is a wrapper for paired socket. It has two fd called mReceiveFd
and mSendFd
for receiver and sender to communication. In its BitTube::init
method, it will initialize the paired socket:
void BitTube::init(size_t rcvbuf, size_t sndbuf) {
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) == 0) {
size_t size = DEFAULT_SOCKET_BUFFER_SIZE;
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
// since we don't use the "return channel", we keep it small...
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
fcntl(sockets[0], F_SETFL, O_NONBLOCK);
fcntl(sockets[1], F_SETFL, O_NONBLOCK);
mReceiveFd.reset(sockets[0]);
mSendFd.reset(sockets[1]);
} else {
mReceiveFd.reset();
ALOGE("BitTube: pipe creation failed (%s)", strerror(errno));
}
}
After initialization, the real receiver will steal the client’s mReceiveFd
to its BitTube
instance mReceiveFd
, and receive the socket data from client’s BitTube
. It’s useful for IPC
. The Connection
has a BitTube
instance as its field.
The EventThread::Connection::postEvent
just uses DisplayEventReceiver::sendEvents
to use mChannel
(type is BitTube
) to send event data based on socket:
status_t EventThread::Connection::postEvent(const DisplayEventReceiver::Event& event) {
ssize_t size = DisplayEventReceiver::sendEvents(&mChannel, &event, 1);
return size < 0 ? status_t(size) : status_t(NO_ERROR);
}
ssize_t DisplayEventReceiver::sendEvents(gui::BitTube* dataChannel,
Event const* events, size_t count)
{
return gui::BitTube::sendObjects(dataChannel, events, count);
}
DisplayEventReceiver
and DisplayEventDispatcher
The DisplayEventReceiver
is the Connection
receiver in app part, and the DisplayEventDispatcher
is the dispatcher in app native part. When the SurfaceFlinger
received an vsync
event, it will use BitTube
to send data to DisplayEventReceiver
and notify the DisplayEventDispatcher
to dispatch to java part; when the app part wants to next vsync
event, it will invoke DisplayEventReceiver
method to invoke its internal Connection
method to use binder to update SurfaceFlinger
part state.
In DisplayEventReceiver::DisplayEventReceiver
, it will call SurfaceFlinger::createDisplayEvent
to create a Connection
instance:
DisplayEventReceiver::DisplayEventReceiver(ISurfaceComposer::VsyncSource vsyncSource) {
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
if (sf != NULL) {
mEventConnection = sf->createDisplayEventConnection(vsyncSource);
if (mEventConnection != NULL) {
mDataChannel = std::make_unique<gui::BitTube>();
mEventConnection->stealReceiveChannel(mDataChannel.get());
}
}
}
sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(
ISurfaceComposer::VsyncSource vsyncSource) {
if (vsyncSource == eVsyncSourceSurfaceFlinger) {
return mSFEventThread->createEventConnection();
} else {
return mEventThread->createEventConnection();
}
}
The SurfaceFlinger::createDisplayEventConnection
will add Connection
instance in EventThread
managed list. In DisplayEventReceiver::DisplayEventReceiver
, the DisplayEventReceiver
will use Connection::stealReceiveChannel
to change the connection’s receive fd to its data channel. So when the EventThread::Connection::postEvent
post event by BitTube
, the DisplayEventReceiver
’s mDataChannel
will receive the socket data.
The DisplayEventDispatcher
has a DisplayEventReceiver
instance, and use its mDataChannel
’s mReceiveFd
as the Looper
listening fd. When the EventThread::Connection::postEvent
, the mLooper
will be notified. And then DisplayEventDispatcher::handleEvent
will be called to call DisplayEventReceiver::getEvents
to read event data from socket.
DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper,
ISurfaceComposer::VsyncSource vsyncSource) :
mLooper(looper), mReceiver(vsyncSource), mWaitingForVsync(false) {
ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);
}
status_t DisplayEventDispatcher::initialize() {
status_t result = mReceiver.initCheck();
if (result) {
ALOGW("Failed to initialize display event receiver, status=%d", result);
return result;
}
int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT,
this, NULL);
if (rc < 0) {
return UNKNOWN_ERROR;
}
return OK;
}
int DisplayEventDispatcher::handleEvent(int, int events, void*) {
if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
return 0; // remove the callback
}
if (!(events & Looper::EVENT_INPUT)) {
return 1; // keep the callback
}
// Drain all pending events, keep the last vsync.
nsecs_t vsyncTimestamp;
int32_t vsyncDisplayId;
uint32_t vsyncCount;
if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
mWaitingForVsync = false;
dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
}
return 1; // keep the callback
}
bool DisplayEventDispatcher::processPendingEvents(
nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) {
bool gotVsync = false;
DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
ssize_t n;
while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
for (ssize_t i = 0; i < n; i++) {
const DisplayEventReceiver::Event& ev = buf[i];
switch (ev.header.type) {
case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
// Later vsync events will just overwrite the info from earlier
// ones. That's fine, we only care about the most recent.
gotVsync = true;
*outTimestamp = ev.header.timestamp;
*outId = ev.header.id;
*outCount = ev.vsync.count;
break;
// Other code
}
}
}
return gotVsync;
}
The dispatchVsync
will be invoked to dispatch vsync
event to java part after reading event data from socket. The dispatchVsync
is implemented by NativeDisplayEventReceiver
in android_view_DisplayEventReceiver.cpp
.
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
if (receiverObj.get()) {
env->CallVoidMethod(receiverObj.get(),
gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, id, count);
}
mMessageQueue->raiseAndClearException(env, "dispatchVsync");
}
It will call DisplayEventReceiver.java
’s dispatchVsync
to dispatch vsync
event. And finally, Choreographer.onVsync
will be invoked. And onVsync
will use Handler
to call the Choreographer.doFrame
to notify the java part to update the UI content.
void doFrame(long frameTimeNanos, int frame) {
// Other check code
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// Other code
}
There are four stages: input, animation, traversal and commit. The most of them will be processed by ViewRootImpl
. The CALLBACK_TRAVERSAL
callback is ViewRootImpl.doTraversal
, that is the entry point of view subsystem updating in java part.
MessageQueue
The DisplayEventReceiver
is used by app part, so what about SurfaceFlinger
part? The receiver for SurfaceFlinger
is MessageQueue
.
In SurfaceFlinger::init
, the SurfaceFlinger
will pass mSFEventThread
to MessageQueue
:
mEventQueue->setEventThread(mSFEventThread.get());
void SurfaceFlinger::onFirstRef()
{
mEventQueue->init(this);
}
void MessageQueue::init(const sp<SurfaceFlinger>& flinger) {
mFlinger = flinger;
mLooper = new Looper(true);
mHandler = new Handler(*this);
}
void MessageQueue::setEventThread(android::EventThread* eventThread) {
if (mEventThread == eventThread) {
return;
}
if (mEventTube.getFd() >= 0) {
mLooper->removeFd(mEventTube.getFd());
}
mEventThread = eventThread;
mEvents = eventThread->createEventConnection();
mEvents->stealReceiveChannel(&mEventTube);
mLooper->addFd(mEventTube.getFd(), 0, Looper::EVENT_INPUT, MessageQueue::cb_eventReceiver,
this);
}
The MessageQueue::setEventThread
will create Connection
of passed EventThread
, which is mSFEventThread
. And then it will steal created Connection
’s receiver fd to its mEventTube.getFd()
, just likes DisplayEventReceiver::DisplayEventReceiver
. So we can consider MessageQueue
as SurfaceFlinger
’s DisplayEventReceiver
.
When the mLooper
notified by socket data, it will call MessageQueue::cb_eventReceiver
to process the event:
int MessageQueue::cb_eventReceiver(int fd, int events, void* data) {
MessageQueue* queue = reinterpret_cast<MessageQueue*>(data);
return queue->eventReceiver(fd, events);
}
int MessageQueue::eventReceiver(int /*fd*/, int /*events*/) {
for (int i = 0; i < n; i++) {
if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
mHandler->dispatchInvalidate();
break;
}
}
}
return 1;
}
It’s very similar with DisplayEventDispatcher
. When the vsyn
event coming for SurfaceFlinger
, the MessageQueue::dispatchInvalidate
will trigger SurfaceFlinger::onMessageReceived
to invalidate the surfaces. The next operation is SurfaceFlinger::handleMessageRefresh
, that combines many familiar operations: rebuildLayerStacks
, doComposition
, postComposition
. They will compose the surfaces with GPU and HWC
, and post them to GPU to display to show.
When to enable vsync
?
From the above analyzing, we know if no-body is interested in vsync
, the SurfaceFlinger
will disable the vsync
, and remove calback from DispSync
(see DisplayVsyncSource::setVSynvEnabled
). So there is a problem we should to sovle is when to enable vsync
after vsync
is disabled?
To explain this problem, we will use a real occasion - clicking the launcher icon after system is stable without any view changing - to see what happens.
After click the launcher icon, the InputFlinger
starts to dispatch touch event to ViewRootImpl.java
. And the Launcher3
responses onTouchEvent
to start animation. The AnimationHandler.MyFrameCallbackProvider
calls the Choreographer.postCallback
to post callback, that will trigger Choreographer.scheduleVsyncLocked
.
The scheduleVsyncLocked
will notify the EventThread
with a long invoking-chain:
Choreographer.java scheduleVsyncLocked
-> DisplayEventReceiver.java scheduleVsync
-> DisplayEventReceiver.java nativeScheduleVsync
-> NativeDisplayEventReceiver::scheduleVsync
-> DisplayEventDispatcher::scheduleVsync
-> DisplayEventReceiver::requestNextVsync
-> EventThread::Connection::requestNextVsync
-> EventThread::requestNextVsync
.
void EventThread::requestNextVsync(const sp<EventThread::Connection>& connection) {
std::lock_guard<std::mutex> lock(mMutex);
if (mResyncWithRateLimitCallback) {
mResyncWithRateLimitCallback();
}
if (connection->count < 0) {
connection->count = 0;
mCondition.notify_all();
}
}
The EventThread::requestNextVsync
will call mResyncWithRateLimitCallback
, passed by EventThread
constructor. In SurfaceFlinger::init
, we knows the mResyncWithRateLimitCallback
is SurfaceFlinger::resyncWithRateLimit
:
void SurfaceFlinger::resyncWithRateLimit() {
static constexpr nsecs_t kIgnoreDelay = ms2ns(500);
// No explicit locking is needed here since EventThread holds a lock while calling this method
static nsecs_t sLastResyncAttempted = 0;
const nsecs_t now = systemTime();
if (now - sLastResyncAttempted > kIgnoreDelay) {
resyncToHardwareVsync(false);
}
sLastResyncAttempted = now;
}
void SurfaceFlinger::resyncToHardwareVsync(bool makeAvailable) {
Mutex::Autolock _l(mHWVsyncLock);
if (makeAvailable) {
mHWVsyncAvailable = true;
} else if (!mHWVsyncAvailable) {
// Hardware vsync is not currently available, so abort the resync
// attempt for now
return;
}
const auto& activeConfig = getBE().mHwc->getActiveConfig(HWC_DISPLAY_PRIMARY);
const nsecs_t period = activeConfig->getVsyncPeriod();
mPrimaryDispSync.reset();
mPrimaryDispSync.setPeriod(period);
if (!mPrimaryHWVsyncEnabled) {
mPrimaryDispSync.beginResync();
//eventControl(HWC_DISPLAY_PRIMARY, SurfaceFlinger::EVENT_VSYNC, true);
mEventControlThread->setVsyncEnabled(true);
mPrimaryHWVsyncEnabled = true;
}
}
The SurfaceFlinger::resyncWithRateLimit
will call SurfaceFlinger::resyncToHardwareVsync
to enable vsync
, which will add DispSyncSource
as callback to DispSync
to receive the vsync
event.
The above process is for app, what about SurfaceFlinger
?
In the end of ViewRootImpl.draw
, it will call ThreadedRenderer.draw
to send updated frame to GPU. The invoking process is also long:
ThreadedRenderer.draw
-> ThreadedRenderer.nSyncAndDrawFrame
-> android_view_ThreadedRenderer::android_view_ThreadedRenderer_syncAndDrawFrame
-> RenderProxy::syncAndDrawFrame
-> DrawFrameTask::drawFrame
-> DrawFrameTask::run
-> CanvasContext::draw
-> RenderPipeline::draw
(in my occasion is OpenGLPipeline
) -> EglManager::swapBuffer
-> eglApi::eglSwapBuffersWithDamageKHR
-> Surface::queueBuffer
-> BpGraphicBufferProducer::queueBuffer
-> BufferQueueProducer::queueBuffer
-> BpConsumerListener::onFrameAvailable
-> ProxyConsumerListener::onFrameAvailable
-> ConsumerBase::onFrameAvailable
-> BufferLayer::onFrameAvailable
-> SurfaceFlinger::signalLayerUpdate
-> MessageQueue::invalidate
.
The invoking after MessageQueue::invalidate
, and it will add mSfEventThreadSource
as the callback for vsync
event.
Pull down refresh rate
In high refresh device, we can pull down the refresh rate to let app run smoothly in high refresh rate.
The DispSYnc::setRefreshSkipCount
will set the mRefreshSkipCount
, and use it to calculate its mPeriod
in DispSync::updateModelLocked
:
mPeriod += mPeriod * mRefreshSkipCount;
For exmple, current refresh rate is 90fps, the default mPeriod
is 11ms. If we set mRefreshSkipCount
to 1, the mPeriod
will be 22ms, and the real refresh rate pulls down to 45fps. And it will give app more computing time before composition.
Summary
The HWC
generates hardware-based vsync
events, and it will be feed to DispSync
to simulate its internal model corresponding HWC
’s frame rate. If it finds the DispSync
’s internal model is not accurate, it will use hardware-based vsync
to correct it. If the DispSync
internal model is thought as accurate, the SurfaceFlinger
will disable the HWC
generating.
The DispSync
matains a internal model, and its internal thread DispSyncThread
will use its model to find the next target time, and wait to it. When the next target time coming, it will send simulated vsync
event to callback. If the upper level wants to receive the vsync
event, it should add callback to DispSync
. If there are no callbacks, the DispSyncThread
will sleep to wait notify to reduce the unncessary computing. The DispSync
is an one-multiple pattern for callback.
The EventThread
is the real callback of DispSync
, although there is a DispSyncSource
in the middle. The EventThread
manages EventThread::Connection
to communicate with the real receiver. There are two EventThread
instances, one is mEventThread
for app, and another is mSFEventThread
for SurfaceFlinger
. So the EventThread
is also an one-multiple pattern for EventThread::Connection
. The EventThread::Connection
is a Bn
binder type, and it manages a paired socket. So the EventThread::Connection
can use binder and socket to communicate.
For app, there are classes called DisplayEventReceiver
and DisplayEventDispatcher
to receive the event data from EventThread::Connection
, and dispatch the event to Choreographer
in java part.
When Choreographer
receives the vsync
event, it will arrange java part to update the view frame, and send it to GPU to draw. The draw operation will trigger SurfaceFlinger
to receive the vsync
event to do composition for surfaces, and send it to display to show. If the system receives input event after stable, it will trigger resync to EventThread
, and notify the EventThread
, DispSyncThread
to do work.
The SurfaceFlinger
provides a mechanism based on hardware-based vsync
to provide period event to notify system to update content. And the system will control how to enable/disable this mechanism to reduce unncessary computing.