Android 图形系统(8)---- Bufferqueue

BufferQueue

BufferQueue要解决的是生产者和消费者的同步问题,应用程序产生画面,SurfaceFlinger 消费画面;SurfaceFlinger 生成画面而HWC service 消费画面;用来存储这些画面的区域我们称为缓冲区,为此需要按照下面需求设计:

  1. 需要有缓冲区供生产者消费者使用
  2. 生产者生产完成需要能够通知到消费者
  3. 生产者一般为app,消费者是 sf,因此需要拷贝跨进程通信的问题

下面我们以应用程序为生产者,SurfaceFlinger 为消费者为例,了解一下BufferQueue 的内部设计

BufferState 的切换

在BufferQueue 的设计中,Buffer 存在下面几种状态:
FREE:表示该Buffer 是空闲的,可以给到应用程序,由应用程序来绘图
DEQUEUED:表示Buffer 的控制权已经交给了应用程序侧,这个状态下应用程序可以在上面绘图了
QUEUED:表示该Buffer已经由应用程序绘图完成,Buffer 的控制权已经回到SurfaceFlinger 了
ACQUIRED:表示该Buffer 已经交给 HWC service去合成了,这个时候控制权已经给到HWC service了
Buffer 的初始状态是Free

  1. 当生产者通过dequeueBuffer 来申请Buffer 成功时,buffer 的状态变为 QEQUEUED
  2. 应用程序绘制完成后通过queueBuffer 把BufferState 状态改为 QUEUED 状态
  3. SurfaceFlinger 通过 acquiredBuffer 把 Buffer 拿去给 HWC service合成,这时的Buffer 变为 ACQUIRED 状态
  4. 合成完成后通过 release buffer 把 Buffer 状态改为FREE状态,
    状态切换图如下图所示:


    BBQ_base.jpg

从时间轴上一个Buffer 的变化过程是:
FREE->DEQUEUED->QUEUED->ACQUIRED->FREE

BBQ_timeline.jpg

BufferSlot

每一个应用程序图层在SurfaceFlinger 里称为一个Layer,每个Layer都有一个独特的BufferQueue,每个 BufferQueue都会有多个Buffer,目前Android 系统上单图层最多支持 64个Buffer

BufferSlot 的定义如下:
framework/native/libs/gui/include/gui/BufferQueueDefs.h

namespace android {
    class BufferQueueCore;

    namespace BufferQueueDefs {
        typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
    } // namespace BufferQueueDefs
} // namespace android

SlotsType 被 typedef 定义成了数组类型,数组元素是BufferSlot,数组的大小为NUM_BUFFER_SLOTS(64)
BufferSlot 是对GraphicBuffer 的封装,重要成员如下:

struct BufferSlot {
.....
    BufferState mBufferState;  //当前的 Buffer 状态,FREE/DEQUEUED/QUEUED/ACQUIRED
    sp<GraphicBuffer> mGraphicBuffer; // 代表了Buffer 存储空间
    uint64_t mFrameNumber; //表示这个slot被queued的编号,在应用调dequeueBuffer申请slot时会参考该值
    sp<Fence> mFence;
}

64 个 BufferSlot 可以分为两部分,usedslot(使用中的) 和 unusedlot(未使用中的)
usedslot = active slot + unactive slot
而usedslot又可以分为 active slot 和 unactive slot,处在 DEQUEUED,QUEUED,ACQUIRED状态的被称为active slot,剩下FREE状态的称为unactive Slots;
所有activeSlots都是有人正在使用中的 slot,使用者可以是生产者也可以是消费者

unactive slot = Free state slot = mFreeBuffers + mFreeSlots
而Free 状态的slot 根据是否为其分配内存可以分为mFreeBuffers 和 mFreeSlots

  • mFreeBuffers:已经分配过内存的
  • mFreeSlots:没有分配过内存的
    如果从代码中看到mFreeSlots中拿出一个 Bufferslot 那说明这个Bufferslot 还没有配置过GraphicBuffer的,这个slot 可能第一次用到

Buffer 的分配流程

framework/native/libs/gui/Surface.cpp

  1. 应用第一次dequeueBuffer前会通过connect接口和SurfaceFlinger 连接
int Surface::connect(
    int api, const sp<IProducerListener>& listener, bool reportBufferRemoval) {
    ATRACE_CALL();
    ...
    int err = mGraphicBufferProducer->connect(listener, api, mProducerControlledByApp, &output);

}
  1. 调用 dequeueBuffer时会先调用requestBuffer
    result 标志第一次会带 BUFFER_NEEDS_REALLOCATION 标志
int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    ATRACE_CALL();//这里可以在systrace中看到
    ......
    //这里尝试去dequeueBuffer,因为这时SurfaceFlinger对应Layer的slot还没有分配buffer,这时SurfaceFlinger会回复的flag会有BUFFER_NEEDS_REALLOCATION
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight, 
                                       reqFormat, reqUsage, &mBufferAge, 
                                        enableFrameTimestamps?&frameTimestamps:nullptr);
    ......
    if((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == nullptr) {
        ......
        //这里检查到dequeueBuffer返回的结果里带有BUFFER_NEEDS_REALLOCATION标志就会发出一次requestBuffer
        result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
        ......
    }
    ......
}
  1. 因为应用侧 Surface 对象中 包含的是 GraphicBufferProducer 的 binder client端对象,所以实际的调用是在
    Surfaceflinger 进程的 server 端 BufferQueueProducer.cpp,注意dequeueBuffer 实际返回的是一个空间 slot
status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,
                                            uint32_t width, uint32_t height, PixelFormat format,
                                            uint64_t usage, uint64_t* outBufferAge,
                                            FrameEventHistoryDelta* outTimestamps) {
    if ((buffer == NULL) ||
        buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage))//检查是否已分配了GraphicBuffer
    {
        ......
        returnFlags |= BUFFER_NEEDS_REALLOCATION;//发现需要分配buffer,置个标记
    }
    ......
    if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
        ......
        //新创建一个新的GraphicBuffer给到对应的slot
        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
               width, height, format, BQ_LAYER_COUNT, usage,
               {mConsumerName.string(), mConsumerName.size()});
        ......
               mSlots[*outSlot].mGraphicBuffer = graphicBuffer;//把GraphicBuffer给到对应的slot
        ......
    }
    ......
    return returnFlags;//注意在应用第一次请求buffer, dequeueBuffer返回时对应的GraphicBuffer已经创建完成并给到了对应的slot上,但返回给应用的flags里还是带有BUFFER_NEEDS_REALLOCATION标记的
}

根据 client 端的调用,会继续调用到requestBuffer

status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
    ATRACE_CALL();

    mSlots[slot].mRequestBufferCalled = true;
    *buf = mSlots[slot].mGraphicBuffer;
    return NO_ERROR;
}

tips: 为什么不在 dequeueBuffer时,直接返回 GraphicBuffer 对象,而是返回一个空闲的 slot,再调用requestBuffer去获得一个 GraphicBuffer?
因为这个接口调用太频繁了,比如在90FPS的设备上,一秒钟该接口要执行90次,太频繁了,而且这个信息只需要传递一次就可以了,如果每次这个接口都要带上GraphicBuffer的信息,传输了很多冗余数据,所以不如加入一个新的api(requestBuffer)来完成GraphicBuffer传递的事情

GraphicBuffer 继承了 Parcel 对象,可以通过Binder跨进程传递

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容