Android中的GraphicBuffer同步机制-Fence

本文深入解析了Android系统中Fence同步机制的原理及其应用场景。详细介绍了Fence如何解决跨硬件同步问题,特别是在CPU、GPU和HWC之间的同步,并探讨了其在GraphicBuffer传输中的关键作用。

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

文章出处:http://blog.youkuaiyun.com/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触发才能使用。


<think>我们正在讨论GraphicBuffer,这是一个在图形编程中常见的概念,特别是在Android系统中。GraphicBuffer用于管理图形内存缓冲区,通常用于在应用程序和系统之间高效地传递图形数据。以下是对GraphicBuffer的用途和实现的详细说明: ### GraphicBuffer的用途 GraphicBuffer主要用于以下场景: 1. **SurfaceFlinger合成**:在Android系统中,SurfaceFlinger使用GraphicBuffer来合成多个应用的界面[^1]。 2. **跨进程共享**:GraphicBuffer基于共享内存实现,允许在不同进程之间传递图像数据,例如从应用进程传递到系统服务进程[^2]。 3. **硬件加速渲染**:在OpenGL ES或Vulkan等图形API中,GraphicBuffer可以作为纹理(texture)或渲染目标(render target)使用[^3]。 4. **相机预览和视频解码**:GraphicBuffer用于接收相机预览帧或视频解码帧,然后显示在屏幕上[^4]。 ### GraphicBuffer的实现 在Android底层,GraphicBuffer的实现涉及以下关键点: 1. **内存分配**:GraphicBuffer通过Gralloc(Graphics Memory Allocator)分配内存,Gralloc是一个HAL层模块,负责管理图形内存[^5]。 2. **跨进程传递**:GraphicBuffer实现了Parcelable接口,因此可以通过Binder跨进程传递。接收进程可以映射同一块物理内存[^6]。 3. **格式支持**:GraphicBuffer支持多种像素格式(如RGBA_8888、RGBX_8888、YV12等),以及多种使用标志(如用于软件读写、硬件纹理、渲染目标等)[^7]。 4. **同步机制**:当多个生产者(如GPU、视频解码器)写入同一个GraphicBuffer时,需要使用同步原语(如fence)来协调访问[^8]。 以下是一个简化的GraphicBuffer使用示例(基于C++): ```cpp #include <ui/GraphicBuffer.h> using namespace android; // 创建GraphicBuffer sp<GraphicBuffer> buffer = new GraphicBuffer( 1920, 1080, HAL_PIXEL_FORMAT_RGBA_8888, GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_OFTEN); // 锁定缓冲区以进行CPU写入 void* data; buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &data); // 写入数据... buffer->unlock(); // 将缓冲区作为OpenGL ES纹理使用 GLuint textureId; glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_2D, textureId); // 将GraphicBuffer绑定到EGLImage buffer->getNativeBuffer()->handle, ...); // 使用EGLImage创建纹理... ``` ### 性能优化 使用GraphicBuffer时需注意: 1. **避免频繁分配**:GraphicBuffer的分配和释放成本较高,应尽量复用[^9]。 2. **正确使用标志**:根据实际用途设置正确的USAGE标志,以优化内存访问路径[^10]。 3. **同步处理**:在多线程或跨进程场景中,必须使用fence确保数据一致性[^11]。 §§相关问题§§ 1. GraphicBuffer与SurfaceTexture有什么关系? 2. 如何避免GraphicBuffer的内存泄漏? 3. GraphicBufferAndroid相机预览中的具体工作流程是什么?</think>### GraphicBuffer在图形编程中的用途与实现 GraphicBufferAndroid图形系统中的核心组件,主要用于管理图形内存缓冲区的分配和共享。以下是其关键用途和实现细节: #### 主要用途 1. **跨进程图形数据共享** 允许不同进程(如应用、SurfaceFlinger、媒体服务)共享同一块图形内存,避免数据拷贝。例如相机预览数据直接传递给显示系统[^1]。 2. **硬件加速渲染支持** 作为OpenGL ES/Vulkan的纹理(Texture)或渲染目标(Render Target),支持GPU直接操作: ```cpp // 将GraphicBuffer绑定为OpenGL ES纹理 EGLImageKHR image = eglCreateImageKHR(..., buffer->getNativeBuffer()); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); ``` 3. **屏幕合成基础单元** SurfaceFlinger使用GraphicBuffer存储各层(Layer)的像素数据,进行最终合成[^2]。 4. **多媒体处理** 视频编解码器直接读写GraphicBuffer,实现零拷贝视频流水线,如: ``` 相机 → GraphicBuffer → 编码器 → 网络传输 ``` #### 实现机制 1. **内存分配** 通过Gralloc(HAL层图形内存分配器)分配: ```cpp // 创建1920x1080的RGBA缓冲区 GraphicBuffer buffer(1920, 1080, HAL_PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_WRITE); ``` 2. **跨进程传递** 基于Binder和共享内存实现: - 序列化:`GraphicBuffer::writeToParcel()` - 反序列化:`GraphicBuffer::readFromParcel()` - 接收方获取相同物理内存的映射 3. **访问控制** 支持多种访问模式: ```cpp void* vaddr; buffer.lock(GRALLOC_USAGE_SW_WRITE_OFTEN, &vaddr); // CPU访问 // 写入像素数据... buffer.unlock(); ``` 4. **同步机制** 使用同步栅栏(Sync Fence)协调生产者-消费者: ```cpp int releaseFence = -1; buffer.lockAsync(..., &releaseFence); // 获取释放栅栏 ``` #### 性能优化要点 1. **缓冲区复用** 通过`BufferQueue`实现三重缓冲,减少分配开销[^3]。 2. **使用标志优化** 正确设置usage标志指导内存分配策略: - `GRALLOC_USAGE_HW_RENDER`:GPU写入 - `GRALLOC_USAGE_PROTECTED`:DRM保护内容 3. **像素格式选择** 优先使用YUV格式(YCbCr_420_SP)减少视频内存占用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值