android vsync机制 7.0,Vsync同步机制 一

本文深入解析了Vsync机制的工作原理,包括硬件Vsync信号的产生、软件Vsync模型的建立及更新过程,以及如何通过Vsync同步App UI与SurfaceFlinger的渲染流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是Vsync同步机制?

Vsync(垂直同步信号量),用来同步渲染,让AppUI和SurfaceFlinger可以按硬件产生的VSync节奏进行工作。

Vsync要解决的问题:

a5632f962608

Vsync要解决的问题

为什么会产生这样的问题?

CPU负责对UI进行更新,GPU负责对UI进行渲染,两者的频率不一致,会导致CPU还未更新完成,就被GPU渲染到了屏幕上。所以会出现图片上的问题。

如何解决这个问题?

解决这个问题的方法就是让CPU和GPU以相同的节奏进行工作。如下图所示

a5632f962608

Vsync

让CPU和GPU以相同的频率进行工作,这就是Vsync要做的工作。Vsync以固定的频率发出信号,每当收到CPU先对UI进行更新,然后GPU再进行绘制,这样就可以解决上面的问题了。

那Android的Vsync机制是如何进行的呢?

Android的Vsync整体框架图

a5632f962608

disp_sync_arch.png

这张图可以很明显的看出Vsync事件的传递过程。

Vsync信号并不是有硬件直接产生,而是由DispSync线程产生的。DispSync会根据HWC产生的VSync进行采样,创建模型,然后输出了SW_VSYNC信号,SW_VSYNC再根据SF和APP的phase offset做调整,分别输出给Vsync-sf和Vsync-app。

再看下几个类之间的关系图

HWC产生硬件Vsync信号

在分析HWComposer的时候,HWComposer中会注册硬件Vsync事件回调,在硬件Vsync事件到来的时候,回调HWComposer的vsync函数。

void HWComposer::vsync(int disp, int64_t timestamp) {

if (uint32_t(disp) < HWC_NUM_PHYSICAL_DISPLAY_TYPES) {

{

Mutex::Autolock _l(mLock);

//记录对应硬件设备Vsync信号产生的时间

mLastHwVSync[disp] = timestamp;

}

//然后直接通知SurfaceFlinger的onVsyncReceived函数

mEventHandler.onVSyncReceived(disp, timestamp);

}

}

void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) {

bool needsHwVsync = false;

{ // Scope for the lock

Mutex::Autolock _l(mHWVsyncLock);

//如果是主显示设备的Vsync信号,且当前硬件Vsync事件是打开的,则调用DispSync的addResyncSample函数

//将硬件VSync事件添加到DispSYnc的样本中,用于创建软件Vsync时间模型

if (type == 0 && mPrimaryHWVsyncEnabled) {

needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);

}

}

//更加样本采样结果,决定是否要关掉硬件Vsync事件。

if (needsHwVsync) {

enableHardwareVsync();

} else {

disableHardwareVsync(false);

}

}

HWComposer接收到硬件的Vsync事件并没有直接传递给系统使用,而是通过SurfaceFlinger将Vsync事件,添加到了DispSync的Vsync事件样本,当DispSync采样完成后,则会停止硬件Vsync事件,由软件Vsync根据样本的计算结果产生Vsync事件。

mPrimaryHWVsyncEnabled变量控制DispSync是否需要采集样本,当模型Vsync周期有误差时需要重新打开硬件Vsync,再次采集硬件Vsync样本。

bool DispSync::addResyncSample(nsecs_t timestamp) {

Mutex::Autolock lock(mMutex);

//此处相当于做了一个大小为32环形Buffer,每当有新的样本过来之后就添加到数组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;

}

//当样本更新后,根据32个硬件Vsync样本计算软件Vsync模型.

updateModelLocked();

if (mNumResyncSamplesSincePresent++ > MAX_RESYNC_SAMPLES_WITHOUT_PRESENT) {

resetErrorLocked();

}

//当Vsync周期mPeriod = 0或者误差超过一定的阀值,需要重新采样,否则,则停止采样,关掉硬件Vsync,知道有误差的时候在打开,再次采样

return mPeriod == 0 || mError > kErrorThreshold;

}

addResyncSample方法主要作用是添加采样样本到Buffer中,DispSync中维护了一个环形的Buffer,大小为32个,每当有新样本过来时候,则将样本添加到Buffer中,如果Buffer已经满了,则替换掉最老的样本。

样本更新后调用updateModelLocked来计算更新DispSync模型。

void DispSync::updateModelLocked() {

//只有样本数量大于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就是计算产生的平均Vsync间隔的时间

mPeriod = durationSum / (mNumResyncSamples - 1);

//第二部:开始计算平均的偏差时间,因为不可能每个间隔都非常准时,要求平均偏差

double sampleAvgX = 0;

double sampleAvgY = 0;

double scale = 2.0 * M_PI / double(mPeriod);

for (size_t i = 0; i < mNumResyncSamples; i++) {

//遍历并计算每个样本相对于平均周期取余的偏差值,(如果完全准时的样本,应该可以整除,余数为0),然后将偏差值转换成弧度。

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);

//将平均弧度转换成平均的偏差值

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);

}

}

计算平均周期模型和平均偏差模型。

如何计算平均周期?

将所有样本的间隔时间相加,然后除以间隔数,求出平均间隔时间.

如何计算平均偏差?

如果完全准时的样本,应该可以整除平均,余数为0,有偏差的则余数不为0,所以对所有的样本除以mPeriod平均周期时间,计算余数,将余数又转换成弧度. 计算X和Y, 求X和Y的平均值,然后再由平均X,Y求出弧度,再转换成偏差值。这样就计算出平均偏差了.

void updateModel(nsecs_t period, nsecs_t phase) {

Mutex::Autolock lock(mMutex);

mPeriod = period;

mPhase = phase;

mCond.signal();

}

调用VsyncThread的mCond.signal()通知VsyncThread模型更新完成。

DispSync 软件Vsync模型

SurfaceFlinger的Vsync并不是直接使用硬件产生Vsync,而是由软件根据硬件Vsync创建了一个数据模型来模拟产生Vsync信号。负责产生软件Vsync信号的就是DispSync。

DispSync是SurfaceFlinger中的一个变量。

DispSync mPrimaryDispSync;

mPrimaryDispSync就是Vsync的信号源,我们先看下Vsync信号是如何产生的?

DispSync::DispSync() :

mRefreshSkipCount(0),

mThread(new DispSyncThread()) {

//启动VsyncThread

mThread->run("DispSync", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);

}

DispSync VsyncThread 第一部分

DispSync对象在创建的时候会启动一个VsyncThread线程,该线程用于模拟Vsync信号。然后我们看下VsyncThread线程的threadloop方法

virtual bool threadLoop() {

status_t err;

nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

nsecs_t nextEventTime = 0;

//执行循环

while (true) {

Vector callbackInvocations;

nsecs_t targetTime = 0;

{ // Scope for lock

Mutex::Autolock lock(mMutex);

//如果需要停止Vsync线程,则直接返回

if (mStop) {

return false;

}

//如果mPeriod为0, 则等待

//在VsyncThread创建的时候,mPeriod默认为0,所以会阻塞在这边。

//在VsyncThread updateModel更新模型的时候,会设置mPeriod,然后继续执行,线程刚开始的时候Vsync模型还没有创建好,所以无法产生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,也就是说当前线程还不知道按照什么样的频率产生Vsync信号,所以会等待mPeriod周期模型的更新。

什么时候设置更新mPeriod的时间呢?

就是上面提到的,应用模型样本计算完成后会设置mPeriod和mPhase,这样VsyncThread就会继续执行

DispSync VsyncThread 第二部分

virtual bool threadLoop() {

......

//计算下次Vsync要产生的时间

nextEventTime = computeNextEventTimeLocked(now);

targetTime = nextEventTime;

bool isWakeup = false;

//如果还没有到时间,就等待相应的时间后在处理

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);

//到达执行Vsync时间后,回调监听对象.

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;

}

当有Vsync的周期时间和偏差时间后,VsyncThread就可以模拟产生软件Vsync信号了。产生Vsync信号就会通过回调接口通知监听者.

DispVsync有两个监听者,就是我们上面提到的SurfaceFlinger和APP,SurfaceFlinger和App所需要的Vsync时间不是完全一致。App一般先接到Vsync信号,还是绘制UI,然后SurfaceFlinger再接收到Vsync信号完成UI合成。

VsyncThread做了什么事情呢?

首先计算下次Vsync产生的时间,计算下次产生时间会根据两个监听者上次接收到Vsync时间,然后加上周期时间,偏差时间,和APP或者SF自己的偏差时间。得到下次Vsync时间。

下次Vsync时间 = 上次Vsync时间 + mPeriod(平均周期) + mPhase(平均偏差) + offset(自定义偏差)

得到最近的下次执行时间,Vsync只要Wait相应的时间差就可了,等到执行时间后回调SF或者APP的Vsync信号。这样Vsync信号就产生了.

DisplaySyncSource

上面讲DispSync提到,DispSync有两个监听者,这两个监听这就是两个DisplaySyncSource对象。两个对象是在SurfaceFlinger的init进程创建的.

sp vsyncSrc = new DispSyncSource(&mPrimaryDispSync,

vsyncPhaseOffsetNs, true, "app");

sp sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,

sfVsyncPhaseOffsetNs, true, "sf");

init中创建了两个DispSyncSource对象, 一个是SF的sfVsyncSrc,一个是SurfaceFlinger的sfVsyncSrc对象. DiplaySyncSource构造方法中会保存一个syncOffet值,表示自己收到Vsync偏差时间.

DispSync计算Vsync产生时间的时候,会根据这个偏差进行计算.也就是上面公式中的自定义偏差offset。

DispSync有一些重要的函数,具体如下:

//开始/关闭监听Vsync事件

//开始监听Vsync事件就是将自己添加到DispSync的回调监听中

//关闭监听Vsync事件就是将自己从DispSync监听回调中移除

virtual void setVSyncEnabled(bool enable) {

Mutex::Autolock lock(mVsyncMutex);

if (enable) {

status_t err = mDispSync->addEventListener(mPhaseOffset,

static_cast<: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<:callback>(this));

if (err != NO_ERROR) {

ALOGE("error unregistering vsync callback: %s (%d)",

strerror(-err), err);

}

//ATRACE_INT(mVsyncOnLabel.string(), 0);

}

mEnabled = enable;

}

//设置自己的回调,即收到Vsync事件后,回调给谁

virtual void setCallback(const sp<:callback>& callback) {

Mutex::Autolock lock(mCallbackMutex);

mCallback = callback;

}

//DispSync的回调接口, 当收到DispSync事件后会回到该接口,然后该接口将事件又回调给自己的callBack

//也就是后边讲的EventThread,EventThread会监听DisplaySyncSource。

virtual void onDispSyncEvent(nsecs_t when) {

sp<:callback> callback;

{

Mutex::Autolock lock(mCallbackMutex);

callback = mCallback;

if (callback != NULL) {

callback->onVSyncEvent(when);

}

}

这样,我们就大概理解了Vsync信号的产生过程。

1:HWC硬件产生Vsync信号,给DispSync添加样本

2:DispSync根据样本计算Vsync周期,然后产生软件Vsync信号。计算Vsync事件会根据不同的Listenr计算不同的时间.

3:DisplaySyncSource是DispSync的监听者,有两个DisplaySyncSource对象,分别代表SF和APP的DisplaySyncSource,两个回调的Vsync时间不同.

4:DisplaySyncSource收到Vsync事件后,会发给他自己的监听者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线程进行处理。

为什么要有两个DisplaySyncSource且时间不一样呢?

a5632f962608

Vsync

具体如上图所示

App绘制UI, 绘制完成后交给SurfaceFlinger进行合成, 所以App绘制时间优先于SF合成时间,将两者时间错开,避免资源竞争,增加效率。如何APP和SF的偏差时间设置好的话,APP + SF在一个Vsync周期内就可以绘制合成,在下一个Vsync周期到来的时候就可以显示到屏幕上了。

<think>好的,我现在需要回答用户关于如何实现VSync机制的底层代码示例及原理的问题。首先,我需要回忆VSync的基本概念和作用。根据用户提供的引用,VSync是垂直同步,用于同步屏幕刷新率和GPU渲染帧率,防止屏幕撕裂,但可能导致掉帧。引用[1]和[2]详细解释了VSync的作用和信号生成方式,引用[3]则是个视频显示的示例代码,可能与实际VSync实现关联不大,但可以作为显示部分的参考。 接下来,用户需要底层代码示例和原理。由于VSync的实现通常涉及硬件和操作系统层面,不同平台(如Android、Windows、Linux)的实现方式可能不同。我应该选择个常见平台,比如Android,因为用户提供的引用[2]提到了Android系统中的VSync信号生成。不过,底层代码通常属于系统级或驱动层,可能需要查阅相关开源项目或文档。 首先解释VSync的基本原理:显示器以固定频率(如60Hz)刷新,每次刷新开始时会发出VSync信号。GPU在收到信号后开始渲染下帧,确保在下VSync到来前完成,避免撕裂。如果未能及时完成,则重复上帧,导致掉帧。 然后,关于代码示例,真正的底层代码可能涉及内核模块或硬件抽象层(HAL),比如Android的SurfaceFlinger服务处理VSync。这里可以举个简化的示例,说明如何监听和处理VSync信号,可能使用伪代码或参考Android源码中的部分逻辑。例如,在Android中,VSync信号由HWC(硬件组合器)生成,通过回调通知应用层。 此外,可以提到用户空间和内核空间的交互,比如通过ioctl系统调用与DRM(Direct Rendering Manager)子系统交互,这在Linux系统中常见。例如,使用DRM的API来等待VSync事件。 需要确保代码示例的正确性,但由于无法提供实际的系统级代码,可能需要用伪代码或参考开源项目的部分代码结构。同时,需要提醒用户实际实现复杂,建议参考特定系统的源码,如Android AOSP或Linux DRM文档。 最后,生成相关问题,如不同平台的实现差异、优化方法等,帮助用户进步探索。</think>### 实现 Vsync 机制的底层代码示例及原理 #### **1. Vsync 的基本原理** Vsync(垂直同步)的核心是通过硬件信号强制同步显示器的刷新周期与GPU的渲染节奏。显示器以固定频率(如60Hz,即每16.67ms次)发送Vsync信号,GPU在收到信号后开始渲染下帧画面。若渲染在下Vsync信号到来前完成,则画面流畅;若未完成,则重复上帧,导致掉帧[^1][^2]。 #### **2. 底层实现的关键步骤** - **信号生成**:由显示控制器(如GPU或专用硬件模块)生成Vsync信号。在Android中,系统定时器通过硬件抽象层(HAL)触发信号[^2]。 - **信号传递**:操作系统通过中断或事件机制Vsync信号传递给图形子系统(如Android的`SurfaceFlinger`)。 - **渲染同步**:应用或图形引擎根据信号调整渲染时序,确保在下Vsync周期前提交帧数据。 #### **3. 简化代码示例(伪代码)** 以下是基于Linux DRM(Direct Rendering Manager)子系统的简化逻辑: ```c // 等待Vsync事件(Linux DRM示例) struct drm_event_vblank event; ioctl(fd, DRM_IOCTL_WAIT_VBLANK, &event); // 当Vsync信号到达时,触发回调 void handle_vsync() { // 提交已渲染的帧到显示器 submit_frame_to_display(); // 启动下帧渲染 schedule_next_frame_rendering(); } ``` #### **4. Android系统的实现参考** 在Android源码中,`SurfaceFlinger`服务通过`HWComposer`接收Vsync信号: ```cpp // SurfaceFlinger 中的Vsync监听(简化逻辑) void SurfaceFlinger::onVsyncReceived(int64_t timestamp) { // 同步所有图层的合成操作 handleMessageInvalidate(); // 触发帧的合成与提交 signalFrameAvailable(); } ``` #### **5. 关键挑战** - **延迟控制**:需在16ms内完成渲染和合成,否则导致卡顿。 - **多线程同步**:渲染线程、合成线程需与Vsync信号严格同步。 - **硬件适配**:不同显示控制器(如高通、三星芯片)的实现细节差异较大。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值