垂直同步(VSYNC)实现原理

来源:互联网 发布:python 二叉树遍历 编辑:程序博客网 时间:2024/05/14 09:01

VSYNC在显示周期内同步一些确定的事件,APP在VSYNC结束的时间点绘制画面,也是在这个时间点SurfaceFlinger进行画面合成。这种机制消除了卡顿,提高了图形的视觉表现。硬件合成器(HWC)引用了VSYNC的实现函数


 int (waitForVsync*) (int64_t *timestamp) 

在VSYNC发生之前这个函数是阻塞的,函数会返回真实的VSYNC时间戳,每次VSYNC同步时总会有一个消息发出。客户端在指定的时间间隔会收到VSYNC时间戳,你必须用最大1毫秒的延迟来实现垂直同步(推荐0.5毫秒或更少);


显式同步


显示同步是必须的,它提供了一个以同步方式持有和释放Gralloc buffers的方式。显示同步允许graphics buffers的生产者和消费者在处理完一个buffer的时候发送一个信号。这种机制允许安卓进入队列的buffers可以被异步读写,

此时这些buffers不会被其余的第三者消费者或生产者需要。


这个显示同步有很多优点比如:设备之间的行为变化更少,更好的调试支持,改进的测试指标。例如,同步框架更容易找出问题根源,并在系统正常流事件发生时集中表现surfaceflinger时间戳。


这种通信得益于synchronization fences的使用,当请求一个buffer用来消费或者生产的时候synchronization fences是会被使用的。同步框架有三个主要模块:sync_timeline,sync_pt,和sync_fence。


sync_timeline


一个sync_timeline是一个单调递增的时间线,这个时间线会被每一个驱动实例使用,比如,一个GL context, display controller, or 2D blitter.本质上这是一个要提交到内核特定部位的一系列工作。它保证操作顺序和硬件可以实现。


这个sync_timeline是提供一个CPU-only的参考实现,这个实现被称作sw_sync(software sync)。如果可能的话,用它代替sync_timeline以便节省资源和避免复杂性。如果你不使用硬件资源,sw_sync应该足够了。


如果你要实现一个sync_timeline,使用sw_sync驱动。遵循这些准则:

  • 为所有drivers, timelines, 和 fences提供有用的名称。这简化了调试。

  • 在你的timelines实现timeline_value_str和pt_value_str操作让调试信息更加可读。

  • 如果你想让你的用户空间库(如GL库)来访问你的timelines的私有数据,那么你可以填充driver_data算子。这可以让你获得不会改变的sync_fence和sync_pts对象,建立基于命令行的命令。


当实现一个sync_timeline请不要:

  • 建立在任何真正的时间轴,比如墙上的挂钟。最好是创建一个你可以控制抽象的时间轴。

  • 允许用户显式地创建或发送fence。这会导致一个用户管道产生拒绝服务攻击从而导致停止掉了所有的功能。这是因为用户不能对内核的稳定负责。

  • 不要去访问sync_timeline,sync_pt或者sync_fence元素,需要的话请使用相关API.


sync_pt


一个sync_pt是sync_timeline的一个点,它有三个状态:active, signaled, and error. 它开始于active状态过度到signaled或error状态.例如,当一个buffer不再被一个image consumer需要,这个点会发出信号让image producers知道再一次写入这个buffer是可以的.


sync_fence


一个sync_fence是一个sync_pts的集合,这个集合通常有不同的sync_timeline父亲(比如display controller and GPU),这些是主要的驱动程序和用户空间通信原始的依赖关系。fence是内核保证能按时完成一个进入队列的一个工作的承诺。


它允许多个消费者或生产者发出信号说明他们正在使用一个buffer ,并允许这些信息与一个函数的参数进行通信。Fences由文件描述符支持,可以从内核空间传递到用户空间。比如说,一个fence可以包含两个sync_points,这表示两个单独的image consumers正在读取同一个buffer。当fence发出信号时,image producers知道消费者都在消费。


Fences,像sync_pts,开始的状态是active随后sync_pts根据自己的点的状态来进行调整状态。如果所有的sync_pts变成signaled状态,那么sync_fence也会变成signaled状态。如果有一个sync_pt变成error状态,整个sync_fence也会变成error状态。


sync_fence的成员在fence创建完成后是不可以改变的。一个sync_pt只可在一个fence里面,作为一个副本。即使两个points有相同的值,那么也将有两份sync_pt在fence里面。要在fence中获得一个以上的point,就要进行合并操作,从两个不同的fences把points添加到第三个fence。如果原始的fence里面有一个是在signaled状态,另一个则不是,被合成的第三个fence也不会处于signaled状态。


若要实现显式同步,请提供以下文件:

  • 实现特定硬件的同步时间轴的内核空间驱动器。需要fence-aware的驱动程序通常是为了与Hardware Composer进行访问或通信。关键文件包括:


  • 核心文件:

    • kernel/common/include/linux/sync.h

    • kernel/common/drivers/base/sync.c

  • sw_sync:

    • kernel/common/include/linux/sw_sync.h

    • kernel/common/drivers/base/sw_sync.c

  • 文档在 kernel/common//Documentation/sync.txt.

  • 和kernel-space通信的库在 platform/system/core/libsync.


  • Hardware Composer HAL模块(v1.3或更高),支持新的同步功能。你必须提供synchronization fences作为HAL里面set()和prepare()函数的参数。

  • 两个fence-related GL扩展(EGL_ANDROID_native_fence_sync 和 EGL_ANDROID_wait_sync)并且你的图形驱动程序支持fence。


例如,使用支持同步功能的API,可以开发具有显示buffer功能的显示驱动程序。在同步框架存在之前,这个函数会接收dma-bufs,把这些buffers显示,如果buffer是可见的那么这个函数处于block状态。例如:

/*
 * assumes buf is ready to be displayed.  returns when buffer is no longer on
 * screen.
 */

void display_buffer(struct dma_buf *buf);

使用同步框架,API调用稍微复杂一些。在将buffer放在显示器上时,您可以将它与buffer准备就绪时的fence关联起来。在清除fence之后你可以把任务放入队列。


以这种方式,你不会阻塞任何东西。您立即返回您自己的fence,这是保证buffer从显示器消失。当buffers进入队列时,内核将列出同步框架的依赖项:


/*
 * will display buf when fence is signaled.  returns immediately with a fence
 * that will signal when buf is no longer displayed.
 */

struct sync_fence* display_buffer(struct dma_buf *buf, struct sync_fence
*fence);


Sync 集成

本节介绍如何将Android不同部分的低级别同步框架和必须相互通信的驱动程序集成在一起。


整合规范


对于图形的Android HAL接口遵循一致的约定,所以当文件描述符在HAL接口上传递时,文件描述符的所有权总是被转移的。这意味着:

  • 如果从同步框架接收到一个fence文件描述符,则必须关闭它。

  • 如果将一个fence文件描述符返回到同步框架,框架将关闭它。

  • 要继续使用fence文件描述符,必须复制描述符。


每一次,一个fence传递给BufferQueue(如一个窗口通过传递fence给BufferQueue来说明自己新的内容已经准备好)fence对象会被重命名。内核支持fences用字符串表示名称,同步框架使用的窗口名称和正在进入队列的buffer index命名这个fence(比如,SurfaceView:0)。如果这个名字出现在/d/sync形式的log里或bugreports,那么对定位死锁问题的原因非常重要。


ANativeWindow 集成

ANativeWindow是fence一体化,即dequeuebuffer,queuebuffer,和cancelbuffer 包含fence的引用。


OpenGL ES的集成

OpenGL ES同步整合依赖于两个EGL扩展:

  • EGL_ANDROID_native_fence_sync.提供了一种以EGLSyncKHR对象的方式来创建原生的Android fence文件描述符。

  • EGL_ANDROID_wait_sync.允许GPU而不是CPU等待,让GPU等待一个EGLSyncKHR。本质上是和EGL_KHR_wait_sync扩展是相同的(指规范细节)。


这些扩展可以独立使用,被在libgui里面的编译标志位控制。要使用它们,首先实现EGL_ANDROID_native_fence_sync扩展来获得内核的相关支持。下一步,添加一个对于fences支持的ANativeWindow驱动程序,然后打开libgui的编译标志位来确保使用EGL_ANDROID_native_fence_sync扩展。


第二步,打开EGL_ANDROID_wait_sync扩展并把它分开。EGL_ANDROID_native_fence_sync扩展由不同的原生fence EGLSync对象类型组成,因此正在应用于EGLSync对象的类型不需要应用于EGL_ANDROID_native_fence对象类型以避免不必要的相互影响。


EGL_ANDROID_native_fence_sync扩展采用相应的原生fence文件描述符属性。这种属性只能设置在创建时不能直接查询到一个现有的同步对象。此属性可以设置为两种模式之一:

  • 有效的fence文件描述符。把原生Android fence 文件描述符封装为EGLSyncKHR 对象。

  • - 1.从EGLSyncKHR 对象创建一个原生的Android fence文件描述符.


DupNativeFenceFD函数的作用是从原生Android fence文件描述符提取EGLSyncKHR对象。这与查询被设置的属性有相同的结果,但遵守接收者关闭fence的约定(会重复操作)。最后,销毁EGLSync对象时应该关闭内部fence属性。


Hardware Composer集成

The Hardware Composer 处理下面三种类型的sync fences:

  • Acquire fence.每一个层,在调用 HWC::set 之前进行设置。当Hardware Composer读取buffer的时候它变为signals 模式。

  • Release fence.每一个层,在HWC::set调用后被填满。当Hardware Composer读取buffer完毕,它会变成signals 状态,以便framework可以给特定的层再次使用该buffer。

  • Retire fence. 对于每一个完整的层框架,HWC::set 每次被调用都会被填满。所有层的HWC::set 操作全部完成后它会变成signals状态并通知framework 。在下一次HWC::set操作的结果显示在屏幕上的时候,retire fence将变为signals状态。



垂直同步偏移(VSYNC offset)


APP和SurfaceFlinger循环渲染过程应该和hardware VSYNC同步。hardware VSYNC事件发生时顺序是display显示frame N,SurfaceFlinger合成frame N+1,app生成frame N+2。


Synchronizing with VSYNC 发出一致的延时。这样可以减少APP和SurfaceFlinger和display彼此的误差。假定APP和SurfaceFlinger的每帧时间损耗比较固定,那么即使这样,延时至少也是两帧。


为了解决这个问题,你可以采用VSYNC offsets减少input-to-display的延时通过让APP和composition signal与hardware VSYNC进行比较。这是可能的,因为app加composition的过程通常需要少于33ms。


VSYNC offset的结果是三个信号同一段时间偏移相同:

  • HW_VSYNC_0. Display开始显示下一个frame.

  • VSYNC. App读取输入开始展示下一个frame.

  • SF VSYNC. SurfaceFlinger开始合成下一个frame.


VSYNC offset,SurfaceFlinger接收buffer、合成frame,APP处理事件渲染frame,都发生在一个frame的时间周期内。

注意:VSYNC offsets减少了APP和composition的可利用时间从而减少了错误的发生。


DispSync

DispSync保持了一个定期的hardware-based VSYNC显示模型。这个模型保证了执行来自hardware VSYNC的定期回调函数。


DispSync是一个software phase lock loop (PLL) 。它生成Choreographer使用的VSYNC signals 和SurfaceFlinger使用的SF VSYNC信号也包括hardware VSYNC没有偏移(offset )的情况。


图一.DispSync 流程


DispSync具有下面的特点:

Reference. HW_VSYNC_0.

Output. VSYNC and SF VSYNC.

Feedback. Retire fence signal timestamps from Hardware Composer.


VSYNC/Retire offset

retire fences signal 时间戳需要匹配HW VSYNC即使设备没有使用

offset phase。否则会出现严重错误。


三角模型里面Retire fence是direct memory access (DMA)显存显示的最后阶段,但实际display switch and HW VSYNC有时候会在后面。


PRESENT_TIME_OFFSET_FROM_VSYNC_NS宏被设置在设备的BoardConfig.mk文件里。它是基于显示控制器和面板特性。这个值代表的是retire fence和HW VSYNC signal的纳秒级别的时间差。


VSYNC and SF_VSYNC offsets

VSYNC_EVENT_PHASE_OFFSET_NS和SF_VSYNC_EVENT_PHASE_OFFSET_NS高负荷的使用情况下的临界值,如部分GPU在窗口平移或者滚动页面的情況下。这些offsets允许更长的应用程序渲染时间和更长的GPU合成时间。


超过1ms或者两个延迟是明显的。我们建议减少延迟,而不是增加错误次数。

注意:这两个offsets也被配置在了设备的BoardConfig.mk文件里面。表示和

HW_VSYNC_0时间戳的差单位是纳秒,如果这两个值不被赋值那么默认是0,也可以被设置为负数。

0 0
原创粉丝点击