Android SurfaceFlinger导读(08)理解Gralloc1 FrameBuffer

该系列文章总纲链接:Android GUI系统之SurfaceFlinger 系列文章目录


本章关键点总结 & 说明:

本章节思维导图如上。主要讲述了FrameBuffer的概念,这里 主要关注 FrameBuffer的基本概念、实现原理以及 PageFlipping/双缓冲技术。


1 FrameBuffer说明

FrameBuffer的中文名叫帧缓冲,它实际上包括两个不同的方面:

  1. Frame:帧,就是指一幅图像。在屏幕上看到的那幅图像就是一帧。
  2. Buffer:缓冲,就是一段存储区域,可这个区域存储的是帧。

FrameBuffer 的概念很清晰,它就是一个存储图形/图像帧数据的缓冲。FrameBuffer(简称FB)是Linux系统中的一个虚拟设备,设备文件对应为/dev/fb%d(比如/dev/fb0)。这个虚拟设备将不同硬件厂商实现的真实设备统一在一个框架下,这样应用层就可以通过标准的接口进行图形/图像的输入和输出了。以下展示了FBD示意图:

从上图中可以看出,应用层通过标准的ioctl或mmap等系统调用,就可以操作显示设备,用起来非常方便。FrameBuffer中的Buffer,就是通过mmap把设备中的显存映射到用户空间的,在这块缓冲上写数据,就相当于在屏幕上绘画。

注意:上面所说的框架将引出另外一个概念Linux FrameBuffer(简称LFB)。LFB是Linux平台提供的一种可直接操作FB的机制,依托这个机制,应用层通过标准的系统调用,就可以操作显示设备了。

下面是在DDMS工具中实现屏幕截图功能的demo(framwork代码中直接检索framebuffer_service.c即可),如下所示:

struct fbinfo {//定义一个结构体
    unsigned int version;
    unsigned int bpp;
    unsigned int size;
    unsigned int width;
    unsigned int height;
    unsigned int red_offset;
    unsigned int red_length;
    unsigned int blue_offset;
    unsigned int blue_length;
    unsigned int green_offset;
    unsigned int green_length;
    unsigned int alpha_offset;
    unsigned int alpha_length;
} __attribute__((packed));

//fd是一个文件的描述符,这个函数的目的,是把当前屏幕的内容写到一个文件中
void framebuffer_service(int fd, void *cookie)
{
    struct fbinfo fbinfo;
    unsigned int i, bsize;
    char buf[640];
    int fd_screencap;
    int w, h, f;
    int fds[2];
    //管道通信机制
    if (pipe2(fds, O_CLOEXEC) < 0) goto pipefail;

    pid_t pid = fork();
    if (pid < 0) goto done;

    if (pid == 0) {//子进程
        dup2(fds[1], STDOUT_FILENO);
        close(fds[0]);
        close(fds[1]);
        const char* command = "screencap";
        const char *args[2] = {command, NULL};
        execvp(command, (char**)args);
        exit(1);
    }
    //pid>0,父进程
    fd_screencap = fds[0];

    /* read w, h & format */
    if(readx(fd_screencap, &w, 4)) goto done;
    if(readx(fd_screencap, &h, 4)) goto done;
    if(readx(fd_screencap, &f, 4)) goto done;
    //根据屏幕的属性填充fbinfo结构,这个结构最后要写到输出文件的头部
    fbinfo.version = DDMS_RAWIMAGE_VERSION;
    /* see hardware/hardware.h */
    /*
     下面几个变量和颜色格式有关,以RGB565为例,简单介绍一下。
     RGB565表示一个像素点中R分量为5位,G分量为6位,B分量为5位,并且没有Alpha分量。
     这样一个像素点的大小为16位,占两个字节,比RGB888格式的一个像素少一个字节(它一个像素是三个字节)。
     x_length的值为x分量的位数,例如,RGB565中R分量就是5位。
     x_offset的值代表x分量在内存中的位置。如RGB565一个像素占两个字节,那么x_offeset
     表示x分量在这两个字节内存区域中的起始位置,但这个顺序是反的,也就是B分量在前,
     R在最后。所以red_offset的值就是11,而blue_offset的值是0,green_offset的值是6。
     这些信息在做格式转换时(例如从RGB565转到RGB888的时候)有用。
    */
    switch (f) {
        case 1: /* RGBA_8888 */
            fbinfo.bpp = 32;
            fbinfo.size = w * h * 4;
            fbinfo.width = w;
            fbinfo.height = h;
            fbinfo.red_offset = 0;
            fbinfo.red_length = 8;
            fbinfo.green_offset = 8;
            fbinfo.green_length = 8;
            fbinfo.blue_offset = 16;
            fbinfo.blue_length = 8;
            fbinfo.alpha_offset = 24;
            fbinfo.alpha_length = 8;
            break;
        case 2: /* RGBX_8888 */
            fbinfo.bpp = 32;
            fbinfo.size = w * h * 4;
            fbinfo.width = w;
            fbinfo.height = h;
            fbinfo.red_offset = 0;
            fbinfo.red_length = 8;
            fbinfo.green_offset = 8;
            fbinfo.green_length = 8;
            fbinfo.blue_offset = 16;
            fbinfo.blue_length = 8;
            fbinfo.alpha_offset = 24;
            fbinfo.alpha_length = 0;
            break;
        case 3: /* RGB_888 */
            fbinfo.bpp = 24;
            fbinfo.size = w * h * 3;
            fbinfo.width = w;
            fbinfo.height = h;
            fbinfo.red_offset = 0;
            fbinfo.red_length = 8;
            fbinfo.green_offset = 8;
            fbinfo.green_length = 8;
            fbinfo.blue_offset = 16;
            fbinfo.blue_length = 8;
            fbinfo.alpha_offset = 24;
            fbinfo.alpha_length = 0;
            break;
        case 4: /* RGB_565 */
            fbinfo.bpp = 16;
            fbinfo.size = w * h * 2;
            fbinfo.width = w;
            fbinfo.height = h;
            fbinfo.red_offset = 11;
            fbinfo.red_length = 5;
            fbinfo.green_offset = 5;
            fbinfo.green_length = 6;
            fbinfo.blue_offset = 0;
            fbinfo.blue_length = 5;
            fbinfo.alpha_offset = 0;
            fbinfo.alpha_length = 0;
            break;
        case 5: /* BGRA_8888 */
            fbinfo.bpp = 32;
            fbinfo.size = w * h * 4;
            fbinfo.width = w;
            fbinfo.height = h;
            fbinfo.red_offset = 16;
            fbinfo.red_length = 8;
            fbinfo.green_offset = 8;
            fbinfo.green_length = 8;
            fbinfo.blue_offset = 0;
            fbinfo.blue_length = 8;
            fbinfo.alpha_offset = 24;
            fbinfo.alpha_length = 8;
           break;
        default:
            goto done;
    }

    //将fb信息写到文件头部
    if(writex(fd, &fbinfo, sizeof(fbinfo))) goto done;

    //写入数据到文件中
    for(i = 0; i < fbinfo.size; i += bsize) {
      bsize = sizeof(buf);
      if (i + bsize > fbinfo.size)
        bsize = fbinfo.size - i;
      if(readx(fd_screencap, buf, bsize)) goto done;//读取FBD中的数据
      if(writex(fd, buf, bsize)) goto done;//将数据写到文件
    }

done:
    TEMP_FAILURE_RETRY(waitpid(pid, NULL, 0));

    close(fds[0]);
    close(fds[1]);
pipefail:
    close(fd);
}

该函数的目的就是截屏,这个例子可加深我们对FB的直观感受。注意:我们可根据这段代码,写一个简单的Native可执行程序,然后adb push到设备上运行。

2 PageFlipping/双缓冲技术简介

在FrameBuffer的实现原理中会涉及 PageFlipping机制,因此这里做个简单的说明。图形/图像 数据是一帧帧 有边界的。所以在图形/图像数据的生产/消费过程中,人们使用了一种叫PageFlipping的技术。中文名叫画面交换(“双缓冲”技术),操作过程如下:

  1. 分配一个能容纳两帧数据的缓冲,前面一个缓冲叫FrontBuffer,后面一个缓冲叫BackBuffer。
  2. 消费者使用FrontBuffer中的旧数据,而生产者用新数据填充BackBuffer,二者互不干扰。
  3. 当需要更新显示时,BackBuffer变成FrontBuffer,FrontBuffer变成BackBuffer。
  4. 如此循环,这样就总能显示最新的内容了。

这个过程很像我们平常的翻书动作,所以它被形象地称为PageFlipping。PageFlipping其实就是使用了一个只有两个成员的帧缓冲队列,后面在分析数据传输的时候还会见到诸如dequeue和queue的操作。

3  FrameBuffer的实现原理

在HWComposer的分析中,loadFbHalModule是加载了管理framework的硬件模块,它的代码如下:

#define GRALLOC_HARDWARE_MODULE_ID "gralloc" //对应ID装载的是Gralloc模块
...
int HWComposer::loadFbHalModule()
{
    hw_module_t const* module;

    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
    if (err != 0) {
        ALOGE("%s module not found", GRALLOC_HARDWARE_MODULE_ID);
        return err;
    }
    return framebuffer_open(module, &mFbDev);
}

继续分析framebuffer_open的实现,代码如下:

#define GRALLOC_HARDWARE_FB0 "fb0"
static inline int framebuffer_open(const struct hw_module_t* module,struct framebuffer_device_t** device) {
    //调用Gralloc模块的open函数
    return module->methods->open(module, GRALLOC_HARDWARE_FB0, (struct hw_device_t**)device);
}

一般来讲Gralloc模块在实际设备中是由硬件厂商提供的,以便于最佳地配合手机硬件。 (研究FB的目的:更好理解图形显示的原理,而不是具体硬件的实现) 缺省实现的研究(仅实现open):实现的目录在hardware/libhardware/modules/gralloc中,open的实现如下:

int gralloc_device_open(const hw_module_t* module, const char* name, hw_device_t** device)
{
    int status = -EINVAL;
    if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {//这里传递的是GRALLOC_HARDWARE_FB0,
        //...走else分支,忽略此处GRALLOC操作
    } else {
        status = fb_device_open(module, name, device);
    }
    return status;
}

继续分析fb_device_open,代码如下:

int fb_device_open(hw_module_t const* module, const char* name,
        hw_device_t** device)
{
    int status = -EINVAL;
    if (!strcmp(name, GRALLOC_HARDWARE_FB0)) {
        fb_context_t *dev = (fb_context_t*)malloc(sizeof(*dev));//创建fb_context_t结构
        memset(dev, 0, sizeof(*dev));

        //初始化dev结构体成员和设置FB的操作函数
        dev->device.common.tag = HARDWARE_DEVICE_TAG;
        dev->device.common.version = 0;
        dev->device.common.module = const_cast<hw_module_t*>(module);
        dev->device.common.close = fb_close;//关键点2
        dev->device.setSwapInterval = fb_setSwapInterval;
        dev->device.post            = fb_post;
        dev->device.setUpdateRect = 0;

        private_module_t* m = (private_module_t*)module;
        status = mapFrameBuffer(m);//打开并读取设备的信息,关键点1
        if (status >= 0) {
            //获取从底层读取的设备参数,继续填充dev结构体
            int stride = m->finfo.line_length / (m->info.bits_per_pixel >> 3);
            int format = (m->info.bits_per_pixel == 32)
                         ? (m->info.red.offset ? HAL_PIXEL_FORMAT_BGRA_8888 : HAL_PIXEL_FORMAT_RGBX_8888)
                         : HAL_PIXEL_FORMAT_RGB_565;
            const_cast<uint32_t&>(dev->device.flags) = 0;
            const_cast<uint32_t&>(dev->device.width) = m->info.xres;
            const_cast<uint32_t&>(dev->device.height) = m->info.yres;
            const_cast<int&>(dev->device.stride) = stride;
            const_cast<int&>(dev->device.format) = format;
            const_cast<float&>(dev->device.xdpi) = m->xdpi;
            const_cast<float&>(dev->device.ydpi) = m->ydpi;
            const_cast<float&>(dev->device.fps) = m->fps;
            const_cast<int&>(dev->device.minSwapInterval) = 1;
            const_cast<int&>(dev->device.maxSwapInterval) = 1;
            *device = &dev->device.common;
        }
    }
    return status;
}

    @1 开始分析关键点1,mapFrameBuffer的代码如下:

static int mapFrameBuffer(struct private_module_t* module)
{
    pthread_mutex_lock(&module->lock);
    int err = mapFrameBufferLocked(module);
    pthread_mutex_unlock(&module->lock);
    return err;
}

        继续分析mapFrameBufferLocked,代码如下:

int mapFrameBufferLocked(struct private_module_t* module)
{
    if (module->framebuffer) {//已经打开,直接返回
        return 0;
    }
        
    char const * const device_template[] = {//设备列表,打开一个就可以
            "/dev/graphics/fb%u",
            "/dev/fb%u",
            0 };
    int fd = -1;
    int i=0;
    char name[64];
    while ((fd==-1) && device_template[i]) {
        snprintf(name, 64, device_template[i], 0);
        fd = open(name, O_RDWR, 0);//打开设备
        i++;
    }
    ...//获取设备的各种信息falgs、info、xdpi、ydpi、fps
    if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1)
        return -errno;
    if (finfo.smem_len <= 0)
        return -errno;
    //把得到的设备信息放到module中
    module->flags = flags;
    module->info = info;
    module->finfo = finfo;
    module->xdpi = xdpi;//x方向dpi
    module->ydpi = ydpi;//y方向dpi
    module->fps = fps;//刷新率
    int err;
    size_t fbSize = roundUpToPageSize(finfo.line_length * info.yres_virtual);//字节对齐,保证fbSize是4的倍数
    module->framebuffer = new private_handle_t(dup(fd), fbSize, 0);
    module->numBuffers = info.yres_virtual / info.yres;//buffer的数量
    module->bufferMask = 0;
    //通过mmap分配内存,让显示器输出图像,只需要把数据复制到对应内存vaddr即可
    void* vaddr = mmap(0, fbSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (vaddr == MAP_FAILED) {
        ALOGE("Error mapping the framebuffer (%s)", strerror(errno));
        return -errno;
    }
    module->framebuffer->base = intptr_t(vaddr);//module结构体framebuffer->base指向mmap的首地址
    memset(vaddr, 0, fbSize);
    return 0;
}

接下来,如果想让屏幕输出图像,只需要把数据复制到mmap分配的内存中即可。

@2 继续分析关键点2,fb_post(设置FB的函数)的实现,代码如下:

static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer)
{
    if (private_handle_t::validate(buffer) < 0)
        return -EINVAL;
    fb_context_t* ctx = (fb_context_t*)dev;
    private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(buffer);
    private_module_t* m = reinterpret_cast<private_module_t*>(dev->common.module);
    if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {//通过PRIV_FLAGS_FRAMEBUFFER判断是否支持多缓冲区
        //如果FB支持Flip,调用ioctl
        const size_t offset = hnd->base - m->framebuffer->base;
        m->info.activate = FB_ACTIVATE_VBL;
        m->info.yoffset = offset / m->finfo.line_length;//计算y偏移量
        //使用FBIOPUT_VSCREENINFO参数来设置当前显示的Buffer,
        if (ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info) == -1) {
            ALOGE("FBIOPUT_VSCREENINFO failed");
            m->base.unlock(&m->base, buffer); 
            return -errno;
        }
        m->currentBuffer = buffer;  //将显示区域指向FB中新的数据帧
    } else {
        //如果不支持Flip,直接复制数据到FB中
        void* fb_vaddr;
        void* buffer_vaddr;
        
        //对FB上锁
        m->base.lock(&m->base, m->framebuffer, GRALLOC_USAGE_SW_WRITE_RARELY, 0, 0, m->info.xres, m->info.yres, &fb_vaddr);
        //对Buffer上锁
        m->base.lock(&m->base, buffer, GRALLOC_USAGE_SW_READ_RARELY, 0, 0, m->info.xres, m->info.yres,&buffer_vaddr);
        memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres); //将Buffer中数据复制到FB中
        //对Buffer解锁 
        m->base.unlock(&m->base, buffer); 
        //对FBr解锁
        m->base.unlock(&m->base, m->framebuffer); 
    }    
    return 0;
}

通过Flip方式切换缓冲区不存在加重闪烁感的问题,但单缓冲存在该问题。同时,支持Flip的设备在分配内存时应该分配至少2倍于屏幕大小的内存,计算内存大小的公式:

  • finfo.line_length(一行大小)*info.yres_virtual(虚拟屏幕高度)

真实屏幕高度的大小为info.yres,因此如果设备支持Flip,返回的yres_virtual至是yres的2倍,甚至更多。

最后总结下:post操作就是 渲染缓冲区的一个过程。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值