Android BufferQueue中核心数据是一个GraphicBuffer的队列。而GraphicBuffer根据使用场合的不同可以从共享内存(即Ashmem,因为这块内存要在应用程序和服务端程序两个进程间共享)或者从硬件图形缓冲区(即Framebuffer,因为它是SurfaceFlinger渲染完要放到屏幕上的)中分配。另外因为用途不同,它的格式,大小,以及在BufferQueue中的数量都可能是不同的.
本文根据网上现有资源进行整合,以及自己的理解,有误之处欢迎指正~~
一、Why
连接图形数据的生产者和消费者,生产者和消费者可以存在于不同的进程,几乎系统中的所有的图形数据buffer的传递都依赖BufferQueue.
二、How
生产者先指定好他想要的一块空闲的buffer的属性,包括宽,高,像素格式,和用法标识,然后向BQ请求一块符合其需求的空闲buffer(dequeueBuffer()).使用完毕后,通过queueBuffer()方法将这块有数据的buffer返回给BQ.接着,消费者会通过acquireBuffer()获得这块buffer并处理其中的内容,当处理完毕后又会通过releaseBuffer()归还这块buffer给BQ.
BufferQueue中核心数据是一个GraphicBuffer的队列。而GraphicBuffer根据使用场合的不同可以从共享内存(即Ashmem,因为这块内存要在应用程序和服务端程序两个进程间共享)或者从硬件图形缓冲区(即Framebuffer,因为它是SurfaceFlinger渲染完要放到屏幕上的)中分配。另外因为用途不同,它的格式,大小,以及在BufferQueue中的数量都可能是不同的
三、What
3.1 Fence
Fence 是一种同步机制,用于Graphics Buffer的同步。用来处理跨硬件平台的情况(例如CPU与GPU),尤其是CPU、GPU和HWC之间的同步。另外,也可用于多个时间点之间的同步,当Graphics Buffer的生产者或消费者在对buffer处理完之后,通过fence发出信号,这样系统可以异步queue当前不需要但有可能接下来会使用读写的buffer。简言之,在合适的时间发一种信号,将先到的buffer拦住,等后来的到达,两者步调一致再一起走。
当grphics buffers的生产者或者消费者对这块buffer处理完成后,可以通过这种显式同步机制来发出信号,这样android系统就可以异步地queue入那些当前不需要但可能接下来就会被读或者写的buffers(理解为在每次同步信号到来之间的周期内,更高效率的做一些准备工作?)
这种通信机制在配合使用同步fence的情况下,变得更优秀.当生产者/消费者需要访问buffer时,必须等到fence这个信号的到来才可以完成这一操作.这种同步框架主要由3部分组成:
1. sync_timeline:
需要为每一个驱动实例实现一条单调递增的时间线.这实际上记录了每个特定硬件向kernel提交的jobs数量
2. sync_pt:
在时间轴上的一个信号值或者一个点.一个点有3种状态:active,signaled,error.一个点从active状态开始,并会过渡到signaled或者error状态.例如,当一块buffer不再被一个图像消费者使用后,这个sync_point就会变为signaled状态,这样图像生产者就知道又可以向这块buffer写入数据了
3. sync_fence:
一系列同步点(sync_pts)的集合,通常都有不同的父时间轴(即为不同的部件工作,例如displayer controller可以有自己的fence,GPU也可以有自己的fence).这样多个生产者/消费者就可以只通过一个函数参数告知其他人他们正在使用某块buffer. Fences拥有一个自己的fd,并且此fd可以从内核空间传递到用户空间.例如,一个fence可以包含两个sync_points,这两个point分别象征着有2个独立的图像消费者已经完成了对一块buffer的读操作.当这个fence信号被发出时(即这两个sync_pt的动作都已完成,其中任何一个没有完成都不会触发fence的signaled),图像生产者就知道所有的消费者都已经消费完毕
3.2 BufferQueue State
FREE:
表明这块buffer现在可以被生产者dequeue出来,但是这块buffer当下这一小段时间内仍
可能被消费者使用着.所以就算生产者dequeue到了这块buffer,它也还不能立即修改
buffer里的任何东西,直到收到fence信号,那就表示消费者已经用完这块buffer了.这时,
生产者才能往里写东西.
DEQUEUED:
表明这块buffer被生产者dequeu出来了,得等到fence信号才能写
QUEUED:
表明这块buffer被生产者填完数据后,重新放回到了队列供消费者使用,得等到fence信号
后其内容才能被访问
ACQUIRED:
表明这块buffer以经被消费者获取到了,得等到fence信号后才能读
以上buffer的属性里,如果其fence的值为NO_FENCE,那么可以立即使用
从上面的状态可以看出,一块Buffer大致经历的过程就是FREE->DEQUEUED->QUEUED->ACQUIRED->FREE。从owner的角度来讲,有点类似于下图的描述:
从图中可以大致看出一个buffer的各个状态、引起状态迁移的条件以及各状态下的owner。参与对buffer进行管理的对象有三个:
1. BufferQueue
可以认为BufferQueue是一个服务中心,其它两个owner必须要通过它来管理buffer。比如说当producer想要获取一个buffer时,它不能越过BufferQueue直接与consumer进行联系,反之亦然。这有点像房产中介一样,房主与买方的任何交易都需要经过中介的同意,私自达成的协议都是违反规定的
2. Producer
生产者就是“填充”buffer空间的人,通常情况下当然就是应用程序。因为应用程序不断地刷新UI,从而将产生的显示数据源源不断地写到buffer中。当Producer需要使用一块buffer时,它首先会向中介BufferQueue发起dequeue申请,然后才能对指定的缓冲区进行操作。这种情况下buffer就属于producer一个人的了,它可以对buffer进行任何必要的操作,而其它owner此刻绝不能擅自插手。
当生产者认为一块buffer已经写入完成后,它进一步调用BufferQueue的queue。从字面上看这个函数是“入列”的意思,形象地表达了buffer此时的操作——把buffer归还到BufferQueue的队列中。一旦queue成功后,owner也就随之改变为BufferQueue了
3. Consumer
消费者是与生产者相对应的,它的操作同样受到BufferQueue的管控。当一块buffer已经就绪后,Consumer就可以开始工作了,具体的细节我们会在SurfaceFlinger中描述。这里需要特别留意的是,从各个对象所扮演的角色来看,BufferQueue是中介机构,属于服务提供方;Producer属于buffer内容的产出方,它对缓冲区的操作是一个“主动”的过程;反之,Consumer对buffer的处理则是“被动”的、“等待式”的——它必须要等到一块buffer填充完成后才能做工作。在这样的模型下,我们怎么保证Consumer可以及时的处理buffer呢?换句话说,当一块buffer数据ready后,应该怎么告知Consumer来操作呢?
仔细观察的话,可以看到BufferQueue里还同时提供了一个特别的类,名称为ConsumerListener,其中的函数接口包括:
void BufferQueue::ProxyConsumerListener::onFrameAvailable(
const BufferItem& item) {
sp
if (listener != NULL) {
listener->onFrameAvailable(item);
}
}
void BufferQueue::ProxyConsumerListener::onBuffersReleased() {
sp
if (listener != NULL) {
listener->onBuffersReleased();
}
}
这样子就很清楚了,当有一帧数据准备就绪后,BufferQueue就会调用onFrameAvailable()来通知Consumer进行消费。
补充:
1. BufferQueue负责在有它需要时会分配buffer,这些分配好的buffer会保留在生产者或者消费者中,直到queue的属性改变;例如,生产者重新申请一堆大小属性改变了的buffer,这是BQ就会free掉之前为生产者分配的buffers(queue),并按照新要求重新分配.
2. BufferQueue永远都不会去复制buffer里的内容.移动这些庞大的数据效率很低,所以所有的buffer的传递都是传递的句柄.
Reference