美文网首页iOS技术文章iOS进阶OpenGL
Android使用Direct Textures提高glRead

Android使用Direct Textures提高glRead

作者: 熊皮皮 | 来源:发表于2017-02-05 15:52 被阅读4393次

    本文档描述改善glReadPixels读取帧缓冲区数据在华为等使用Mali GPU的手机上速度慢的办法。因产品要求应用支持最低平台为Android 4.1,故无法通过Pixel Buffer Object(OpenGL ES 3.0接口,需Android 4.3)提高glReadPixels性能。那么,剩下就一种办法:使用Direct Textures(EGLImage),这是EGL拓展,适用于需要经常更新纹理数据的场合,比如逐帧更新。可用于OpenGL ES 1.0及2.0

    代码示例

    Direct Textures用glEGLImageTargetTexture2DOES接口替代glReadPixels,它依赖于GraphicBuffer数据结构。算法描述如下:

    • 指定宽高及像素格式初始化GraphicsBuffer
    • 锁定
    • 读写纹理数据
    • 解锁

    值得注意的是,一旦解锁,写入的数据将立即反馈在屏幕上。

    初始化代码示例如下:

    #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及其等价函数默认读取后台帧缓冲区,故需要在eglSwapBuffers前调用这些函数。

    根据android 下使用Direct Texture,Android 3.0等老版本因Android EGL库存在缺陷,故需手工加载Mali等GPU驱动。在3.0之后,此问题被修复,因而直接用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等常规初始化操作
    

    编译环境

    由于Android NDK不暴露以上接口,意味着使用Direct Textures需要下载Android源码,编译并打包成动态库。接着,通过dlopen或eglGetProcAddress获取eglCreateImageKHR等接口的地址,再进行调用。

    编译时包含头文件:

    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/
    

    链接选项:

    LOCAL_LDLIBS := -llog -lGLESv2 -lEGL -landroid  -lui -landroid_runtime  -ljnigraphics
    

    存在的问题

    根据火狐(Mozilla Firefox)工程师2011年的博客:using direct textures on android,使用Direct Textures因需要调用dlopen接口,Adreno和Mali等GPU驱动不允许常规应用这么操作。

    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.

    此时可尝试使用eglGetProcAddress替换dlopen接口。

    参考

    相关文章

      网友评论

      • yongbaoqiji:android7.0以上不能用是指源码的版本还是测试手机的系统? 两个版本不需要统一吧
      • 郭植裕:glEGLImageTargetTexture2DOES看起来更像是把image和texture绑定起来,不像是把framebuffer的数据读取到graphicbuffer中,请问作者是有实操过这些代码吗?
        熊皮皮:@郭植裕 直接看安卓源码的实现吧
      • KobeGong:作者用的Android源码是哪个版本的?
        熊皮皮:@KobeGong 4.1,直接拷贝手机上编译好的动态库配合头文件也能用
      • b9e79cd36d60:@熊皮皮 NDK里面没有GraphicBuffer的话,想把这个方案封装so提供给app使用没有办法实现了?
      • 758acf585ecc:赞,作者介绍的这种方法正是Android系统底层针对GLES, MediaCodec数据共享的实现方法,这种方式在7.0以下可以使用,但7.0以上没法用了,原因是需要去动态加载的libui.so库(Graphicbuffer),不属于共享库
      • 1ce5b658494e:我们是对视频每一帧做渲染处理的,视频输入输出是nv21格式的数组(没办法改变),加载nv21到gpu 或 从gpu 读回 nv21都很慢,达不到我们的性能要求。今天看你的文章用Direct Textures,感觉茅塞顿开。并且我们做的事情是离屏渲染的。问下这种方法有大坑吗?机型适配上如何?
        1ce5b658494e:@熊皮皮 谢谢提醒,是要淌些水,glreadpixels效率确实很让人难受
        熊皮皮:@1ce5b658494e 在Android 7.0上不能用,可以用PBO。这种方式开发成本略高,有同步等些问题。
      • b396896dab6f:这一通折腾,真的强!当时我发现
        >Android NDK不暴露以上接口
        就直接放弃了
        熊皮皮:@轻口味 7.0用PBO,如果要兼容4.0,得用两套动态库
        轻口味:android7.0以后非public的动态库不能使用了,巨坑无比
        熊皮皮:@psklf :joy:确实很麻烦

      本文标题:Android使用Direct Textures提高glRead

      本文链接:https://www.haomeiwen.com/subject/mvdeittx.html