BufferQueue研究
我们在工作的过程中,肯定听过分析卡顿或者冻屏问题的时候,定位到APP卡在dequeueBuffer方法里面,或者也听身边的同事老说3Buffer等信息。所以3Buffer是什么鬼?
什么是BufferQueue?
搞Android,你一定知道Graphic Buffer和 Buffer Queue, 你的笔记中肯定也有下面这张Graphic Buffer的状态迁移图。

系统中有两类Buffer Queue,如下图所示:

Layer背后的Buffer Queue
第一类,也是最为大家所熟知的,就是Layer背后的BufferQueue,用来连接App与SurfaceFlinger。App为Producer端,而 SurfaceFlinger 为 Consumer 端。
App 绘制时,先从 Buffer Queue 中 dequeue(调用 Producer 的 dequeueBuffer()函数)出来一块图形缓冲,绘制完成后,再把绘制好的图形缓冲 queue(调用 Producer 的 queueBuffer()函数)到 Buffer Queue 中,并通知 SurfaceFlinger来消费。SurfaceFlinger 收到通知后,从 Buffer Queue 中 acquire 一块绘制过的 Buffer,然后进行合成处理:要么进行 GPU合成,要么交给 HWC 去合成。
合成完成之后,这个块 Buffer 就恢复自由身,会被返回到 Buffer Queue 中(调用 Consumer 的 releaseBuffer()函数),以备下一次使用。
但是在Android S代码上面,谷歌对SurfaceFlinger的代码进行了重构,从个人理解是为了减少SF的负责,Android S开始强制App端创建BufferQueue,也就是强制Client端分配Buffer。
在Android S的代码中引入了一个BLASTBufferQueue.java(后面简称BBQ)这个类,ViewRootImpl.java在调用relayoutWindow函数的时候,会创建BBQ这个对象。
Surface getOrCreateBLASTSurface() {if (!mSurfaceControl.isValid()) {return null;}Surface ret = null;if (mBlastBufferQueue == null) {mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,mSurfaceSize.x, mSurfaceSize.y,mWindowAttributes.format);// We only return the Surface the first time, as otherwise// it hasn't changed and there is no need to update.ret = mBlastBufferQueue.createSurface();} else {mBlastBufferQueue.update(mSurfaceControl,mSurfaceSize.x, mSurfaceSize.y,mWindowAttributes.format);}return ret;
}
在BBQ对象初初始化的时候,会调用nativeCreate方法,BBQ对象会在构造方法中传入SurfaceControl对象,而这样就会和SurfaceFlinger创建了一个连接通道。SurfaceControl.java封装了很多Client调用的binder接口,而服务端是SurfaceFlinger。
通过nativeCreate本地方法,通过JNI(android_graphics_BLASTBufferQueue.cpp)的nativeCreate方法,创建了native层的BBQ。
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,jlong width, jlong height, jint format) {String8 str8;if (jName) {const jchar* str16 = env->GetStringCritical(jName, nullptr);if (str16) {str8 = String8(reinterpret_cast<const char16_t*>(str16), env->GetStringLength(jName));env->ReleaseStringCritical(jName, str16);str16 = nullptr;}}std::string name = str8.string();sp<BLASTBufferQueue> queue =new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,height, format);queue->incStrong((void*)nativeCreate);return reinterpret_cast<jlong>(queue.get());
}
BLASTBufferQueue::BLASTBufferQueue(const std::string& name, const sp<SurfaceControl>& surface,int width, int height, int32_t format): mSurfaceControl(surface),mSize(width, height),mRequestedSize(mSize),mFormat(format),mNextTransaction(nullptr) {createBufferQueue(&mProducer, &mConsumer);// since the adapter is in the client process, set dequeue timeout// explicitly so that dequeueBuffer will blockmProducer->setDequeueTimeout(std::numeric_limits<int64_t>::max());// safe default, most producers are expected to override thismProducer->setMaxDequeuedBufferCount(2);mBufferItemConsumer = new BLASTBufferItemConsumer(mConsumer,GraphicBuffer::USAGE_HW_COMPOSER |GraphicBuffer::USAGE_HW_TEXTURE,1, false);static int32_t id = 0;mName = name + "#" + std::to_string(id);auto consumerName = mName + "(BLAST Consumer)" + std::to_string(id);mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(id);id++;mBufferItemConsumer->setName(String8(consumerName.c_str()));mBufferItemConsumer->setFrameAvailableListener(this);mBufferItemConsumer->setBufferFreedListener(this);mBufferItemConsumer->setDefaultBufferSize(mSize.width, mSize.height);mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format));mBufferItemConsumer->setBlastBufferQueue(this);ComposerService::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers);mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers);mTransformHint = mSurfaceControl->getTransformHint();mBufferItemConsumer->setTransformHint(mTransformHint);SurfaceComposerClient::Transaction().setFlags(surface, layer_state_t::eEnableBackpressure,layer_state_t::eEnableBackpressure).setApplyToken(mApplyToken).apply();mNumAcquired = 0;mNumFrameAvailable = 0;BQA_LOGV("BLASTBufferQueue created width=%d height=%d format=%d mTransformHint=%d", width,height, format, mTransformHint);
}
从上面的代码中,createBufferQueue创建了BufferQueue,同时也创建了Graphic Buffer的生产者和消费者。其中有个代码mProducer -> setMaxDequeuedBufferCount(2),这个就和3Buffer有关系了,我们先整理下Buffer的运转过程,如图所示:

App的RenderThread 调用 Producer.dequeueBuffer()在BufferQueue中拿到一个空闲的Buffer。
App的RenderThread调用Producer.queueBuffer将绘制好的 Buffer 入列。注意,此时入列的 Buffer 可能还未绘制完成,即 GPU 可能还在进行绘制工作。
最终调用到 Procuder 的 Bn 端,即 SurfaceFliner 进程里的某个 Binder 线程里。在 Bn 端,会通过调用SurfaceFlinger的SetTransactionState方法,把当前的带有Buffer信息的State保存到一个TransactionQueue队列中。
当带有Buffer信息的Layer信息保存到队列中, 这个动作称作“上帧”。所以我么可以在 systrace 上看到该Layer待消费的 Buffer 数目+1。
而 Buffer Queue 的消费者就是 SurfaceFlinger,所以在下一个 Vsync信号到来后,在 SurfaceFlinger 的 handleMessageInvalidate()函数中,调用 acquireBuffer()去取 Buffer,取走之后,BufferQueue 中待消费的 Buffer 便减少一个。
因为有上帧,所以要重新进行合成,SurfaceFlinger 调用onMessageRefresh()函数去做合成,一般是 HWC 合成,直接把 Buffer 交给 HWC。合成完成后,在 postComposition()里,会调用binder接口进行通讯。
App端的binder收到消息后调用releaseBuffer()释放 Buffer,如 systrace 所示,这里释放的是上一帧的 Buffer。
上面图中7个步骤就是一个buffer详细的转运过程。
DisplayDevice 背后的Buffer Queue
第二类Buffer Queue是GPU合成特有的,一般在游戏APP渲染过程中会遇到,这个Buffer Queue隐藏在DisplayDevice之后,是在SurfaceFlinger为每个接入系统的显示屏创建DisplayDevice实例时创建的。
执行在SurfaceFlinger::processDisplayAdded函数中。
void SurfaceFlinger::processDisplayAdded(const wp<IBinder>& displayToken,const DisplayDeviceState& state) {......sp<compositionengine::DisplaySurface> displaySurface;sp<IGraphicBufferProducer> producer;sp<IGraphicBufferProducer> bqProducer;sp<IGraphicBufferConsumer> bqConsumer;getFactory().createBufferQueue(&bqProducer, &bqConsumer, /*consumerIsSurfaceFlinger =*/false);......
}这个函数是为DisplaySurface创建BufferQueue, createBufferQueue函数是指向BufferQueue::createBufferQueue,传入的第三个参数 consumerIsSurfaceFlinger 为false,表示BufferQueue的消费者不是SurfaceFlinger。
void SurfaceFlinger::processDisplayAdded(const wp<IBinder>& displayToken,const DisplayDeviceState& state) {......if (state.isVirtual()) {const auto displayId = VirtualDisplayId::tryCast(compositionDisplay->getId());LOG_FATAL_IF(!displayId);auto surface = sp<VirtualDisplaySurface>::make(getHwComposer(), *displayId, state.surface,bqProducer, bqConsumer, state.displayName);displaySurface = surface;producer = std::move(surface);} else {ALOGE_IF(state.surface != nullptr,"adding a supported display, but rendering ""surface is provided (%p), ignoring it",state.surface.get());const auto displayId = PhysicalDisplayId::tryCast(compositionDisplay->getId());LOG_FATAL_IF(!displayId);displaySurface =sp<FramebufferSurface>::make(getHwComposer(), *displayId, bqConsumer,state.physical->activeMode->getSize(),ui::Size(maxGraphicsWidth, maxGraphicsHeight));producer = bqProducer;}......
}除了虚拟盘,主屏或者外屏采用FrameBufferSurface,继承自ConsumerBase,把BufferQueueConsumer封装到FrameBufferSurface里面。
Buffer共享
Buffer分配
Buffer同步:fence
后面三个点的内容还在整理中,等整理完毕,再同步到这章内容中。
相关文章:
BufferQueue研究
我们在工作的过程中,肯定听过分析卡顿或者冻屏问题的时候,定位到APP卡在dequeueBuffer方法里面,或者也听身边的同事老说3Buffer等信息。所以3Buffer是什么鬼?什么是BufferQueue?搞Android,你一定知道Graphic Buffer和…...
【计组笔记08】计算机组成与原理之IO设备系统(输入、输出设备、外存储器)
这篇文章,主要介绍计算机组成与原理之IO设备系统(输入、输出设备、外存储器)。 目录 一、IO设备系统 1.1、IO系统的演变 (1)早期阶段 (2)接口模块和DMA阶段...
使用Vue实现数据可视化大屏功能(一)
导语 现在在很多的工程项目中,都有有关于数据大屏相关的监控内容,这里我们就来看一下如何用Vue来搭建一个数据可视化大屏应用。 创建项目 使用WebStorm工具创建一个Vue的项目。如下图所示,配置好vue的脚手架工具和nodejs的运行环境&#…...
华为OD机试真题Python实现【整数对最小和】真题+解题思路+代码(20222023)
整数对最小和 题目 给定两个整数数组 array1 array2 数组元素按升序排列 假设从array1 array2中分别取出一个元素可构成一对元素 现在需要取出K个元素 并对取出的所有元素求和 计算和的最小值 注意: 两对元素如果对应于array1 array2中的两个下标均相同,则视为同一个元素 �…...
2023年绿色建筑国际会议(ICoGB 2023)
2023年绿色建筑国际会议(ICoGB 2023) 重要信息 会议网址:www.icogb.org 会议时间:2023年5月19-21日 召开地点:斯德哥尔摩 截稿时间:2023年4月1日 录用通知:投稿后2周内 收录检索ÿ…...
【力扣1653】使字符串平衡的最少删除次数
给你一个字符串 s ,它仅包含字符 a 和 b 。你可以删除 s 中任意数目的字符,使得 s 平衡 。当不存在下标对 (i,j) 满足 i < j ,且 s[i] b 的同时 s[j] a ,此时认为 s 是 平衡 的。请你返回使 s 平衡 的 最少 删除次数。…...
链表的中间结点与链表的倒数第k个结点(精美图示详解哦)
全文目录引言链表的中间结点题目描述与思路实现链表的倒数第k个结点题目描述与思路实现总结引言 在上一篇文章中,介绍了反转链表 我们利用了链表是逻辑连续的特点,逆置了链表的逻辑连接顺序,从而实现反转链表: 戳我查看反转链表详…...
防静电监控仪可以检测现场设备是否和实际大地接触
随着电子产品集成化度越来越高,对于电子产品装配来说,静电的危害严重影响到产品的质量、成品率和可靠性, 必须对用于电子产品装配的净化间进行系统防静电措施,将生产过程中的静电危害程度降至最低。近年来电子企业对ESD的危害的深入认识&…...
计算机网络第八版——第二章课后题答案(超详细)
第二章 该答案为博主在网络上整理,排版不易,希望大家多多点赞支持。后续将会持续更新(可以给博主点个关注~ 第一章 答案 【2-01】物理层要解决哪些问题?物理层的主要特点是什么? 解答:物理层考虑的是怎…...
2023年3月全国DAMA-CDGA/CDGP数据管理认证火热报名中...
弘博创新是DAMA中国授权的数据治理人才培养基地,贴合市场需求定制教学体系,采用行业资深名师授课,理论与实践案例相结合,快速全面提升个人/企业数据治理专业知识与实践经验,通过考试还能获得数据专业领域证书。 DAMA认…...
查询与进程调度(CFS)相关信息
目录 查询与进程相关的调度信息 查看CFS调度信息 CPU相关的信息 CFS就绪队列的总运行时间 实时队列与deadline调度的相关信息 所有进程相关的信息 查询与进程相关的调度信息 进程的nice值,优先级,调度策略,vruntime等信息。在proc目录下…...
07对MVC的理解
MVC是一种设计模式,用于将应用程序的不同方面分离开来,以便更容易地管理和维护应用程序。MVC代表模型-视图-控制器,它将应用程序分为三个主要组件:模型(Model):负责管理应用程序的数据和业务逻辑…...
WebSocket与Socket、TCP、HTTP的关系
目录:1、名词解析;2、WebSocket简介与原理;3、WebSocket和Http的关系和异同点;4、WebSocket与Socket的区别;5、Socket和TCP/IP;6、一个应用程序的通信链路;1、基础名词解析:…...
音频基础知识简述 esp-sr 上手指南
此篇博客先对音频基础知识进行简要叙述,然后帮助读者入门 esp-sr SDK。 1 音频的基本概念 1.1 声音的本质 声音的本质是波在介质中的传播现象,声波的本质是一种波,是一种物理量。 两者不一样,声音是一种抽象的,是声…...
Flex弹性布局一文通【最全Flex教学】
文章目录一.Flex布局1.1 传统布局和flex布局1.1.1 传统布局1.1.2 flex弹性布局1.2 flex初步体验1.3 布局原理二.常见Flex属性2.1 常见父项属性2.2 flex-direction主轴的方向2.3 justify-content设置主轴上的子元素排列方式2.4 设置子元素是否flex-wrap换行2.5 align-itmes设置侧…...
Navicat使用教程
Navicat:一个可以对别人的数据库进行操作的软件(需要与如mysql等数据库配套使用) 1. 下载mysql MySQL :: Download MySQL Community Server (Archived Versions) 下载上面那个版本 下载下来是个压缩包,解压 2.配置mysql (1)在…...
35岁测试人该何去何从?10年工作经验的我,只不过是一年的工作经验用了10年......
如果到了这个年龄,还是初级测试,或者只会一些简单的自动化测试,那么真的是不好干了。 35的年龄,企业对员工是有另一层面的考量。 简单来说,就是年龄上去了,能力也要上去,要么是技术专家&#…...
SpringBoot 项目中集成 Prometheus 和 Grafana
项目上线后,除了能保障正常运行以外,也需要服务运行的各个指标进行监控,例如 服务器CPU、内存使用占比,Full GC 执行时间等,针对一些指标出现异常,可以加入一些报警机制能及时反馈给开发运维。这样…...
红队APT——反朔源流量加密CSMSF证书指纹C2项目CDN域前置
目录 0x01 背景交代 0x02 常见红蓝对抗中红队面临问题 0x03 蓝队发现处置情况...
Linux环境下实现并详细分析c/cpp线程池(附源码)
一、线程池原理 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。 线程池是一种多线程处理形式,处理过程中将任务添加到…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
