Android GPU纹理数据拷贝

news2024/11/22 9:25:54

在 Android 开发中读取纹理数据有以下几种方法:

  • glReadPixels
  • ImageReader
  • PBO(Pixel BufferObject)
    HardwareBuffer

1. glReadPixels

glReadPixels 是 OpenGL ES 的 API,通常用于从帧缓冲区中读取像素数据,OpenGL ES 2.0 和 3.0 均支持。使用非常方便,但是效率也是最低的。

  • 当调用 glReadPixels 时,首先会影响 CPU 时钟周期,同时 GPU会等待当前帧绘制完成,读取像素完成之后,才开始下一帧的计算,造成渲染管线停滞。
  • glReadPixels 读取的是当前绑定 FBO 的颜色缓冲区图像,所以当使用多个 FBO(帧缓冲区对象)时,需要确定好我们要读那个FBO 的颜色缓冲区。
  • glReadPixels 性能瓶颈一般出现在大分辨率图像的读取,所以目前通用的优化方法是在 shader 中将处理完成的 RGBA 转成YUV(一般是 YUYV 格式),然后基于 RGBA 的格式读出 YUV 图像,这样传输数据量会降低一半,性能提升明显。

下面我们介绍两种使用 glReadPixels 来进行 RGBA 转换 NV21 的示例:

  • 直接获取 RGBA 数据
    这种方式 GPU 传输数据到 CPU 耗时比较长。
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, rgbaByteAddr);
libyuv::ABGRToNV21(rgbaByteAddr, width * 4, yByte, width, uvByte, width, width, height);;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
  • OpenGL 扩展格式 YUV
// Draw Y
TextureAttributes textureAttriburesY = {
  .minFilter = GL_LINEAR,
  .magFilter = GL_LINEAR,
  .wrapS = GL_CLAMP_TO_EDGE,
  .wrapT = GL_CLAMP_TO_EDGE,
  .internalFormat = GL_RED_EXT,
  .format = GL_RED_EXT,
  .type = GL_UNSIGNED_BYTE
};

varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
 vec4 color = texture2D(inputImageTexture,textureCoordinate);
 gl_FragColor.r = color.r*0.2990+color.g*0.5870+color.b*0.1140;
}

// Draw UV
TextureAttributes textureAttriburesVU = {
  .minFilter = GL_LINEAR,
  .magFilter = GL_LINEAR,
  .wrapS = GL_CLAMP_TO_EDGE,
  .wrapT = GL_CLAMP_TO_EDGE,
  .internalFormat = GL_RG_EXT,
  .format = GL_RG_EXT,
  .type = GL_UNSIGNED_BYTE
};

varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
 vec4 color = texture2D(inputImageTexture,textureCoordinate);
 gl_FragColor.rg = vec2(0.6150*color.r - 0.5150*color.g - 0.1000*color.b+0.5000,-0.1471*color.r - 0.2889*color.g + 0.4360*color.b+0.5000);
}

glBindFramebuffer(GL_FRAMEBUFFER, yFbo);
glReadPixels(0, 0, width, height, GL_RED_EXT, GL_UNSIGNED_BYTE, yuv_byte);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

glBindFramebuffer(GL_FRAMEBUFFER, uvFbo);
glReadPixels(0, 0, width / 2, height / 2, GL_RG_EXT, GL_UNSIGNED_BYTE, yuv_byte + width * height);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

2. ImageReader

2.1 ImageReader 基础描述

ImageReader 是 Android 中的一个类,用于获取相机设备的图像数据。它可以用于捕获相机拍摄的静态图像或实时预览帧,并提供对图像数据的访问和处理。以下是一些 ImageReader 的特点和用法:

  • 获取图像数据:通过创建一个 ImageReader 实例,可以指定要获取的图像的宽度、高度和图像格式。然后,可以使用ImageReader 的 acquireLatestImage() 或 acquireNextImage() 方法获取最新的图像或下一帧图像。这些方法返回一个 Image 对象,它包含了图像的数据和相关信息。
  • 图像数据访问:通过 Image 对象,可以访问图像的像素数据。可以使用 getPlanes() 方法获取图像的平面数组,每个平面对应于图像的不同颜色通道。然后,可以使用 getBuffer() 方法获取每个平面的 ByteBuffer,从中读取或修改像素数据。
  • 回收资源:使用完 Image 对象后,应调用其 close() 方法释放资源,以避免内存泄漏。
  • 设置图像可用监听器:可以为 ImageReader 设置一个 OnImageAvailableListener 监听器,在新图像可用时收到通知,这样可以实现对图像数据的实时处理和分析。
  • 配置图像输出:可以使用 ImageReader 的 setOnImageAvailableListener() 方法设置监听器,并通过 ImageReader 的 getSurface() 方法获取一个 Surface 对象,将其用于预览或拍照时的图像输出目标。

2.2 ImageReader 如何使用

我们可以使用 ImageReader 对象的 Surface 对象搭配 OpenGL 进行数据渲染。

mImageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mHandler);
mSurface = mImageReader.getSurface();
private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
    @Override
    public void onImageAvailable(ImageReader reader) {
        Image image = reader.acquireLatestImage();
        if (image != null) {
           image.close();
        }
    }
};

部分重要 API:

  • acquireLatestImage() 从 ImageReader 队列中获取最新的一帧 Image ,并且将老的 Image 丢弃,如果没有新的可用的 Image 则返回 null 。此操作将会从 ImageReader 中获取所有可获取到的 Images ,并且关闭除了最新的 Image 之外的 Image 。此功能大多数情况下比 acquireNextImage 更推荐使用,更加适用于视频实时处理。需要注意的是 maxImages 应该至少为 2 ,因为丢弃除了最新的之外的所有帧需要至少两帧。换句话说,(maxImages - currentAcquiredImages < 2) 的情况下,丢帧将会不正常。
  • acquireNextImage() 从 ImageReader 的队列中获取下一帧 Image ,如果没有新的则返回 null。Android 推荐我们使用 acquireLatestImage 来代替使用此方法,因为它会自动帮我们 close 掉旧的 Image,并且能让效率比较差的情况下能获取到最新的 Image 。acquireNextImage 更推荐在批处理或者后台程序中使用,不恰当的使用本方法将会导致得到的 images 出现不断增长的延迟。
  • close() 释放所有跟此 ImageReader 关联的资源。调用此方法后,ImageReader 不会再被使用,再调用它的方法或者调用被 acquireLatestImage 或 acquireNextImage 返回的 Image 会抛出 IllegalStateException,尝试读取之前 Plane#getBuffer 返回的 ByteBuffers 将会导致不可预测的行为。
  • newInstance(int width, int height, int format, int maxImages) 创建新的 reader 以获取期望的 size 和 format 的 Images。maxImages 决定了 ImageReader 能同步返回的最大的 Image 的数量,申请越多的 buffers 会耗费越多的内存空间,使用合适的数量很重要。
  • newInstance(int width, int height, int format, int maxImages) 创建新的 reader 以获取期望的 size 和 format 的 Images。maxImages 决定了 ImageReader 能同步返回的最大的 Image 的数量,申请越多的 buffers 会耗费越多的内存空间,使用合适的数量很重要。
  • maxImages:缓存的最大帧数,必须大于 0。

3. PBO(Pixel Buffer Object)

3.1 PBO 基础介绍

OpenGL PBO(Pixel Buffer Object),被称为像素缓冲区对象,主要被用于异步像素传输操作。PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。OpenGL PBO(像素缓冲区对象) 类似于 VBO(顶点缓冲区对象),PBO 开辟的也是 GPU 缓存,而存储的是图像数据。PBO 是 OpenGL ES 3.0 开始提供的一种方式,主要应用于从内存快速复制纹理到显存,或从显存复制像素数据到内存。

在使用 OpenGL 的时候经常需要在 GPU 和 CPU 之间传递数据,例如在使用 OpenGL 将 YUV 数据转换成 RGB 数据时就需要先将 YUV 数据上传到 GPU ,一般使用函数 glTexImage2D ,处理完毕后再将 RGB 结果数据读取到 CPU , 这时使用函数 glReadPixels 即可将数据取回。但是这两个函数都是比较缓慢的,特别是在数据量比较大的时候。PBO 就是为了解决这个访问慢的问题而产生的。

不使用 PBO 加载纹理:
在这里插入图片描述
使用 PBO 加载纹理:
在这里插入图片描述

3.2 PBO 如何使用?

int imgByteSize = m_Image.width * m_Image.height * 4;//RGBA

glGenBuffers(1, &uploadPboId);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboId);
glBufferData(GL_PIXEL_UNPACK_BUFFER, imgByteSize, 0, GL_STREAM_DRAW);

glGenBuffers(1, &downloadPboId);
glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPboId);
glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, 0, GL_STREAM_DRAW);

使用两个 PBO 从帧缓冲区读回图像数据:
在这里插入图片描述
如上图所示,利用 2 个 PBO 从帧缓冲区读回图像数据,使用 glReadPixels 通知 GPU 将图像数据从帧缓冲区读回到 PBO1 中,同时 CPU 可以直接处理 PBO2 中的图像数据。

// 交换 PBO
int index = m_FrameIndex % 2;
int nextIndex = (index + 1) % 2;

// 将图像数据从帧缓冲区读回到 PBO 中
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[index]);
glReadPixels(0, 0, m_RenderImage.width, m_RenderImage.height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

// glMapBufferRange 获取 PBO 缓冲区指针
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[nextIndex]);
GLubyte *bufPtr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0,
                                                       dataSize,
                                                       GL_MAP_READ_BIT));
if (bufPtr) {
    nativeImage.ppPlane[0] = bufPtr;
    //NativeImageUtil::DumpNativeImage(&nativeImage, "/sdcard/DCIM", "PBO");
    glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

4. HardwareBuffer

4.1 HardwareBuffer 基础介绍

HardwareBuffer 官方介绍为一种底层的内存 buffer 对象,可在不同进程间共享,可映射到不同硬件系统,如 GPU、传感器等,从构造函数可以看出,其可以指定 format 和 usage,用来让底层选择最合适的实现,目前 format 主要是渲染相关的纹理格式,Android 11 之后支持了 BLOB 格式,可用来做 NN 相关的数据共享。

如果看一下 HardwareBuffer 的实现,会发现其只是 GraphicBuffer 的一个包装,只是 Android 低版本并没有开放 GraphicBuffer 相关 API,而前面提到的 Surface ,其底层就是基于 GraphicBuffer 来实现的,因此本质上是 Android 系统开放了更底层的 API,我们才可以有更高效的实现,接下来看具体如何基于 HardwareBuffer 跨进程传输纹理。
在这里插入图片描述

4.2 HardwareBuffer 如何使用?

AHardwareBuffer 创建纹理:

if(textureID == 0){
    AHardwareBuffer_Desc h_buffer_desc = {0};
    h_buffer_desc.stride = frameData->i32Width;
    h_buffer_desc.height = frameData->i32Height;
    h_buffer_desc.width = frameData->i32Width;
    h_buffer_desc.layers = 1;
    h_buffer_desc.format = 0x11;
    h_buffer_desc.usage = AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
 
    int ret = AHardwareBuffer_allocate(&h_buffer_desc, &inputHWBuffer);
    EGLint attr[] = {EGL_NONE};
    EGLDisplay edp;
    edp = (EGLDisplay)eglGetCurrentDisplay();
    inputEGLImage) = eglCreateImageKHR(edp, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, eglGetNativeClientBufferANDROID(inputHWBuffer), attr);
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureID);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES , GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES , GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES , (GLeglImageOES)inputEGLImage);
}
AHardwareBuffer_Planes planes_info = {0}; int ret = AHardwareBuffer_lockPlanes(inputHWBuffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK, -1,nullptr,&planes_info);
if (ret == 0) {
    memcpy(planes_info.planes[0].data,frameData->ppu8Plane[0],frameData->i32Width * frameData->i32Height*3/2);
    ret = AHardwareBuffer_unlock(inputHWBuffer, nullptr); 
}
glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureID);

AHardwareBuffer 读取纹理图像数据:

unsigned char *ptrReader = nullptr;
ret = AHardwareBuffer_lock(inputHWBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, -1,     nullptr, (void **) &ptrReader); 
memcpy(dstBuffer, ptrReader, imgWidth * imgHeight * 3 / 2);
ret = AHardwareBuffer_unlock(inputHWBuffer, nullptr);

ImageReader、 PBO 和 HardwareBuffer 明显优于 glReadPixels 方式,HardwareBuffer、ImageReader 以及 PBO 三种方式性能相差不大,但是理论上 HardwareBuffer 性能最优。

补充:OPENGL NCNN GPU零拷贝实现

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

畅捷通T+ RecoverPassword.aspx

用友 畅捷通T RecoverPassword.aspx 存在未授权管理员密码修改漏洞&#xff0c;攻击者可以通过漏洞修改管理员账号密码登录后台 #漏洞影响版本 12.0&#xff0c;12.1.12.2.12.3 13.0 15.0 16.0 18.0 补丁号291都影响 19.0 补丁号167之前也有影响 对于老版本畅捷通已经…

最全最简单理解迭代器

1. 迭代器的基础概念(iterator) 1.1 本质 迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针。 1.2 作用: 能够让迭代器与算法不干扰的相互发展,最后又能无间隙的粘合起来。重载了*,++,==,!=,=运算符。用以操作复杂的数据结构。容器提供迭代…

python数据写入excel文件

主要思路&#xff1a;数据 转DataFrame后写入excel文件 一、数据格式为字典形式1 k e &#xff0c; v [‘1’, ‘e’, 0.83, 437, 0.6, 0.8, 0.9, ‘好’] 1、这种方法使用了 from_dict 方法&#xff0c;指定了 orient‘index’ 表示使用字典的键作为行索引&#xff0c;然…

[CKS] Create/Read/Mount a Secret in K8S

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于读取、创建以及挂载secret的题目。 ​ 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博客[C…

docker之容器设置开机自启(4)

命令语法&#xff1a; docker update --restartalways 容器ID/容器名 选项&#xff1a; --restart参数 no 默认策略&#xff0c;在容器退出时不重启容器 on-failure 在容器非正常退出时&#xff08;退出状态非0&#xff09;&#xff0c;才会重启容器 …

机器学习:决策树——ID3算法、C4.5算法、CART算法

决策树是一种常用于分类和回归问题的机器学习模型。它通过一系列的“决策”来对数据进行分类或预测。在决策树中&#xff0c;每个内部节点表示一个特征的测试&#xff0c;每个分支代表特征测试的结果&#xff0c;而每个叶节点则表示分类结果或回归值。 决策树工作原理 根节点&…

Angular 和 Vue2.0 对比

前言 &#xff1a;“业精于勤&#xff0c;荒于嬉&#xff1b;行成于思&#xff0c;毁于随” 很久没写博客了&#xff0c;大多记录少进一步探查。 Angular 和 Vue2.0 对比&#xff1a; 一.概念 1.1 Angular 框架&#xff1a; 是一款由谷歌开发的开源web前端框架&#xff08;核…

Python酷库之旅-第三方库Pandas(208)

目录 一、用法精讲 971、pandas.MultiIndex.set_levels方法 971-1、语法 971-2、参数 971-3、功能 971-4、返回值 971-5、说明 971-6、用法 971-6-1、数据准备 971-6-2、代码示例 971-6-3、结果输出 972、pandas.MultiIndex.from_arrays类方法 972-1、语法 972-2…

[Linux]:IO多路转接之epoll

1. IO 多路转接之epoll 1.1 epoll概述 epoll是Linux内核为处理大规模并发网络连接而设计的高效I/O多路转接技术。它基于事件驱动模型&#xff0c;通过在内核中维护一个事件表&#xff0c;能够快速响应多个文件描述符上的I/O事件&#xff0c;如可读、可写、异常等&#xff0c;…

Spring Security 认证流程,长话简说

一、代码先行 1、设计模式 SpringSecurity 采用的是 责任链 的设计模式&#xff0c;是一堆过滤器链的组合&#xff0c;它有一条很长的过滤器链。 不过我们不需要去仔细了解每一个过滤器的含义和用法,只需要搞定以下几个问题即可&#xff1a;怎么登录、怎么校验账户、认证失败…

API 接口进行多分支管理的方法

原文链接&#xff1a;API 接口进行多分支管理的方法

链表类算法【leetcode】

链表的定义 面试时&#xff0c;需要自己手写... // 单链表 struct ListNode {int val; // 节点上存储的元素ListNode *next; // 指向下一个节点的指针ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数 }; 【构造函数可以省略&#xff0c;C默认生成一个构造函数…

重构开发之道,Blackbox.AI为技术注入智能新动力

本文目录 一、引言二、Blackbox.AI实战体验2.1 基于网页界面生成前端代码进行应用开发2.2 与AI助手实现实时智能对话2.3 重塑大型文件交互方式2.4 链接Github仓库进行对话编程 三、总结 一、引言 在生产力工具加速进化的浪潮中&#xff0c;Blackbox.AI开始崭露头角&#xff0c…

【STM32F1】——9轴姿态传感器JY901与IIC通信

【STM32F1】——9轴姿态传感器JY901与IIC通信 一、简介 本篇主要对9轴姿态传感器JY901的调试过程进行总结,实现了以下功能。 IIC通信采集+串口收发:使用STM32F103C8T6的GPIO口模拟IIC,从JY901读取数据,并通过USART1串口发送到PC。二、JY901介绍 电压:3.3-5V量程:X/Z轴 …

Linux网络——自定义协议与序列化

一、协议 协议是一种 " 约定 ". socket api 的接口 , 在读写数据时 , 都是按 " 字符串 " 的方式来发送接收的。如 果我们要传输一些 " 结构化的数据 "&#xff0c;依然可以通过协议。 其实&#xff0c;协议就是双方约定好的结构化的数据。…

Windows,虚拟机Ubuntu和开发板三者之间的NFS服务器搭建

Windows,虚拟机Ubuntu和开发板三者之间的NFS服务器搭建 &#xff08;1&#xff09;虚拟机 ubuntu 要使用桥接模式&#xff0c;不能使用其他模式 &#xff08;2&#xff09;通过网线将PC和开发板网口直连:这样的连接&#xff0c;开发板是无法连接外网的 &#xff08;3&#xff…

更改Ubuntu22.04锁屏壁纸

更改Ubuntu22.04锁屏壁纸 sudo apt install gnome-shell-extensions gnome-shell-extension-manager安装Gnome Shell 扩展管理器后&#xff0c;打开“扩展管理器”并使用搜索栏找到“锁屏背景”扩展

大模型推理优化技术-KV Cache

近两年大模型火出天际&#xff1b;同时&#xff0c;也诞生了大量针对大模型的优化技术。本系列将针对一些常见大模型优化技术进行讲解。 大模型推理优化技术-KV Cache大模型推理服务调度优化技术-Continuous batching大模型底显存推理优化-Offload技术大模型推理优化技术-KV C…

力扣 LeetCode 24. 两两交换链表中的节点(Day2:链表)

解题思路&#xff1a; 暂存节点tmp和tmp1 注意&#xff1a;while (cur.next ! null && cur.next.next ! null)表示为偶数和奇数时的循环停止条件&#xff0c;并且while语句中的顺序不可交换&#xff0c;交换会报空指针异常 class Solution {public ListNode swapPai…

动态规划-背包问题——494.目标和

1.状态表示 题目来源 494.目标和——力扣 测试用例 2.算法原理 1.状态表示 首先我们需要将问题简化&#xff0c;这里需要找到能将数组组合计算成为指定数字target的添加方式&#xff0c;那么我们就可以将数字分为两类&#xff0c;一类是前面添加""的&#xff0c;另…