Control synchronization of VSYNC signal in SurfaceFlinger

The drawing and composition process of SurfaceFlinger (SF) is synchronized under the control of VSYNC signal (VSYNC signal), so VSYNC signal can be said to be the commander of SF, and its coordinated synchronization control is crucial to the efficiency of interface drawing. This article describes how VYSNC signals play this commander role in SF services.

The VSYNC SurfaceFlinger

SufaceFlinger is initialized in init method. In this method, there are two DISPsyncsources of VYSNC signal, draw delay source for App and SF composite delay source. These two sources are based on the same VSYNC signal model mPrimaryDispSync. DispSync is a DispSync object, which is a synchronization model for hardware Hwc vertical signals. Why is there a need for such a synchronization model in the presence of hardware VSYNC signals? In fact, this is an optimization strategy of Android system. After the arrival of VYSNC signal, App drawing and SF composition process may compete with EACH other in CPU, which will affect drawing efficiency. To avoid competition, VYSNC synchronization model DispSync is introduced. The model will turn on the VYSNC signal of the hardware for sampling as required, and then synchronize the VSYNC signal model, so as to provide VYSNC signals for the upper layer of the drawing delay source and the synthesis delay source. Based on the synchronization model, A phase offset (vsyncPhaseOffsetNs and sfVsyncPhaseOffsetNs) can be added to the rendering delay source and the synthesis delay source respectively to staggate the rendering and synthesis execution after the arrival of the VYSNC signal.

void SurfaceFlinger::init() {
    ...
    // Create a synthetic object HWComposer. Fb and HWC hardware devices are turned on. HWComposer does not necessarily represent the actual underlying hardware devices
    mHwc = new HWComposer(this,
        *static_cast<HWComposer::EventHandler *>(this)); ...// start the EventThread
    sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
        vsyncPhaseOffsetNs, true);//App draw delay draw vSYNC source
    mEventThread = new EventThread(vsyncSrc);// This EventThread is responsible for managing the drawn Vsync source
    sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
        sfVsyncPhaseOffsetNs, false);//SF compositing delay Due to the different delays, rendering and compositing are staggered after the actual VSync signal is received.
    mSFEventThread = new EventThread(sfVsyncSrc);// This EventThread is responsible for managing the synthesized Vsync source
    mEventQueue.setEventThread(mSFEventThread);// An EventConnection is set up, essentially registering as a listener so that MessageQueue can be notified when a vsync signal is present...// A message is sent to HWC hardware to enable or disable Vsync signal
    mEventControlThread = new EventControlThread(this);
    mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY); ...// set initial conditions (e.g. unblank default device)
    initializeDisplays();// Initialize the display, where the Vsync signal is turned on again, which is turned off by default in EventControlThread.. }Copy the code

Hardware VSYNC signal generation

Before introducing how the VSYNC signal is used to coordinate drawing and composition, let’s first look at how the hardware VSYNC signal is generated and passed to the synchronization model DispSync. First, the hardware’s vertical signal is generated by the display device, which sends the hardware’s vertical signal to SF through the HAL layer’s HWComposer module.

//frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.cpp
HWComposer::HWComposer(
        const sp<SurfaceFlinger>& flinger,
        EventHandler& handler)
    : mFlinger(flinger),
      mFbDev(0), mHwc(0), mNumDisplays(1),
      mCBContext(new cb_context),
      mEventHandler(handler),
      mDebugForceFakeVSync(false) {...int fberr = loadFbHalModule();// Open the fb device
    loadHwcModule();// Start the HWC module

    if (mHwc) {// Support hardware composition
        if (mHwc->registerProcs) {
            mCBContext->hwc = this;
            mCBContext->procs.invalidate = &hook_invalidate;
            mCBContext->procs.vsync = &hook_vsync;// Hardware vertical signal callback
            if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1))// Version 1.1 supports hot swap
                mCBContext->procs.hotplug = &hook_hotplug;
            else
                mCBContext->procs.hotplug = NULL;
            memset(mCBContext->procs.zero, 0, sizeof(mCBContext->procs.zero));
            mHwc->registerProcs(mHwc, &mCBContext->procs);// Register a callback
        }

        // don't need a vsync thread if we have a hardware composer
        needVSyncThread = false;// Support hardware composition without the need for software simulation
        // always turn vsync off when we start
        // Close VSYNC first, then open it again
        eventControl(HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, 0); ... }if (mFbDev) {// Fb device has been opened
        DisplayData& disp(mDisplayData[HWC_DISPLAY_PRIMARY]);
        disp.connected = true;
        // Set the parameters of the home screen, including display parameters including width, height, pixel format and refresh frequency
        disp.width = mFbDev->width;
        disp.height = mFbDev->height;
        disp.format = mFbDev->format;
        disp.xdpi = mFbDev->xdpi;
        disp.ydpi = mFbDev->ydpi;
        if (disp.refresh == 0) {
            disp.refresh = nsecs_t(1e9 / mFbDev->fps);
        }
        if (disp.refresh == 0) {
            disp.refresh = nsecs_t(1e9 / 60.0); }}// The Vsync signal needs to be simulated by software
    if (needVSyncThread) {
        // we don't have VSYNC support, we need to fake it
        mVSyncThread = new VSyncThread(*this); }}Copy the code

The HWComposer object is responsible for the hardware composition of SF, and of course the VSYNC signal should also be provided by it. In its constructor, the HWC device module will be loaded. Register the VSYNC signal callback hook_vsync with the HWC so that the VSYNC signal generated by the hardware can be called back to the HWCoposer object hook_vsync. It is important to note that HWComposer does not have to be the underlying hardware device. It can also represent a virtual device, so that the VSYNC signal is generated through a VSyncThread that simulates the hardware.

// Notify HWComposer of the arrival of a vertical event
void HWComposer::hook_vsync(const struct hwc_procs* procs, int disp,
        int64_t timestamp) {
    cb_context* ctx = reinterpret_cast<cb_context*>(
            const_cast<hwc_procs_t*>(procs));
    ctx->hwc->vsync(disp, timestamp);
}
//VSYNC event arrived
void HWComposer::vsync(int disp, int64_t timestamp) {
    if (uint32_t(disp) < HWC_NUM_PHYSICAL_DISPLAY_TYPES) {
        {
            Mutex::Autolock _l(mLock);

            // There have been reports of HWCs that signal several vsync events
            // with the same timestamp when turning the display off and on. This
            // is a bug in the HWC implementation, but filter the extra events
            // out here so they don't cause havoc downstream.
            if (timestamp == mLastHwVSync[disp]) {
                ALOGW("Ignoring duplicate VSYNC event from HWC (t=%lld)",
                        timestamp);
                return;
            }

            mLastHwVSync[disp] = timestamp;
        }
        ……
        mEventHandler.onVSyncReceived(disp, timestamp);// the sf callback advertises the vsync message to sf}}Copy the code

The vsync method of HWComposer is further called in the hook_vsync method to notify the Vsync signal event, which in the vsync method is ultimately notified to SF by EventHandler’s onVsyncReceived method, HWComposer::EventHandler ::EventHandler ::EventHandler

class HWComposer
{
public:
    class EventHandler {
        friend class HWComposer;
        virtual void onVSyncReceived(int disp, nsecs_t timestamp) = 0;// VynSC message callback
        virtual void onHotplugReceived(int disp, bool connected) = 0;
    protected:
        virtual ~EventHandler() {}
    };

}
Copy the code

So the VSYNC signal is notified to SF via the mEventHandler callback of the VSYNC method onVsyncReceived

void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) {
    bool needsHwVsync = false;
    {
    	// Scope for the lock
        Mutex::Autolock _l(mHWVsyncLock);
        if (type == 0 && mPrimaryHWVsyncEnabled) {//mPrimaryHWVsyncEnabled indicates whether the VSYNC function of the HWC corresponding to the main screen is enabled
            needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);// Count Vsync samples}}if (needsHwVsync) {
        enableHardwareVsync();
    } else {
        disableHardwareVsync(false); }}Copy the code

In this step, we can see that the synchronization signal generated by the hardware is sent to the addResyncSample method of the synchronization model DispSync. According to the return value of this method, we can control whether the hardware continues to send the vertical signal as needed. Instead, the synchronous model is opened when it is needed, and when it is needed is calculated by addResyncSample.

Here we take a look at how the hardware’s vertical signal is turned on and off.

// Enable hardware Vsync
void SurfaceFlinger::enableHardwareVsync() {
    Mutex::Autolock _l(mHWVsyncLock);
    if(! mPrimaryHWVsyncEnabled && mHWVsyncAvailable) { mPrimaryDispSync.beginResync();//eventControl(HWC_DISPLAY_PRIMARY, SurfaceFlinger::EVENT_VSYNC, true);
        mEventControlThread->setVsyncEnabled(true);
        mPrimaryHWVsyncEnabled = true; }}// Disable hardware Vsync
void SurfaceFlinger::disableHardwareVsync(bool makeUnavailable) {
    Mutex::Autolock _l(mHWVsyncLock);
    if (mPrimaryHWVsyncEnabled) {
        //eventControl(HWC_DISPLAY_PRIMARY, SurfaceFlinger::EVENT_VSYNC, false);
        mEventControlThread->setVsyncEnabled(false);
        mPrimaryDispSync.endResync();
        mPrimaryHWVsyncEnabled = false;
    }
    if (makeUnavailable) {
        mHWVsyncAvailable = false; }}Copy the code

The switch of the hardware vertical signal is controlled by the setVysncEnable method of the mEventControlThread, which is created in the INIT method of SF, which is an EventControlThread as well as a thread.

void EventControlThread::setVsyncEnabled(bool enabled) {
    Mutex::Autolock lock(mMutex);
    mVsyncEnabled = enabled;
    mCond.signal();
}

bool EventControlThread::threadLoop() {//EventControlThread controls whether the hardware should send the Vsync signal
    Mutex::Autolock lock(mMutex);

    bool vsyncEnabled = mVsyncEnabled;
    //EventControlThread is created in SF Init, so the hardware Vsync signal is off by default, which is turned on when the screen lights up
    mFlinger->eventControl(HWC_DISPLAY_PRIMARY, SurfaceFlinger::EVENT_VSYNC,
            mVsyncEnabled);

    while (true) {
        status_t err = mCond.wait(mMutex);// Block when there is no control signal
        if(err ! = NO_ERROR) { ALOGE("error waiting for new events: %s (%d)",
                strerror(-err), err);
            return false;
        }
        // If the status changes, SF is notified to control Vysnc, HWC_DISPLAY_PRIMARY represents the main display, and EVENT_VSYNC indicates that VSync is controlled
        if (vsyncEnabled != mVsyncEnabled) {
            mFlinger->eventControl(HWC_DISPLAY_PRIMARY,
                    SurfaceFlinger::EVENT_VSYNC, mVsyncEnabled);
            vsyncEnabled = mVsyncEnabled;
        }
    }

    return false;
}
Copy the code

In threadLoop, it starts by default by turning off hardware VSYNC via SF eventControl and then blocks in the while loop. When mVsyncEnabled is set via setVsyncEnabled and the thread wakes up, depending on the state of the setting, The HWC switch VSYNC signal is notified via SF eventControl. Of course, the hardware’s VSYNC signal is turned on after SF initialization is complete. Let me briefly introduce this process.

SurfaceFlinger::initializeDisplays SurfaceFlinger::onInitializeDisplays SurfaceFlinger::onScreenAcquired SurfaceFlinger::resyncToHardwareVsync

void SurfaceFlinger::resyncToHardwareVsync(bool makeAvailable) {
    Mutex::Autolock _l(mHWVsyncLock);

    if (makeAvailable) {
        mHWVsyncAvailable = true;
    } else if(! mHWVsyncAvailable) { ALOGE("resyncToHardwareVsync called when HW vsync unavailable");
        return;
    }

    const nsecs_t period =
            getHwComposer().getRefreshPeriod(HWC_DISPLAY_PRIMARY);// // get the refresh rate of the display device

    mPrimaryDispSync.reset();
    mPrimaryDispSync.setPeriod(period);// Set "period" to DispSync model to display device frequency

    if(! mPrimaryHWVsyncEnabled) { mPrimaryDispSync.beginResync();//eventControl(HWC_DISPLAY_PRIMARY, SurfaceFlinger::EVENT_VSYNC, true);
        // If the hardware vsync is not enabled, notify EventControlThread to notify the hardware to enable vsync
        mEventControlThread->setVsyncEnabled(true);// Enable VSYNC on the hardware, that is, when the screen is lit up
        mPrimaryHWVsyncEnabled = true; }}Copy the code

SF initializes the display device in init. This initialization process will eventually be completed through resyncToHardwareVsync to set the refresh frequency of the synchronization model according to the display device and also turn on the VSYNC signal of the hardware to prepare for the synchronization of the VSYNC model.

DispSync Synchronization model

Now that we’ve seen how hardware VSYNC is generated and delivered to the synchronization model, let’s look at how the synchronization model processes hardware synchronization signals and provides VSYNC signals to upper-level listeners.

// The synchronization model object of Vsync starts a thread for signal synchronization by default
DispSync::DispSync() {
    mThread = new DispSyncThread();// Start the synchronization thread and wait for the vertical signal to arrive
    mThread->run("DispSync", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE); reset(); beginResync(); ... }Copy the code

In the DisySync constructor, by default, we open a synchronous thread to DispSyncThread, which is responsible for synchronizing VYSNC signals and sending vSYNcThread to interested listeners (such as the two delay sources we created in SF init).

class DispSyncThread: public Thread {...virtual bool threadLoop(a) {
        status_t err;
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);// The current time
        nsecs_t nextEventTime = 0;

        while (true) {
            Vector<CallbackInvocation> callbackInvocations;// Callback list

            nsecs_t targetTime = 0;

            { // Scope for lock
                Mutex::Autolock lock(mMutex);

                if (mStop) {
                    return false;
                }

                if (mPeriod == 0) {// If the model frequency is not set, wait
                    err = mCond.wait(mMutex);
                    if(err ! = NO_ERROR) { ALOGE("error waiting for new events: %s (%d)",
                                strerror(-err), err);
                        return false;
                    }
                    continue;
                }

                nextEventTime = computeNextEventTimeLocked(now);
                targetTime = nextEventTime;// Trigger time

                bool isWakeup = false;

                if (now < targetTime) {// If the trigger time is not reached, wait for some time
                    err = mCond.waitRelative(mMutex, targetTime - now);

                    if (err == TIMED_OUT) {// Wait time reaches trigger time
                        isWakeup = true;
                    } else if(err ! = NO_ERROR) { ALOGE("error waiting for next event: %s (%d)",
                                strerror(-err), err);
                        return false;
                    }
                }

                now = systemTime(SYSTEM_TIME_MONOTONIC);

                if (isWakeup) {
                    mWakeupLatency = ((mWakeupLatency * 63) +
                            (now - targetTime)) / 64;
                    if (mWakeupLatency > 500000) {
                        // Don't correct by more than 500 us
                        mWakeupLatency = 500000;
                    }
                    if (traceDetailedInfo) {
                        ATRACE_INT64("DispSync:WakeupLat", now - nextEventTime);
                        ATRACE_INT64("DispSync:AvgWakeupLat", mWakeupLatency); }}// Collect the listener that should be notified this time
                callbackInvocations = gatherCallbackInvocationsLocked(now);
            }

            if (callbackInvocations.size() > 0) {
            	// Call the registered events, namely drawing delay and compositing delay objects in SFfireCallbackInvocations(callbackInvocations); }}return false; }}Copy the code

DispSyncThread logic is simple, it through a while loop, to continuously by calculating the next computeNextEventTimeLocked VSYNC signal of the time, And then collected through gatherCallbackInvocationsLocked to inform the listener, finally to inform them by fireCallbackInvocations VSYNC signal arrival event. Here, the most critical is the calculation process of VYSNC signal. How does the synchronization model provide VSYNC signal according to the delay requirements of the listener, and how does it ensure the accuracy and order of VSYNC signal? And how to adjust the model after error? Answering these questions requires a complete and comprehensive understanding of the entire mechanism of the synchronization model. To ensure the length of this article, we will not give a detailed description of the synchronization model, but only a brief description.

As we know previously, SF sends the VSYNC signal to the synchronous model for sample statistics through addResyncSample method after receiving the VSYNC signal from the hardware. Now let’s look at the implementation,

bool DispSync::addResyncSample(nsecs_t timestamp) {
    Mutex::Autolock lock(mMutex);
    size_t idx = (mFirstResyncSample + mNumResyncSamples) % MAX_RESYNC_SAMPLES;
    mResyncSamples[idx] = timestamp;

    if (mNumResyncSamples < MAX_RESYNC_SAMPLES) {
        mNumResyncSamples++;
    } else {
        mFirstResyncSample = (mFirstResyncSample + 1) % MAX_RESYNC_SAMPLES;
    }
    updateModelLocked();
    if (mNumResyncSamplesSincePresent++ > MAX_RESYNC_SAMPLES_WITHOUT_PRESENT) {
        resetErrorLocked();
    }
    if (runningWithoutSyncFramework) {
        // 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();
    }

    return mPeriod == 0 || mError > errorThreshold;
}
Copy the code

In this method, the synchronization model will collect the time stamp information of Vsync signals of hardware into mResyncSamples, saving the time information of Vsync signals of MAX_RESYNC_SAMPLES(defined as 32) hardware at most. MNumResyncSamples and mFirstResyncSample constitute a VSYNC signal window with a size of 32, which can contain the timestamp information of VSYNC signals of 32 hardware at most. Where mFirstResyncSample is the first VSYNC signal sample of the window, and mNumResyncSamples indicates how many signal samples there are already. With these samples, we can use this to update our synchronization model to synchronize it with the hardware’s VSYNC signal. This is done with the Update locked method.

void DispSync::updateModelLocked() {
    if (mNumResyncSamples >= MIN_RESYNC_SAMPLES_FOR_UPDATE) {
        nsecs_t durationSum = 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;
            // durationSum represents the time interval saved for all samples (except the first vsync) and is used to calculate the average mPeriod later
            // mResyncSamples[IDx] -mresyncsamples [prev] This difference is the time interval calculated between the two hardware vsync samples
            durationSum += mResyncSamples[idx] - mResyncSamples[prev];
        }
        // This average is the time interval between hardware vsync generation
        mPeriod = durationSum / (mNumResyncSamples - 1);

        // Calculate the offset required by the model
        double sampleAvgX = 0;
        double sampleAvgY = 0;
        double scale = 2.0 * M_PI / double(mPeriod);
        // Convert the hardware vsync interval to the corresponding degree, instantness, where the scale represents the number of degrees per ns
        for (size_t i = 0; i < mNumResyncSamples; i++) {
            size_t idx = (mFirstResyncSample + i) % MAX_RESYNC_SAMPLES;
            nsecs_t sample = mResyncSamples[idx];
            double samplePhase = double(sample % mPeriod) * scale;
            sampleAvgX += cos(samplePhase);
            sampleAvgY += sin(samplePhase);
        }
        // Get the average value of the offset between the x and y axes
        sampleAvgX /= double(mNumResyncSamples);
        sampleAvgY /= double(mNumResyncSamples);
        // Atan2 is used to obtain the final phase shift value
        mPhase = nsecs_t(atan2(sampleAvgY, sampleAvgX) / scale);
        // If the phase shift is less than 0, then reset it
        if (mPhase < 0) {
            mPhase += mPeriod;
        }
        if (traceDetailedInfo) {
            ATRACE_INT64("DispSync:Period", mPeriod);
            ATRACE_INT64("DispSync:Phase", mPhase);
        }
        // Update the model with the latest offset mPhase and vsync interval mPeriodmThread->updateModel(mPeriod, mPhase); }}Copy the code

The updateModelLocked method updates the synchronized model based on the number of samples counted. Updates are performed only when the number of samples is greater than or equal to MIN_RESYNC_SAMPLES_FOR_UPDATE(defined as 3). MPerid is calculated from the sample when the number of samples is greater than or equal to 3. This value is calculated by summing up the time intervals of all adjacent samples into durationSum, and then dividing by the number of samples minus 1 is the frequency of the sample, mPeriod. Then calculate the offset of the model, because the current mPeriod is calculated as the average value, so it is not true hardware Vsync interval is mPeriod, there are errors, that is, the time interval of some sample signals is greater than the average value, while the time interval of some samples is less than the average value, and these differences with mPriod are the offset. The next step is to calculate these average offsets. After calculating the offsets, the offset values mPhase and the time interval mPeriod are updated into the model.

 void updateModel(nsecs_t period, nsecs_t phase) {
    Mutex::Autolock lock(mMutex);
    mPeriod = period;
    mPhase = phase;
    mCond.signal();
}
Copy the code

Later in the thread synchronization model computeNextEventTimeLocked mPeriod and mPhase calculation based on the statistical sample calculation next VSYNC signal, then we look at how computeNextEventTimeLocked.

// Get the time of the nearest next VSYNC signal
nsecs_t computeNextEventTimeLocked(nsecs_t now) {
    nsecs_t nextEventTime = INT64_MAX;
    for (size_t i = 0; i < mEventListeners.size(); i++) {
        nsecs_t t = computeListenerNextEventTimeLocked(mEventListeners[i],now);
        if(t < nextEventTime) { nextEventTime = t; }}return nextEventTime;
}

// Calculate the time of the next Vsync signal event for the listener
nsecs_t computeListenerNextEventTimeLocked(const EventListener& listener,
        nsecs_t ref) {

    nsecs_t lastEventTime = listener.mLastEventTime;// The last event
    if (ref < lastEventTime) {
        ref = lastEventTime;
    }

    nsecs_t phase = mPhase + listener.mPhase;
    nsecs_t t = (((ref - phase) / mPeriod) + 1) * mPeriod + phase;

    if (t - listener.mLastEventTime < mPeriod / 2) {
        t += mPeriod;
    }

    return t;
}
Copy the code

ComputeNextEventTimeLocked to all listeners of computing the next VSYNC signal the occurrence of the time, and will be one of the most close to the current time as a result returned, each listener VSYNC signal next time could be different, because they may have set up different migration, So for each listener computing VSYNC signal next time was done by computeListenerNextEventTimeLocked.

The synchronization model is not always completely accurate, and there may be errors in each calculation. When errors occur, it is necessary to update the error value, and according to the error value, it is necessary to determine whether to enable hardware VSYNC to add samples to the synchronization model for calculation. This process is carried out in the SF postCompostion.

void SurfaceFlinger::postComposition()
{
    ...
    const HWComposer& hwc = getHwComposer();
    sp<Fence> presentFence = hwc.getDisplayFence(HWC_DISPLAY_PRIMARY);

    if (presentFence->isValid()) {
        if (mPrimaryDispSync.addPresentFence(presentFence)) {
            enableHardwareVsync();
        } else {
            disableHardwareVsync(false); }}... }Copy the code

Get the Fence of the current device in postComposition, and then use addPresentFence to calculate the error value of the synchronization model to determine whether hardware VSYNC needs to be enabled.

Notify listener

// Collect the callback and calculate whether to trigger the callback based on the delay bias of the callback
Vector<CallbackInvocation> gatherCallbackInvocationsLocked(nsecs_t now) {
    Vector<CallbackInvocation> callbackInvocations;
    nsecs_t ref = now - mPeriod;

    for (size_t i = 0; i < mEventListeners.size(); i++) {
        nsecs_t t = computeListenerNextEventTimeLocked(mEventListeners[i],
                ref);

        if (t < now) {// Anything less than the current time is a listener that needs to be notifiedCallbackInvocation ci; ci.mCallback = mEventListeners[i].mCallback; ci.mEventTime = t; callbackInvocations.push(ci); mEventListeners.editItemAt(i).mLastEventTime = t; }}return callbackInvocations;
}
Copy the code

After calculating the next time to trigger the VSYNC signal time, may need to wait for a period of time, because the current time has not yet arrived in recent triggering event, after the time of arrival in trigger, thread synchronization model will be collected through gatherCallbackInvocationsLocked requires notification listener, If the time of the listener’s next VSYNC signal occurrence is less than that of the current VSYNC signal, it indicates that the listener needs to be notified and added to the collection to wait for the callback.

// Calls back listeners interested in the VSync signal for the synchronized model
void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) {
    for (size_t i = 0; i < callbacks.size(); i++) { callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime); }}Copy the code

Finally, the listener is notified of the arrival of the VSYNC signal by an added callback.

Drawing Delay Source and Composition Delay Source (DispSyncSource)

SF init creates two delay source objects to handle VSYNC signals based on the synchronization model. These two different delay sources are managed by two different eventthreads. These are called mEventThread and mSFEventThread, and EventThread here is for delay sources to facilitate the management of VSYNC signals. For instance, for drawing delay sources, the well-known listener is Choreographer, The drawing process of the upper App is synchronized under Choreographer, which registers with the EventThread of the drawing delay source to listen for VSYNC signals, while SF registers with the EventThread of the composite delay source.

Let’s look at how the VSYNC signal passed by the synchronization model to the delay source can be used to pass it to the desired listener.

class DispSyncSource : public VSyncSource.private DispSync: :Callback {//VsyncSource is defined in eventThread.h
public:
	//DispSync is the model object of the Vsync signal
    DispSyncSource(DispSync* dispSync, nsecs_t phaseOffset, bool traceVsync) :
            mValue(0),
            mPhaseOffset(phaseOffset),// Distance Vsync signal delay offset
            mTraceVsync(traceVsync),
            mDispSync(dispSync) {}

    virtual ~DispSyncSource() {}
   
    virtual void setVSyncEnabled(bool enable) {
        // Do NOT lock the mutex here so as to avoid any mutex ordering issues
        // with locking it in the onDispSyncEvent callback.
        if (enable) {// If Vsync is enabled
            status_t err = mDispSync->addEventListener(mPhaseOffset,
                    static_cast<DispSync::Callback*>(this));// Add event callback for Vsync signal
            if(err ! = NO_ERROR) { ALOGE("error registering vsync callback: %s (%d)",
                        strerror(-err), err);
            }
            ATRACE_INT("VsyncOn".1);
        } 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);
            }
            ATRACE_INT("VsyncOn".0); }}virtual void setCallback(const sp<VSyncSource::Callback>& callback) {
        Mutex::Autolock lock(mMutex);
        mCallback = callback;
    }

private:
   // DispSync is called via fireCallbackInvocations
    virtual void onDispSyncEvent(nsecs_t when) {
        sp<VSyncSource::Callback> callback;
        {
            Mutex::Autolock lock(mMutex);
            callback = mCallback;

            if (mTraceVsync) {
                mValue = (mValue + 1) % 2;
                ATRACE_INT("VSYNC", mValue); }}if(callback ! = NULL) { callback->onVSyncEvent(when);// Invoke the set callback}}int mValue;

    const nsecs_t mPhaseOffset;
    const bool mTraceVsync;

    DispSync* mDispSync;
    sp<VSyncSource::Callback> mCallback;
    Mutex mMutex;
};

Copy the code

The delay source is constituted by the synchronization model DispSync, which is sent to the delay source VSYNC signal through the onDispSyncEvent interface. The arrival event of the VSYNC signal is sent to the setter (in effect, EventThread) via the onVSyncEvent method of the callback set by the delayed source. At the same time, the delay source can control whether to listen for the VSYNC signal from the synchronous model by setVSyncEnabled method.

EventThread

An EventThread is, of course, a Thread, and it starts when it is created. Let’s look at its thread callback

//frameworks/native/services/surfaceflinger/EventThread.cpp
bool EventThread::threadLoop() {
    DisplayEventReceiver::Event event;
    Vector< sp<EventThread::Connection> > signalConnections;
    signalConnections = waitForEvent(&event);// Waiting for VYSNC signal notification

    // 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);/ / post event
        if (err == -EAGAIN || err == -EWOULDBLOCK) {
            // The destination doesn't accept events anymore, it's probably
            // full. For now, we just drop the events on the floor.
            // FIXME: Note that some events cannot be dropped and would have
            // to be re-sent later.
            // Right-now we don't have the ability to do this.
            ALOGW("EventThread: dropping event (%08x) for connection %p",
                    event.header.type, conn.get());
        } 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.removeDisplayEventConnection(signalConnections[i]); }}return true;
}
Copy the code

The thread callback method first waits for the notification of VSYNC signal through waitForEvent, and at the same time obtains the Connection to be notified, where the Connection is the listener of the delayed source VSYNC signal. Events are then sent to listeners via the Connection postEvent method, so the core content of EventThread should be processed in waitForEvent.

This method is returned to the caller when the vsync signal is received or at least one connection is interested in the vsync signal
Vector< sp<EventThread::Connection> > EventThread::waitForEvent(
        DisplayEventReceiver::Event* event)
{
    Mutex::Autolock _l(mLock);
    Vector< sp<EventThread::Connection> > signalConnections;

    do {
        bool eventPending = false;
        bool waitForVSync = false;

        size_t vsyncCount = 0;
        nsecs_t timestamp = 0;
        // Keypoint 1 vertical signal event
        for (int32_t i=0 ; i<DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES ; i++) {
            timestamp = mVSyncEvent[i].header.timestamp;
            if (timestamp) {
                // we have a vsync event to dispatch
                *event = mVSyncEvent[i];// The vertical event arrives
                mVSyncEvent[i].header.timestamp = 0;
                vsyncCount = mVSyncEvent[i].vsync.count;
                break; }}if(! timestamp) {// no vsync event, see if there are some other eventeventPending = ! mPendingEvents.isEmpty();if (eventPending) {
                // we have some other event to dispatch
                *event = mPendingEvents[0];// Other events
                mPendingEvents.removeAt(0); }}// find out connections waiting for events
        // Keypoint 2 To see if there are connections that are interested in the VSYNC signal
        size_t count = mDisplayEventConnections.size();
        for (size_t i=0 ; i<count ; i++) {
            sp<Connection> connection(mDisplayEventConnections[i].promote());// Get the connection
            if(connection ! = NULL) { bool added =false;
                if (connection->count >= 0) {// The number of clients with vertical signals on the display is greater than or equal to 0
                    // we need vsync events because at least
                    // one connection is waiting for it
                    waitForVSync = true;// At least one connection is waiting for VSync events
                    if (timestamp) {
                        // we consume the event only if it's time
                        // (ie: we received a vsync event)
                        if (connection->count == 0) {
                            // fired this time around
                            connection->count = -1;
                            signalConnections.add(connection);
                            added = true;
                        } else if (connection->count == 1 ||
                                (vsyncCount % connection->count) == 0) {
                            // continuous event, and time to report it
                            signalConnections.add(connection);
                            added = true; }}}if(eventPending && ! timestamp && ! added) {// we don't have a vsync event to process
                    // (timestamp==0), but we have some pending
                    // messages.signalConnections.add(connection); }}else {
                // we couldn't promote this reference, the connection has
                // died, so clean-up!mDisplayEventConnections.removeAt(i); --i; --count; }}// Here we figure out if we need to enable or disable vsyncs
        // Key point 3: Turn on and off the VSYNC signal as needed. This refers to registering callbacks to the delay source and removing or adding listeners from the sync source
        if(timestamp && ! waitForVSync) {// A vertical signal was received when no client was interested
            // we received a VSYNC but we have no clients
            // don't report it, and disable VSYNC events
            disableVSyncLocked();
        } else if(! timestamp && waitForVSync) {// we have at least one client, so we want vsync enabled
            / / (TODO: this function is called right after we finish
            // notifying clients of a vsync, so this call will be made
            // at the vsync rate, e.g. 60fps. If we can accurately
            // track the current state we could avoid making this call
            // so often.)
            enableVSyncLocked();/ / to enable Vsync
        }

        // note: ! timestamp implies signalConnections.isEmpty(), because we
        // don't populate signalConnections if there's no vsync pending
        if(! timestamp && ! eventPending) {// wait for something to happen
            if (waitForVSync) {// If the VSYNC signal does not arrive, but a connection with interest is waiting for VSYNC, the thread will wait for VSYNC signal and block waiting to wake up
                // This is where we spend most of our time, waiting
                // for vsync events and new client registrations.
                //
                // If the screen is off, we can't use h/w vsync, so we
                // use a 16ms timeout instead. It doesn't need to be
                // precise, we just need to keep feeding our clients.
                //
                // We don't want to stall if there's a driver bug, so we
                // use a (long) timeout when waiting for h/w vsync, and
                // generate fake events when necessary.
                bool softwareSync = mUseSoftwareVSync;
                nsecs_t timeout = softwareSync ? ms2ns(16) : ms2ns(1000);
                if (mCondition.waitRelative(mLock, timeout) == TIMED_OUT) {
                    if(! softwareSync) { ALOGW("Timed out waiting for hw vsync; faking it");
                    }
                    // how do we decide which display id the fake
                    // vsync came from ?
                    mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;// Message type
                    mVSyncEvent[0].header.id = DisplayDevice::DISPLAY_PRIMARY;
                    mVSyncEvent[0].header.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
                    mVSyncEvent[0].vsync.count++; }}else {// No interest in VSync signal, just block the thread
                // Nobody is interested in vsync, so we just want to sleep.
                // h/w vsync should be disabled, so this will wait until we
                // get a new connection, or an existing connection becomes
                // interested in receiving vsync again.mCondition.wait(mLock); }}}while (signalConnections.isEmpty());

    // here we're guaranteed to have a timestamp and some connections to signal
    // (The connections might have dropped out of mDisplayEventConnections
    // while we were asleep, but we'll still have strong references to them.)
    return signalConnections;
}
Copy the code

When the vertical event arrives, it is saved in the event parameter. If timestamp is 0, no VSYNC signal arrives. Registered listeners are saved in mDisplayEventConnections. If the connection count is greater than or equal to 0, a listener is interested in the VSYNC signal, set waitForVSync to true, and add the listener to the signalConnections collection. The connection count value has the following meanings:

  1. Count >= 1: Continuous Event. Count is the vsync rate. If the vsync rate is greater than or equal to 1, vsync events are continuously received
  2. Count == 0: one-shot event that has not fired indicates that it is received only once
  3. Count ==-1: one-shot event that fired this round/disabled equals -1, indicating that no more vsync events can be received

When timestamp &&! WaitForVSync indicates that the VSYNC signal has arrived, but there are no interested listeners at this time. Therefore, no more VSYNC signals need to be received. Use disableVSyncLocked to remove the listening of VSYNC signals in the synchronized model.

When!!!! If timestamp && waitForVSync is met, there are interested listeners, but VSYNC signal has not been reached. In this case, you need to use enableVSyncLocked to add the delay source to the listener set in the synchronization model.

If! timestamp && ! EventPending indicates that no VSYNC signal has arrived and no other events are waiting for, but if waitForVSync is true, the VSYNC signal is waiting, but instead of waiting indefinitely, there is a timeout.

When the VSYNC signal arrives, the EventThread callback receives the VSYNC signal from the synchronization model. This callback is the onVSyncEvent method of EventThread

void EventThread::onVSyncEvent(nsecs_t timestamp) {
    Mutex::Autolock _l(mLock);
    mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;// Prepare the message to be sent
    mVSyncEvent[0].header.id = 0;
    mVSyncEvent[0].header.timestamp = timestamp;
    mVSyncEvent[0].vsync.count++;
    mCondition.broadcast();// broadcast to unblock threadloop
}
Copy the code

In the onVSyncEvent method, it unblocks waiting for a VSYNC signal in waitForEvent. Thus exits the while loop, notifying the corresponding listener of the arrival of the VSYNC signal.

conclusion

At this point, we have completed the introduction of VSYNC signal transmission and control process in SF. Let’s briefly summarize the whole transmission process:

  1. Hardware or software emulation triggers the VSYNC signal to inform SF
  2. SF receives the VSYNC signal from the hardware and adds it to the sample array of synchronous model DispSync to count and calculate the offset and period of the model
  3. The synchronization model calculates the next VSYNC signal occurrence time according to the calculated offset and period, and notifies the listener of the event of VSYNC signal arrival
  4. The VSYNC signal of the synchronization model is transmitted to the delay source, which manages the sending and receiving of the VSYNC signal through EventThread