Android SurfaceFlinger导读(04)理解BufferQueue

该系列文章总纲链接: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的图形合成和显示管道,以确保图形数据的正确顺序和同步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/149837.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不死马的利用与克制(基于条件竞争)及变种不死马

不死马即内存马&#xff0c;它会写进进程里&#xff0c;并且无限地在指定目录中生成木马文件 这里以PHP不死马为例 测试代码&#xff1a; <?phpignore_user_abort(true);set_time_limit(0);unlink(__FILE__);$file .test.php;$code <?php if(md5($_GET["pass…

项目规划得心应手:Plane 助你打造高效能团队 | 开源日报 No.48

streamlit/streamlit Stars: 27.5k License: Apache-2.0 Streamlit 是一个快速构建和共享数据应用程序的方法。它可以将数据脚本转换为可分享的 Web 应用&#xff0c;只需几分钟即可完成。该项目完全由 Python 编写&#xff0c;开源且免费&#xff01;一旦创建了一个应用程序&…

竞赛 深度学习 opencv python 公式识别(图像识别 机器视觉)

文章目录 0 前言1 课题说明2 效果展示3 具体实现4 关键代码实现5 算法综合效果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的数学公式识别算法实现 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学…

notion + nextjs搭建博客

SaaS可以通过博客来获得SEO流量&#xff0c;之前我自己在nextjs上&#xff0c;基于MarkDown Cloudfare来构建博客&#xff0c;很快我就了解到更优雅的方案&#xff1a;notion nextjs搭建博客&#xff0c;之前搭建了过&#xff0c;没有记录&#xff0c;这次刚好又要弄&#xf…

算法题:分发饼干

这个题目是贪心算法的基础练习题&#xff0c;解决思路是排序双指针谈心法&#xff0c;先将两个数组分别排序&#xff0c;优先满足最小胃口的孩子。&#xff08;本题完整题目附在了最后面&#xff09; 代码如下&#xff1a; class Solution(object):def findContentChildren(se…

[笔记] Windows内核课程:保护模式《二》段寄存器介绍

文章目录 前言1、什么是段寄存器? 有哪些 ?2. 段寄存器的结构 前言 段寄存器&#xff0c;页寄存器 1、什么是段寄存器? 有哪些 ? 当我们用汇编读写某一个地址时: mov dword ptr ds:[0x123456],eax我们真正读写的地址是: ds.base 0x123456ES、CS、SS、DS、FS、GS、LDTR…

云原生边缘计算KubeEdge安装配置

1. K8S集群部署&#xff0c;可以参考如下博客 请安装k8s集群&#xff0c;centos安装k8s集群 请安装k8s集群&#xff0c;ubuntu安装k8s集群 2.安装kubEedge 2.1 编辑kube-proxy使用ipvs代理 kubectl edit configmaps kube-proxy -n kube-system #修改kube-proxy#大约在40多行…

华为云云耀云服务器L实例评测|SpringCloud相关组件——nacos和sentinel的安装和配置 运行内存情况 服务器被非法登陆尝试的解决

前言 最近华为云云耀云服务器L实例上新&#xff0c;也搞了一台来玩&#xff0c;期间遇到各种问题&#xff0c;在解决问题的过程中学到不少和运维相关的知识。 本篇博客介绍SpringCloud相关组件——nacos和sentinel的安装和配置&#xff0c;并分析了运行内存情况&#xff0c;此…

RHCE---作业2

文章目录 目录 文章目录 一.远程连接服务器 二.基于域名和虚目录建立网站 一.远程连接服务器 配置 ssh 免密登陆&#xff1a;客户端主机通过 redhat 用户基于秘钥验证方式进行远程连接服务器的 root 用户 #服务端关闭防火墙 [roottimeserver ~]# systemctl disable --now fir…

Spring的事务控制

基于AOP的声明事务控制 Spring事务编程概述 事务是开发过程中必不可少的东西&#xff0c;使用JDBC开发时&#xff0c;我们使用connection对事务进行控制&#xff0c;使用MyBatis时&#xff0c;我们使用SqlSession对事物进行控制&#xff0c;缺点显而易见&#xff0c;当我们切…

C++设计模式-桥接(Bridge)

目录 C设计模式-桥接&#xff08;Bridge&#xff09; 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-桥接&#xff08;Bridge&#xff09; 一、意图 将抽象部分与它的实现部分分离&#xff0c;使它们都可以独立地变化。 二、适用性 你不希望在抽象和它…

桌面应用开发:Go 语言和 Web 技术的融合创新 | 开源日报 No.46

TheAlgorithms/Python Stars: 161.5k License: MIT 这个开源项目是一个用 Python 实现的算法库&#xff0c;旨在提供教育目的下使用的各种算法。 提供了大量常见算法的 Python 实现。适合学习和教育目的&#xff0c;可以帮助读者更好地理解不同类型的算法。 airbnb/javascri…

CSS3实现动画加载效果

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>加载效果</title><link rel"style…

数据结构与算法-(7)---栈的应用-(4)后缀表达式求值

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

1.7.C++项目:仿muduo库实现并发服务器之Poller模块的设计

项目完整在&#xff1a; 文章目录 一、Poller模块&#xff1a;描述符IO事件监控模块二、提供的功能三、实现思想&#xff08;一&#xff09;功能&#xff08;二&#xff09;意义&#xff08;三&#xff09;功能设计 四、封装思想五、代码&#xff08;一&#xff09;框架&#…

CLIP与DINOv2的图像相似度对比

在计算机视觉领域有两个主要的自监督模型:CLIP和DINOv2。CLIP彻底改变了图像理解并且成为图片和文字之间的桥梁&#xff0c;而DINOv2带来了一种新的自监督学习方法。 在本文中&#xff0c;我们将探讨CLIP和DINOv2的优势和它们直接微妙的差别。我们的目标是发现哪些模型在图像相…

WEB各类常用测试工具

一、单元测试/测试运行器 1、Jest 知名的 Java 单元测试工具&#xff0c;由 Facebook 开源&#xff0c;开箱即用。它在最基础层面被设计用于快速、简单地编写地道的 Java 测试&#xff0c;能自动模拟 require() 返回的 CommonJS 模块&#xff0c;并提供了包括内置的测试环境 …

UDP通信程序的详细解析

2.UDP通信程序 2.1 UDP发送数据 Java中的UDP通信 UDP协议是一种不可靠的网络协议&#xff0c;它在通信的两端各建立一个Socket对象&#xff0c;但是这两个Socket只是发送&#xff0c;接收数据的对象&#xff0c;因此对于基于UDP协议的通信双方而言&#xff0c;没有所谓的客户端…

JMeter学习第一、二、三天

首先&#xff0c;我们来了解一下到底什么是接口测试与性能测试&#xff1a; 接口测试 定义 接口测试主要关注系统组件之间的交互&#xff0c;确保各个接口按预期工作。这包括验证传递的数据、数据格式、调用的频率和其他与接口调用相关的任何限制。 目的 确保系统的各个组件可…

Qt中 QMap 类、QHash 类、QVector 类详解

目录 一、QMap 类 1.插入数据信息 2.删除数据信息 3.迭代器 4.STL类型迭代 5.key键/T键查找 6.修改键值 7. 一个键对应多个值 直接使用QMultiMap类来实例化一个QMap对象 二、QHash 类 三、QVector类 一、QMap 类 QMap<Key,T>提供一个从类型为 Key 的键到类型为…