4. 分配图形缓冲区
前面提到,用户空间的应用程序用到的图形缓冲区是由Gralloc模块中的函数gralloc_alloc来分配的,这个函数实现在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
- static int gralloc_alloc(alloc_device_t* dev,
- int w, int h, int format, int usage,
- buffer_handle_t* pHandle, int* pStride)
- {
- if (!pHandle || !pStride)
- return -EINVAL;
-
- size_t size, stride;
-
- int align = 4;
- int bpp = 0;
- switch (format) {
- case HAL_PIXEL_FORMAT_RGBA_8888:
- case HAL_PIXEL_FORMAT_RGBX_8888:
- case HAL_PIXEL_FORMAT_BGRA_8888:
- bpp = 4;
- break;
- case HAL_PIXEL_FORMAT_RGB_888:
- bpp = 3;
- break;
- case HAL_PIXEL_FORMAT_RGB_565:
- case HAL_PIXEL_FORMAT_RGBA_5551:
- case HAL_PIXEL_FORMAT_RGBA_4444:
- bpp = 2;
- break;
- default:
- return -EINVAL;
- }
- size_t bpr = (w*bpp + (align-1)) & ~(align-1);
- size = bpr * h;
- stride = bpr / bpp;
-
- int err;
- if (usage & GRALLOC_USAGE_HW_FB) {
- err = gralloc_alloc_framebuffer(dev, size, usage, pHandle);
- } else {
- err = gralloc_alloc_buffer(dev, size, usage, pHandle);
- }
-
- if (err < 0) {
- return err;
- }
-
- *pStride = stride;
- return 0;
- }
参数format用来描述要分配的图形缓冲区的颜色格式。当format值等于HAL_PIXEL_FORMAT_RGBA_8888、HAL_PIXEL_FORMAT_RGBX_8888或者HAL_PIXEL_FORMAT_BGRA_8888的时候,一个像素需要使用32位来表示,即4个字节。当format值等于HAL_PIXEL_FORMAT_RGB_888的时候,一个像素需要使用24位来描述,即3个字节。当format值等于HAL_PIXEL_FORMAT_RGB_565、HAL_PIXEL_FORMAT_RGBA_5551或者HAL_PIXEL_FORMAT_RGBA_4444的时候,一个像需要使用16位来描述,即2个字节。最终一个像素需要使用的字节数保存在变量bpp中。
参数w表示要分配的图形缓冲区所保存的图像的宽度,将它乘以bpp,就可以得到保存一行像素所需要使用的字节数。我们需要将这个字节数对齐到4个字节边界,最后得到一行像素所需要的字节数就保存在变量bpr中。
参数h表示要分配的图形缓冲区所保存的图像的高度,将它乘以bpr,就可以得到保存整个图像所需要使用的字节数。
将变量bpr的值除以变量bpp的值,就得到要分配的图形缓冲区一行包含有多少个像素点,这个结果需要保存在输出参数pStride中,以便可以返回给调用者。
参数usage用来描述要分配的图形缓冲区的用途。如果是用来在系统帧缓冲区中渲染的,即参数usage的GRALLOC_USAGE_HW_FB位等于1,那么就必须要系统帧缓冲区中分配,否则的话,就在内存中分配。注意,在内存中分配的图形缓冲区,最终是需要拷贝到系统帧缓冲区去的,以便可以将它所描述的图形渲染出来。
函数gralloc_alloc_framebuffer用来在系统帧缓冲区中分配图形缓冲区,而函数gralloc_alloc_buffer用来在内存在分配图形缓冲区,接下来我们就分别分析这两个函数的实现。
函数gralloc_alloc_framebuffer实现在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
- static int gralloc_alloc_framebuffer(alloc_device_t* dev,
- size_t size, int usage, buffer_handle_t* pHandle)
- {
- private_module_t* m = reinterpret_cast<private_module_t*>(
- dev->common.module);
- pthread_mutex_lock(&m->lock);
- int err = gralloc_alloc_framebuffer_locked(dev, size, usage, pHandle);
- pthread_mutex_unlock(&m->lock);
- return err;
- }
这个函数调用了另外一个函数gralloc_alloc_framebuffer_locked来分配图形缓冲区。
函数gralloc_alloc_framebuffer_locked也是实现在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
- static int gralloc_alloc_framebuffer_locked(alloc_device_t* dev,
- size_t size, int usage, buffer_handle_t* pHandle)
- {
- private_module_t* m = reinterpret_cast<private_module_t*>(
- dev->common.module);
-
- // allocate the framebuffer
- if (m->framebuffer == NULL) {
- // initialize the framebuffer, the framebuffer is mapped once
- // and forever.
- int err = mapFrameBufferLocked(m);
- if (err < 0) {
- return err;
- }
- }
-
- const uint32_t bufferMask = m->bufferMask;
- const uint32_t numBuffers = m->numBuffers;
- const size_t bufferSize = m->finfo.line_length * m->info.yres;
- if (numBuffers == 1) {
- // If we have only one buffer, we never use page-flipping. Instead,
- // we return a regular buffer which will be memcpy'ed to the main
- // screen when post is called.
- int newUsage = (usage & ~GRALLOC_USAGE_HW_FB) | GRALLOC_USAGE_HW_2D;
- return gralloc_alloc_buffer(dev, bufferSize, newUsage, pHandle);
- }
-
- if (bufferMask >= ((1LU<<numBuffers)-1)) {
- // We ran out of buffers.
- return -ENOMEM;
- }
-
- // create a "fake" handles for it
- intptr_t vaddr = intptr_t(m->framebuffer->base);
- private_handle_t* hnd = new private_handle_t(dup(m->framebuffer->fd), size,
- private_handle_t::PRIV_FLAGS_FRAMEBUFFER);
-
- // find a free slot
- for (uint32_t i=0 ; i<numBuffers ; i++) {
- if ((bufferMask & (1LU<<i)) == 0) {
- m->bufferMask |= (1LU<<i);
- break;
- }
- vaddr += bufferSize;
- }
-
- hnd->base = vaddr;
- hnd->offset = vaddr - intptr_t(m->framebuffer->base);
- *pHandle = hnd;
-
- return 0;
- }
在系统帧缓冲区分配图形缓冲区之前,首先要对系统帧缓冲区进行过初始化,即这里的变量m所指向的一个private_module_t结构体的成员变量framebuffer的值不能等于NULL。如果等于NULL的话,那么就必须要调用另外一个函数mapFrameBufferLocked来初始化系统帧缓冲区。初始化系统帧缓冲区的过程可以参考前面第3部分的内容。
变量bufferMask用来描述系统帧缓冲区的使用情况,而变量numBuffers用来描述系统帧缓冲区可以划分为多少个图形缓冲区来使用,另外一个变量bufferSize用来描述设备显示屏一屏内容所占用的内存的大小。
如果系统帧缓冲区只有一个图形缓冲区大小,即变量numBuffers的值等于1,那么这个图形缓冲区就始终用作系统主图形缓冲区来使用。在这种情况下,我们就不能够在系统帧缓冲区中分配图形缓冲区来给用户空间的应用程序使用,因此,这时候就会转向内存中来分配图形缓冲区,即调用函数gralloc_alloc_buffer来分配图形缓冲区。注意,这时候分配的图形缓冲区的大小为一屏内容的大小,即bufferSize。
如果bufferMask的值大于等于((1LU<<numBuffers)-1)的值,那么就说明系统帧缓冲区中的图形缓冲区全部都分配出去了,这时候分配图形缓冲区就失败了。例如,假设图形缓冲区的个数为2,那么((1LU<<numBuffers)-1)的值就等于3,即二制制0x11。如果这时候bufferMask的值也等于0x11,那么就表示第一个和第二个图形缓冲区都已经分配出去了。因此,这时候就不能再在系统帧缓冲区中分配图形缓冲区。
假设此时系统帧缓冲区中尚有空闲的图形缓冲区的,接下来函数就会创建一个private_handle_t结构体hnd来描述这个即将要分配出去的图形缓冲区。注意,这个图形缓冲区的标志值等于PRIV_FLAGS_FRAMEBUFFER,即表示这是一块在系统帧缓冲区中分配的图形缓冲区。
接下来的for循环从低位到高位检查变量bufferMask的值,并且找到第一个值等于0的位,这样就可以知道在系统帧缓冲区中,第几个图形缓冲区的是空闲的。注意,变量vadrr的值开始的时候指向系统帧缓冲区的基地址,在下面的for循环中,每循环一次它的值都会增加bufferSize。从这里就可以看出,每次从系统帧缓冲区中分配出去的图形缓冲区的大小都是刚好等于显示屏一屏内容大小的。
最后分配出去的图形缓冲区的开始地址就保存在前面所创建的private_handle_t结构体hnd的成员变量base中,这样,用户空间的应用程序就可以直接将要渲染的图形内容拷贝到这个地址上去,这就相当于是直接将图形渲染到系统帧缓冲区中去。
在将private_handle_t结构体hnd返回给调用者之前,还需要设置它的成员变量offset,以便可以知道它所描述的图形缓冲区的起始地址相对于系统帧缓冲区的基地址的偏移量。
至此,在系统帧缓冲区中分配图形缓冲区的过程就分析完成了,接下来我们再分析在内存在分析图形缓冲区的过程,即分析函数gralloc_alloc_buffer的实现。
函数gralloc_alloc_buffer也是实现在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
- static int gralloc_alloc_buffer(alloc_device_t* dev,
- size_t size, int usage, buffer_handle_t* pHandle)
- {
- int err = 0;
- int fd = -1;
-
- size = roundUpToPageSize(size);
-
- fd = ashmem_create_region("gralloc-buffer", size);
- if (fd < 0) {
- LOGE("couldn't create ashmem (%s)", strerror(-errno));
- err = -errno;
- }
-
- if (err == 0) {
- private_handle_t* hnd = new private_handle_t(fd, size, 0);
- gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
- dev->common.module);
- err = mapBuffer(module, hnd);
- if (err == 0) {
- *pHandle = hnd;
- }
- }
-
- LOGE_IF(err, "gralloc failed err=%s", strerror(-err));
-
- return err;
- }
这个函数的实现很简单,它首先调用函数ashmem_create_region来创建一块匿名共享内存,接着再在这块匿名共享内存上分配一个图形缓冲区。注意,这个图形缓冲区也是使用一个private_handle_t结构体来描述的,不过这个图形缓冲区的标志值等于0,以区别于在系统帧缓冲区中分配的图形缓冲区。匿名共享内存的相关知识,可以参考前面 一文,以及 这篇文章。
从匿名共享内存中分配的图形缓冲区还需要映射到进程的地址空间来,然后才可以使用,这是通过调用函数mapBuffer来实现的。
函数mapBuffer实现在文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
- int mapBuffer(gralloc_module_t const* module,
- private_handle_t* hnd)
- {
- void* vaddr;
- return gralloc_map(module, hnd, &vaddr);
- }
它通过调用另外一个函数gralloc_map来将参数hnd所描述的一个图形缓冲区映射到当前进程的地址空间来。后面在分析图形缓冲区的注册过程时,我们再分析函数gralloc_map的实现。
注意,在Android系统中,在系统帧缓冲区中分配的图形缓冲区是在SurfaceFlinger服务中使用的,而在内存中分配的图形缓冲区既可以在SurfaceFlinger服务中使用,也可以在其它的应用程序中使用。当其它的应用程序需要使用图形缓冲区的时候,它们就会请求SurfaceFlinger服务为它们分配,因此,对于其它的应用程序来说,它们只需要将SurfaceFlinger服务返回来的图形缓冲区映射到自己的进程地址空间来使用就可以了,这就是后面我们所要分析的图形缓冲区的注册过程。
至此,图形缓冲区的分配过程就分析完成了,接下来我们继续分析图形缓冲区的释放过程。