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 mFreeBuffersmActiveBuffers 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 mActiveBuffersmFreeBuffers 本次提交不再产生新 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,再把它传给 BufferQueueProducerBufferQueueConsumer。省略空指针检查后,创建关系只剩下面几行:

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 实现侧的 BufferQueueProducerBufferQueueConsumer。外部 Producer 和 Consumer 面对 IGraphicBufferProducerIGraphicBufferConsumer 接口;调用可以发生在同一进程,也可以通过 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 进入 mActiveBuffersBufferState 从 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].buffermRequestBufferCalled 用于验证 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 之一。

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

BufferSlotBufferItem 都有 mGraphicBuffermFrameNumbermFence,但它们承担的时间尺度不同:

字段 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 -1wait() 遇到它会直接成功,表示没有待等待的异步依赖,并非错误。有效 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 能做的事
t0queueBuffer(A) 返回 Slot iQUEUEDBufferItem(A)mQueue acquire Fence 随 Item 等待 Producer 写入完成 不能再对 i 写入
t1acquireBuffer() Item 离开 mQueue,Slot iACQUIRED 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

相关推荐
XiaoLeisj1 小时前
Kotlin Flow 常用操作符:数据变换 map、filter、onEach,时间控制 debounce、sample,终端聚合 reduce、fold
android·kotlin·android jetpack·协程·响应式编程·flow
EQ-雪梨蛋花汤1 小时前
Android 3D 开发教程(三):Sceneform-EQR 配置 PBR 材质、光照、相机与实时阴影
android·3d·材质
Dovis(誓平步青云)2 小时前
《 固井工程软件 Cemsol 的数据管理与国产化适配实践》
android·java·开发语言·人工智能
Kapaseker2 小时前
小白都看得懂的 Skill 教程 - 创建第一个 Skill
android·kotlin·vibecoding
zhangphil2 小时前
Android BitmapFactory实现AOSP ContentResolver.loadThumbnail快速取小缩略图,Kotlin
android·kotlin
weixin_440784112 小时前
【OkHttp实现原理】
android·java·okhttp
恋猫de小郭2 小时前
Jetpack Compose 8 月版正式发布,核心模块 1.12
android·前端·flutter
delta_hell3 小时前
【阅读源码--Android】动画之AnimatorSet--1
android·源码·animatorset
2501_9159214315 小时前
appuploader-cli 命令行上传 IPA 到 App Store Connect upload CI 集成
android·ci/cd·小程序·https·uni-app·iphone·webview