OpenGL纹理读取方案

2,636 阅读2分钟

glReadPixels

glReadPixels读取的是当前绑定的FBO的颜色缓冲区,所以当使用多个 FBO时,需要先绑定我们要读取的PBO。

调用glReadPixels时,首先会把Command Queue的所有GL命令发送到GPU,并且等待GPU执行完所有指令(隐式调用了glFinish),然后再读取像素数据,所以耗时比较久。

PBO

ImageReader

EGLImage

  1. API 26之前,EGLImageKHR
  2. API 26以后,Android NDK提供了Hardware Buffer APIs类
// EGLClientBuffer可以通过GraphicBuffer获取
EGLClientBuffer cbuf = static_cast<EGLClientBuffer>(graphicBuffer->getNativeBuffer());
// EGLClientBuffer可以通过AHardwareBuffer获取
EGLClientBuffer clientBuf = eglGetNativeClientBufferANDROID(AHardwareBuffer);

// 创建EGLImageKHR,在Android平台上,ctx可以是EGL_NO_CONTEXT,target是EGL_NATIVE_BUFFER_ANDROID,buffer是由GraphicBuffer创建来的。
EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);


// 销毁EGLImageKHR
EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR image);

NDK提供了hardware_buffer_jni.h头文件用于转换Java层HardwareBuffer类和Native层AHardwareBuffer结构体。Java层HardwareBuffer类其实就是对Native层AHardwareBuffer结构的封装。

还提供了hardware_buffer.h用于操作AHardwareBuffer,具体实现在AHardwareBuffer.cpp源文件。

AHardwareBuffer和GraphicBuffer是一个结构体,之间可以直接转换,如下所示:

// AHardwareBuffer.cpp
GraphicBuffer* AHardwareBuffer_to_GraphicBuffer(AHardwareBuffer* buffer) {
    return reinterpret_cast<GraphicBuffer*>(buffer);
}

AHardwareBuffer* AHardwareBuffer_from_GraphicBuffer(GraphicBuffer* buffer) {
    return reinterpret_cast<AHardwareBuffer*>(buffer);
}

GraphicBuffer并没有对外暴露接口,而是从API26开始,通过AHardwareBuffer结构体对外暴露了操作跨进程零拷贝图形缓冲区的能力。

通过AHardwareBuffer,可以从GPU零拷贝获取纹理像素数据,解决了glReadPixels的耗时问题。

  1. 先创建AHardwareBuffer,然后通过AHardwareBuffer创建EGLImageKHR(eglCreateImageKHR)。
  2. 接着把EGLImageKHR绑定到纹理(glEGLImageTargetTexture2DOES),这里的纹理可以是GL_TEXTURE_2D,也可以是GL_TEXTURE_EXTERNAL_OES。
  3. 把纹理绑定到FBO,进行OpenGL绘制。此时绘制内容会保存到AHardwareBuffer。
  4. 通过CPU访问方式从AHardwareBuffer copy出像素数据,AHardwareBuffer表示的图形数据在CPU和GPU之间是共享的,所以是零拷贝(不同进程间也是共享的)。具体方式是:先通过AHardwareBuffer_lock函数获取内存地址,然后从内存地址memcpy出像素数据,最后配对调用AHardwareBuffer_unlock函数。

HardwareBuffer(GraphicBuffer) <-> EGLImageKHR <-> 纹理

借助HardwareBuffer和EGLImageKHR,既可以实现把HardwareBuffer中的像素数据上传到纹理,也可以实现把纹理像素数据下载到HardwareBuffer。

  • CPU -> AHardwareBuffer -> 纹理
  • 纹理 -> AHardwareBuffer -> CPU

参考文档

  1. 谈一谈Android上的SurfaceTexture
  2. Android OpenGL 渲染图像读取哪家强?
  3. VideoEditor的纹理读取优化