BufferQueue 对象交接与同步

BufferQueue 位于图形内容的生产者和消费者之间。应用、相机或解码器不断提交画面,SurfaceFlinger、ImageReader 等消费者再按各自的节奏取走它们。

要看清这个周转过程,可以盯住一个 Slot:Producer 如何连接、取得和提交它,Consumer 如何取得并归还它,以及 release 之后哪些对象会留到下一轮。

以下以 Android 15 为参考版本;正文中的源码链接固定到同一版本的 AOSP 实现。

一帧在 BufferQueue 中由四类生命周期不同的记录共同完成交接:BufferSlot 保留长期索引和当前资源绑定,GraphicBuffer 表示本地可管理的图形资源包装,BufferItem 记录一次提交的快照,Fence 约束前后所有者何时能安全访问。四个接口改变这些记录的状态、集合归属和同步依赖。

正文沿一个 Slot 的生命周期,跟踪 Producer 的 connect()、dequeueBuffer()、requestBuffer()、queueBuffer()、cancelBuffer(),再进入 Consumer 的 acquireBuffer()、releaseBuffer()。Shared Buffer Mode、批量接口、attach/detach、Sideband Stream、可丢弃 Item 的替换路径、HWC present Fence 和驱动内部实现不在本篇展开;容量、背压与跨进程 import 分别由同组另外两篇文章承担。

一帧走完,Item 离开队列,Slot 始终留在 mSlots

下面跟踪一个已经绑定 GraphicBuffer A、本次规格不变的 BufferSlot i。首次分配和规格变化时的重新分配留到后文说明。

sequenceDiagram participant P as Producer participant BQP as BufferQueueProducer participant Core as BufferQueueCore participant BQC as BufferQueueConsumer participant C as Consumer Note over Core: mSlots[i]:FREE<br/>位于 mFreeBuffers<br/>绑定 GraphicBuffer A P->>BQP: dequeueBuffer() BQP->>Core: mFreeBuffers → mActiveBuffers<br/>Slot:FREE → DEQUEUED BQP-->>P: slot i + release Fence P->>BQP: queueBuffer(acquire Fence, 帧信息) BQP->>Core: Slot:DEQUEUED → QUEUED<br/>创建 BufferItem → mQueue C->>BQC: acquireBuffer() BQC->>Core: Item 离开 mQueue<br/>Slot:QUEUED → ACQUIRED BQC-->>C: BufferItem + acquire Fence C->>BQC: releaseBuffer(release Fence) BQC->>Core: mActiveBuffers → mFreeBuffers<br/>Slot:ACQUIRED → FREE Note over Core: mSlots[i] 与 GraphicBuffer A 保留<br/>本次 Item 不再位于 mQueue

BufferSlot i 始终是 BufferQueueCore::mSlots[i] 中的对象,不随接口调用进入 Producer、Consumer 或 mQueue。四个接口改变 Slot 的状态、集合归属和 Fence 去向;queueBuffer() 创建的 BufferItem 才会进入 mQueue。

同一次交接包含四个维度:

接口 Slot 状态 集合归属 BufferItem Fence 交接
dequeueBuffer() FREE → DEQUEUED mFreeBuffers → mActiveBuffers 无 Slot 中的 release Fence 返回 Producer
queueBuffer() DEQUEUED → QUEUED 保持在 mActiveBuffers 创建后进入 mQueue Producer 的 acquire Fence 写入 Slot 和 Item
acquireBuffer() QUEUED → ACQUIRED 保持在 mActiveBuffers 离开 Core 的 mQueue,作为输出交给 Consumer acquire Fence 随 Item 交给 Consumer
releaseBuffer() ACQUIRED → FREE mActiveBuffers → mFreeBuffers 本次提交不再产生新 Item Consumer 的 release Fence 写回 Slot

BufferItem::mSlot 把本次提交指向 mSlots[i],BufferSlot::mGraphicBuffer 保存 Core 对这个 Slot 的当前 Buffer 绑定。Consumer 尚未缓存这组 Slot/Buffer 映射时,Item 还会携带 mGraphicBuffer;映射仍然有效时,后续 Item 只需要携带 Slot 索引和帧级信息。

共享 Core 的是实现对象,不一定是两端调用者

BufferQueue.cpp 中的 createBufferQueue() 先创建一个 BufferQueueCore,再把它传给 BufferQueueProducer 和 BufferQueueConsumer。省略空指针检查后,创建关系只剩下面几行:

cpp 复制代码
sp<BufferQueueCore> core(new BufferQueueCore());

sp<IGraphicBufferProducer> producer(
        new BufferQueueProducer(core, consumerIsSurfaceFlinger));
sp<IGraphicBufferConsumer> consumer(
        new BufferQueueConsumer(core));

*outProducer = producer;
*outConsumer = consumer;

共享同一个 Core 的是 BufferQueue 实现侧的 BufferQueueProducer 与 BufferQueueConsumer。外部 Producer 和 Consumer 面对 IGraphicBufferProducer、IGraphicBufferConsumer 接口;调用可以发生在同一进程,也可以通过 Binder 到达实现对象。两端调用者不需要直接持有同一个 BufferQueueCore C++ 对象。

flowchart TB P[Producer 调用端<br/>Surface / EGL / Camera / Codec] CON[Consumer 调用端<br/>SurfaceFlinger / ImageReader / Codec] subgraph IMPL[BufferQueue 实现侧] BQP[BufferQueueProducer] BQC[BufferQueueConsumer] C[BufferQueueCore] S[mSlots<br/>BufferSlot 池] Q[mQueue<br/>BufferItem FIFO] BQP --> C BQC --> C C --> S C --> Q end P -->|IGraphicBufferProducer<br/>本地调用或 Binder| BQP CON -->|IGraphicBufferConsumer<br/>本地调用或 Binder| BQC classDef endpoint fill:#F1F5F3,stroke:#7F988F,color:#142B25,stroke-width:1px; classDef adapter fill:#DDF7E8,stroke:#0F7A4D,color:#103B2D,stroke-width:1px; classDef core fill:#0F7A4D,stroke:#0B5637,color:#FFFFFF,stroke-width:2px; classDef storage fill:#FFFFFF,stroke:#0F7A4D,color:#142B25,stroke-width:1px; class P,CON endpoint; class BQP,BQC adapter; class C core; class S,Q storage; linkStyle default stroke:#29463E,stroke-width:1.5px;

connect:先确定谁能按哪种 API 使用这条队列

Producer 真正开始申请 Slot 前,必须先经 IGraphicBufferProducer::connect() 建立契约。固定版本的 BufferQueueProducer::connect() 会拒绝已 abandon、尚无 Consumer 或已经连接 Producer API 的队列;并且只接受 EGL、CPU、MEDIA、CAMERA 四类 API。连接成功后,Core 记录当前 API,并返回默认尺寸、transform hint、待处理帧数、下一帧号和最大 Buffer 数等输出。

dequeueBuffer() 发生在已经确定 Producer API 和 Consumer 端点的单 BufferQueue 契约中。调用方可以是 Java Surface、native Surface/ANativeWindow、EGL、Camera 或 Codec;它们都是相对于当前队列的 Producer 入口。

Core 同时维护 Slot 池、提交队列和 Slot 集合

BufferQueueCore.h 中与对象周转直接相关的成员如下,片段省略连接、分配策略和监听器等字段:

cpp 复制代码
class BufferQueueCore : public virtual RefBase {
    // ...
    mutable std::mutex mMutex;
    BufferQueueDefs::SlotsType mSlots;
    Fifo mQueue;
    std::set<int> mFreeSlots;
    std::list<int> mFreeBuffers;
    std::list<int> mUnusedSlots;
    std::set<int> mActiveBuffers;
    mutable std::condition_variable mDequeueCondition;
};

mSlots 是固定的 Slot 数组,其他集合保存的是索引。排除这些特殊模式后,同一个索引在同一时刻只会属于下面一种集合:

集合 Slot 是否参与当前缓冲池 是否绑定 GraphicBuffer 普通路径中的含义
mUnusedSlots 否 否 超出当前参与数量的 Slot 元数据
mFreeSlots 是 否 可供分配新 Buffer 的 FREE Slot
mFreeBuffers 是 是 已有可复用 Buffer 的 FREE Slot
mActiveBuffers 是 通常是;为新 Buffer 预留 Slot 后、分配完成前可以短暂为空 正处于 DEQUEUED、QUEUED 或 ACQUIRED 的 Slot

mQueue 不在这张 Slot 集合表中,因为它保存的是 BufferItem,不是 BufferSlot。一个 QUEUED Slot 的索引仍在 mActiveBuffers,同时有一条指向它的 Item 位于 mQueue。

BufferQueueProducer::waitForFreeSlotThenRelock() 选择 Slot 时优先复用 mFreeBuffers 中已经绑定 Buffer 的位置;没有可复用 Buffer 且允许分配时,才从 mFreeSlots 取得空位置:

cpp 复制代码
int slot = getFreeBufferLocked();
if (slot != BufferQueueCore::INVALID_BUFFER_SLOT) {
    *found = slot;
} else if (mCore->mAllowAllocation) {
    *found = getFreeSlotLocked();
}

// 省略 Slot 属性检查与 Shared Buffer Mode 分支
mCore->mActiveBuffers.insert(found);

releaseBuffer() 走相反方向。Consumer 释放 Slot 后,Core 从 mActiveBuffers 删除索引,再把它放入 mFreeBuffers:

cpp 复制代码
mSlots[slot].mFence = releaseFence;
mSlots[slot].mBufferState.release();

// 排除 Shared Buffer Mode 后
mCore->mActiveBuffers.erase(slot);
mCore->mFreeBuffers.push_back(slot);

release 后仍保留 Buffer 绑定,下一次 dequeue 会优先从 mFreeBuffers 取 Slot;这就是 GraphicBuffer 能够复用的直接原因。

mMutex 保护这些共享状态。没有可用 Slot,或队列中的 Buffer 已达到上限时,waitForFreeSlotThenRelock() 会在 mDequeueCondition 上等待。等待期间会暂时释放 Core 锁,让 acquire、release 或配置变化继续推进;队列缩短或 Slot 回到 free 集合后,等待者会被唤醒。完整的阻塞条件与背压行为留给后续专题。

三个对象处在三个生命周期层级

GraphicBuffer 是被管理的图形资源包装,BufferSlot 是缓冲池中的长期位置,BufferItem 是一次提交的帧级记录。

对象 主要位置 生命周期 主要职责
BufferSlot BufferQueueCore::mSlots 的固定数组位置 随 BufferQueue 长期存在并反复流转 保存当前 Buffer 绑定、所有权状态、缓存标志和待交接 Fence;Slot 索引来自数组位置,不是结构体内部字段
GraphicBuffer Core 侧由 BufferSlot::mGraphicBuffer 持有,两端可维护自己的 Slot 映射 底层分配可跨多次提交复用 封装 buffer handle、尺寸、格式、usage 等信息,并连接底层图形内存
BufferItem 先进入 BufferQueueCore::mQueue,随后作为 acquire 输出交给 Consumer 对应一次 queueBuffer() 提交 保存 Slot 索引、帧号、时间戳、裁剪、变换、Fence 等帧级语义

BufferSlot:64 是索引容量,参与数量动态变化

ui/BufferQueueDefs.h 将 Slot 索引空间固定为 64,gui/BufferQueueDefs.h 再把 mSlots 使用的 SlotsType 定义为同样大小的 BufferSlot 数组:

cpp 复制代码
static constexpr int NUM_BUFFER_SLOTS = 64;
typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];

Core 创建时,64 个 Slot 对象一起完成初始化,状态都是 FREE,mGraphicBuffer 为空。64 是索引容量,不代表已经分配 64 块图形内存。

实际参与当前缓冲池的数量由 getMaxBufferCountLocked() 计算:

cpp 复制代码
int maxBufferCount = mMaxAcquiredBufferCount +
        mMaxDequeuedBufferCount +
        ((mAsyncMode || mDequeueBufferCannotBlock) ? 1 : 0);

return std::min(mMaxBufferCount, maxBufferCount);

构造时 acquired、dequeued 上限都为 1,async 与 non-blocking 都未开启,因此初始结果是 2:两个索引进入 mFreeSlots,其余进入 mUnusedSlots。连接方式、异步或非阻塞设置以及 Producer/Consumer 配置都可能调整参与数量,但不会超过 64。mUnusedSlots 中的索引没有绑定 Buffer,所以"2 / 64"既不是固定运行配置,也不是图形内存使用率。

BufferSlot.h 中与这条交接链相关的字段如下:

cpp 复制代码
struct BufferSlot {
    sp<GraphicBuffer> mGraphicBuffer;
    BufferState mBufferState;
    bool mRequestBufferCalled;
    uint64_t mFrameNumber;
    sp<Fence> mFence;
    bool mAcquireCalled;
};

mGraphicBuffer 是单个强引用,因此一个 Slot 同一时刻只能不绑定 Buffer,或者绑定一个 GraphicBuffer。Slot 本身持续存在,绑定却可以被清空、替换或继续保留;同一个 Slot 在生命周期中可以先后绑定不同的底层分配。

GraphicBuffer:分配发生在 dequeue,Producer 按 flag 更新缓存

GraphicBuffer 是 buffer handle 和尺寸、格式、usage 等属性的 C++ 包装。实际像素存储位于 handle 指向的底层分配中。

Producer 调用 dequeueBuffer() 时,BufferQueueProducer 会在当前可用集合中选择 Slot;没有可用 Slot 时,普通可阻塞路径会等待条件变化。选中后,Slot 进入 mActiveBuffers,BufferState 从 FREE 投影为 DEQUEUED,之前 Consumer 留下的 release Fence 作为输出交给 Producer。这个返回只表示 Producer 已得到逻辑写入权;是否已经能覆写底层内存还要看 Fence。

随后它检查当前绑定是否满足本次请求。Buffer 为空或宽高、格式、层数、usage 不匹配时,BufferQueueProducer 清空旧绑定以及用于刷新两端缓存的标志,并返回 BUFFER_NEEDS_REALLOCATION:

cpp 复制代码
bool needsReallocation = buffer == nullptr ||
        buffer->needsReallocation(
                width, height, format, BQ_LAYER_COUNT, usage);

if (needsReallocation) {
    mSlots[found].mAcquireCalled = false;
    mSlots[found].mGraphicBuffer = nullptr;
    mSlots[found].mRequestBufferCalled = false;
    returnFlags |= BUFFER_NEEDS_REALLOCATION;
}

随后 BufferQueueProducer 在不持有 Core 主锁的阶段创建新的 GraphicBuffer,再重新加锁写回 Slot。GraphicBuffer 构造过程调用 GraphicBufferAllocator,最终由 Gralloc allocator 完成底层缓冲区分配。Slot 自己不申请图形内存,它保存分配结果和交接状态。

requestBuffer() 也没有分配 Buffer,它只返回 Slot 中已经写入的对象:

cpp 复制代码
mSlots[slot].mRequestBufferCalled = true;
*buf = mSlots[slot].mGraphicBuffer;

Surface::dequeueBuffer() 展示了一个真实 Producer 调用方如何完成协议:

cpp 复制代码
sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);

if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) ||
        gbuf == nullptr) {
    result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
}

Surface 收到 Slot 索引和返回 flags 后,只在需要重新分配或本地缓存为空时调用 requestBuffer(),然后把结果保存在自己的 mSlots[buf].buffer。mRequestBufferCalled 用于验证 Producer 在收到更新要求后确实取得了新映射,避免直接用陈旧缓存提交。

如果下一帧规格仍然匹配,dequeue 会保留原来的 GraphicBuffer,Surface 也继续使用本地 Slot 映射,不再调用 requestBuffer()。BufferSlot 复用的是固定数组位置和管理状态,GraphicBuffer 复用的才是 handle 指向的底层图形内存。

因此,requestBuffer() 高频出现是规格变化或本地缓存失效的线索,而不是每帧固定步骤。dequeueBuffer() 变慢也只说明可用 Slot、分配或受保护条件尚未满足;要定位 GPU、SurfaceFlinger 或 HWC 中的具体原因,仍需要结合队列深度、Fence 与调度 Trace。

跨 Binder 时,"复用同一个 GraphicBuffer"也不等于两端持有同一个 C++ 包装对象。GraphicBuffer::flatten() 与 unflatten() 会序列化尺寸、格式、usage 和 handle 中的文件描述符、整数数据,接收端再导入 handle。稳定的是 Slot 与底层分配的映射关系;包装对象可能因进程边界而不同。

BufferQueue 在 Producer 与 Consumer 之间传递 Slot 索引、Buffer handle、元数据和 Fence,不会因为一次 queue 或 acquire 就复制整幅像素。这个结论只描述 BufferQueue 的交接过程;渲染、格式转换、缩放和 Client Composition 仍可能产生额外读写。

BufferItem:一次提交,以及 Consumer 缓存是否需要刷新

BufferItem.h 中与这条交接链相关的字段可以压缩为:

cpp 复制代码
class BufferItem : public Flattenable<BufferItem> {
public:
    sp<GraphicBuffer> mGraphicBuffer;
    sp<Fence> mFence;
    Rect mCrop;
    uint32_t mTransform;
    int64_t mTimestamp;
    uint64_t mFrameNumber;
    int mSlot;
    bool mAcquireCalled;
    Region mSurfaceDamage;
};

queueBuffer() 只接受当前由 Producer 持有、并已经完成 requestBuffer() 契约的 DEQUEUED Slot;它同时校验裁剪、缩放模式和 Fence 等输入。校验通过后,才将 Slot 转为 QUEUED,并组装 Item。Item 会把 Slot 的 Consumer 缓存状态一起快照下来:

cpp 复制代码
item.mAcquireCalled = mSlots[slot].mAcquireCalled;
item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
item.mFrameNumber = currentFrameNumber;
item.mSlot = slot;
item.mFence = acquireFence;

// 普通且不丢帧的路径
mCore->mQueue.push_back(item);

acquireBuffer() 取得 Item 后,先把 Core 侧 Slot 标记为已经被 Consumer 见过,再根据 Item 中保存的旧值决定是否返回 mGraphicBuffer:

cpp 复制代码
mSlots[slot].mAcquireCalled = true;
mSlots[slot].mBufferState.acquire();
mSlots[slot].mFence = Fence::NO_FENCE;

if (outBuffer->mAcquireCalled) {
    outBuffer->mGraphicBuffer = nullptr;
}

第一次 acquire 之前,Item 中的 mAcquireCalled 为 false,Consumer 会收到 GraphicBuffer 并建立自己的 Slot → Buffer 缓存。后续提交的 Item 保存 true,acquireBuffer() 可以清空输出指针,只传 Slot 索引和本次帧信息。这样省去的是 GraphicBuffer 的 Binder flatten/unflatten 和 handle 传递,不涉及像素复制或整幅内存重新映射。

Consumer 侧具体由哪个类保存映射取决于实际消费者,但接口契约要求它在 mGraphicBuffer == nullptr 时使用自己的 Slot 缓存。重新分配、清理 Slot 或断开连接会把 mAcquireCalled 复位为 false,强制下一次 acquire 重新发送 GraphicBuffer。因此,Item 中的空指针表示"沿用已知映射",不表示这个 Slot 没有 Buffer。

连续两帧可以复用同一个 Slot 和底层分配,但仍然需要两条独立的 BufferItem。两次提交的帧号、时间戳、Fence、裁剪、变换和损伤区域都可能不同;这些信息属于一次提交,不能固化在长寿命的 Slot 或 GraphicBuffer 上。

cancelBuffer:未提交的 Slot 也必须归还

如果 Producer 在 dequeue 后放弃提交,cancelBuffer() 要求 Slot 仍处于 DEQUEUED,并接收一条非空 Fence。它撤销 dequeue 计数,把普通 Slot 从 mActiveBuffers 放回 mFreeBuffers,并把 Fence 留在 Slot 中供下一次复用。成功 dequeue 后必须走 queue、cancel,或范围外的 detach/disconnect 之一。

同名字段保存的是长期状态与本次快照

BufferSlot 和 BufferItem 都有 mGraphicBuffer、mFrameNumber、mFence,但它们承担的时间尺度不同:

字段 BufferSlot 中的含义 BufferItem 中的含义
mGraphicBuffer Core 对这个 Slot 的当前绑定 Consumer 需要刷新缓存时随本次提交携带的对象
mFrameNumber 该 Slot 最近一次 queue 的帧号,用于计算 buffer age 并校验过期 release 本次提交的帧号
mFence 当前状态下等待下一位所有者接收的同步条件;交给对端后清为 NO_FENCE Producer 为本次提交附带的 acquire Fence

releaseBuffer(slot, frameNumber, ...) 会比较传入帧号和 Slot 的最新 mFrameNumber。两者不一致时,普通路径返回 STALE_BUFFER_SLOT,避免把过期 release 的 Fence 和状态写到这个 Slot 当前记录的帧上。

BufferState:普通路径是计数状态的一组投影

BufferState 没有保存单一枚举,而是使用三个计数和一个 shared 标志:

cpp 复制代码
struct BufferState {
    uint32_t mDequeueCount;
    uint32_t mQueueCount;
    uint32_t mAcquireCount;
    bool mShared;
};

排除 Shared Buffer Mode 后,三个计数在当前主路径中形成下面四组值:

状态 mDequeueCount mQueueCount mAcquireCount 当前所有者
FREE 0 0 0 BufferQueue,可供 Producer 取得
DEQUEUED 1 0 0 Producer
QUEUED 0 1 0 BufferQueue,等待 Consumer
ACQUIRED 0 0 1 Consumer
flowchart TB START(( )) --> FREE[FREE<br/>D=0 / Q=0 / A=0<br/>BufferQueue 持有] FREE -->|dequeueBuffer#40;#41;| DEQUEUED[DEQUEUED<br/>D=1 / Q=0 / A=0<br/>Producer 持有] DEQUEUED -->|queueBuffer#40;#41;| QUEUED[QUEUED<br/>D=0 / Q=1 / A=0<br/>BufferQueue 持有] DEQUEUED -->|cancelBuffer#40;#41;| FREE QUEUED -->|acquireBuffer#40;#41;| ACQUIRED[ACQUIRED<br/>D=0 / Q=0 / A=1<br/>Consumer 持有] ACQUIRED -->|releaseBuffer#40;#41;| FREE classDef start fill:#29463E,stroke:#29463E,color:#29463E,stroke-width:1px; classDef available fill:#F1F5F3,stroke:#7F988F,color:#142B25,stroke-width:1px; classDef producer fill:#DDF7E8,stroke:#0F7A4D,color:#103B2D,stroke-width:2px; classDef queued fill:#FFF4D6,stroke:#A56B00,color:#493300,stroke-width:1px; classDef consumer fill:#E8F0FE,stroke:#3569A8,color:#17385F,stroke-width:1px; class START start; class FREE available; class DEQUEUED producer; class QUEUED queued; class ACQUIRED consumer; linkStyle default stroke:#29463E,stroke-width:1.5px;

dequeue() 增加 dequeue 计数;queue() 减少 dequeue 计数并增加 queue 计数;acquire() 再把 queue 计数转为 acquire 计数;release() 减少 acquire 计数。图中的四个状态是这组计数在普通路径中的投影,不是源码中的独立枚举。

计数结构是为 Shared Buffer Mode 保留的表达能力:shared Slot 可以同时处在多种占用关系中,同一计数也可能大于 1。当前只讨论普通路径,因此使用上表中的四组值。

Fence:所有权可以先转移,安全访问必须等待信号

BufferState 只能说明当前所有权,不能说明前一个所有者发起的 GPU 或硬件工作是否结束。Fence 把这个时间条件随所有权一起传给下一方。

BufferSlot::mFence 的含义随 Slot 状态变化;它始终指向前一个所有者启动的工作何时结束,而不是固定的"GPU 写 Fence"。

Slot 状态 Fence 表达的完成条件 下一个需要等待的一方
FREE Consumer 读取已完成,或 cancel 前 Producer 写入已完成 下一个 Producer
DEQUEUED Fence 已经随 dequeue 交给 Producer;Slot 内部为 NO_FENCE Producer 持有输出 Fence
QUEUED Producer 写入已完成 Consumer
ACQUIRED Fence 已经随 acquire 输出交给 Consumer;Slot 内部为 NO_FENCE Consumer 持有输出 Fence

四个接口中的关键赋值可以压缩为:

cpp 复制代码
// BufferQueueProducer::queueBuffer()
mSlots[slot].mFence = acquireFence;
item.mFence = acquireFence;

// BufferQueueConsumer::acquireBuffer()
mSlots[slot].mBufferState.acquire();
mSlots[slot].mFence = Fence::NO_FENCE;

// BufferQueueConsumer::releaseBuffer()
mSlots[slot].mFence = releaseFence;
mSlots[slot].mBufferState.release();

// BufferQueueProducer::dequeueBuffer()
*outFence = mSlots[found].mFence;
mSlots[found].mFence = Fence::NO_FENCE;

Producer 在 queueBuffer() 中提交 acquire Fence,表示写入工作何时完成。acquireBuffer() 把带有这条 Fence 的 Item 交给 Consumer,同时清空 Slot 中的引用;Consumer 必须在读取 Buffer 前等待 Fence 满足。

Consumer 在 releaseBuffer() 中提交 release Fence,表示读取或使用工作何时完成。下一次 dequeueBuffer() 把它返回给 Producer,再清空 Slot 中的引用;Producer 必须在重新写入前等待 Fence 满足。

在这条 native Fence 主路径中,BufferQueue 只保存并转交 Fence。状态变化表示当前所有权,Fence signal 表示新所有者最早可以访问 Buffer 的时间,两者可以错开。

NO_FENCE 与 HWC 命名边界

固定版本的 Fence.cpp 中,Fence::NO_FENCE 使用 fd -1;wait() 遇到它会直接成功,表示没有待等待的异步依赖,并非错误。有效 Fence 则由 sync_wait() 等待;这是 Android synchronization framework 所说的显式同步。

HWC 语境也有 acquire、release、present Fence,但命名取决于具体交接的接收者和资源。当前只讨论 BufferQueue Producer/Consumer 的边界。判断某条 Fence 时,应先写清它由谁生成、交给谁、保护哪一次读或写。

一次"queue 已接受但下一次仍不能覆盖"的推演

假设 Producer 把帧 A 放入 Slot i,Consumer 随后 acquire 并开始读取;这时 queueBuffer() 已经成功,但 Producer 不能立刻把 A 覆盖成帧 B。沿 Slot 和 Fence 两条线看,状态如下:

时刻 Slot/Item 状态 Fence 所在位置 Producer 能做的事
t0:queueBuffer(A) 返回 Slot i 为 QUEUED,BufferItem(A) 在 mQueue acquire Fence 随 Item 等待 Producer 写入完成 不能再对 i 写入
t1:acquireBuffer() Item 离开 mQueue,Slot i 为 ACQUIRED Consumer 持有 acquire Fence;Slot 内清为空 只能等待其他 FREE Slot,或处理别的在途帧
t2:Consumer releaseBuffer() Slot i 回到 FREE release Fence 写回 Slot,表示 Consumer 使用何时结束 下一次 dequeueBuffer() 可能选中 i,但必须等待该 Fence
t3:Producer 再次 dequeue 新一轮 DEQUEUED,旧 Item 已结束 release Fence 返回 Producer 后由 Producer 等待/传递 Fence 满足后才能安全写入帧 B

queueBuffer() 成功表示提交记录进入队列,不能据此重写底层内存;releaseBuffer() 完成表示所有权归还,Fence 仍可能未 signal。能否覆盖由 Slot 的所有权状态和 Fence 完成点共同决定。Shared Buffer、批量接口和驱动内部等待不在这条普通路径内。

一次交接完成后留下什么

完成一次 dequeue/queue/acquire/release 交接后,本次 BufferItem 已经离开 Core 的 mQueue;Consumer 可以暂时持有 acquire 输出,但下一次提交会产生新的 Item。mSlots[i] 中的 BufferSlot 始终存在。规格没有变化且 Buffer 未被清理时,mGraphicBuffer 仍指向同一个底层分配,Slot 索引对应的两端缓存也可以继续使用。

复用涉及三个层次:

  • mFreeBuffers 让 dequeue 优先选择仍然绑定 Buffer 的 FREE Slot。
  • Slot 索引让 Core、Producer 缓存和 Consumer 缓存用稳定编号定位同一份底层分配。
  • BufferItem 把帧号、时间戳、裁剪、变换、damage 和 acquire Fence 固定在一次提交上,不污染长期 Slot 状态。

排查时按"对象身份 → 所有权 → 提交快照 → 时间依赖"检查:先确认索引是否仍指向同一资源,再看 Slot 是否 FREE/DEQUEUED/QUEUED/ACQUIRED,随后核对本次 BufferItem 的帧号与缓存状态,最后确认 acquire/release Fence 的生产者和等待者。这样可以区分"没有空 Slot""Item 尚未被消费""缓存需要刷新"和"资源仍被硬件占用"四类问题。

固定源码索引

以下链接均固定到同一份 frameworks/native 源码提交 f7274fca5e36082674740bc6c976f73c4578d009:

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui