Perfetto 中的 Android VSYNC信号

Perfetto 中的 Android VSYNC信号

Google VSYNC 文档

VSYNC信号用来同步 显示流水线。显示流水线由

  1. 应用渲染、
  2. SurfaceFlinger 合成
  3. 屏幕上显示图像的硬件混合渲染器 (HWC) 组成。

VSYNC 同步 应用唤醒并开始渲染的时间、SurfaceFlinger唤醒合成图层的时间以及屏幕刷新周期。这种同步可以消除卡顿,并提升UI的流畅性。

HWC 可生成 VSYNC 事件并通过回调将事件发送到 SurfaceFlinger:

typedef void (*HWC2_PFN_VSYNC)(hwc2_callback_data_t callbackData,
        hwc2_display_t display, int64_t timestamp);

HWC 从 HAL_PRIORITY_URGENT_DISPLAY 的线程触发 hwc2_callback_data_t,延迟时间尽可能短,通常小于 0.5 毫秒。

SurfaceFlinger 可以通过调用 setVsyncEnabled 来控制 HWC 是否生成 VSYNC 事件。

  1. SurfaceFlinger 调用 setVsyncEnabled 生成 VSYNC 事件,VSYNC 事件与屏幕的刷新周期同步。

  2. 当 SurfaceFlinger 同步到屏幕刷新周期时,SurfaceFlinger 会停用 setVsyncEnabled 以阻止 HWC 生成 VSYNC 事件。

  3. 如果 SurfaceFlinger 检测到实际 VSYNC 与它先前建立的 VSYNC 之间存在差异,则 SurfaceFlinger 会重新启动 VSYNC 事件生成过程。

其实就是 VSYNC 信号可以由HWC 产生, HWC产生的VSYNC 信号到达Surface以后,Surface 可以通过软件模拟VSYNC 信号,如果产生了一定的误差被SurfaceFlinger 检测到,SurfaceFlinger 重新通知HWC 产生VSYNC信号做校验。

VSYNC 偏移

APP 渲染和 SurfaceFlinger 渲染循环应该和硬件 VSYNC同步。优点就是它可以减少应用和 SurfaceFlinger 中的错误,并最大限度减小相位内外屏幕之间的偏移

在 VSYNC 事件中,屏幕开始显示帧 N,而 SurfaceFlinger 开始为帧 N+1 合成窗口。应用处理等待的输入并生成帧 N+2。

假定应用和 SurfaceFlinger 的每帧时间没有很大变化, 与VSYNC信号 同步会有一致的延迟时间。延迟至少为两帧。

为了解决这个问题,可以通过使应用渲染和SurfaceFlinger合成信号与硬件 VSYNC 相关,引入 VSYNC 偏移减少输入设备到屏幕的延迟。这是有可能的,因为应用加合成通常需要不到 33 毫秒的时间。

VSYNC 偏移的结果是具有相同周期和偏移相位的三个信号:

  • HW_VSYNC_0 - 屏幕开始显示下一帧。
  • VSYNC - 应用读取输入内容并生成下一帧。
  • SF_VSYNC - SurfaceFlinger 开始为下一帧进行合成。

通过 VSYNC 偏移,SurfaceFlinger 接收缓冲区并合成帧,而应用同时处理输入内容并渲染帧

VSYNC 偏移会缩短可用于应用和合成的时间,因此增加了出错几率。

DispSync

DispSync 维护屏幕基于硬件的周期性 VSYNC 事件的模型,并使用该模型在硬件 VSYNC 事件的特定相位偏移处执行回调。

DispSync 是一个软件锁相回路 (PLL),它可以生成由 Choreographer 和 SurfaceFlinger 使用的 VSYNC 和 SF_VSYNC 信号,即使没有来自硬件 VSYNC 的偏移也是如此。

DispSync 流程

DispSync 具有以下特点:

  1. 参考 - HW_VSYNC_0。
  2. 输出 - VSYNC 和 SF_VSYNC。
  3. 反馈 - 自硬件混合渲染器的退出栅栏有信号状态时间戳。

VSYNC 退出偏移

退出栅栏的有信号状态时间戳必须与 HW VSYNC 相符,即使在不使用偏移相位的设备上也是如此。否则,实际造成的错误会更加严重。智能面板通常有一个增量:退出栅栏是对显示内存进行直接内存访问 (DMA) 的终点,但是实际的显示切换和 HW VSYNC 会晚一段时间。

PRESENT_TIME_OFFSET_FROM_VSYNC_NS 在设备的 BoardConfig.mk makefile 中设置。它取决于屏幕控制器和面板特性。从退出栅栏时间戳到 HW VSYNC 信号的时间以纳秒为单位进行测量。

VSYNC 和 SF_VSYNC 偏移

VSYNC_EVENT_PHASE_OFFSET_NSSF_VSYNC_EVENT_PHASE_OFFSET_NS 根据高负载使用情形进行了保守设置,例如在窗口过渡期间进行部分 GPU 合成或 Chrome 滚动显示包含动画的网页。这些偏移允许较长的应用渲染时间和较长的 GPU 合成时间。

超过一两毫秒的延迟时间是非常明显的。为了最大限度地缩短延迟时间而不显著增加错误计数,请集成彻底的自动化错误测试。

注意:VSYNC 和 SF_VSYNC 偏移同样在设备的 BoardConfig.mk 文件中配置。两个设置都是 HW_VSYNC_0 之后以纳秒为单位的偏移,默认值为零(如未设置的话),也可以为负值。

U-PF3QQB9A:~$ adb shell dumpsys window displays | grep DisplayInfo

mDisplayInfo=DisplayInfo{“内置屏幕, displayId 0”, uniqueId “local:131”, app 1280 x 720, real 1280 x 800, largest app 1280 x 1280, smallest app 800 x 648, mode 1, defaultMode 1, modes [{id=1, width=1280, height=800, fps=50.0}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@40f16308, rotation 0, density 160 (318.745 x 317.5) dpi, layerStack 0, appVsyncOff 1000000, presDeadline 20000000, type BUILT_IN, address {port=131}, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}
mDisplayInfoOverscan=[0,0][0,0]
mRotatedDisplayInfoOverscan=[0,0][0,0]

VSYNC

Perfetto 中的VSYNC信号

在这里插入图片描述

通过Android Profile 的CPU面板抓取 Android Trace。 在Perfetto UI 页面载入。

左侧一列有

  1. surfaceflinger 544: surfaceFlinger 主线程
  2. HW_VSYNC_ON_131 : HWC 的VSYNC 信号是否打开
  3. HW_VSYNC_131: HWC VSNC 信号
  4. VSYNC-sf : SurfaceFlinger 的合成 VSYNC 信号
  5. VSYNC_app: App 渲染的VSYNC 信号
  6. DispSync 609: DispSync 的CPU 运行情况
  7. DispSyncModelLocked: DispSync VSYNC 信号

VSYNC 信号的周期

在这里插入图片描述

查看 VSYNC 信号的周期, 发现是基本都是在20ms 毫秒左右,并不是我们常识的16.6ms. 并且在UI上统计帧率也显示是50帧。看来并没有卡顿。

通过dumpsys 命令查看 VSYNC period: 20000000 ns 帧率确实是50帧,设备厂商做了修改。

@U-PF3QQB9A:~$ adb shell dumpsys SurfaceFlinger

Display 131 (HWC display 0): unknown identification data:

Sync configuration: [using: EGL_ANDROID_native_fence_sync EGL_KHR_wait_sync]

VSYNC configuration:
app phase: 1000000 ns SF phase: 1000000 ns
early app phase: 1000000 ns early SF phase: 1000000 ns
GL early app phase: 1000000 ns GL early SF phase: 1000000 ns
present offset: 0 ns VSYNC period: 20000000 ns

Scheduler enabled.+ Smart 90 for video detection: off

Allowed Display Configs: 50Hz, (config override by backdoor: no)

VSYNC 信号的offset

phase-app:VSync-app与hw_vsync的相位差, 1毫秒

phase-sf:VSync-sf与hw_vsync的相位差 1毫秒

通过 上面的图和 dumpsys 命令可知: Offset 为 0:(sf phase - app phase = 0)

  1. app phase和SF phase就是正常情况下使用的。

  2. early app phase和early SF phase是在切换屏幕帧率的时候使用的。

  3. GL early app phase和GL early SF phase是在SF使用GPU合成的时候使用的

在这里插入图片描述

HWC VSYNC 和 DisplayVSYNC 的关系

HWC 产生一定的VSYNC 信号后,会被关闭掉,Surface 的DisplayVsync 接管产生VSYNC 信号,在DisplayVSYNC 超出误差的阈值后,重新启动HWVSYNC
在这里插入图片描述

HW_VSYNC_ON 代表 HWC VSYNC 被使能,从 图上1 可以看到,HW_VSYNC_ON 打开后,HW_VSYNC 发出了三个VSYNC信号,在圆圈2中。

DispSyncModelLocked 代表了 软件VSYNC信号,可以看到和HWC的HW_VSYNC_ON 是一个互斥的关系。

在放大后,可以看在如果DispSync 开始工作,CPU都有一个Running状态。 DispSync 609 代表了CPU的运行情况,浅绿色表示Runnable 状态, 绿色表示Running状态。

在这里插入图片描述

点击第一个圆圈后中的Running 后显示, 在每个VSYNC 信号开始时 DispSync 被唤醒,然后休眠。

在这里插入图片描述

点击一个VSYNC 周期中最后一个状态,在Current Selection 中可以看到 Next state sleep 16.1ms 知道下一个VSVNC 周期开始。

在这里插入图片描述

HWC VSYNC 的开启和关闭

HWC VSYNC 的开启和关闭通过SurfaceFlinger#setVsyncEnabledInternal 函数来完成。

注意DispSync 和HWC sync 时序。

在开启时, 先关闭DispSync , 然后调用setVsyncEnabledInternal,HWC sync 开关打开

在关闭时,先调用setVsyncEnabledInternal, HWC sync 开关打开, 关闭DispSync.

在这里插入图片描述
在这里插入图片描述

HWC VSYNC 信号的生成

从下面第一个图中可以看到,硬件VSYNC 开启后,SurfaceFlinger 通过Hwbinder 线程接收VSYNC信号
在这里插入图片描述

图二三显示了 VSYNC 信号的跨binder 调用。 在Compose Service 进程中 DisplayBuiltIn::Commit::RegisterVsync 注册VSYNC 信号

在这里插入图片描述
在这里插入图片描述

SurfaceFlinger 中的VSYNC信号

设备是Android10, 看下Android10中SurfaceFlinger 中的onMessageReceived 函数

android 10 onMessageReceived

SurfaceFlinger onMessageReceived

handleMessageTransaction handleMessageInvalidate handleMessageRefresh 三个函数在不同的版本上有差异,在一些低版本上 需要三个消息分别处理这三个函数。

void SurfaceFlinger::onMessageReceived(int32_t what) NO_THREAD_SAFETY_ANALYSIS {
1639      ATRACE_CALL();
1640      switch (what) {
1641          case MessageQueue::INVALIDATE: {
                  ...
                  
1684              bool refreshNeeded = handleMessageTransaction();
1685              refreshNeeded |= handleMessageInvalidate();
                  ...

1697              break;
1698          }
1699          case MessageQueue::REFRESH: {
1700              handleMessageRefresh();
1701              break;
1702          }
1703      }
1704  }

从图中可以看到 SurfaceFlinger 主线程先后运行了这三个函数,完成一帧的合成绘制。

  1. handleMessageTransaction 标记个layer 的状态。
  2. handleMessageInvalidate 主要是调用 handlePageFlip, 该阶段会遍历各个Layer,在每个Layer中,取得并锁住该Layer的frontBuffer,然后利用frontBuffer中的图像数据生成该Layer的2D贴图(Texture),并且计算更新区域,为后续的混合操作做准备
  3. handleMessageRefresh 主要工作就是合成,分为 doComposeSurfaces , queueBuffer 和 postFrameBuffer 三步。分别对应,合成Surface, surface buferr queue和 写framebufer 三步。
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

SurfaceFlinger 的onMessageReceived 函数开启时间和VSYNC 信号有300微秒的延迟。

App 渲染中的SYNC 信号。

抓取的case 和平常的UI绘制有些不同,主要抓取的通过 3D 引擎 Filement 绘制一些3D画面。所以和平常的UI绘制有些不同,差别主要是在RenderThread 上。

Filement 的绘制通过向Choreographer 注册FrameCallback 实现。在这里我们也统计了帧数

inner class FrameCallback : Choreographer.FrameCallback {
    override fun doFrame(frameTimeNanos: Long) {
        // Schedule the next frame

        choreographer.postFrameCallback(this)
        //o This check guarantees that we have a swap chain
        if (uiHelper.isReadyToRender) {
            // If beginFrame() returns false you should skip the frame
            // This means you are sending frames too quickly to the GPU

            if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
                renderer.render(view)
                frameCount++
                renderer.endFrame()
            }
        }
    }
}

choreographer 申请VSYNC信号

在每一次的回调中我们都会再重新注册一下 FrameCallback

choreographer.postFrameCallback(this)

在Perfetto 上也可以看到 一个三角形的图标。点击这个图标可以关联到 SurfaceFlinger 进程中的 binder aync rcv, 表示SurfaceFlinger 收到这个这个请求。

然后SurfaceFlinger 调用requestNextVsync 请求VSYANC。 注意连线是跨进程binde调用

在这里插入图片描述

在这里插入图片描述

APP 中Filement 生成的buffer

APP VSYNC 信号整体图示。在一个VSYNC 信号到来后,FrameCallback的doFrame 开始工作。需要注意的是这个doFrame 产生的UI数据是Filement 引擎中,所以 RenderThread 不会work. 从图中也可以看出 RenderThead 基本处于空闲状态,FEngine loop 线程占满了VSYNC 时间。

注意 在图中最后两行代表了 APP 进程中的Surface 中的buffer 数量。

  1. 倒数第二行是 Filement 申请的SurfaceView ,
  2. 倒数第一行是 Activity 本身的Surface。

doFrame 产生的buffer 的流动顺序是: doFrame --> FEngine::loop

在这里插入图片描述

这张图显示了 由Filement 生成的buffer的数据流动。

  1. Vsync 信号到来,SurfaceFlinger 开始合成,消耗掉SurfaceView中的buffer
  2. SurfaceView 中的buffer 被消费,数量变为0,
  3. FEngine::loop 线程开始工作,通过eglSwapBuffers 向SurfaceView 中生产数据
  4. 同时主线程也开始渲染,产生下一帧FEngine::loop 需要的数据。

在这里插入图片描述

APP 中UI 生成的buffer

在大图中可以看到,图中最后RenderThread 渲染了 一帧数据。局部放大以后可以看到在 doFrame 中,除了Filement 的渲染,同时执行了UI绘制的 traversal 函数,然后RenderThead 线程执行DrawFrame 函数,渲染数据,红箭头头处的 Activity 的Surface 中buffer 数量变为1, 在一下次VSYNC信号到来时,被SurfaceFlinger 和Filement 生成的buffer 一起消费。
在这里插入图片描述

dequeueBuffer

第一次观察到50帧的数据时,以为发生了卡段,通过Trace 看了以后,发现FEngine:loop dequeueBuffer 函数线程时间比较多,基本占满了VSYNC信号周期,但是dequeueBuffer 在VSYNC 开始时,CPU是runnable 状态,后边基本都是sleep 状态,截取一个 VSNC 周期中最后一个Running状态的Current Selection 可以看到。说明线程并不繁忙,应该是主动让出了CPU,通过 dumpsys surfaceflinger 也验证了这个想法。

dequeueBuffer的调用

但是dequeueBuffer 为什么生成一个阻塞呢。

dequeueBuffer 是一个跨进程调用,向SurfaceFlinger 申请 Buffer

  1. 点击 FEngine::loop 中的dequeueBuffer , 可以看到通过binder 调用关联到SurfaceFlinger 进程的一个 binder_reply.
    在这里插入图片描述

  2. SurfaceFlinger 进程的一个 binder_reply 通过CPU的状态看下 是怎么被激活转为 runable 状态的。
    在这里插入图片描述

从图上可以看出是被 SurfaceFlinger主线程的激活的,SurfaceFlinger主线程在被激活前也是处于sleep 状态。

  1. 从这张图中可以看出 SurfaceFlinger 被compose 进程的HwBinder 线程阻塞。 SurfaceFlinger 合成postFramebuffer 等待HWC 返回。
    在这里插入图片描述
    从这张放大图中可以看出, SurfaceFlinger 在完成合成后的 postComposition 中release了buffer
    在这里插入图片描述
    在这里插入图片描述
  2. compose 的HwBinder 线程通过CPU的runable 状态可以看到是被一个crct 内核线程激活。

在这里插入图片描述

  1. crct 线程。

    CRTC对应的Layer Mixer以及DSPP(Destination Surface Processor Pipes)同样是DPU的一部分,Layer Mixer很好理解,就是将上述PANEL中提到的图层进行融合,DSPP则是基于面板特性进行Gamma校正和亮度,对比度,饱和度等调整。所以Android中护眼模式,色彩模式不应用于每个Layers而是应用于屏幕上就是指生成的颜色矩阵最终都是通过DSPP来实现的对应的模式效果吧,部分不开源封装在libsdm-color.so
    ————————————————
    原文链接:https://blog.csdn.net/qq_40405527/article/details/123395261

crct 线程是一个内核线程,具体负责 图层的混合,可以看到 crtct 线程一直处于 Uninterruptible Sleep (non-IO) 状态,在VSYNC 的最后才进入runable 状态。
在这里插入图片描述

crct 线程激活 ----> compose 进程的HwBinder 线程激活 —> SurfaceFlinger 主线程激活 —> SurfaceFlinger binder reply 线程激活—> App FEngine::loop 的dequeuebuffer 激活,

dequeueBuffer 的buffer

在surfaceflinger 主线程binder 阻塞返回后, postCompositon 阶段,释放了SurfaceView的 buffer 0
在这里插入图片描述

surfaceflinger 的binder 进程,dequeuebuffer 到这个buffer

surfaceflinger 主线程完成合成后,releaseBuffer 释放, 然后 在binder线程中获取 到这个buffer。

在这里插入图片描述

参考

android Gui系统之SurfaceFlinger(5)—Vsync(2)

SurfaceFlinger事务处理

SurfaceFlinger之handlePageFlip

SurfaceFlinger之Refresh

Android Systrace 基础知识 - Vsync 解读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值