Android 图形系统(三)—— 软件 VSYNC 与 DispSync 模型详解

Posted by JasonWu on November 19, 2021

硬件 VSYNC

在 Android 图形系统显示的过程之中,屏幕一帧要显示的内容按顺序经历了以下三步:

  • 各个应用、系统组件生成各自的图形数据;
  • 经由 SurfaceFlinger 合成图形数据;
  • HwComoser 制作并用于在屏幕上显示图像;

上述的过程我们可以称之为显示流水线

原始 VSYNC 信号是由硬件定时发出,称之为硬件 VSYNC。假设当前屏幕的刷新率为 60fps,那么每次硬件 VSYNC 到来的时间间隔约为 1000/60 = 16.7ms。

VSYNC 信号的作用在于同步整个显示流水线,可同步应用唤醒以开始渲染的时间、SurfaceFlinger 唤醒以合成屏幕的时间、以及屏幕刷新周期:

信号 流水线
第 1 个 VSYNC 信号到来 应用处理输入并生成帧 N
第 2 个 VSYNC 信号到来 应用处理输入并生成帧 N + 1、SurfaceFlinger 合成帧 N
第 3 个 VSYNC 信号到来 应用处理输入并生成帧 N + 2、SurfaceFlinger 合成帧 N + 1、屏幕开始显示帧 N

与 VSYNC 同步可以实现一致的延迟时间。它可以减少应用和 SurfaceFlinger 中的错误,并最大限度减小相位内外屏幕之间的偏移。提升图形的视觉表现。但同时也意味着在每帧时间没有很大的变化下,一帧的数据从生成到显示要经过至少两个 VSYNC 周期即 33ms 的延迟

事实上,一帧的数据从应用加合成通常不需要 33ms 的时间。因此,为了解决延迟的问题,Android 系统引入了软件 VSYNC 模型 DispSync

软件 DispSync 模型

承接上文,SurfaceFlinger 和 App 端都需要依据 VSYNC 来“行事”。在引入软件 VSYNC 模型后,SurfaceFlinger 和 App 端将改为依赖软件生成的 VSYNC 信号

DispSync 接收来自硬件的 VSYNC 信号,然后在指定的偏移(Phase)之后分别发送软件 VSYNC 至 SurfaceFlinger 和 App。

为了方便区分,硬件 VYSNC 表示为 HW_VSYNC_0,发送至 SurfaceFlinger 以及 App 的软件 VSYNC 表示 VSYNC-sf 以及 VSYNC-app。那么会有:

  • VSYNC-sf = HW_VSYNC_0 + phase-sf
  • VSYNC-app = HW_VSYNC_0 + phase-app

软件 VSYNC 的生成可以不依赖硬件 VSYNC。实际上,当误差在可接收的范围内,将会关闭 硬件 VSYNC,而软件 VYSNC 保持运作。

DispSync 是一个软件 phase-lock loop(PLL)模型,有以下特点:

  • 软件 VSYNC 的周期(Period)和偏移(Phase)通过接收多个硬件 VSYNC 时间后计算得出;
  • 软件 VSYNC 模型更新后,在误差范围内,会通知关闭硬件 VSYNC;
  • 在 SurfaceFlinger 完成图形数据合成后,会把反馈(PresentFence)给到 DispSync 来判断是否超出误差范围内,若超出,则通知重启硬件 VSYNC 来矫正模型。

DispSync 工作原理

接下来我们深入看一下整个 DispSync 软件模型的启动和运作机制。

Scheduler 初始化与 DispSync 的创建

DispSync 软件 VSYNC 模型运行在 surfaceflinger 进程中。

承接上一篇文章 SurfaceFlinger 的启动与初始化 中详细介绍的 SurfaccFlinger 服务的启动过程,在 SurfaceFlinger 启动时,先是创建和初始化 Scheduler 对象:

frameworks/native/services/surfaceflinger/Scheduler/
    - Scheduler.h
    - Scheduler.cpp

进一步看 initScheduler() 方法的实现:

// frameworks/native/services/surfaceflinger/Scheduler/Scheduler.h
class SurfaceFlinger : ...
private:
    ...
    std::unique_ptr<Scheduler> mScheduler;
    scheduler::ConnectionHandle mAppConnectionHandle;
    scheduler::ConnectionHandle mSfConnectionHandle;

// frameworks/native/services/surfaceflinger/Scheduler/Scheduler.cpp
void SurfaceFlinger::initScheduler(DisplayId primaryDisplayId) {
    ...
    // 创建 Scheduler 对象,并传入控制硬件 VSYNC 是否启用的回调 `setPrimaryVsyncEnabled()`;
    mScheduler =
            getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); },
                                         *mRefreshRateConfigs, *this);
}

在 Scheduler 类中,有 DispSync 类的成员变量 mPrimaryDispSync,会在 Scheduler 的构造函数中创建实例:

// frameworks/native/services/surfaceflinger/Scheduler/Scheduler.h
class Scheduler : ... {
private:
    ...
    std::unique_ptr<DispSync> mPrimaryDispSync;

// frameworks/native/services/surfaceflinger/Scheduler/Scheduler.cpp
Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function, ...
      : mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
        mPrimaryDispSync(createDispSync(mSupportKernelTimer)), ... {
    ...
}
...
std::unique_ptr<DispSync> createDispSync(bool supportKernelTimer) {
    ...
    return std::make_unique<impl::DispSync>("SchedulerDispSync",
                                            sysprop::running_without_sync_framework(true));
}

而在 DispSync 类内部,有专用的线程类 DispSyncThread负责分发软件 VSYNC。DispSyncThread 实例是在 DispSync 的构造方法中创建并运行,整个过程调用如下:

frameworks/native/services/surfaceflinger/Scheduler/
    - DispSync.h
    - DispSync.cpp

线程 DispSyncThread 首次启动后会调用到 threadLoop() 方法进入阻塞:

virtual bool threadLoop() {
    status_t err;
    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

    while (true) {
        std::vector<CallbackInvocation> callbackInvocations;

        nsecs_t targetTime = 0;

        { // Scope for lock
            Mutex::Autolock lock(mMutex);
            ...
            // 首次执行时 mPeriod == 0,进入阻塞
            if (mPeriod == 0) {
                err = mCond.wait(mMutex);
                ...
            }
            ...
        }
        ...
    }
    ... 
    return false;
}

软件 VSYNC 连接的建立

在 Scheduler 内,把每一个想接收软件 VSYNC 事件的端抽象成了一个 Connection,并在其内部维护着一个 map:

// frameworks/native/services/surfaceflinger/Scheduler/SchedulerUtils.h
struct ConnectionHandle {
    using Id = std::uintptr_t;
    static constexpr Id INVALID_ID = static_cast<Id>(-1);

    Id id = INVALID_ID;

    explicit operator bool() const { return id != INVALID_ID; }
};

// frameworks/native/services/surfaceflinger/Scheduler/Scheduler.h
class Scheduler : public IPhaseOffsetControl {
...
private:
    struct Connection {
        sp<EventThreadConnection> connection;
        std::unique_ptr<EventThread> thread;
    };

    ConnectionHandle::Id mNextConnectionHandleId = 0;
    std::unordered_map<ConnectionHandle, Connection> mConnections;
    ...

SurfaceFlinger::initScheduler() 方法中,创建了 Scheduler 对象之后,会调用其 createConnection() 方法来其来分别创建与 SurfaceFlingerApp 的连接:

void SurfaceFlinger::initScheduler(DisplayId primaryDisplayId) {
    ...
    // 根据刷新率获取偏移量配置
    mPhaseConfiguration = getFactory().createPhaseConfiguration(*mRefreshRateConfigs);
    mScheduler =
            getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); },
                                         *mRefreshRateConfigs, *this);
    // 创建与 App 的 Connection
    mAppConnectionHandle =
            mScheduler->createConnection("app", mPhaseConfiguration->getCurrentOffsets().late.app,
                                         impl::EventThread::InterceptVSyncsCallback());
    // 创建与 SurfaceFlinger 的 Connection                       
    mSfConnectionHandle =
            mScheduler->createConnection("sf", mPhaseConfiguration->getCurrentOffsets().late.sf,
                                         [this](nsecs_t timestamp) {
                                             mInterceptor->saveVSyncEvent(timestamp);
                                         });
    ...
}

进一步深入 createConnection() 方法,其会先启动一个 EventThread 线程,并新建一个 DispSyncSource 对象负责连接起 DispSync 和 EventThread,然后创建负责构建连接的 EventThreadConnection 对象,最终返回一个可以查找到 Connection 的 handle,调用栈如下:

frameworks/native/services/surfaceflinger/Scheduler/
    - DispSyncSource.h
    - DispSyncSource.cpp
    - EventThread.h
    - EventThread.cpp

EventThread 线程创建后同样会执行其 loop 运行的函数 threadMain()

接下来,便是要把负责生成软件 VSYNC 事件的线程 DispSyncThread 和负责分发事件到消费端的线程 EventThread 关联起来

在 DispSyncThread 类中,有一个 EventListener 的集合,表示所有需要接收软件 VSYNC 事件 的监听器:

// frameworks/native/services/surfaceflinger/Scheduler/DispSync.cpp
class DispSyncThread : public Thread {
...
private:
    struct EventListener {
        const char* mName;
        nsecs_t mPhase; // 当前端的事件偏移时间
        nsecs_t mLastEventTime; // 当前端上一次接收时间
        nsecs_t mLastCallbackTime;
        DispSync::Callback* mCallback;
    };
    ...
    std::vector<EventListener> mEventListeners;
    ...
}

在一个端创建了 EventThreadConnection 对象后,便可以调用其 requestNextVsync() 来请求接收下一次的 VSYNC 事件,往下便会建立起对应的 EventThead 与 DispSyncThread 的连接。以 SurfaceFlinger 的 Connection 为例,流程如下:

至此,整个软件 vysnc 的分发路径便搭建起来了。

接着我们先来看下 DispSync 模型参数与更新机制。

DispSync 的输入与模型更新

DispSync 中有几个关键的成员变量,组成了软件模型的重要参数,整理如下:

变量名 类型 含义
mPeriod int64_t 软件 VSYNC 事件计算后的周期,单位 ns。
mPhase int64_t 软件 VSYNC 事件的相位偏移,单位 ns。
mReferenceTime int64_t 软件 VSYNC 事件的参考时间,单位 ns。
mModelUpdated bool 重新启用硬件 VSYNC 后,软件模型是否有更新。
mError int64_t 计算出的软件 VSYNC 模型的误差,它是基于估计的 VSYNC 事件时间与在 mPresentFences 数组中观察到的时间之间的差异。
mResyncSamples int64_t[32] 存储硬件 VSYNC 事件的时间,用于计算软件 VSYNC 模型。个数最多为 32 个。
mPresentFences std::shared_ptr<FenceTime>[8] 存储用于验证当前软件模型是否准确的时间信息。

首先,DispSync 会接收硬件 VSYNC 信号回调。当硬件 VSYNC 到来时,SurfaceFlinger 会先回调 onVsyncReceived() 方法,然后调用 Scheduler 的 addResyncSample() 方法把当前硬件 VSYNC 的时间戳传至 DispSync:

在 DispSync 的 addResyncSample() 方法中,完成了:

当软件模型中的参数更新后,DispSync 会把最新的 mPeriod、mPhase & mReferenceTime 三个参数更新到 DispSyncThread 中(updateModel() 方法),并尝试唤醒 DispSyncThread(如果是阻塞中的状态)。

软件 VSYNC 的分发

在线程 DispSyncThread 的 threadLoop() 方法中,当 mPeriod(软件 VSYNC 周期)不为 0 时,线程唤醒,开始计算下一次 VSYNC 分发的具体时间,规则为:由于不同接收端有各自的偏移时间(phase),即实际要接收 VSYNC 的时间不同,所以先遍历 mEventListeners 队列,计算出最近的下一次需要接收 VSYNC 的时间,等到该时间到来时,便回调到对应 EventListener 的 Callback 上去

代码部分示例如下:

// frameworks/native/services/surfaceflinger/Scheduler/DispSync.cpp
virtual bool threadLoop() {
    ...
    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

    while (true) {
        std::vector<CallbackInvocation> callbackInvocations;
        nsecs_t targetTime = 0;
        {
            ...
            // 根据当前时间计算下一次 VSYNC 事件的时间
            targetTime = computeNextEventTimeLocked(now);
            ...
            if (now < targetTime) {
                ...
                // 若下一次 VSYNC 事件的时间还没到,阻塞等到其到来
                mCond.waitRelative(mMutex, targetTime - now);
            }
            now = systemTime(SYSTEM_TIME_MONOTONIC);
            ...
            // 重新计算收集需要接收事件的 EventLister 的 Callback
            callbackInvocations =
                    gatherCallbackInvocationsLocked(now, computeNextRefreshLocked(0, now));
        }
        if (callbackInvocations.size() > 0) {
            // 分发软件 vsync
            fireCallbackInvocations(callbackInvocations);
        }
    }
    return false;
}

nsecs_t computeNextEventTimeLocked(nsecs_t now) {
    ...
    nsecs_t nextEventTime = INT64_MAX;
    for (size_t i = 0; i < mEventListeners.size(); i++) {
        // 计算当前 EventListener 下一次接收 VSYNC 时间的时间
        nsecs_t t = computeListenerNextEventTimeLocked(mEventListeners[i], now);
        // 对比,找到最小值
        if (t < nextEventTime) {
            nextEventTime = t;
        }
    }
    // 返回最近的时间
    return nextEventTime;
}

EventThread 类中,有 mPendingEvents 表示要处理的事件队列:

class EventThread : public android::EventThread, private VSyncSource::Callback {
...
private:
    std::deque<DisplayEventReceiver::Event> mPendingEvents GUARDED_BY(mMutex);
    ...

从 DispSyncThread 分发出的 VSYNC 事件会最终添加到 mPendingEvents 中,等待被处理。整个过程如下:

至此,一次软件 VSYNC 事件便从 DispSync 模型中生成分发出去了。

硬件 VSYNC 启用的控制

在 DispSync 软件模型更新后,在一段时间内,DispSyncThread 会持续自行地计算下一次软件 VSYNC 的分发时间并分发事件到消费端。这时候,硬件 VSYNC 便没有继续工作的必要。因此,当 DispSync 软件模型更新后,便会通知关闭硬件 vsync

Scheduler 类中,启动了一个专用于控制 VSYNC 信号是否启用的线程:EventControlThread

// frameworks/native/services/surfaceflinger/Scheduler/Scheduler.h
class Scheduler : ... {
private:
    ...
    std::unique_ptr<DispSync> mEventControlThread;

// frameworks/native/services/surfaceflinger/Scheduler/Scheduler.cpp
Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
                     ...)
      : mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
        mPrimaryDispSync(createDispSync(mSupportKernelTimer)),
        mEventControlThread(new impl::EventControlThread(std::move(function))),
        ...

对 VSYNC 信号的实际控制,最终是通过调用 HWComposersetVsyncEnabled() 方法来实现。因此,在 SurfaceFlinger 初始化 Scheduler 时,需要把相关的方法回调传入:

void SurfaceFlinger::initScheduler(DisplayId primaryDisplayId) {
    ...
    // 创建 Scheduler 对象
    mScheduler =
            getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); },
                                         *mRefreshRateConfigs, *this);
    ...
}

EventControlThread 线程启动后,会执行到内部 threadMain() 方法,整个调用过程如下:

frameworks/native/services/surfaceflinger/Scheduler/
    - EventControlThread.h
    - EventControlThread.cpp

threadMain() 方法内同样会进入循环,若当前的 VSYNC 启用状态没有发生改变时,便会进入阻塞:

void EventControlThread::threadMain() NO_THREAD_SAFETY_ANALYSIS {
    auto keepRunning = true;
    auto currentVsyncEnabled = false;

    while (keepRunning) {
        mSetVSyncEnabled(currentVsyncEnabled);

        std::unique_lock<std::mutex> lock(mMutex);
        mCondition.wait(lock, [this, currentVsyncEnabled, keepRunning]() NO_THREAD_SAFETY_ANALYSIS {
            // 若 VSYNC 启动状态没有发生变化时,进入阻塞
            return currentVsyncEnabled != mVsyncEnabled || keepRunning != mKeepRunning;
        });
        currentVsyncEnabled = mVsyncEnabled;
        keepRunning = mKeepRunning; // mKeepRunning = false 表示线程退出
    }
}

在 Scheduler 中,提供了 enableHardwareVsync() 以及 disableHardwareVsync() 方法来分别实现启用和停用硬件 VSYNC

此外,在 Scheduler::addResyncSample() 的方法实现中,在向 DispSync 输入一次硬件 VSYNC 时间后,会返回一个布尔值,表示当前是否需要启用硬件 vsync:当 DispSync 模型更新且在误差范围内则返回 false,表示不再需要硬件 vsync,返回返回 true

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

    if (needsHwVsync) {
        enableHardwareVsync(); // 启用 硬件 vsync
    } else {
        disableHardwareVsync(false); // 停用 硬件 vsync
    }
}

enableHardwareVsync() 方法为例,启用 VSYNC 信号的调用链路如下:

DispSync 的反馈与校准

在停用了硬件 vsync,软件 VSYNC 持续运作一段时间之后,相对于硬件 vsync,软件 VSYNC 可能会出现一定程度的误差。因此,需要实时对 DispSync 模型进行反馈输入,以确保当软件 VSYNC 超出误差范围内时及时地做校准

在 Android 图形系统中,为了保证每一帧的 buffer 能够在整个绘制、合成、显示的过程中保持同步,引入了同步机制 Fence。Fence 的直译为“栅栏”,在某一方使用到该 buffer 时,会为其设置一个 fence,当 buffer 移交到下一方时,需要确保当前的 Fence 已释放(signal)以保证对 buffer 的操作是安全的。

在硬件显示设备展示了一帧的 buffer 时,便会有一个表示展示的 fence:PresentFence

在 SurfaceFlinger 合成一帧的内容后,会去取到上一帧的 PresentFence,封装成一个 FenceTime 对象,反馈回 DispSync 中。过程如下:

frameworks/native/libs/ui/include/ui/
    - Fence.h
    - FenceTime.h

frameworks/native/libs/ui/
    - Fence.cpp
    - FenceTime.cpp

DispSync 在接收到 PresentFence 后,会存在 mPresentFences 数组中(最多 8 个),然后调用 updateErrorLocked() 方法来计算模型误差 mError —— 误差时间的平方的平均值。

mError 大于 160000000000(400000ns 的平方),认为误差已超阈值,DispSync::addPresentFence() 将返回布尔值 true,表示要重新启用硬件 VSYNC 来重新更新 DispSync 模型:

void Scheduler::addPresentFence(const std::shared_ptr<FenceTime>& fenceTime) {
    if (mPrimaryDispSync->addPresentFence(fenceTime)) {
        enableHardwareVsync();
    } else {
        disableHardwareVsync(false);
    }
}

总结

Android Framework 在硬件 VSYNC 的基础设计出了软件 VSYNC,两者共用运作,组成了整个 Android 系统的 VSYNC 机制,实现了显示流水线的同步。

其中,DispSync 是软件 VSYNC 机制的模型核心,它接收一定数量的硬件 VSYNC 后更新软件模型参数,发送软件 VSYNC 事件至 App 端用于渲染内容,至 SurfaceFlinger 端用于合成内容。同时在合成后接收 PresentFence 反馈,用来及时校正更新模型。

整个软件 VSYNC 的分发过程涉及了几个线程,整理如下:

线程 功能
DispSyncThread 负责分发软件 VSYNC 事件
EventThread 接收软件 VSYNC 事件并进一步传递到接收端
EventControlThread 控制硬件 VSYNC 是否启用