本文檔描述改善glReadPixels讀取幀緩沖區(qū)數(shù)據(jù)在華為等使用Mali GPU的手機(jī)上速度慢的辦法。因產(chǎn)品要求應(yīng)用支持最低平臺為Android 4.1,故無法通過Pixel Buffer Object(OpenGL ES 3.0接口,需Android 4.3)提高glReadPixels性能。那么,剩下就一種辦法:使用Direct Textures(EGLImage),這是EGL拓展,適用于需要經(jīng)常更新紋理數(shù)據(jù)的場合,比如逐幀更新??捎糜贠penGL ES 1.0及2.0。
代碼示例
Direct Textures用glEGLImageTargetTexture2DOES接口替代glReadPixels,它依賴于GraphicBuffer數(shù)據(jù)結(jié)構(gòu)。算法描述如下:
- 指定寬高及像素格式初始化GraphicsBuffer
- 鎖定
- 讀寫紋理數(shù)據(jù)
- 解鎖
值得注意的是,一旦解鎖,寫入的數(shù)據(jù)將立即反饋在屏幕上。
初始化代碼示例如下:
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <android/native_window.h>
#include <ui/GraphicBuffer.h>
#include <dlfcn.h>
// .......
GraphicBuffer* buffer = new GraphicBuffer(1024/*width*/, 1024/*height*/,
PIXEL_FORMAT_RGB_565,
GraphicBuffer::USAGE_SW_WRITE_OFTEN |
GraphicBuffer::USAGE_HW_TEXTURE);
unsigned char* bits = NULL;
buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, (void**)&bits);
// Write bitmap data into 'bits' here
buffer->unlock();
// Create the EGLImageKHR from the native buffer
EGLint eglImgAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE, EGL_NONE };
EGLImageKHR img = eglCreateImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), EGL_NO_CONTEXT,
EGL_NATIVE_BUFFER_ANDROID,
(EGLClientBuffer)buffer->getNativeBuffer(),
eglImgAttrs);
// Create GL texture, bind to GL_TEXTURE_2D, etc.
// Attach the EGLImage to whatever texture is bound to GL_TEXTURE_2D
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, img);
1、使用glEGLImageTargetTexture2DOES替換glTexImage2D或glTexSubImage2D。
// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0,GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, EGLImage);
2、使用glEGLImageTargetTexture2DOES替換glReadPixels。
// glReadPixels(0, 0,
// textureWidth, textureHeight,
// GL_RGBA,
// GL_UNSIGNED_BYTE,
// dataReadFromFramebuffer);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, pEGLImage);
值得注意的是,由于glReadPixels及其等價(jià)函數(shù)默認(rèn)讀取后臺幀緩沖區(qū),故需要在eglSwapBuffers前調(diào)用這些函數(shù)。
根據(jù)android 下使用Direct Texture,Android 3.0等老版本因Android EGL庫存在缺陷,故需手工加載Mali等GPU驅(qū)動。在3.0之后,此問題被修復(fù),因而直接用eglGetProcAddress替代dlopen更為簡單,完整示例代碼如下所示。
const char* const driver_absolute_path = "/system/lib/egl/libEGL_mali.so";
// On Gingerbread you have to load symbols manually from Mali driver because
// Android EGL library has a bug.
// From ICE CREAM SANDWICH you can freely use the eglGetProcAddress function.
// You might be able to get away with just eglGetProcAddress (no dlopen).
// Try it, else revert to the following code
void* dso = dlopen(driver_absolute_path, RTLD_LAZY);
if (dso != 0)
{
LOGI("dlopen: SUCCEEDED");
_eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)dlsym(dso, "eglCreateImageKHR");
_eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) dlsym(dso,"eglDestroyImageKHR");
}
else
{
LOGI("dlopen: FAILED! Loading functions in common way!");
_eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR");
_eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR");
}
if(_eglCreateImageKHR == NULL)
{
LOGE("Error: Failed to find eglCreateImageKHR at %s:%in", __FILE__, __LINE__);
exit(1);
}
if(_eglDestroyImageKHR == NULL)
{
LOGE("Error: Failed to find eglDestroyImageKHR at %s:%in", __FILE__, __LINE__);
exit(1);
}
_glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) eglGetProcAddress("glEGLImageTargetTexture2DOES");
if(_glEGLImageTargetTexture2DOES == NULL)
{
LOGI("Error: Failed to find glEGLImageTargetTexture2DOES at %s:%in", __FILE__, __LINE__);
return 0;
}
graphicBuffer = new GraphicBuffer(width, height,
HAL_PIXEL_FORMAT_RGBA_8888,
GraphicBuffer::USAGE_HW_TEXTURE |
GraphicBuffer::USAGE_HW_2D |
GRALLOC_USAGE_SW_READ_OFTEN |
GRALLOC_USAGE_SW_WRITE_OFTEN);
status_t err = graphicBuffer->initCheck();
if (err != NO_ERROR)
{
LOGI("Error: %sn", strerror(-err));
return 0;
}
GGLSurface t;
graphicBuffer->lock(&t, GRALLOC_USAGE_SW_WRITE_OFTEN);
memset(t.data, 128, t.stride * t.height);
graphicBuffer->unlock();
// Retrieve andorid native buffer
android_native_buffer_t *anb = graphicBuffer->getNativeBuffer();
// create the new EGLImageKHR
const EGLint attrs[] =
{
EGL_IMAGE_PRESERVED_KHR,
EGL_TRUE,
EGL_NONE,
EGL_NONE
};
mEngine.mTexture.pEGLImage = _eglCreateImageKHR(eglGetCurrentDisplay(),
mEngine.nContext,
EGL_NATIVE_BUFFER_ANDROID,
(EGLClientBuffer)anb,
attrs);
if(mEngine.mTexture.pEGLImage == EGL_NO_IMAGE_KHR)
{
LOGI("Error: eglCreateImage() failed at %s:%in", __FILE__, __LINE__);
return 0;
}
checkGlError("eglCreateImageKHR");
// Create Program等常規(guī)初始化操作
編譯環(huán)境
由于Android NDK不暴露以上接口,意味著使用Direct Textures需要下載Android源碼,編譯并打包成動態(tài)庫。接著,通過dlopen或eglGetProcAddress獲取eglCreateImageKHR等接口的地址,再進(jìn)行調(diào)用。
編譯時(shí)包含頭文件:
LOCAL_C_INCLUDES +=
$(ANDROID_SRC_HOME)/frameworks/base/core/jni/android/graphics
$(ANDROID_SRC_HOME)/frameworks/base/include/
$(ANDROID_SRC_HOME)/hardware/libhardware/include
$(ANDROID_SRC_HOME)/system/core/include
$(ANDROID_SRC_HOME)/frameworks/base/native/include/
$(ANDROID_SRC_HOME)/frameworks/base/opengl/include/
鏈接選項(xiàng):
LOCAL_LDLIBS := -llog -lGLESv2 -lEGL -landroid -lui -landroid_runtime -ljnigraphics
存在的問題
根據(jù)火狐(Mozilla Firefox)工程師2011年的博客:using direct textures on android,使用Direct Textures因需要調(diào)用dlopen接口,Adreno和Mali等GPU驅(qū)動不允許常規(guī)應(yīng)用這么操作。
If you’ve ever used the Android NDK, it won’t be surprising that GraphicBuffer (or anything similar) doesn’t exist there. In order to use any of this in your app you’ll need to resort to
dlopen
hacks. It’s a pretty depressing situation. Google uses this all over the OS, but doesn’t seem to think that apps need a high performance API. But wait, it gets worse. Even after jumping through these hoops, some gralloc drivers don’t allow regular apps to play ball. So far, testing indicates that this is the case on Adreno and Mali GPUs. Thankfully, PowerVR and Tegra allow it, which covers a fair number of devices.
此時(shí)可嘗試使用eglGetProcAddress替換dlopen接口。
參考
- using direct textures on android
- android 下使用Direct Texture
- EGL_KHR_image_base
- EGLImage - updating a texture without copying memory under Android 起始提供了一個預(yù)編譯的Android運(yùn)行環(huán)境的包裝資源,然而被Mali GPU的合作伙伴要求撤消,故此鏈接參考意義不大。