该系列文章总纲链接:Android GUI系统之SurfaceFlinger 系列文章目录
说明:
- 关于导读:导读部分主要是方便初学者理解SurfaceFlinger代码中的机制,为后面分析代码打下一个更好的基础,这样就可以把更多的精力放在surfaceFlinger的业务逻辑分析上。
- 关于代码分支:以下代码分析均在android5.1.1_r3分支上 目录frameworks/native/services/surfaceflinger为root目录
在SurfaceFlinger中理解BufferQueue 相关内容对于图形渲染和显示非常重要:
- BufferQueue 的作用:BufferQueue 是 SurfaceFlinger 的一部分,用于协调生产者(通常是应用程序或图形渲染引擎)和消费者(通常是显示系统)之间的图形数据传递。它允许生产者将渲染好的图形数据排队以供消费者显示,并确保在异步图形处理过程中进行同步和缓冲区的管理。
- 图形缓冲区(Buffers):BufferQueue 管理图形缓冲区,这些缓冲区可以包含图像数据、帧数据或其他图形数据。了解如何创建、填充、排队和释放这些缓冲区是非常重要的,因为它们是图形渲染和显示的核心。
- BufferQueue 的状态:BufferQueue 可以处于不同的状态,如 FREE、DEQUEUED、QUEUED 和 ACQUIRED。这些状态用于表示图形缓冲区的可用性和所有权,以确保生产者和消费者之间的正确同步和数据传递。
- BufferQueueProducer 和 BufferQueueConsumer:生产者通过 BufferQueueProducer 接口与 BufferQueue 交互,而消费者通过 BufferQueueConsumer 接口与之交互。了解这两个接口以及它们的用法对于理解SurfaceFlinger 如何工作至关重要。
- 同步和时间戳:了解如何使用同步信号(fence)以及如何处理时间戳(timestamp)对于确保图形帧在正确时间显示非常重要。这些功能帮助确保图形帧的正确顺序和时间戳。
- BufferQueue 的线程处理:SurfaceFlinger 使用线程来管理和处理图形缓冲区。理解这些线程的角色和工作方式对于理解图形渲染和显示的异步性质非常关键。
- 硬件加速和图形合成:SurfaceFlinger 可以与硬件加速图形合成引擎一起工作,以实现高性能的图形渲染。了解如何与硬件合成引擎交互以及如何配置硬件加速也很重要。
总之,理解这些概念是 Android 图形渲染和显示的关键,尤其是在处理图形性能和显示质量时。接下来根据上面的基本概念我们主要研究以下内容:
- BufferQueue的4种状态详细解读。
- BufferQueue的生产者消费者模型。
- BufferQueue中的fence同步。
研究BufferQueue,主要涉及的几个关键文件如下:
- AOSP/frameworks/native/libs/gui/BufferQueueProducer.cpp
- AOSP/frameworks/native/libs/gui/BufferQueueCore.cpp
- AOSP/frameworks/native/libs/gui/BufferQueueConsumer.cpp
- AOSP/frameworks/native/libs/gui/BufferQueue.cpp
- AOSP/frameworks/native/libs/gui/BufferSlot.cpp
- AOSP/frameworks/native/include/gui/BufferSlot.h
- AOSP/frameworks/native/include/gui/BufferQueueProducer.h
- AOSP/frameworks/native/include/gui/BufferQueueCore.h
- AOSP/frameworks/native/include/gui/BufferQueueConsumer.h
- AOSP/frameworks/native/include/gui/BufferQueue.h
- AOSP/frameworks/native/include/gui/BufferQueueDefs.h
本文主要针对BufferQueue的原理进行解读,关于流程的解读会在后面篇章中进行详细说明。
1 BufferQueue的4种状态解读
BufferQueue的状态定义在文件AOSP/frameworks/native/libs/gui/BufferSlot.cpp中, 结构体 struct BufferSlot中定义为BufferState,实现如下:
enum BufferState {
FREE = 0,
DEQUEUED = 1,
QUEUED = 2,
ACQUIRED = 3
};
这段代码定义了一个名为BufferState的枚举类型,用于表示图形缓冲区的不同状态。这些状态通常在 Android 的 SurfaceFlinger 系统中使用,用于协调图形数据的生产者和消费者之间的操作。每个状态解释如下:
- FREE(自由):表示缓冲区是可供生产者(dequeue)使用的状态。在此状态下,缓冲区可能在有限的时间内由消费者使用,因此在相关联的"ready fence"(准备就绪标志)被触发之前,不得修改缓冲区。这个状态的槽位(slot)由BufferQueue拥有,当调用dequeueBuffer时,它会转换为DEQUEUED状态。
- DEQUEUED(已出队):表示生产者已经从队列中取出了缓冲区,但尚未将其重新排入队列或取消。在此状态下,生产者可以在相关的"ready fence"被触发后立即修改缓冲区内容。这个状态的槽位仍然由生产者拥有,可以通过queueBuffer转换为QUEUED状态,或通过cancelBuffer转换回FREE状态。
- QUEUED(已排队):表示生产者已经填充了缓冲区并将其排队以供消费者使用。在此状态下,缓冲区内容可能在有限时间内继续被修改,因此在相关的"ready fence"被触发之前,不得访问缓冲区内容。这个状态的槽位由BufferQueue拥有,可以通过acquireBuffer转换为ACQUIRED状态,或者在异步模式下通过排队另一个缓冲区转换回FREE状态。
- ACQUIRED(已获取):表示消费者已经获取了缓冲区。与QUEUED状态一样,消费者在相关的"ready fence"被触发之前不得访问缓冲区内容。这个状态的槽位由消费者拥有,当调用releaseBuffer时,它会转换回FREE状态。
这些状态用于确保生产者和消费者之间对图形缓冲区的访问是协调的,以防止竞态条件和不一致性。这是 Android 图形系统中重要的一部分,用于实现高性能的图形渲染和显示。
2 BufferQueue的生产者/消费者模型
2.1 生产者-消费者流程
应用端为生产者,执行DequeueBuffer操作,然后填充Buffer,完成后执行QueueBuffer操作;SurfaceFlinger为消费者,执行AcquireBuffer,接下来调用HWC来合成绘制Buffer。完成后执行ReleaseBuffer。整体流程如下图所示:
同时,这里BufferQueue的状态变化为:FREE->DEQUEUED->QUEUED->ACQUIRED->FREE。
BufferQueue的通信过程如下(以下部分源自官网翻译):
BufferQueues 是 Android 图形组件之间的粘合剂。它们是一对队列,可以调解缓冲区从生产方到消耗方的固定周期。一旦生产方移交其缓冲区,SurfaceFlinger 便会负责将所有内容合成到显示部分。
BufferQueue 包含将图像流生产方与图像流消耗方结合在一起的逻辑。图像生产方的一些示例包括由相机 HAL 或 OpenGL ES 游戏生成的相机预览。图像消耗方的一些示例包括 SurfaceFlinger 或显示 OpenGL ES 流的另一个应用,如显示相机取景器的相机应用。
其中,BufferQueue 可以在三种不同的模式下运行:
- 类同步模式 - 默认情况下,BufferQueue 在类同步模式下运行,在该模式下,从生产方进入的每个缓冲区都在消耗方那退出。在此模式下不会舍弃任何缓冲区。如果生产方速度太快,创建缓冲区的速度比消耗缓冲区的速度更快,它将阻塞并等待可用的缓冲区。
- 非阻塞模式 - BufferQueue 还可以在非阻塞模式下运行,在此类情况下,它会生成错误,而不是等待缓冲区。在此模式下也不会舍弃缓冲区。这有助于避免可能不了解图形框架的复杂依赖项的应用软件出现潜在死锁现象。
- 舍弃模式 - 最后,BufferQueue 可以配置为丢弃旧缓冲区,而不是生成错误或进行等待。例如,如果对纹理视图执行 GL 渲染并尽快绘制,则必须丢弃缓冲区。
为了执行这项工作的大部分环节,SurfaceFlinger 就像另一个 OpenGL ES 客户端一样工作。例如,当 SurfaceFlinger 正在积极地将一个缓冲区或两个缓冲区合成到第三个缓冲区中时,它使用的是 OpenGL ES。
2.2 测试源码解读,深入理解生产者-消费者模式
接下来我们专注于BufferQueue代码是如何使用的,先看原生代码中给出的测试程序。在文件AOSP/frameworks/native/libs/gui/tests/BufferQueue_test.cpp,这里抽取了其中给一个有代表性的测试用例,对应代码如下:
class BufferQueueTest : public ::testing::Test {
public:
protected:
BufferQueueTest() {
const ::testing::TestInfo* const testInfo =
::testing::UnitTest::GetInstance()->current_test_info();
ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
testInfo->name());
}
~BufferQueueTest() {
const ::testing::TestInfo* const testInfo =
::testing::UnitTest::GetInstance()->current_test_info();
ALOGV("End test: %s.%s", testInfo->test_case_name(),
testInfo->name());
}
void GetMinUndequeuedBufferCount(int* bufferCount) {
ASSERT_TRUE(bufferCount != NULL);
ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
bufferCount));
ASSERT_GE(*bufferCount, 0);
}
void createBufferQueue() {
BufferQueue::createBufferQueue(&mProducer, &mConsumer);
}
sp<IGraphicBufferProducer> mProducer;
sp<IGraphicBufferConsumer> mConsumer;
};
struct DummyConsumer : public BnConsumerListener {
virtual void onFrameAvailable() {}
virtual void onBuffersReleased() {}
virtual void onSidebandStreamChanged() {}
};
// XXX: Tests that fork a process to hold the BufferQueue must run before tests
// that use a local BufferQueue, or else Binder will get unhappy
TEST_F(BufferQueueTest, BufferQueueInAnotherProcess) {
const String16 PRODUCER_NAME = String16("BQTestProducer");
const String16 CONSUMER_NAME = String16("BQTestConsumer");
pid_t forkPid = fork();
ASSERT_NE(forkPid, -1);
if (forkPid == 0) {//子进程
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
sp<IServiceManager> serviceManager = defaultServiceManager();
serviceManager->addService(PRODUCER_NAME, producer->asBinder());
serviceManager->addService(CONSUMER_NAME, consumer->asBinder());
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
LOG_ALWAYS_FATAL("Shouldn't be here");
}
//父进程
sp<IServiceManager> serviceManager = defaultServiceManager();
sp<IBinder> binderProducer =
serviceManager->getService(PRODUCER_NAME);
mProducer = interface_cast<IGraphicBufferProducer>(binderProducer);
EXPECT_TRUE(mProducer != NULL);
sp<IBinder> binderConsumer =
serviceManager->getService(CONSUMER_NAME);
mConsumer = interface_cast<IGraphicBufferConsumer>(binderConsumer);
EXPECT_TRUE(mConsumer != NULL);
sp<DummyConsumer> dc(new DummyConsumer);
ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false));
IGraphicBufferProducer::QueueBufferOutput output;
ASSERT_EQ(OK,
mProducer->connect(NULL, NATIVE_WINDOW_API_CPU, false, &output));
int slot;
sp<Fence> fence;
sp<GraphicBuffer> buffer;
//生产者 dequeueBuffer操作
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
GRALLOC_USAGE_SW_WRITE_OFTEN));
//生产者 requestBuffer操作
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
//生产者填充Buffer缓冲区
uint32_t* dataIn;
ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN,
reinterpret_cast<void**>(&dataIn)));
*dataIn = 0x12345678;
ASSERT_EQ(OK, buffer->unlock());
IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1),
NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE);
//生产者 queueBuffer操作
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
IGraphicBufferConsumer::BufferItem item;
//消费者 acquireBuffer操作
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
//消费者 缓冲区数据验证操作,看上屏的Buffer内容和之前渲染的Buffer内容是否一致
uint32_t* dataOut;
ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
reinterpret_cast<void**>(&dataOut)));
ASSERT_EQ(*dataOut, 0x12345678);
ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
}
//...
} // namespace android
从这里也可以看到生产者和消费者模式在代码中的实际使用case,先是生产者执行了dequeuebuffer操作->requestbuffer操作->对buffer进行填充渲染;消费者执行acquire操作,之后验证缓冲区内容是否和之前填充的内容一致。而在实际的android源码中,BufferQueue的生产者在一个进程中,完成渲染和填充后,通过binder通信通知消费者的进程获取Buffer并通过HWC去合成绘制到屏幕上。
3 BufferQueue的Fence机制
3.1 理解Fence机制
在 Android 的 BufferQueue 中,Fence(也称为同步信号或屏障)用于确保图形缓冲区的正确同步和顺序处理。Fence主要用于表示某个操作的完成状态或某个资源的可用性状态。在图形缓冲区的上下文中,Fence 主要用于跟踪图形数据的生产和消费的状态。例如,一个Fence可以表示图形缓冲区的填充已经完成,或者表示图形缓冲区的内容已经准备好用于显示。Fence的应用如下:
@1 Fence在生产者与消费者之间的同步:
- 在异步图形处理中,生产者(例如应用程序或图形渲染引擎)通常会填充图形缓冲区,并使用 Fence 来表示缓冲区是否已经填充完毕。
- 消费者(例如显示系统)使用 Fence 来等待缓冲区的就绪状态,确保不会在缓冲区尚未填充完毕时访问其内容。
这种同步机制有助于防止竞态条件和不一致性,确保图形数据的正确传递和显示。
@2 Fence 的等待和触发:
- 消费者可以使用 Fence 来等待某个操作的完成,通过等待操作的 Fence 变为“已触发”状态。
- 生产者可以触发 Fence,表示相关操作已经完成,Fence 变为“已触发”状态,从而通知消费者可以安全地使用相关资源或数据。
- Fence 通常与系统的同步机制一起使用,例如 sync_wait(等待 Fence 触发)和 sync_merge(合并多个 Fence)等函数。
@3 时间戳和缓冲区的正确顺序:
- Fence 通常伴随着时间戳一起使用,以确保图形缓冲区的正确顺序。时间戳可以用于指示每个图形帧的呈现时间。
- 使用时间戳和 Fence,消费者可以按照正确的顺序呈现图形帧,从而实现平滑的图形显示。
总之,Fence 在 Android 的 BufferQueue 中用于确保图形数据的正确同步、顺序处理和及时呈现。它是异步图形处理中的重要工具,有助于防止竞态条件和确保图形帧的正确显示。在实际开发中,开发者通常会与 Fence 交互,以确保图形数据的正确传递和显示。
3.2 BufferSlot中的mEglFence和mFence 解读
在SurfaceFlinger中,BufferSlot 类中的定义了2个fence相关成员变量:mEglFence 和 mFence ,两者都是用于同步和管理图形缓冲区状态的属性,但它们在具体的分工上有一些不同:
- mEglFence:mEglFence是一个 EGL同步对象。它用于表示 GPU是否已经完成了与图形缓冲区相关的渲染操作。当 GPU渲染操作完成时,它会信号通知到EGLFence。EglFence主要用于同步GPU的图形渲染操作,确保 GPU已经完成了渲染,以确保渲染的完整性。
- mFence:mFence用于SurfaceFlinger内部的图形合成和显示操作。它用于表示CPU和GPU之间的同步,确保图形缓冲区中的图形数据在合成和显示之前正确准备和完成。mFence通常用于 SurfaceFlinger的图形合成和显示管道,以确保图形数据的正确顺序和同步。