显示框架之Vsync原理

vsync的介绍和由来网上介绍的有很多,个人理解vsync是统一app、sf、lcm刷新的步调,就好像人走路,走的快和走的慢。网上介绍都是从宏观的角度分析vsync的原理,但作为底层工作者,还是需要从代码层弄懂它实际工作的原理。
vsync的基础介绍:https://blog.csdn.net/zhaizu/article/details/51882768
vsync分为硬件vsync和软件vsync,硬件vsync可以理解为屏幕的te信号,当hwc通过commit把数据提交给屏侧时,屏会在下个te信号把数据刷出来;软件vsync可以理解为在SurfaceFlinger内部通过一套计算模型模拟硬件vsync。为什么需要在SurfaceFlinger里面搞一套计算模型?试想下,如果没有,那SurfaceFlinger每一帧的刷新都需要接收从屏幕发过来的vsync,中间经过了HWBinder调用,多增加调用就多一份功耗,当然,时间戳也不一定准确,所以在SurfaceFlinger里面搞了一套软件vsync计算模型。
模型的输入为硬件vsync的时间戳:

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

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

    // 先校验硬件te时间戳的有效性,如果无效,则继续从屏幕那边采集
    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信号有效,则把时间戳放在mTimestamps 队列里面
    if (mTimestamps.size() != kHistorySize) {
        mTimestamps.push_back(timestamp);
        mLastTimestampIndex = next(mLastTimestampIndex);
    } else {
        mLastTimestampIndex = next(mLastTimestampIndex);
        mTimestamps[mLastTimestampIndex] = timestamp;
    }
    
    //  如果mTimestamps 的size小于6,则继续从屏幕那边采集
    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)

    // 这部分代码是对输入的6个时间戳做一个线性回归,模拟出一条直线,这条直线的斜率就是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,则采样结束,关闭硬件Vsync
        disableHardwareVsync(false);
    }
}

void Scheduler::disableHardwareVsync(bool makeUnavailable) {
    std::lock_guard<std::mutex> lock(mHWVsyncLock);
    if (mPrimaryHWVsyncEnabled) {
        // 通知hwc,关掉硬件vsync
        mEventControlThread->setVsyncEnabled(false);
        mPrimaryDispSync->endResync();
        // 将mPrimaryHWVsyncEnabled 设置为false, 这个是sf侧是否打开hw vsync的标志
        mPrimaryHWVsyncEnabled = false;
    }
    if (makeUnavailable) {
        mHWVsyncAvailable = false;
    }
}

其实软件Vsync的计算模型就是简单的线性回归,采样6个硬件 te信号,来拟合出Surfaceflinger要跑的vsync周期和截距,这个截距的作用还不是很清楚。采样完毕后,将硬件Vsync关闭。
接下来看下,SurfaceFlinger是如何利用模型的输出值计算下一个vsync的时间戳。


TimerDispatch线程创建Vsync Event.png

从trace来看,TimerDispatch线程会执行vsync的callback来创建vsync event,app拿这个vsync event唤醒app的EventThread去给应用绘制,sf拿这个vsync event唤醒sf的EventThread去刷帧,之后再计算下一个vsync的时间戳,重复如此。代码从这里切入进来看:

文件: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对象
        mCallback->onDispSyncEvent(wakeupTime, vsynctime);

        {
            std::lock_guard<std::mutex> lk(mMutex);
            if (mStopped) {
                return;
            }
            // 计算下一个vsync时间戳
            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 对象
        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) {
    // 计算下一个vsync的时间戳
    auto nextVsyncTime =
            tracker.nextAnticipatedVSyncTimeFrom(std::max(earliestVsync, now + workDuration));
    ...
    // 下一个唤醒时间是下一个vsync时间-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)) {
            // 将唤醒时间设给min
            nextWakeupName = callback->name();
            min = wakeupTime;
            targetVsync = callback->targetVsync();
        }
    }

    if (min && (min < mIntendedWakeupTime)) {
        if (targetVsync && nextWakeupName) {
            mTraceBuffer.note(*nextWakeupName, *min - now, *targetVsync - now);
        }
        // 给定时器输入时间
        setTimer(*min, now);
    } else {
        ATRACE_NAME("cancel timer");
        cancelTimer();
    }
}

void VSyncDispatchTimerQueue::setTimer(nsecs_t targetTime, nsecs_t now) {
    mIntendedWakeupTime = targetTime;
    // 定时器定时的时间是 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带进来
    mCallback = cb;

    // 时间单位转换
    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 时创建了一个 mTimerFd,可以理解为创建了一个定时器,然后设置了定时的时间
    if (timerfd_settime(mTimerFd, 0, &new_timer, &old_timer)) {
        ALOGW("Failed to set timerfd %s (%i)", strerror(errno), errno);
    }
}

利用vsync模型值的函数在nextAnticipatedVSyncTimeFrom 逻辑里面,这个函数也是计算下一个vsync时间戳的最主要的函数,来看下

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

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

    //从vsync 计算模型获得值,slope表示计算出来的vsync period,intercept表示截距
    auto const [slope, intercept] = getVSyncPredictionModel(lk);

    ...
    // 从mTimestamps 时间戳拿最小的一个时间
    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, 为什么是约等于,因为取余运算是个陷阱
    auto const zeroPoint = oldest + intercept;
    auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
    auto const prediction = (ordinalRequest * slope) + intercept + oldest;

    ...
    return prediction;
}

对于这个运算,其实可以这么简单理解,now + workDuration + slope 约等于 nextVsyncTime , 而 wakeuptime = nextVsyncTime - workDuration,所以给定时器设置的时间就是 slope,也就是过一个vsync 周期,回调一次。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容