顯示框架之Vsync原理

vsync的介紹和由來網(wǎng)上介紹的有很多,個(gè)人理解vsync是統(tǒng)一app尤误、sf侠畔、lcm刷新的步調(diào),就好像人走路损晤,走的快和走的慢软棺。網(wǎng)上介紹都是從宏觀的角度分析vsync的原理,但作為底層工作者尤勋,還是需要從代碼層弄懂它實(shí)際工作的原理喘落。
vsync的基礎(chǔ)介紹:https://blog.csdn.net/zhaizu/article/details/51882768
vsync分為硬件vsync和軟件vsync,硬件vsync可以理解為屏幕的te信號(hào)最冰,當(dāng)hwc通過commit把數(shù)據(jù)提交給屏側(cè)時(shí)瘦棋,屏?xí)谙聜€(gè)te信號(hào)把數(shù)據(jù)刷出來;軟件vsync可以理解為在SurfaceFlinger內(nèi)部通過一套計(jì)算模型模擬硬件vsync暖哨。為什么需要在SurfaceFlinger里面搞一套計(jì)算模型赌朋?試想下,如果沒有篇裁,那SurfaceFlinger每一幀的刷新都需要接收從屏幕發(fā)過來的vsync沛慢,中間經(jīng)過了HWBinder調(diào)用,多增加調(diào)用就多一份功耗达布,當(dāng)然团甲,時(shí)間戳也不一定準(zhǔn)確,所以在SurfaceFlinger里面搞了一套軟件vsync計(jì)算模型黍聂。
模型的輸入為硬件vsync的時(shí)間戳:

文件:frameworks/native/services/surfaceflinger/Scheduler/VSyncPredictor.cpp

bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
    std::lock_guard<std::mutex> lk(mMutex);

    // 先校驗(yàn)硬件te時(shí)間戳的有效性躺苦,如果無效,則繼續(xù)從屏幕那邊采集
    if (!validate(timestamp)) {
        // VSR could elect to ignore the incongruent timestamp or resetModel(). If ts is ignored,
        // don't insert this ts into mTimestamps ringbuffer.
        if (!mTimestamps.empty()) {
            mKnownTimestamp =
                    std::max(timestamp, *std::max_element(mTimestamps.begin(), mTimestamps.end()));
        } else {
            mKnownTimestamp = timestamp;
        }
        return false;
    }

    //  如果硬件te信號(hào)有效产还,則把時(shí)間戳放在mTimestamps 隊(duì)列里面
    if (mTimestamps.size() != kHistorySize) {
        mTimestamps.push_back(timestamp);
        mLastTimestampIndex = next(mLastTimestampIndex);
    } else {
        mLastTimestampIndex = next(mLastTimestampIndex);
        mTimestamps[mLastTimestampIndex] = timestamp;
    }
    
    //  如果mTimestamps 的size小于6圾另,則繼續(xù)從屏幕那邊采集
    if (mTimestamps.size() < kMinimumSamplesForPrediction) {
        mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
        return true;
    }

    // This is a 'simple linear regression' calculation of Y over X, with Y being the
    // vsync timestamps, and X being the ordinal of vsync count.
    // The calculated slope is the vsync period.
    // Formula for reference:
    // Sigma_i: means sum over all timestamps.
    // mean(variable): statistical mean of variable.
    // X: snapped ordinal of the timestamp
    // Y: vsync timestamp
    //
    //         Sigma_i( (X_i - mean(X)) * (Y_i - mean(Y) )
    // slope = -------------------------------------------
    //         Sigma_i ( X_i - mean(X) ) ^ 2
    //
    // intercept = mean(Y) - slope * mean(X)

    // 這部分代碼是對(duì)輸入的6個(gè)時(shí)間戳做一個(gè)線性回歸,模擬出一條直線雕沉,這條直線的斜率就是vsync的周期集乔,截距是intercept
    ...

    // 模型的輸出就是斜率和截距
    it->second = {anticipatedPeriod, intercept};

    ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp,
          anticipatedPeriod, intercept);
    return true;
}

文件:frameworks/native/services/surfaceflinger/Scheduler/Scheduler.cpp

void Scheduler::addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
                                bool* periodFlushed) {
    bool needsHwVsync = false;
    *periodFlushed = false;
    { // Scope for the lock
        std::lock_guard<std::mutex> lock(mHWVsyncLock);
        if (mPrimaryHWVsyncEnabled) {
            needsHwVsync =
                    mPrimaryDispSync->addResyncSample(timestamp, hwcVsyncPeriod, periodFlushed);
        }
    }

    if (needsHwVsync) {
        enableHardwareVsync();
    } else {
        //  如果不再需要HW vsync,則采樣結(jié)束,關(guān)閉硬件Vsync
        disableHardwareVsync(false);
    }
}

void Scheduler::disableHardwareVsync(bool makeUnavailable) {
    std::lock_guard<std::mutex> lock(mHWVsyncLock);
    if (mPrimaryHWVsyncEnabled) {
        // 通知hwc,關(guān)掉硬件vsync
        mEventControlThread->setVsyncEnabled(false);
        mPrimaryDispSync->endResync();
        // 將mPrimaryHWVsyncEnabled 設(shè)置為false, 這個(gè)是sf側(cè)是否打開hw vsync的標(biāo)志
        mPrimaryHWVsyncEnabled = false;
    }
    if (makeUnavailable) {
        mHWVsyncAvailable = false;
    }
}

其實(shí)軟件Vsync的計(jì)算模型就是簡(jiǎn)單的線性回歸涯保,采樣6個(gè)硬件 te信號(hào)彬向,來擬合出Surfaceflinger要跑的vsync周期和截距,這個(gè)截距的作用還不是很清楚宫莱。采樣完畢后,將硬件Vsync關(guān)閉哩罪。
接下來看下授霸,SurfaceFlinger是如何利用模型的輸出值計(jì)算下一個(gè)vsync的時(shí)間戳。


TimerDispatch線程創(chuàng)建Vsync Event.png

從trace來看际插,TimerDispatch線程會(huì)執(zhí)行vsync的callback來創(chuàng)建vsync event碘耳,app拿這個(gè)vsync event喚醒a(bǔ)pp的EventThread去給應(yīng)用繪制,sf拿這個(gè)vsync event喚醒sf的EventThread去刷幀框弛,之后再計(jì)算下一個(gè)vsync的時(shí)間戳辛辨,重復(fù)如此。代碼從這里切入進(jìn)來看:

文件:frameworks/native/services/surfaceflinger/Scheduler/VSyncReactor.cpp

void callback(nsecs_t vsynctime, nsecs_t wakeupTime) {
        {
            std::lock_guard<std::mutex> lk(mMutex);
            mLastCallTime = vsynctime;
        }
         // mCallback 是DispSyncSource對(duì)象
        mCallback->onDispSyncEvent(wakeupTime, vsynctime);

        {
            std::lock_guard<std::mutex> lk(mMutex);
            if (mStopped) {
                return;
            }
            // 計(jì)算下一個(gè)vsync時(shí)間戳
            auto const schedule_result = mRegistration.schedule(calculateWorkload(), vsynctime);
            LOG_ALWAYS_FATAL_IF((schedule_result != ScheduleResult::Scheduled),
                                "Error rescheduling callback: rc %X", schedule_result);
        }
    }

文件:frameworks/native/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp

ScheduleResult VSyncDispatchTimerQueue::schedule(CallbackToken token, nsecs_t workDuration,
                                                 nsecs_t earliestVsync) {
    
        ...
        // 這里的callback為VSyncDispatchTimerQueueEntry 對(duì)象
        result = callback->schedule(workDuration, earliestVsync, mTracker, now);
        if (result == ScheduleResult::CannotSchedule) {
            return result;
        }

        if (callback->wakeupTime() < mIntendedWakeupTime - mTimerSlack) {
            
            rearmTimerSkippingUpdateFor(now, it);
        }
    }

    return result;
}

ScheduleResult VSyncDispatchTimerQueueEntry::schedule(nsecs_t workDuration, nsecs_t earliestVsync,
                                                      VSyncTracker& tracker, nsecs_t now) {
    // 計(jì)算下一個(gè)vsync的時(shí)間戳
    auto nextVsyncTime =
            tracker.nextAnticipatedVSyncTimeFrom(std::max(earliestVsync, now + workDuration));
    ...
    // 下一個(gè)喚醒時(shí)間是下一個(gè)vsync時(shí)間-workDuration, workDuration = Vsync period - offset
    auto const nextWakeupTime = nextVsyncTime - workDuration;
    mWorkDuration = workDuration;
    mEarliestVsync = earliestVsync;
    // 更新mActualWakeupTime 和 mActualVsyncTime 值
    mArmedInfo = {nextWakeupTime, nextVsyncTime};
    return ScheduleResult::Scheduled;
}

void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor(
        nsecs_t now, CallbackMap::iterator const& skipUpdateIt) {
    std::optional<nsecs_t> min;
    std::optional<nsecs_t> targetVsync;
    std::optional<std::string_view> nextWakeupName;
    for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
        ...
        
        auto const wakeupTime = *callback->wakeupTime();
        if (!min || (min && *min > wakeupTime)) {
            // 將喚醒時(shí)間設(shè)給min
            nextWakeupName = callback->name();
            min = wakeupTime;
            targetVsync = callback->targetVsync();
        }
    }

    if (min && (min < mIntendedWakeupTime)) {
        if (targetVsync && nextWakeupName) {
            mTraceBuffer.note(*nextWakeupName, *min - now, *targetVsync - now);
        }
        // 給定時(shí)器輸入時(shí)間
        setTimer(*min, now);
    } else {
        ATRACE_NAME("cancel timer");
        cancelTimer();
    }
}

void VSyncDispatchTimerQueue::setTimer(nsecs_t targetTime, nsecs_t now) {
    mIntendedWakeupTime = targetTime;
    // 定時(shí)器定時(shí)的時(shí)間是 mActualWakeupTime - now
    mTimeKeeper->alarmIn(std::bind(&VSyncDispatchTimerQueue::timerCallback, this),
                         targetTime - now);
    mLastTimerSchedule = mTimeKeeper->now();
}

文件:frameworks/native/services/surfaceflinger/Scheduler/Timer.cpp

void Timer::alarmIn(std::function<void()> const& cb, nsecs_t fireIn) {
     ...
    // 把callback帶進(jìn)來
    mCallback = cb;

    // 時(shí)間單位轉(zhuǎn)換
    struct itimerspec old_timer;
    struct itimerspec new_timer {
        .it_interval = {.tv_sec = 0, .tv_nsec = 0},
        .it_value = {.tv_sec = static_cast<long>(fireIn / ns_per_s),
                     .tv_nsec = static_cast<long>(fireIn % ns_per_s)},
    };

    // 在Timer::reset 時(shí)創(chuàng)建了一個(gè) mTimerFd瑟枫,可以理解為創(chuàng)建了一個(gè)定時(shí)器斗搞,然后設(shè)置了定時(shí)的時(shí)間
    if (timerfd_settime(mTimerFd, 0, &new_timer, &old_timer)) {
        ALOGW("Failed to set timerfd %s (%i)", strerror(errno), errno);
    }
}

利用vsync模型值的函數(shù)在nextAnticipatedVSyncTimeFrom 邏輯里面,這個(gè)函數(shù)也是計(jì)算下一個(gè)vsync時(shí)間戳的最主要的函數(shù)慷妙,來看下

文件: frameworks/native/services/surfaceflinger/Scheduler/VSyncPredictor.cpp

nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
    std::lock_guard<std::mutex> lk(mMutex);

    //從vsync 計(jì)算模型獲得值僻焚,slope表示計(jì)算出來的vsync period,intercept表示截距
    auto const [slope, intercept] = getVSyncPredictionModel(lk);

    ...
    // 從mTimestamps 時(shí)間戳拿最小的一個(gè)時(shí)間
    auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end());

    // See b/145667109, the ordinal calculation must take into account the intercept.
    // 這套算式大致理解為 prediction 約等于 timepoint + slope膝擂, 為什么是約等于溅呢,因?yàn)槿∮噙\(yùn)算是個(gè)陷阱
    auto const zeroPoint = oldest + intercept;
    auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
    auto const prediction = (ordinalRequest * slope) + intercept + oldest;

    ...
    return prediction;
}

對(duì)于這個(gè)運(yùn)算,其實(shí)可以這么簡(jiǎn)單理解猿挚,now + workDuration + slope 約等于 nextVsyncTime , 而 wakeuptime = nextVsyncTime - workDuration咐旧,所以給定時(shí)器設(shè)置的時(shí)間就是 slope,也就是過一個(gè)vsync 周期绩蜻,回調(diào)一次铣墨。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市办绝,隨后出現(xiàn)的幾起案子伊约,更是在濱河造成了極大的恐慌,老刑警劉巖孕蝉,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屡律,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡降淮,警方通過查閱死者的電腦和手機(jī)超埋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人霍殴,你說我怎么就攤上這事媒惕。” “怎么了来庭?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵妒蔚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我月弛,道長(zhǎng)肴盏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任帽衙,我火速辦了婚禮菜皂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘佛寿。我一直安慰自己,他們只是感情好但壮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布冀泻。 她就那樣靜靜地躺著,像睡著了一般蜡饵。 火紅的嫁衣襯著肌膚如雪弹渔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天溯祸,我揣著相機(jī)與錄音肢专,去河邊找鬼。 笑死焦辅,一個(gè)胖子當(dāng)著我的面吹牛博杖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播筷登,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼剃根,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了前方?” 一聲冷哼從身側(cè)響起狈醉,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惠险,沒想到半個(gè)月后苗傅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡班巩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年渣慕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摇庙,死狀恐怖旱物,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卫袒,我是刑警寧澤宵呛,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站夕凝,受9級(jí)特大地震影響宝穗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜码秉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一逮矛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧转砖,春花似錦须鼎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姓赤,卻和暖如春赡译,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背不铆。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工蝌焚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人誓斥。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓只洒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親劳坑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子红碑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容