当前位置: 首页 > news >正文

Android GLES渲染——渲染回读

渲染回读

渲染数据回读在项目上经常会使用到,不管是用于排查渲染流程还是用于后续其他的操作,如何高效的获取渲染数据是项目在实际开发过程中需要重点考虑的。如下整理了Android侧常用的渲染回读(部分属于GL通用方案,即其他端侧亦可使用)

glReadPixels

glReadPixels 是 OpenGL ES 的 API ,OpenGL ES 2.0 和 3.0 均支持。 使用非常方便,下面一行代码即可搞定,但是效率也是最低的。

glReadPixels(0, 0, UI_WIDTH, UI_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, pScreen);

glReadPixel是最方便,也是最常用的渲染数据回读方案,其回读的数据主要是当前渲染绑定的FBO的数据。因此,若存在多个FBO,则需要关注当前bind的FBO是否是需要回读的FBO。

glReadPixel的问题在于,当调用 glReadPixels 时, GPU 会等待当前帧绘制完成,相当于调用了一次glFinish,读取像素完成之后,才开始下一帧的计算,造成渲染管线停滞。 不仅如此,由于串行执行GPU->CPU数据的拷贝,如果数据量大,频繁执行会导致CPU占用率显著提高。、

PBO

OpenGL PBO(Pixel Buffer Object),被称为像素缓冲区对象,主要被用于异步像素传输操作。PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。类似于VBO,PBO开辟的也是GPU的缓存,不同于VBO,PBO存储的是图像数据。

在使用PBO时,常见的标签为GL_PIXEL_UNPACK_BUFFER 和 GL_PIXEL_PACK_BUFFER。绑定为 GL_PIXEL_UNPACK_BUFFER 表示该 PBO 用于将像素数据从程序传送到 OpenGL 中;绑定为 GL_PIXEL_PACK_BUFFER 表示该 PBO 用于从 OpenGL 中读回像素数据。 本文主要解释如何用PBO实现渲染的回读(PBO实现纹理更新方式类似,绑定PBO后,使用glTexImage2D或者glTexSubImage2D,借助PBO的异步操作更新GPU纹理数据,减少了CPU的等待)。 首先是创建标签为GL_PIXEL_PACK_BUFFER的PBO

int imgByteSize = m_Image.width * m_Image.height * 4;//RGBA
glGenBuffers(1, &downloadPboId); 
glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPboId); 
glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, 0, GL_STREAM_DRAW);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

渲染后,回读数据

glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPboId);
glReadPixels(0, 0, UI_WIDTH, UI_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, pScreen);
//或者使用glMapBufferRange///
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);
}
//

经过实测,PBO相对于直接调用glReadPixel,通过PBO异步的特性,可以很好的减低CPU的使用。

HardwareBuffer

HardwareBuffer,即native的AHardwareBuffer的Java层包装,官方介绍为一种底层的内存 buffer 对象,可在不同进程间共享,可映射到不同硬件系统,如 GPU、传感器等。

该部分主要介绍Android在API26开放的native层接口AHardwareBuffer实现渲染回读。

// 离屏渲染
void SeihoRenderContext::CreateOffScreenRenderOESTexture(int outputWidth, int outputHeight) {绑定离屏渲染的framebuffer和texture,同时用AHardwarebuffer绑定到OES。创建FramebufferglGenFramebuffers(1, &outputFramebufferID);glBindFramebuffer(GL_FRAMEBUFFER, outputFramebufferID);GLUtils::CheckGLError("glGenFramebuffers");创建纹理glGenTextures(1, &outputFrameBufferTextureID);glBindTexture(GL_TEXTURE_2D, outputFrameBufferTextureID);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);GLUtils::CheckGLError("glGenTextures");AHardwareBuffer_Desc desc = {static_cast<uint32_t>(UI_WIDTH),static_cast<uint32_t>(UI_HEIGHT),1,AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT,0,0,0};AHardwareBuffer_allocate(&desc, &outputHWBuffer);EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);clientBuffer = eglGetNativeClientBufferANDROID(outputHWBuffer);eglImage_ = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,clientBuffer, eglImageAttributes);if (eglImage_ == EGL_NO_IMAGE_KHR) {GLUtils::CheckGLError("eglCreateImageKHR");}GLUtils::CheckGLError("eglCreateImageKHR");glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) eglImage_);GLUtils::CheckGLError("glEGLImageTargetTexture2DOES");glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, outputFrameBufferTextureID, 0);glBindFramebuffer(GL_FRAMEBUFFER, 0);glBindFramebuffer(GL_RENDERBUFFER, 0);glBindTexture(GL_TEXTURE_2D, 0);
}

上述代码主要通过EGLImageKHR和AHardwareBuffer绑定,实现渲染的回读。EGLImage 来自于 EGL 的一个扩展 EGL_KHR_image_base,用于在不同的 EGL 环境之间共享数据,而 Android 系统通过另一个扩展 EGL_ANDROID_get_native_client_buffer 支持从 HardwareBuffer 创建 EGLImage。AHardwareBuffer实现回读,必须将EGLImage,通过glEGLImageTargetTexture2DOES绑定。OES纹理为OpenGL ES的拓展,在Android侧,OES纹理可以同GraphicBuffer共享,实现零拷贝,而AHardwareBuffer本质依旧是GraphicBuffer。

至此,AHardwareBuffer和FBO的绑定完成。通过如下代码回读即可。

unsigned char *ptrReader = nullptr;
ret = AHardwareBuffer_lock(inputHWBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, -1, nullptr, (void **) &ptrReader); 
/ptrReader已拿到了渲染的纹理,后续可以任意操作。
ret = AHardwareBuffer_unlock(inputHWBuffer, nullptr);

经过项目侧测试,AHardwareBuffer可以完全实现回读零拷贝,也不占用CPU时钟周期,若数据在native层传递,或Java层通过SurfaceTexture、HardwareBuffer传递,AHardwareBuffer的渲染回读应该作为首选。

相关文章:

  • MFC序列号输入框
  • 一套轻量、安全的问卷系统基座,提供面向个人和企业的一站式产品级解决方案
  • K-Means 算法详解
  • 游戏中的寻路算法研究
  • 解决内核模块加载使用-f参数无法加载的问题
  • 为什么要学Java?
  • Linux驱动开发(二)--字符设备驱动开发提升 LED驱动开发实验
  • 18个机器学习核心算法模型总结
  • 2025计算机毕业设计选题题目推荐-毕设题目汇总大全
  • 智慧校园综合管理系统:打造高效智慧的学校管理平台
  • 契约锁电子签章平台 add 远程命令执行漏洞复现(XVE-2023-23720)
  • 关于面试被面试官暴怼:“几年研究生白读” 的前因后果
  • React获取DOM节点
  • 【Android】基于webView打造富文本编辑器(H5)
  • 网络故障排查-TCP标志位
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • gcc介绍及安装
  • JS 面试题总结
  • Laravel Mix运行时关于es2015报错解决方案
  • node学习系列之简单文件上传
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 从tcpdump抓包看TCP/IP协议
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 你不可错过的前端面试题(一)
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • # linux从入门到精通(三)
  • #define 用法
  • #define、const、typedef的差别
  • #pragma预处理命令
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • #知识分享#笔记#学习方法
  • ${factoryList }后面有空格不影响
  • (4)事件处理——(6)给.ready()回调函数传递一个参数(Passing an argument to the .ready() callback)...
  • (9)目标检测_SSD的原理
  • (Java数据结构)ArrayList
  • (二)WCF的Binding模型
  • (二刷)代码随想录第16天|104.二叉树的最大深度 559.n叉树的最大深度● 111.二叉树的最小深度● 222.完全二叉树的节点个数
  • (亲测有效)解决windows11无法使用1500000波特率的问题
  • (太强大了) - Linux 性能监控、测试、优化工具
  • (完整代码)R语言中利用SVM-RFE机器学习算法筛选关键因子
  • (循环依赖问题)学习spring的第九天
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • (转)VC++中ondraw在什么时候调用的
  • (转载)hibernate缓存
  • ./configure,make,make install的作用(转)
  • .htaccess配置常用技巧
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .NET Core引入性能分析引导优化
  • .NET 分布式技术比较
  • .NET 依赖注入和配置系统