• Android OpenGL ES 离屏渲染(offscreen render)


    通常在Android上使用OpenGL ES,都是希望把渲染后的结果显示在屏幕上,例如图片处理、模型显示等。这种情况下,只需要使用Android API中提供的GLSurfaceView类和Renderer类,在这两个类提供的初始化、回调函数中设置/编写相应的代码即可。不过,如果不希望把渲染结果显示在屏幕上,也就是所说的离屏渲染(offscreen render),这两个类就帮不上忙了。在此介绍一下如何在Android系统上做OpenGL ES 的离屏渲染。

    1.基础知识

    要想使用OpenGL ES,一般包括如下几个步骤:

      (1)EGL初始化

      (2)OpenGL ES初始化

      (3)OpenGL ES设置选项&绘制

      (4)OpenGL ES资源释放(可选)

      (5)EGL资源释放

    Android提供的GLSurfaceView和Renderer自动完成了(1)(5)两个部分,这部分只需要开发者做一些简单配置即可。另外(4)这一步是可选的,因为随着EGL中上下文的销毁,openGL ES用到的资源也跟着释放了。因此只有(2)(3)是开发者必须做的。这大大简化了开发过程,但是灵活性也有所降低,利用这两个类是无法完成offscreen render的。要想完成offscreen render其实也很简单,相信大家也都猜到了,只要我们把(1)~(5)都自己完成就可以了。后续部分的代码大部分都是C/C++,少部分是Java。

    2.EGL初始化

    EGL的功能是将OpenGL ES API和设备当前的窗口系统粘合在一起,起到了沟通桥梁的作用。不同设备的窗口系统千变万化,但是OpenGL ES提供的API却是统一的,所以EGL需要协调当前设备的窗口系统和OpenGL ES。下面EGL初始化的代码我是用C++写的,然后通过jni调用。Android在Java层面上也提供了对应的Java接口函数。

    static EGLConfig eglConf;
    static EGLSurface eglSurface;
    static EGLContext eglCtx;
    static EGLDisplay eglDisp;
    
    JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_init
    (JNIEnv*env,jobject obj)
    {
        // EGL config attributes
        const EGLint confAttr[] =
        {
                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,// very important!
                EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,//EGL_WINDOW_BIT EGL_PBUFFER_BIT we will create a pixelbuffer surface
                EGL_RED_SIZE,   8,
                EGL_GREEN_SIZE, 8,
                EGL_BLUE_SIZE,  8,
                EGL_ALPHA_SIZE, 8,// if you need the alpha channel
                EGL_DEPTH_SIZE, 8,// if you need the depth buffer
                EGL_STENCIL_SIZE,8,
                EGL_NONE
        };
        // EGL context attributes
        const EGLint ctxAttr[] = {
                EGL_CONTEXT_CLIENT_VERSION, 2,// very important!
                EGL_NONE
        };
        // surface attributes
        // the surface size is set to the input frame size
        const EGLint surfaceAttr[] = {
                 EGL_WIDTH,512,
                 EGL_HEIGHT,512,
                 EGL_NONE
        };
        EGLint eglMajVers, eglMinVers;
        EGLint numConfigs;
    
        eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        if(eglDisp == EGL_NO_DISPLAY)
        {
            //Unable to open connection to local windowing system
            LOGI("Unable to open connection to local windowing system");
        }
        if(!eglInitialize(eglDisp, &eglMajVers, &eglMinVers))
        {
            // Unable to initialize EGL. Handle and recover
            LOGI("Unable to initialize EGL");
        }
        LOGI("EGL init with version %d.%d", eglMajVers, eglMinVers);
        // choose the first config, i.e. best config
        if(!eglChooseConfig(eglDisp, confAttr, &eglConf, 1, &numConfigs))
        {
            LOGI("some config is wrong");
        }
        else
        {
            LOGI("all configs is OK");
        }
        // create a pixelbuffer surface
        eglSurface = eglCreatePbufferSurface(eglDisp, eglConf, surfaceAttr);
        if(eglSurface == EGL_NO_SURFACE)
        {
            switch(eglGetError())
            {
            case EGL_BAD_ALLOC:
            // Not enough resources available. Handle and recover
                LOGI("Not enough resources available");
                break;
            case EGL_BAD_CONFIG:
            // Verify that provided EGLConfig is valid
                LOGI("provided EGLConfig is invalid");
                break;
            case EGL_BAD_PARAMETER:
            // Verify that the EGL_WIDTH and EGL_HEIGHT are
            // non-negative values
                LOGI("provided EGL_WIDTH and EGL_HEIGHT is invalid");
                break;
            case EGL_BAD_MATCH:
            // Check window and EGLConfig attributes to determine
            // compatibility and pbuffer-texture parameters
                LOGI("Check window and EGLConfig attributes");
                break;
            }
        }
        eglCtx = eglCreateContext(eglDisp, eglConf, EGL_NO_CONTEXT, ctxAttr);
        if(eglCtx == EGL_NO_CONTEXT)
        {
            EGLint error = eglGetError();
            if(error == EGL_BAD_CONFIG)
            {
                // Handle error and recover
                LOGI("EGL_BAD_CONFIG");
            }
        }
        if(!eglMakeCurrent(eglDisp, eglSurface, eglSurface, eglCtx))
        {
            LOGI("MakeCurrent failed");
        }
        LOGI("initialize success!");
    }

    代码比较长,不过大部分都是检测当前函数调用是否出错的,核心的函数只有6个,只要它们的调用没有问题即可:

    eglGetDisplay(EGL_DEFAULT_DISPLAY)

    eglInitialize(eglDisp, &eglMajVers, &eglMinVers)

    eglChooseConfig(eglDisp, confAttr, &eglConf, 1, &numConfigs)

    eglCreatePbufferSurface(eglDisp, eglConf, surfaceAttr)

    eglCreateContext(eglDisp, eglConf, EGL_NO_CONTEXT, ctxAttr)

    eglMakeCurrent(eglDisp, eglSurface, eglSurface, eglCtx)

    这些函数中参数的具体定义可以在这个网站找到:  https://www.khronos.org/registry/egl/sdk/docs/man/

    需要说明的是,eglChooseConfig(eglDisp, confAttr, &eglConf, 1, &numConfigs)中confAttr参数一定要有EGL_SURFACE_TYPE,EGL_PBUFFER_BIT这个配置,它决定了是渲染surface的类型,是屏幕还是内存。另外,还有一些选项和OpenGL ES版本有关,具体选用1.x还是2.x,这个视个人情况而定,我使用的是2.x。

    3.OpenGL ES部分

    为了方便说明,我把OpenGL ES部分都写在一起了,代码如下:

    JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_draw
    (JNIEnv*env,jobject obj)
    {
        const char*vertex_shader=vertex_shader_fix;
        const char*fragment_shader=fragment_shader_simple;
        glPixelStorei(GL_UNPACK_ALIGNMENT,1);
        glClearColor(0.0,0.0,0.0,0.0);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LESS);
        glCullFace(GL_BACK);
        glViewport(0,0,512,512);
        GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader,1,&vertex_shader,NULL);
        glCompileShader(vertexShader);
        GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader,1,&fragment_shader,NULL);
        glCompileShader(fragmentShader);
        GLuint program = glCreateProgram();
        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        glLinkProgram(program);
        glUseProgram(program);
        GLuint aPositionLocation =glGetAttribLocation(program, "a_Position");
        glVertexAttribPointer(aPositionLocation,2,GL_FLOAT,GL_FALSE,0,tableVerticesWithTriangles);
        glEnableVertexAttribArray(aPositionLocation);
        //draw something
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawArrays(GL_TRIANGLES,0,6);
        eglSwapBuffers(eglDisp,eglSurface);
    }

    需要说明的是,绘制完成后,需要调用eglSwapBuffers(eglDisp,eglSurface)函数,因为在初始化EGL时默认设置的是双缓冲模式,即一份缓冲用于绘制图像,一份缓冲用于显示图像,每次显示时需要交换两份缓冲,把绘制好的图像显示出来。

    上面openGL绘制需要的两个shader在此不写了,可供下载的demo里会有。

    4.EGL资源释放

    最后还差一个函数,用于EGL资源释放,代码如下:

    JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_release
    (JNIEnv*env,jobject obj)
    {
        eglMakeCurrent(eglDisp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        eglDestroyContext(eglDisp, eglCtx);
        eglDestroySurface(eglDisp, eglSurface);
        eglTerminate(eglDisp);
    
        eglDisp = EGL_NO_DISPLAY;
        eglSurface = EGL_NO_SURFACE;
        eglCtx = EGL_NO_CONTEXT;
    }

    5.总结

    大功告成,其实吃透了openGL ES的原理后,整个过程还是很简单的。为了测试是否真的做到了offscreen render,我们把framebuffer中的图片保存成图片,来检测结果。代码如下:

            RGBABuffer = IntBuffer.allocate(512 * 512);
            MyGles.release();
            MyGles.init();
            MyGles.draw();
            RGBABuffer.position(0);
            GLES20.glReadPixels(0, 0, 512, 512,GLES20.GL_RGBA,GLES20.GL_UNSIGNED_BYTE,RGBABuffer);
            int[] modelData=RGBABuffer.array();
            int[] ArData=new int[modelData.length];
            int offset1, offset2;
            for (int i = 0; i < 512; i++) {
                offset1 = i * 512;
                offset2 = (512 - i - 1) * 512;
                for (int j = 0; j < 512; j++) {
                    int texturePixel = modelData[offset1 + j];
                    int blue = (texturePixel >> 16) & 0xff;
                    int red = (texturePixel << 16) & 0x00ff0000;
                    int pixel = (texturePixel & 0xff00ff00) | red | blue;
                    ArData[offset2 + j] = pixel;
                }
            }
            Bitmap modelBitmap = Bitmap.createBitmap(ArData,512,512,Bitmap.Config.ARGB_8888);
            saveBitmap(modelBitmap);

    要注意的是,因为openGL ES framebuffer和图像通道的存储顺序不同,需要做一次ABGR到ARGB的转换。

    一般来说,offscreen render的用处主要是做GPU加速,如果你的算法是计算密集型的,可以通过一些方法将其转化成位图形式,作为纹理(texture)载入到GPU显存中,再利用GPU的并行计算能力,对其进行处理,最后利用glReadPixels将计算结果读取到内存中。就说这么多吧,更多的用法还需要大家的发掘。

    这里是demo下载链接

  • 相关阅读:
    【最短路】The 2019 Asia Nanchang First Round Online Programming Contest Fire-Fighting Hero (Dijkstra)
    【积累】The 2019 Asia Nanchang First Round Online Programming Contest The Nth Item (矩阵快速幂 k进制快速幂)
    【线段树】The Preliminary Contest for ICPC Asia Xuzhou 2019 Colorful String(回文树+线段树+状压/bitset)
    allure参数说明及代码示例
    Idea+maven+testng+reportng生成测试报告
    ubuntu 16.04 镜像下载
    new AndroidDriver报错java.lang.NoSuchMethodError: com.google.common.base.Throwables.throwIfUnchecked
    Appium常用的API
    Appium常用的定位方法
    adb 指令总结
  • 原文地址:https://www.cnblogs.com/hrlnw/p/4642272.html
Copyright © 2020-2023  润新知