8.4.6 GraphicBuffer介绍

最后更新于:2022-04-02 05:52:38

GraphicBuffer是Surface系统中一个高层次的显示内存管理类,它封装了和硬件相关的一些细节,简化了应用层的处理逻辑。先来认识一下它。 1. 初识GraphicBuffer GraphicBuffer的代码如下所示: **GraphicBuffer.h** ~~~ class GraphicBuffer :public EGLNativeBase>, public Flattenable ~~~ 其中,EGLNativeBase是一个模板类。它的定义,代码如下所示: **Android_natives.h** ~~~ template class EGLNativeBase : public NATIVE_TYPE, publicREF ~~~ 通过替换,可得到GraphicBuffer的派生关系,如图8-21所示: :-: ![](http://img.blog.csdn.net/20150802162900888?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图8-21 GraphicBuffer派生关系的示意图 从图中可以看出: - 从LightRefBase派生使GraphicBuffer支持轻量级的引用计数控制。 - 从Flattenable派生使GraphicBuffer支持序列化,它的flatten和unflatten函数用于序列化和反序列化,这样,GraphicBuffer的信息就可以存储到Parcel包中并被Binder传输了。 另外,图中的android_native_buffer_t是GraphicBuffer的父类,它是一个struct结构体。可以将C++语言中的struct和class当作同一个东西,所以GraphicBuffer能从它派生。其代码如下所示: **android_native_buffer.h** ~~~ typedef struct android_native_buffer_t { #ifdef __cplusplus android_native_buffer_t() { common.magic = ANDROID_NATIVE_BUFFER_MAGIC; common.version = sizeof(android_native_buffer_t); memset(common.reserved, 0, sizeof(common.reserved)); } #endif //这个android_native_base_t是struct的第一个成员,根据C/C++编译的特性,这个成员 //在它的派生类对象所占有的内存中也是排第一个。 structandroid_native_base_t common; intwidth; intheight; intstride; intformat; intusage; void* reserved[2]; //这是一个关键成员,保存一些和显示内存分配/管理相关的内容 buffer_handle_t handle; void*reserved_proc[8]; } android_native_buffer_t; ~~~ GraphicBuffer和显示内存分配相关的部分主要集中在buffer_handle_t这个变量上,它实际上是一个指针,定义如下: **gralloc.h** ~~~ typedef const native_handle* buffer_handle_t; ~~~ native_handle的定义如下: **native_handle.h** ~~~ typedef struct { intversion; /* version值为sizeof(native_handle_t) */ intnumFds; intnumInts; intdata[0]; /* data是数据存储空间的首地址 */ } native_handle_t; typedef native_handle_t native_handle; ~~~ 读者可能要问,一个小小的GraphicBuffer为什么这么复杂?要回答这个问题,应先对GraphicBuffer有比较全面的了解。按照图8-20中的流程来看GraphicBuffer。 2. GraphicBuffer和存储的分配 GraphicBuffer的构造函数最有可能分配存储了。注意,流程中使用的是无参构造函数,所以应先看无参构造函数。 (1)无参构造函数的分析 代码如下所示: **GraphicBuffer.cpp** ~~~ GraphicBuffer::GraphicBuffer() :BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()), mInitCheck(NO_ERROR), mVStride(0), mIndex(-1) { /* 其中mBufferMapper为GraphicBufferMapper类型,它的创建采用的是单例模式,也就是每个 进程只有一个GraphicBufferMapper对象,读者可以去看看get的实现。 */ width = height= stride= format= usage = 0; handle= NULL; //handle为空 } ~~~ 在无参构造函数中没有发现和存储分配有关的操作。那么,根据流程,下一个有可能的地方就是reallocate函数了。 (2)reallocate的分析 Reallocate的代码如下所示: **GraphicBuffer.cpp** ~~~ status_t GraphicBuffer::reallocate(uint32_t w,uint32_t h, PixelFormat f, uint32_t reqUsage) { if(mOwner != ownData) return INVALID_OPERATION; if(handle) {//handle值在无参构造函数中初始化为空,所以不满足if的条件 GraphicBufferAllocator& allocator(GraphicBufferAllocator::get()); allocator.free(handle); handle = 0; } returninitSize(w, h, f, reqUsage);//调用initSize函数 } ~~~ InitSize函数的代码如下所示: **GraphicBuffer.cpp** ~~~ status_t GraphicBuffer::initSize(uint32_t w,uint32_t h, PixelFormat format, uint32_t reqUsage) { if(format == PIXEL_FORMAT_RGBX_8888) format = PIXEL_FORMAT_RGBA_8888; /* GraphicBufferAllocator才是真正的存储分配的管理类,它的创建也是采用的单例模式, 也就是每个进程只有一个GraphicBufferAllocator对象 */ GraphicBufferAllocator& allocator =GraphicBufferAllocator::get(); //调用GraphicBufferAllocator的alloc来分配存储,注意handle作为指针 //被传了进去,看来handle的值会被修改 status_t err = allocator.alloc(w, h, format, reqUsage, &handle,&stride); if(err == NO_ERROR) { this->width = w; this->height = h; this->format = format; this->usage = reqUsage; mVStride = 0; } returnerr; } ~~~ (3)GraphicBufferAllocator的介绍 从上面的代码中可以发现,GraphicBuffer的存储分配和GraphicBufferAllocator有关。一个小小的存储分配为什么需要经过这么多道工序呢?还是先来看GraphicBufferAllocator,代码如下所示: **GraphicBufferAllocator.cpp** ~~~ GraphicBufferAllocator::GraphicBufferAllocator() :mAllocDev(0) { hw_module_t const* module; //调用hw_get_module,得到hw_module_t interr = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module); if (err == 0) { //调用gralloc_open函数,注意我们把module参数传了进去。 gralloc_open(module, &mAllocDev); } } ~~~ GraphicBufferAllocator在创建时,会首先调用hw_get_module取出一个hw_module_t类型的对象。从名字上看,它和硬件平台有关系。它会加载一个叫libgralloc.硬件平台名.so的动态库。比如,我的HTC G7手机上加载的库是/system/lib/hw/libgraolloc.qsd-8k.so。这个库的源代码在hardware/msm7k/libgralloc-qsd8k目录下。 这个库有什么用呢?简言之,就是为了分配一块用于显示的内存,但为什么需要这种层层封装呢?答案很简单: 封装的目的就是为了屏蔽不同硬件平台的差别。 读者可通过执行adb getprop ro.board.platform命令,得到具体手机上硬件平台的名字。图8-22总结了GraphicBufferAllocator分配内存的途径。这部分代码,读者可参考hardware/libhardware/hardware.c和hardware/msm7k/libgralloc-qsd8k/gralloc.cpp,后文将不再深入探讨和硬件平台有关的知识。 :-: ![](http://img.blog.csdn.net/20150802163035193?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图8-22 GraphicBufferAllocator内存的分配途径 >[warning] **注意**,这里是以G7的libgralloc.qsk-8k.so为示例的。其中pmem设备用来创建一块连续的内存,因为有些硬件设备(例如Camera)工作时需要使用一块连续的内存,对于这种情况,一般就会使用pmem设备来分配内存。 这里,仅讨论图8-22中与硬件无关的分配方式。在这种情况下,将使用ashmem分配共享内存。下面看GraphicBufferAllocator的alloc函数,其代码如下所示: **GraphicBufferAllocator.cpp** ~~~ status_t GraphicBufferAllocator::alloc(uint32_tw, uint32_t h, PixelFormat format,int usage, buffer_handle_t* handle, int32_t*stride) { //根据前面的定义可知buffer_handle_t为native_handle_t*类型 status_t err; if (usage & GRALLOC_USAGE_HW_MASK) { err =mAllocDev->alloc(mAllocDev, w, h, format, usage, handle, stride); } else { //SW分配,可以做到和HW无关了。 err = sw_gralloc_handle_t::alloc(w, h, format, usage, handle, stride); } ...... returnerr; } ~~~ 下面,来看软件分配的方式: **GraphicBufferAllocator.cpp** ~~~ status_t sw_gralloc_handle_t::alloc(uint32_t w,uint32_t h, int format, int usage, buffer_handle_t* pHandle, int32_t*pStride) { intalign = 4; intbpp = 0; ......//格式转换 size_tbpr = (w*bpp + (align-1)) & ~(align-1); size_tsize = bpr * h; size_tstride = bpr / bpp; size =(size + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1); //直接使用了ashmem创建共享内存 int fd= ashmem_create_region("sw-gralloc-buffer", size); ...... //进行内存映射,得到共享内存起始地址 void*base = mmap(0, size, prot, MAP_SHARED, fd, 0); sw_gralloc_handle_t* hnd = new sw_gralloc_handle_t(); hnd->fd = fd;//保存文件描述符 hnd->size = size;//保存共享内存的大小 hnd->base = intptr_t(base);//intptr_t将void*类型转换成int*类型 hnd->prot = prot;//保存属性 *pStride = stride; *pHandle = hnd; //pHandle就是传入的那个handle变量的指针,这里对它进行赋值 returnNO_ERROR; } ~~~ 我们知道,调用GraphicBuffer的reallocate函数后,会导致物理存储被分配。前面曾说过,Layer会创建两个GraphicBuffer,而Native Surface端也会创建两个GraphicBuffer,那么这两个GraphicBuffer是怎么建立联系的呢?为什么说native_handle_t是GraphicBuffer的精髓呢? 3. flatten和unflatten的分析 试想,Native Surface的GraphicBuffer是怎么和Layer的GraphicBuffer建立联系的: 先通过requestBuffer函数返回一个GraphicBuffer,然后这个GraphicBuffer被Native Surface保存。 这中间的过程其实是一个mini版的乾坤挪移,来看看,代码如下所示: **ISurface.cpp** ~~~ //requestBuffer的响应端 status_t BnSurface::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { switch(code) { case REQUEST_BUFFER: { CHECK_INTERFACE(ISurface, data, reply); int bufferIdx = data.readInt32(); int usage = data.readInt32(); sp buffer(requestBuffer(bufferIdx, usage)); ...... /* requestBuffer的返回值被写到Parcel包中,由于GraphicBuffer从 Flattenable类派生,这将导致它的flatten函数被调用 */ return reply->write(*buffer); } ....... } //再来看请求端的处理,在BpSurface中 virtual sp requestBuffer(intbufferIdx, int usage) { Parcel data, reply; data.writeInterfaceToken(ISurface::getInterfaceDescriptor()); data.writeInt32(bufferIdx); data.writeInt32(usage); remote()->transact(REQUEST_BUFFER, data, &reply); sp buffer = new GraphicBuffer(); reply.read(*buffer);//Parcel调用unflatten函数把信息反序列化到这个buffer中。 return buffer;//requestBuffer实际上返回的是本地new出来的这个GraphicBuffer } ~~~ 通过上面的代码可以发现,挪移的关键体现在flatten和unflatten函数上。请看: (1)flatten的分析 flatten的代码如下所示: **GraphicBuffer.cpp** ~~~ status_t GraphicBuffer::flatten(void* buffer,size_t size, int fds[], size_t count) const { //buffer是装载数据的缓冲区,由Parcel提供 ...... if(handle) { buf[6] = handle->numFds; buf[7] = handle->numInts; native_handle_t const* const h = handle; //把handle的信息也写到buffer中 memcpy(fds, h->data, h->numFds*sizeof(int)); memcpy(&buf[8], h->data + h->numFds,h->numInts*sizeof(int)); } returnNO_ERROR; } ~~~ flatten的工作就是把GraphicBuffer的handle变量信息写到Parcel包中。那么接收端如何使用这个包呢?这就是unflatten的工作了。 (2)unflatten分析 unflatten的代码如下所示: **GraphicBuffer.cpp** ~~~ status_t GraphicBuffer::unflatten(void const*buffer, size_t size, int fds[], size_t count) { ...... if(numFds || numInts) { width = buf[1]; height = buf[2]; stride = buf[3]; format = buf[4]; usage = buf[5]; native_handle* h =native_handle_create(numFds, numInts); memcpy(h->data, fds, numFds*sizeof(int)); memcpy(h->data + numFds, &buf[8],numInts*sizeof(int)); handle = h;//根据Parcel包中的数据还原一个handle } else{ width = height = stride = format = usage = 0; handle = NULL; } mOwner= ownHandle; returnNO_ERROR; } ~~~ unflatten最重要的工作是,根据Parcel包中native_handle的信息,在Native Surface端构造一个对等的GraphicBuffer。这样,Native Surface端的GraphicBuffer实际上就和Layer端的GraphicBuffer管理着同一块共享内存。 3. registerBuffer的分析 registerBuffer有什么用呢?上一步调用unflatten后得到了代表共享内存的文件句柄,regiserBuffer的目的就是对它进行内存映射,代码如下所示: **GraphicBufferMapper.cpp** ~~~ status_tsw_gralloc_handle_t::registerBuffer(sw_gralloc_handle_t* hnd) { if (hnd->pid != getpid()) { //原来是做一次内存映射操作 void* base = mmap(0, hnd->size, hnd->prot, MAP_SHARED, hnd->fd,0); ...... //base保存着共享内存的起始地址 hnd->base = intptr_t(base); } returnNO_ERROR; } ~~~ 4. lock和unlock的分析 GraphicBuffer在使用前需要通过lock来得到内存地址,使用完后又会通过unlock释放这块地址。在SW分配方案中,这两个函数实现却非常简单,如下所示: **GraphicBufferMapper.cpp** ~~~ //lock操作 int sw_gralloc_handle_t::lock(sw_gralloc_handle_t*hnd, int usage, int l, int t, int w, int h, void** vaddr) { *vaddr= (void*)hnd->base;//得到共享内存的起始地址,后续作画就使用这块内存了。 returnNO_ERROR; } //unlock操作 status_tsw_gralloc_handle_t::unlock(sw_gralloc_handle_t* hnd) { returnNO_ERROR;//没有任何操作 } ~~~ 对GraphicBuffer的介绍就到这里。虽然采用的是SW方式,但是相信读者也能通过树木领略到森林的风采。从应用层角度看,可以把GraphicBuffer当做一个构架在共享内存之上的数据缓冲。对想深入研究的读者,我建议可按图8-20中的流程来分析。因为流程体现了调用顺序,表达了调用者的意图和目的,只有把握了流程,分析时才不会迷失在茫茫的源码海洋中,才不会被不熟悉的知识阻拦前进的脚步。
';