Vsync同步機(jī)制 一

什么是Vsync同步機(jī)制?

Vsync(垂直同步信號量),用來同步渲染,讓AppUI和SurfaceFlinger可以按硬件產(chǎn)生的VSync節(jié)奏進(jìn)行工作。
Vsync要解決的問題:

Vsync要解決的問題

為什么會產(chǎn)生這樣的問題?

CPU負(fù)責(zé)對UI進(jìn)行更新,GPU負(fù)責(zé)對UI進(jìn)行渲染,兩者的頻率不一致,會導(dǎo)致CPU還未更新完成,就被GPU渲染到了屏幕上。所以會出現(xiàn)圖片上的問題。

如何解決這個問題?

解決這個問題的方法就是讓CPU和GPU以相同的節(jié)奏進(jìn)行工作。如下圖所示

Vsync

讓CPU和GPU以相同的頻率進(jìn)行工作,這就是Vsync要做的工作。Vsync以固定的頻率發(fā)出信號,每當(dāng)收到CPU先對UI進(jìn)行更新,然后GPU再進(jìn)行繪制,這樣就可以解決上面的問題了。

那Android的Vsync機(jī)制是如何進(jìn)行的呢?

Android的Vsync整體框架圖

disp_sync_arch.png

這張圖可以很明顯的看出Vsync事件的傳遞過程。
Vsync信號并不是有硬件直接產(chǎn)生,而是由DispSync線程產(chǎn)生的。DispSync會根據(jù)HWC產(chǎn)生的VSync進(jìn)行采樣,創(chuàng)建模型,然后輸出了SW_VSYNC信號,SW_VSYNC再根據(jù)SF和APP的phase offset做調(diào)整,分別輸出給Vsync-sf和Vsync-app。

再看下幾個類之間的關(guān)系圖

HWC產(chǎn)生硬件Vsync信號

在分析HWComposer的時候,HWComposer中會注冊硬件Vsync事件回調(diào),在硬件Vsync事件到來的時候,回調(diào)HWComposer的vsync函數(shù)。

void HWComposer::vsync(int disp, int64_t timestamp) {
    if (uint32_t(disp) < HWC_NUM_PHYSICAL_DISPLAY_TYPES) {
        {
            Mutex::Autolock _l(mLock);
            //記錄對應(yīng)硬件設(shè)備Vsync信號產(chǎn)生的時間
            mLastHwVSync[disp] = timestamp;
        }
        //然后直接通知SurfaceFlinger的onVsyncReceived函數(shù)
        mEventHandler.onVSyncReceived(disp, timestamp);
    }
}

void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) {
    bool needsHwVsync = false;

    { // Scope for the lock
        Mutex::Autolock _l(mHWVsyncLock);
        //如果是主顯示設(shè)備的Vsync信號,且當(dāng)前硬件Vsync事件是打開的,則調(diào)用DispSync的addResyncSample函數(shù)
        //將硬件VSync事件添加到DispSYnc的樣本中,用于創(chuàng)建軟件Vsync時間模型
        if (type == 0 && mPrimaryHWVsyncEnabled) {
            needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);
        }
    }
     
    //更加樣本采樣結(jié)果,決定是否要關(guān)掉硬件Vsync事件。
    if (needsHwVsync) {
        enableHardwareVsync();
    } else {
        disableHardwareVsync(false);
    }
}

HWComposer接收到硬件的Vsync事件并沒有直接傳遞給系統(tǒng)使用,而是通過SurfaceFlinger將Vsync事件,添加到了DispSync的Vsync事件樣本,當(dāng)DispSync采樣完成后,則會停止硬件Vsync事件,由軟件Vsync根據(jù)樣本的計算結(jié)果產(chǎn)生Vsync事件。
mPrimaryHWVsyncEnabled變量控制DispSync是否需要采集樣本,當(dāng)模型Vsync周期有誤差時需要重新打開硬件Vsync,再次采集硬件Vsync樣本。

bool DispSync::addResyncSample(nsecs_t timestamp) {
    Mutex::Autolock lock(mMutex);

    //此處相當(dāng)于做了一個大小為32環(huán)形Buffer,每當(dāng)有新的樣本過來之后就添加到數(shù)組Buffer中,如果Buffer已滿,則替換掉最老的樣本
    size_t idx = (mFirstResyncSample + mNumResyncSamples) % MAX_RESYNC_SAMPLES;
    mResyncSamples[idx] = timestamp;

    if (mNumResyncSamples < MAX_RESYNC_SAMPLES) {
        mNumResyncSamples++;
    } else {
        mFirstResyncSample = (mFirstResyncSample + 1) % MAX_RESYNC_SAMPLES;
    }

    //當(dāng)樣本更新后,根據(jù)32個硬件Vsync樣本計算軟件Vsync模型.
    updateModelLocked();

    if (mNumResyncSamplesSincePresent++ > MAX_RESYNC_SAMPLES_WITHOUT_PRESENT) {
        resetErrorLocked();
    }

    //當(dāng)Vsync周期mPeriod = 0或者誤差超過一定的閥值,需要重新采樣,否則,則停止采樣,關(guān)掉硬件Vsync,知道有誤差的時候在打開,再次采樣
    return mPeriod == 0 || mError > kErrorThreshold;
}

addResyncSample方法主要作用是添加采樣樣本到Buffer中,DispSync中維護(hù)了一個環(huán)形的Buffer,大小為32個,每當(dāng)有新樣本過來時候,則將樣本添加到Buffer中,如果Buffer已經(jīng)滿了,則替換掉最老的樣本。
樣本更新后調(diào)用updateModelLocked來計算更新DispSync模型。

void DispSync::updateModelLocked() {
    //只有樣本數(shù)量大于3個的時候才會計算,太少則沒有意義
    if (mNumResyncSamples >= MIN_RESYNC_SAMPLES_FOR_UPDATE) {
        nsecs_t durationSum = 0;
        //第一步 :從最老的時間樣本開始計算Vsync間隔,然后再計算平均的時間間隔
        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 += mResyncSamples[idx] - mResyncSamples[prev];
        }
        //mPeriod就是計算產(chǎn)生的平均Vsync間隔的時間
        mPeriod = durationSum / (mNumResyncSamples - 1);

        //第二部:開始計算平均的偏差時間,因為不可能每個間隔都非常準(zhǔn)時,要求平均偏差
        double sampleAvgX = 0;
        double sampleAvgY = 0;
        double scale = 2.0 * M_PI / double(mPeriod);
        for (size_t i = 0; i < mNumResyncSamples; i++) {
            //遍歷并計算每個樣本相對于平均周期取余的偏差值,(如果完全準(zhǔn)時的樣本,應(yīng)該可以整除,余數(shù)為0),然后將偏差值轉(zhuǎn)換成弧度。
            size_t idx = (mFirstResyncSample + i) % MAX_RESYNC_SAMPLES;
            nsecs_t sample = mResyncSamples[idx];
            double samplePhase = double(sample % mPeriod) * scale;
            //計算弧度的X和Y
            sampleAvgX += cos(samplePhase);
            sampleAvgY += sin(samplePhase);
        }
        //求平均弧度X,Y
        sampleAvgX /= double(mNumResyncSamples);
        sampleAvgY /= double(mNumResyncSamples);
        
        //將平均弧度轉(zhuǎn)換成平均的偏差值
        mPhase = nsecs_t(atan2(sampleAvgY, sampleAvgX) / scale);

        if (mPhase < 0) {
            mPhase += mPeriod;
        }

        if (kTraceDetailedInfo) {
            ATRACE_INT64("DispSync:Period", mPeriod);
            ATRACE_INT64("DispSync:Phase", mPhase);
        }

        // 人為減少Vsync刷新頻率
        mPeriod += mPeriod * mRefreshSkipCount;
        //更新周期和偏差模型
        mThread->updateModel(mPeriod, mPhase);
    }
}

計算平均周期模型和平均偏差模型。
如何計算平均周期?
將所有樣本的間隔時間相加,然后除以間隔數(shù),求出平均間隔時間.
如何計算平均偏差?
如果完全準(zhǔn)時的樣本,應(yīng)該可以整除平均,余數(shù)為0,有偏差的則余數(shù)不為0,所以對所有的樣本除以mPeriod平均周期時間,計算余數(shù),將余數(shù)又轉(zhuǎn)換成弧度. 計算X和Y, 求X和Y的平均值,然后再由平均X,Y求出弧度,再轉(zhuǎn)換成偏差值。這樣就計算出平均偏差了.

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

調(diào)用VsyncThread的mCond.signal()通知VsyncThread模型更新完成。

DispSync 軟件Vsync模型

SurfaceFlinger的Vsync并不是直接使用硬件產(chǎn)生Vsync,而是由軟件根據(jù)硬件Vsync創(chuàng)建了一個數(shù)據(jù)模型來模擬產(chǎn)生Vsync信號。負(fù)責(zé)產(chǎn)生軟件Vsync信號的就是DispSync。
DispSync是SurfaceFlinger中的一個變量。

DispSync mPrimaryDispSync;

mPrimaryDispSync就是Vsync的信號源,我們先看下Vsync信號是如何產(chǎn)生的?


DispSync::DispSync() :
        mRefreshSkipCount(0),
        mThread(new DispSyncThread()) {
    //啟動VsyncThread
    mThread->run("DispSync", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);
}

DispSync VsyncThread 第一部分

DispSync對象在創(chuàng)建的時候會啟動一個VsyncThread線程,該線程用于模擬Vsync信號。然后我們看下VsyncThread線程的threadloop方法


    virtual bool threadLoop() {
        status_t err;
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        nsecs_t nextEventTime = 0;
        //執(zhí)行循環(huán)
        while (true) {
            Vector<CallbackInvocation> callbackInvocations;

            nsecs_t targetTime = 0;

            { // Scope for lock
                Mutex::Autolock lock(mMutex);
                //如果需要停止Vsync線程,則直接返回
                if (mStop) {
                    return false;
                }
                
                //如果mPeriod為0, 則等待
                //在VsyncThread創(chuàng)建的時候,mPeriod默認(rèn)為0,所以會阻塞在這邊。
                //在VsyncThread updateModel更新模型的時候,會設(shè)置mPeriod,然后繼續(xù)執(zhí)行,線程剛開始的時候Vsync模型還沒有創(chuàng)建好,所以無法產(chǎn)生SW Vsync信號
                if (mPeriod == 0) {
                    err = mCond.wait(mMutex);
                    if (err != NO_ERROR) {
                        ALOGE("error waiting for new events: %s (%d)",
                                strerror(-err), err);
                        return false;
                    }
                    continue;
                }

                ......
    }

VsyncThread剛開始由于mPeriod=0,也就是說當(dāng)前線程還不知道按照什么樣的頻率產(chǎn)生Vsync信號,所以會等待mPeriod周期模型的更新。
什么時候設(shè)置更新mPeriod的時間呢?
就是上面提到的,應(yīng)用模型樣本計算完成后會設(shè)置mPeriod和mPhase,這樣VsyncThread就會繼續(xù)執(zhí)行

DispSync VsyncThread 第二部分

    virtual bool threadLoop() {

                ......

                //計算下次Vsync要產(chǎn)生的時間
                nextEventTime = computeNextEventTimeLocked(now);
                targetTime = nextEventTime;

                bool isWakeup = false;

                //如果還沒有到時間,就等待相應(yīng)的時間后在處理
                if (now < targetTime) {
                    err = mCond.waitRelative(mMutex, targetTime - now);

                    if (err == TIMED_OUT) {
                        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);

                //到達(dá)執(zhí)行Vsync時間后,回調(diào)監(jiān)聽對象.
                if (isWakeup) {
                    mWakeupLatency = ((mWakeupLatency * 63) +
                            (now - targetTime)) / 64;
                    if (mWakeupLatency > 500000) {
                        // Don't correct by more than 500 us
                        mWakeupLatency = 500000;
                    }
                    if (kTraceDetailedInfo) {
                        ATRACE_INT64("DispSync:WakeupLat", now - nextEventTime);
                        ATRACE_INT64("DispSync:AvgWakeupLat", mWakeupLatency);
                    }
                }

                callbackInvocations = gatherCallbackInvocationsLocked(now);
            }

            if (callbackInvocations.size() > 0) {
                fireCallbackInvocations(callbackInvocations);
            }
        }

        return false;
    }

當(dāng)有Vsync的周期時間和偏差時間后,VsyncThread就可以模擬產(chǎn)生軟件Vsync信號了。產(chǎn)生Vsync信號就會通過回調(diào)接口通知監(jiān)聽者.
DispVsync有兩個監(jiān)聽者,就是我們上面提到的SurfaceFlinger和APP,SurfaceFlinger和App所需要的Vsync時間不是完全一致。App一般先接到Vsync信號,還是繪制UI,然后SurfaceFlinger再接收到Vsync信號完成UI合成。
VsyncThread做了什么事情呢?
首先計算下次Vsync產(chǎn)生的時間,計算下次產(chǎn)生時間會根據(jù)兩個監(jiān)聽者上次接收到Vsync時間,然后加上周期時間,偏差時間,和APP或者SF自己的偏差時間。得到下次Vsync時間。

下次Vsync時間 = 上次Vsync時間 + mPeriod(平均周期) + mPhase(平均偏差) + offset(自定義偏差)

得到最近的下次執(zhí)行時間,Vsync只要Wait相應(yīng)的時間差就可了,等到執(zhí)行時間后回調(diào)SF或者APP的Vsync信號。這樣Vsync信號就產(chǎn)生了.

DisplaySyncSource

上面講DispSync提到,DispSync有兩個監(jiān)聽者,這兩個監(jiān)聽這就是兩個DisplaySyncSource對象。兩個對象是在SurfaceFlinger的init進(jìn)程創(chuàng)建的.

    sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            vsyncPhaseOffsetNs, true, "app");
    sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            sfVsyncPhaseOffsetNs, true, "sf");

init中創(chuàng)建了兩個DispSyncSource對象, 一個是SF的sfVsyncSrc,一個是SurfaceFlinger的sfVsyncSrc對象. DiplaySyncSource構(gòu)造方法中會保存一個syncOffet值,表示自己收到Vsync偏差時間.
DispSync計算Vsync產(chǎn)生時間的時候,會根據(jù)這個偏差進(jìn)行計算.也就是上面公式中的自定義偏差offset。
DispSync有一些重要的函數(shù),具體如下:

    //開始/關(guān)閉監(jiān)聽Vsync事件
    //開始監(jiān)聽Vsync事件就是將自己添加到DispSync的回調(diào)監(jiān)聽中
    //關(guān)閉監(jiān)聽Vsync事件就是將自己從DispSync監(jiān)聽回調(diào)中移除
    virtual void setVSyncEnabled(bool enable) {
        Mutex::Autolock lock(mVsyncMutex);
        if (enable) {
            status_t err = mDispSync->addEventListener(mPhaseOffset,
                    static_cast<DispSync::Callback*>(this));
            if (err != NO_ERROR) {
                ALOGE("error registering vsync callback: %s (%d)",
                        strerror(-err), err);
            }
            //ATRACE_INT(mVsyncOnLabel.string(), 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(mVsyncOnLabel.string(), 0);
        }
        mEnabled = enable;
    }

    //設(shè)置自己的回調(diào),即收到Vsync事件后,回調(diào)給誰
    virtual void setCallback(const sp<VSyncSource::Callback>& callback) {
        Mutex::Autolock lock(mCallbackMutex);
        mCallback = callback;
    }

    //DispSync的回調(diào)接口, 當(dāng)收到DispSync事件后會回到該接口,然后該接口將事件又回調(diào)給自己的callBack
    //也就是后邊講的EventThread,EventThread會監(jiān)聽DisplaySyncSource。
    virtual void onDispSyncEvent(nsecs_t when) {
        sp<VSyncSource::Callback> callback;
        {
            Mutex::Autolock lock(mCallbackMutex);
            callback = mCallback;

        if (callback != NULL) {
            callback->onVSyncEvent(when);
        }
    }

這樣,我們就大概理解了Vsync信號的產(chǎn)生過程。
1:HWC硬件產(chǎn)生Vsync信號,給DispSync添加樣本
2:DispSync根據(jù)樣本計算Vsync周期,然后產(chǎn)生軟件Vsync信號。計算Vsync事件會根據(jù)不同的Listenr計算不同的時間.
3:DisplaySyncSource是DispSync的監(jiān)聽者,有兩個DisplaySyncSource對象,分別代表SF和APP的DisplaySyncSource,兩個回調(diào)的Vsync時間不同.
4:DisplaySyncSource收到Vsync事件后,會發(fā)給他自己的監(jiān)聽者EventThread

void EventThread::onVSyncEvent(nsecs_t timestamp) {
    Mutex::Autolock _l(mLock);
    mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
    mVSyncEvent[0].header.id = 0;
    mVSyncEvent[0].header.timestamp = timestamp;
    mVSyncEvent[0].vsync.count++;
    mCondition.broadcast();
}

EventThread收到Vsync時間后會存放到mVSyncEvent[0]中,通知EventThread線程進(jìn)行處理。

為什么要有兩個DisplaySyncSource且時間不一樣呢?

Vsync

具體如上圖所示
App繪制UI, 繪制完成后交給SurfaceFlinger進(jìn)行合成, 所以App繪制時間優(yōu)先于SF合成時間,將兩者時間錯開,避免資源競爭,增加效率。如何APP和SF的偏差時間設(shè)置好的話,APP + SF在一個Vsync周期內(nèi)就可以繪制合成,在下一個Vsync周期到來的時候就可以顯示到屏幕上了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容