glReadPixels 是 OpenGL ES 的 API ,OpenGL ES 2.0 和 3.0 均支持。 使用非常方便,下面一行代码即可搞定,但是效率也是最低的。
glReadPixels(0, 0, outImage.width, outImage.height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
当调用 glReadPixels 时,首先会影响 CPU 时钟周期,同时 GPU 会等待当前帧绘制完成,读取像素完成之后,才开始下一帧的计算,造成渲染管线停滞。
值得注意的是 glReadPixels 读取的是当前绑定 FBO 的颜色缓冲区图像,所以当使用多个 FBO(帧缓冲区对象)时,需要确定好我们要读那个 FBO 的颜色缓冲区。
glReadPixels 性能瓶颈一般出现在大分辨率图像的读取,所以目前通用的优化方法是在 shader 中将处理完成的 RGBA 转成 YUV (一般是 YUYV),然后基于 RGBA 的格式读出 YUV 图像,这样传输数据量会降低一半,性能提升明显。
PBO(Pixel Buffer Object)PBO 是 OpenGL ES 3.0 的概念,称为像素缓冲区对象,主要被用于异步像素传输操作。 PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。
PBO 类似于 VBO(顶点缓冲区对象),PBO 开辟的也是 GPU 缓存,而存储的是图像数据。
PBO 可以在 GPU 的缓存间快速传递像素数据,不影响 CPU 时钟周期,除此之外,PBO 还支持异步传输。
PBO 类似于“以空间换时间”策略,在使用一个 PBO 的情况下,性能无法有效地提升,通常需要多个 PBO 交替配合使用。
2 个 PBO read pixels
如上图所示,利用 2 个 PBO 从帧缓冲区读回图像数据,使用 glReadPixels 通知 GPU 将图像数据从帧缓冲区读回到 PBO1 中,同时 CPU 可以直接处理 PBO2 中的图像数据。
关于 PBO 的详细使用可以参考文章:OpenGL ES 3.0 开发连载(22):PBO , 这里不再赘述。
ImageReaderImageReader 是 Android SDK 提供的 Java 层对象,其内部会创建一个 Surface 对象。
常用于 Android Camera2.0 相机预览,通过 addTarget 将 Surface 对象作为相机预览图像的输出载体,通过回调接口获取预览图像。
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 2);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
mSurface = mImageReader.getSurface();
private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
if (image != null) {
//处理相机预览图像 image
image.close();
}
}
};
那么 ImageReader 怎么跟 OpenGL ES 结合使用呢?
我们知道利用 EGL 创建 OpenGL 上下文环境时,eglCreateWindowSurface 需要传入 ANativeWindow 对象,而 ANativeWindow 又基于 Surface 对象创建的。
那我们可以利用 ImageReader 对象的 Surface 对象作为 OpenGL 展示渲染结果的 Window Surface ,每次渲染的结果可以通过 ImageReader 对象的回调获取。
HardwareBufferHardwareBuffer 是一个更底层的对象,代表可由各种硬件单元访问的缓冲区。特别地,HardwareBuffer 可以映射到各种硬件系统的存储器,例如 GPU 、 传感器或上下文集线器或其他辅助处理单元。
HardwareBuffer 是 Android 8 API >= 26 提供的用于替换 GraphicBuffer 的接口,在 API PBO > ImageReader > glReadPixels 。
结合实测性能和实现难度,Native 层建议选择 PBO 方式,超大分辨率建议尝试 HardwareBuffer 方式,Java 层建议使用 ImageReader 方式。