Android中的GraphicBuffer同步机制-Fence

来源:互联网 发布:gta5捏脸数据男欧美 编辑:程序博客网 时间:2024/05/21 21:50

转载地址:http://blog.csdn.net/jinzhuojun/article/details/39698317

Fence是一种同步机制,在Android里主要用于图形系统中GraphicBuffer的同步。那它和已有同步机制相比有什么特点呢?它主要被用来处理跨硬件的情况,尤其是CPU,GPU和HWC之间的同步,另外它还可以用于多个时间点之间的同步。GPU编程和纯CPU编程一个很大的不同是它是异步的,也就是说当我们调用GL command返回时这条命令并不一定完成了,只是把这个命令放在本地的command buffer里。具体什么时候这条GL command被真正执行完毕CPU是不知道的,除非CPU使用glFinish()等待这些命令执行完,另外一种方法就是基于同步对象的Fence机制。下面举个生产者把GraphicBuffer交给消费者的例子。如生产者是App中的renderer,消费者是SurfaceFlinger。GraphicBuffer的队列放在缓冲队列BufferQueue中。BufferQueue对App端的接口为IGraphicBufferProducer,实现类为Surface,对SurfaceFlinger端的接口为IGraphicBufferConsumer,实现类为SurfaceFlingerConsumer。BufferQueue中对每个GraphiBuffer都有BufferState标记着它的状态:

这个状态一定程度上说明了该GraphicBuffer的归属,但只指示了CPU里的状态,而GraphicBuffer的真正使用者是GPU。也就是说,当生产者把一个GraphicBuffer放入BufferQueue时,只是在CPU层面完成了归属的转移。但GPU说不定还在用,如果还在用的话消费者是不能拿去合成的。这时候GraphicBuffer和生产消费者的关系就比较暧昧了,消费者对GraphicBuffer具有拥有权,但无使用权,它需要等一个信号,告诉它GPU用完了,消费者才真正拥有使用权。一个简化的模型如下:

这个通知GraphicBuffer被上一个使用者用完的信号就是由Fence完成的。Fence的存在非常单纯,从诞生开始就是为了在合适的时间发出一个信号。另一个角度来说,为什么不在生产者把GraphicBuffer交给消费者时就调用glFinish()等GPU完成呢?这样拥有权和使用权就一并传递了,无需Fence。就功能上这样做是可以的,但性能会有影响,因为glFinish()是阻塞的,这时CPU为了等GPU自己也不能工作了。如果用Fence的话就可以等这个GraphicBuffer真正要被消费者用到时再阻塞,而那之前CPU和GPU是可以并行工作的。这样相当于实现了临界资源的lazy passing。

说完Fence的基本作用,再说下它的实现。Fence,顾名思义就是把先到的拦住,等后来的,两者步调一致了再往前走。抽象地说,Fence包含了同一或不同时间轴上的多个时间点,只有当这些点同时到达时Fence才会被触发。更详细的介绍可以参考这篇文章(http://netaz.blogspot.com/2013/10/android-fences-introduction-in-any.html)。

Fence可以由硬件实现(Graphic driver),也可以由软件实现(Android kernel中的sw_sync)。EGL中提供了同步对象的扩展KHR_fence_sync(http://www.khronos.org/registry/vg/extensions/KHR/EGL_KHR_fence_sync.txt)。其中提供了eglCreateSyncKHR (),eglDestroySyncKHR()产生和销毁同步对象。这个同步对象是往GL command队列中插入的一个特殊操作,当执行到它时,会发出信号指示队列前面的命令已全部执行完毕。函数eglClientWaitSyncKHR()可让调用者阻塞等待信号发生。

在此基础之上,Android对其进行了扩展-ANDROID_native_fence_sync  (http://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_native_fence_sync.txt),新加了接口eglDupNativeFenceFDANDROID()。它可以把一个同步对象转化为一个文件描述符(反过来,eglCreateSyncKHR()可以把文件描述符转成同步对象)。这个扩展相当于让CPU中有了GPU中同步对象的句柄,文件描述符可以在进程间传递(通过binder或domain socket等IPC机制),这就为多进程间的同步提供了基础。我们知道Unix系统一切皆文件,因此,有个这个扩展以后Fence的通用性大大增强了。

Android还进一步丰富了Fence的software stack。主要分布在三部分:C++ Fence类位于/frameworks/native/libs/ui/Fence.cpp; C的libsync库位于/system/core/libsync/sync.c; Kernel driver部分位于/drivers/base/sync.c。总得来说,kernel driver部分是同步的主要实现,libsync是对driver接口的封装,Fence是对libsync的进一步的C++封装。Fence会被作为GraphicBuffer的附属随着GraphicBuffer在生产者和消费间传输。另外Fence的软件实现位于/drivers/base/sw_sync.c。SyncFeatures用以查询系统支持的同步机制:/frameworks/native/libs/gui/SyncFeatures.cpp。


下面分析下Fence在Android中的具体用法。它主要的作用是GraphicBuffer在App, GPU和HWC三者间传递时作同步。

首先温故一下GraphicBuffer从App到Display的旅程。GraphicBuffer先由App端作为生产者进行绘制,然后放入到BufferQueue,等待消费者取出作下一步的渲染合成。SurfaceFlinger作为消费者,会把每个层对应的GraphicBuffer取来生成EGLImageKHR对象。合成时对于GraphicBuffer的处理分两种情况。对于Overlay的层,SurfaceFlinger会直接将其buffer handle放入HWC的Layer list。对于需要GPU绘制的层(超出HWC处理层数或者有复杂变换的),SurfaceFlinger会将前面生成的EGLImageKHR通过glEGLImageTargetTexture2DOES()作为纹理进行合成(http://snorp.net/2011/12/16/android-direct-texture.html)。合成完后SurfaceFlinger又作为生产者,把GPU合成好的framebuffer的handle置到HWC中的FramebufferTarget中(HWC中hwc_display_contents_1_t中的hwc_layer_1_t列表最后一个slot用于放GPU的渲染结果所在buffer)。HWC最后叠加Overlay层再往Display上扔,这时HWC是消费者。整个大致流程如图:

可以看到,对于非Overlay的层来说GraphicBuffer先后经过两个生产消费者模型。我们知道GraphicBuffer核心包含的是buffer_handle_t结构,它指向的native_handle_t包含了gralloc中申请出来的图形缓冲区的文件描述符和其它基本属性,这个文件描述符会被同时映射到客户端和服务端,作为共享内存。

由于服务和客户端进程都可以访问同一物理内存,因此不加同步的话会引起错误。为了协调客户端和服务端,在传输GraphicBuffer时,还带有Fence,标志了它是否被上一个使用者使用完毕。Fence按作用大体分两种:acquireFence和releaseFence。前者用于生产者通知消费者生产已完成,后者用于消费者通知生产者消费已完成。下面分别看一下这两种Fence的产生和使用过程。首先是acquireFence的使用流程:

当App端通过queueBuffer()向BufferQueue插入GraphicBuffer时,会顺带一个Fence,这个Fence指示这个GraphicBuffer是否已被生产者用好。之后该GraphicBuffer被消费者通过acquireBuffer()拿走,同时也会取出这个acquireFence。之后消费者(也就是SurfaceFlinger)要把它拿来渲染时,需要等待Fence被触发。如果该层是通过GPU渲染的,那么使用它的地方是Layer::onDraw(),其中会通过bindTextureImage()绑定纹理:
486    status_t err = mSurfaceFlingerConsumer->bindTextureImage();
该函数最后会调用doGLFenceWaitLocked()等待acquireFence触发。因为再接下来就是要拿来画了,如果这儿不等待直接往下走,那渲染出来的就是错误的内容。

如果该层是HWC渲染的Overlay层,那么不需要经过GPU,那就需要把这些层对应的acquireFence传到HWC中。这样,HWC在合成前就能确认这个buffer是否已被生产者使用完,因此一个正常点的HWC需要等这些个acquireFence全被触发才能去绘制。这个设置的工作是在SurfaceFlinger::doComposeSurfaces()中完成的,该函数会调用每个层的layer::setAcquireFence()函数:
428    if (layer.getCompositionType() == HWC_OVERLAY) {
429        sp<Fence> fence = mSurfaceFlingerConsumer->getCurrentFence();
...
431            fenceFd = fence->dup();
...
437    layer.setAcquireFenceFd(fenceFd);
可以看到其中忽略了非Overlay的层,因为HWC不需要直接和非Overlay层同步,它只要和这些非Overlay层合成的结果FramebufferTarget同步就可以了。GPU渲染完非Overlay的层后,通过queueBuffer()将GraphicBuffer放入FramebufferSurface对应的BufferQueue,然后FramebufferSurface::onFrameAvailable()被调用。它先会通过nextBuffer()->acquireBufferLocked()从BufferQueue中拿一个GraphicBuffer,附带拿到它的acquireFence。接着调用HWComposer::fbPost()->setFramebufferTarget(),其中会把刚才acquire的GraphicBuffer连带acquireFence设到HWC的Layer list中的FramebufferTarget slot中:
580        acquireFenceFd = acquireFence->dup();
...
586    disp.framebufferTarget->acquireFenceFd = acquireFenceFd;
综上,HWC进行最后处理的前提是Overlay层的acquireFence及FramebufferTarget的acquireFence都被触发。

看完acquireFence,再看看releaseFence的使用流程:

前面提到合成的过程先是GPU工作,在doComposition()函数中合成非Overlay的层,结果放在framebuffer中。然后SurfaceFlinger会调用postFramebuffer()让HWC开始工作。postFramebuffer()中最主要是调用HWC的set()接口通知HWC进行合成显示,然后会将HWC中产生的releaseFence(如有)同步到SurfaceFlingerConsumer中。实现位于Layer的onLayerDisplayed()函数中:
151        mSurfaceFlingerConsumer->setReleaseFence(layer->getAndResetReleaseFence());
上面主要是针对Overlay的层,那对于GPU绘制的层呢?在收到INVALIDATE消息时,SurfaceFlinger会依次调用handleMessageInvalidate()->handlePageFlip()->Layer::latchBuffer()->SurfaceFlingerConsumer::updateTexImage() ,其中会调用该层对应Consumer的GLConsumer::updateAndReleaseLocked() 函数。该函数会释放老的GraphicBuffer,释放前会通过syncForReleaseLocked()函数插入releaseFence,代表如果触发时该GraphicBuffer消费者已经使用完毕。然后调用releaseBufferLocked()还给BufferQueue,当然还带着这个releaseFence。这样,当这个GraphicBuffer被生产者再次通过dequeueBuffer()拿出时,就可以通过这个releaseFence来判断消费者是否仍然在使用。

另一方面,HWC合成完毕后,SurfaceFlinger会依次调用DisplayDevice::onSwapBuffersCompleted() -> FramebufferSurface::onFrameCommitted()。onFrameCommitted()核心代码如下:
148    sp<Fence> fence = mHwc.getAndResetReleaseFence(mDisplayType);
...
151        status_t err = addReleaseFence(mCurrentBufferSlot,
152                mCurrentBuffer, fence);
此处拿到HWC生成的FramebufferTarget的releaseFence,设到FramebufferSurface中相应的GraphicBuffer Slot中。这样FramebufferSurface对应的GraphicBuffer也可以被释放回BufferQueue了。当将来EGL从中拿到这个buffer时,照例也要先等待这个releaseFence触发才能使用。


1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 锤基意外怀孕怎么办 08 蛐蛐罐底翻砂了怎么办 剑网3中被盗号后怎么办 本人想离婚对方躲避怎么办 微信不能说语音怎么办 微信语音发不了怎么办 吃鸡语音用不了怎么办 要杀我的人见面怎么办 转晕了想吐怎么办 原地转圈头晕恶心想吐怎么办 孩子吃凉的呕吐头还晕怎么办 孩子转晕了想吐怎么办 转圈晕了想吐怎么办 我爸总是骂我妈怎么办 转圈转的想吐怎么办 大便干燥拉不出来怎么办 吹完头发很干燥怎么办 腿摔伤了结痂疼怎么办 蹭wifi被禁止后怎么办 电脑wifi给拉黑怎么办 电脑想用无线网怎么办 中路被对方打崩了怎么办 英雄联盟队友太坑怎么办 匹配被王者虐了怎么办 lol碰到嘴臭的怎么办 小婴儿脾气大怎么办呢? 玩游戏输入法会跳出出来怎么办 逆水寒fps太低怎么办 我dcj没地速怎么办 电焊看久眼睛疼怎么办 装修忘了窗帘盒怎么办? 纹眉导致眼肿了怎么办 哭泣引起的眼肿怎么办 在酒店忘记拉窗帘了怎么办 湿气重喉咙有痰怎么办 眼睛上火了肿了怎么办 陌陌直播没人看怎么办 陌陌直播没人气怎么办 我真的爱上你了怎么办 弯腰时间久了腰疼怎么办 斗鱼pk输的怎么办