使用EGL_KHR_image_base替换glReadPixels以实现更快的像素复制

6
我正在尝试在Android原生进程中使用EGL_KHR_image_base来替换glReadPixels,因为它速度太慢了(1280x800 RGBA需要220毫秒)。
到目前为止,我只得到了一个空缓冲区(仅包含零)。
uint8_t *ptr;
GLuint mTexture;
status_t error;

GraphicBufferAlloc* mGraphicBufferAlloc  = new GraphicBufferAlloc();    
sp<GraphicBuffer> window = mGraphicBufferAlloc->createGraphicBuffer(width, height, PIXEL_FORMAT_RGBA_8888, GraphicBuffer::USAGE_SW_READ_OFTEN | GraphicBuffer::USAGE_HW_TEXTURE,&error);
EGLClientBuffer buffer = (EGLClientBuffer)window->getNativeBuffer();
EGLint eglImageAttributes[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_MATCH_FORMAT_KHR,  EGL_FORMAT_RGBA_8888_KHR, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
EGLImageKHR image = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,buffer, eglImageAttributes);

glGenTextures(1, &mTexture);    
glBindTexture(GL_TEXTURE_2D, mTexture);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
window->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, (void**)&ptr); 
memcpy(texture, ptr, width * height * 4);
window->unlock();

我做错了什么?

你的宽度和高度是否不为零? - HalR
是的,我检查过了。它是1280*800。 - jacob
2个回答

8
您正在创建一个空缓冲区,然后从中读取内容。按照代码进行以下步骤:
GraphicBufferAlloc* mGraphicBufferAlloc = new GraphicBufferAlloc();
sp<GraphicBuffer> window = mGraphicBufferAlloc->createGraphicBuffer(width, height, PIXEL_FORMAT_RGBA_8888, GraphicBuffer::USAGE_SW_READ_OFTEN | GraphicBuffer::USAGE_HW_TEXTURE,&error);

这将创建一个新的GraphicBuffer(有时称为“gralloc缓冲区”),具有指定的尺寸和像素格式。使用标志允许它被用作纹理或从软件中读取,这正是您想要的。
EGLClientBuffer buffer = (EGLClientBuffer)window->getNativeBuffer();
EGLint eglImageAttributes[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_MATCH_FORMAT_KHR,  EGL_FORMAT_RGBA_8888_KHR, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
EGLImageKHR image = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,buffer, eglImageAttributes);

这个操作拿到了一个ANativeWindow对象(作为底层缓冲区队列),并将一个EGLImage“句柄”附加到它上面。

glGenTextures(1, &mTexture);    
glBindTexture(GL_TEXTURE_2D, mTexture);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);

这将创建一个新的纹理对象,并将EGLImage附加到它上面。因此,现在可以使用ANativeWindow作为纹理对象。

window->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, (void**)&ptr); 
memcpy(texture, ptr, width * height * 4);
window->unlock();

这会将缓冲区锁定以进行读取,将数据从中复制出来并解锁。由于您尚未绘制任何内容,因此没有内容可读取。

要使其有用,您必须将某些东西渲染到纹理中。您可以通过创建FBO并将纹理附加为颜色缓冲区来实现此目的,或者使用glCopyTexImage2D()从帧缓冲区复制像素到纹理中。

在调用grallocBuffer-> lock()之前,我成功地添加了以下内容:

glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, width, height, 0);
glFinish();
glFinish()是必要的,以确保GL在我们尝试查看像素之前已经完成了复制。
编辑:我的同事建议将GL_TEXTURE_2D更改为GL_TEXTURE_EXTERNAL_OES。还没有尝试过。
编辑:也可以参考此问题

谢谢你的帮助,它对你有用吗? 即使我添加了以下内容: glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, width, height, 0); glFinish(); 我仍然得到一个空缓冲区(尽管现在需要大约50毫秒)。你能看一下吗? https://dl.dropboxusercontent.com/u/36894441/Overlay.cpp https://dl.dropboxusercontent.com/u/36894441/EglWindow.cpp - jacob
我现在看到它不是完全空的缓冲区,而是每4096000个字节的开头有数据,其余全部为零。你能发一下你尝试过的代码吗? - jacob
嗯,我没有将帧保存为图像,只是检查它是否全为零。在您的函数开头添加glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT);。这将用50%的灰色填充帧,确保没有字节值为零。然后你会看到什么?(我需要几天时间才能发布我的实验。) - fadden
在我测试的Nexus 5上,我得到了看起来不错的数据,但是在第一帧之后,'glCopyTexImage2D()'开始抛出错误。在Nexus 10上,只要我不指定任何属性给'eglCreateImageKHR()',一切都能正常返回,但缓冲区总是填充为零(你也看到了同样的情况)。所以有些地方还不太对。 - fadden
@fadden 我想要复制像素,当手机支持OpenGL es 3.0时,我已经从PBO中复制了像素。当它们只支持OpenGL es 2.0时,我想在Android NDK项目中使用GraphicBufferAlloc。但是,当我编译cpp文件时,我遇到了一个错误:“error:'GraphicBufferAlloc' was not declared in this scope”,我不知道需要导入哪个头文件。你能帮我吗?我的包含文件如下:#include <jni.h> #include <stdint.h> #include <sys/types.h> #include <EGL/egl.h> #include <EGL/eglext.h> #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> - dragonfly
@jacob 亲爱的jacob,我尝试做和你一样的事情,但是我甚至无法使用ndk成功编译源代码。原因应该是GraphicBufferAlloc不在ndk中。但是为什么你可以编译源代码呢?你是怎么做到的?能否给我一些帮助? - dragonfly

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接