美文网首页
显示框架之Vsync原理

显示框架之Vsync原理

作者: Android图形显示之路 | 来源:发表于2022-04-03 07:37 被阅读0次

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 周期,回调一次。

相关文章

  • 显示框架之Vsync原理

    vsync的介绍和由来网上介绍的有很多,个人理解vsync是统一app、sf、lcm刷新的步调,就好像人走路,走的...

  • 显示框架之显示流程图

    用几个图总结下显示流程 1.请求Vsync-app流程 这部分内容可以查看<<显示框架之Choreographer...

  • iOS 性能优化

    1.界面卡顿原因 (1)图像显示的原理:收到一个Vsync信号 ,系统就会利用CADisplayLink通知系统。...

  • Vsync 原理

  • Android性能优化之绘制优化

    前言 1 绘制原理 CPU负责计算显示内容 GPU负责栅格化(UI元素绘制到屏幕上) 16ms发出VSync信号触...

  • Matrix-FrameTracer源码阅读

    参考Choreographer原理View、Window、WindowManager---VSYNC信号 运行Ma...

  • iOS--性能优化

    卡顿 屏幕成像原理 卡顿产生的原因 CPU计算和GPU渲染耗时较长,在下一 VSync信号到来之前没有准备好要显示...

  • FPS 检测

    iOS 的显示系统是由 VSync 信号驱动的,VSync 信号由硬件时钟生成,每秒钟发出 60 次(这个值取决设...

  • 垂直同步(VSYNC)实现原理

    VSYNC在显示周期内同步一些确定的事件,APP在VSYNC结束的时间点绘制画面,也是在这个时间点SurfaceF...

  • [Unity优化] Unity Profiler性能分析

    Profiler窗口 1. CPU A. WaitForTargetFPS: Vsync(垂直同步)功能所,即显示...

网友评论

      本文标题:显示框架之Vsync原理

      本文链接:https://www.haomeiwen.com/subject/rhmajrtx.html