Opengl ES之EGL

作者: FlyerGo | 来源:发表于2022-09-07 10:35 被阅读0次

    前言

    前面我们发布了一系列的入门教程,例如C++系列的指针扫盲、多线程的使用等,JNI入门系列,ffmpeg入门系列等,有感兴趣的童鞋们可以关注往回自行查阅。

    今天我们的主题依然是音视频开发的范畴,做过音视频开发的都知道Opengl也是音视频开发中的一项重要技能,特别是涉及到视频录制、特效处理、画质渲染细分功能。因此后续笔者打算再出一系列的Opengl ES的学习笔记,
    希望能与大家共同温故知新。

    因为前面介绍了一些NDK和C++的教程,所以为了巩固,后续的一些demo多以NDK的形式呈现给大家,使用Opengl ES3的版本。

    今天我们的主题是Opengl ES的第一篇-->EGL

    EGL是什么

    众所周知,Opengl是跨平台的,那么面对各种平台的差异性,Opengl是如何抹平而做到跨平台的呢?这也许就是EGL的功劳吧,简单地说EGL就是Opengl和平台各平台之间的一个适配器,是一系列的接口,具体实现是由具体的设备厂商实现的。

    EGL 是渲染 API(如 OpenGL ES)和原生窗口系统之间的接口.通常来说,OpenGL 是一个操作 GPU 的 API,它通过驱动向 GPU 发送相关指令,控制图形渲染管线状态机的运行状态,但是当涉及到与本地窗口系统进行交互时,就需要这么一个中间层,且它最好是与平台无关的,
    因此 EGL 被设计出来,作为 OpenGL 和原生窗口系统之间的桥梁。

    [图片上传失败...(image-9ed82b-1662518126650)]

    EGL API 是独立于 OpenGL 各版本标准的独立的一套 API,其主要作用是为 OpenGL 指令 创建上下文 Context 、绘制目标 Surface 、配置 FrameBuffer 属性、Swap 提交绘制结果等。EGL提供如下机制:

    • 与设备的原生窗口系统通信
    • 查询绘图表面的可用类型和配置
    • 创建绘图表面
    • 在 OpenGL ES 和其他图形渲染 API 之间同步渲染
    • 管理纹理贴图等渲染资源

    下面这张图可简要看出EGL的接口能力:
    [图片上传失败...(image-14b629-1662518126651)]

    EGL创建流程

    想要在安卓上使用Opengl ES我们可以直接使用GLSurfaceView来进行Opengl的渲染,因为GLSurfaceView内部给我们封装好了EGL环境和渲染线程。如果我们想要更高的拓展性,我们也使用SurfaceView,然后参考SurfaceView中的EGL环境搭建、线程模型来
    自行搭建Opengl ES的渲染环境。

    本着学习探索的目的,我们尝试在NDK搭建EGL环境。

    下面这张图展示的是安卓系统上EGL的主要使用API:
    [图片上传失败...(image-c07680-1662518126651)]

    需要说明的一点是EGL是单线程模型的,也就说说EGL环境的创建、渲染操作、EGL环境的销毁都必须在同一个线程内完成,否则是无效的。当然我们可以通过共享EGL上下文来做多多线程渲染,但这些都是后话了...

    任何OpenGL ES应用程序都必须在开始渲染之前使用EGL执行如下任务:

    1. 查询并初始化设备商可用的显示器。
    2. 创建渲染表面。
      EGL中创建的表面可以分类为屏幕上的表面或者屏幕外的表面。屏幕上的表面连接到原生窗口系统,而屏幕外的表面是不显示但是可以用作渲染表面的像素缓冲区。这些表面可以用来渲染纹理,并可以在多个Khronos API之间共享。
    3. 创建渲染上下文。
      EGL是创建OpenGL ES渲染上下文所必需的。这个上下文必须连接到合适的表面才能开始渲染。

    下面是EGL环境创建的主要流程:
    [图片上传失败...(image-911bb2-1662518126651)]

    说完烦躁的基础理论,那就放码过来吧!!!

    使用Android Studio创建一个Native工程,然后配置好CMakeLists.txt引入相关库:

    
    cmake_minimum_required(VERSION 3.18.1)
    
    project("learn")
    
    #找到包含所有的cpp文件
    file(GLOB allCpp *.cpp **/**.cpp **/**/**.cpp  **/**/**/**.cpp  **/**/**/**/**.cpp)
    
    add_library( # Sets the name of the library.
            # 库名称
            learn
    
            SHARED
    
            ${allCpp})
    
    target_link_libraries(
            learn
            # 引入egl
            egl
            # 引入gles 3
            GLESv3
            # 安卓相关库
            android
            # 安卓log
            log)
    

    下面我们创建一个与Native映射的EGLHelper类:

    package com.fly.opengles.learn.egl;
    
    import android.view.Surface;
    
    public class EGLHelper {
        protected long nativePtr;
    
        public void surfaceCreated(Surface surface) {
            nativePtrInit();
            n_surfaceCreated(nativePtr,surface);
        }
    
        public void surfaceChanged(int width, int height) {
            nativePtrInit();
            n_surfaceChanged(nativePtr,width,height);
        }
    
        public void surfaceDestroyed() {
            if(nativePtr != 0){
                n_surfaceDestroyed(nativePtr);
                nativePtr = 0;
            }
        }
    
        private void nativePtrInit(){
            if(nativePtr == 0){
                nativePtr = n_nativePtrInit();
            }
        }
    
        private native long n_nativePtrInit();
        private native void n_surfaceCreated(long nativePtr,Surface surface);
        private native void n_surfaceChanged(long nativePtr,int width, int height);
        private native void n_surfaceDestroyed(long nativePtr);
    }
    

    然后自定义一个MySurfaceView继承于SurfaceView,在它的Callback回调方法中对EGL进行操作:

    public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    
        private EGLHelper eglHelper;
    
        public MySurfaceView(Context context) {
            this(context,null);
        }
    
        public MySurfaceView(Context context, AttributeSet attrs) {
            super(context, attrs);
            eglHelper = new EGLHelper();
            getHolder().addCallback(this);
        }
    
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
            eglHelper.surfaceCreated(surfaceHolder.getSurface());
        }
    
        @Override
        public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int w, int h) {
            eglHelper.surfaceChanged(w,h);
        }
    
        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
            eglHelper.surfaceDestroyed();
        }
    }
    

    测试效果时,我们在布局中使用我们自定义好MySurfaceView即可,自此java层代码编写完毕,在NDK层我们将EGL环境创建完毕后即可通过MySurfaceView看到渲染结果。

    为了方便调试和debug,我们定义Log.h日志工具:

    #ifndef NDK_OPENGLES_LEARN_LOG_H
    #define NDK_OPENGLES_LEARN_LOG_H
    
    #include "android/log.h"
    
    #define LOGD(FORMAT, ...) __android_log_print(ANDROID_LOG_DEBUG, "fly_learn_opengl", FORMAT, ##__VA_ARGS__);
    #define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR, "fly_learn_opengl", FORMAT, ##__VA_ARGS__);
    
    #endif //NDK_OPENGLES_LEARN_LOG_H
    

    将EGL的相关操作封装在类C++的类EglHelper中:

    EglHelper.h
    
    #ifndef NDK_OPENGLES_LEARN_EGLHELPER_H
    #define NDK_OPENGLES_LEARN_EGLHELPER_H
    
    #include "EGL/egl.h"
    
    class EglHelper {
    
    public:
        EGLDisplay  mEglDisplay;
        EGLSurface  mEglSurface;
        EGLConfig  mEglConfig;
        EGLContext mEglContext;
    
    public:
        EglHelper();
        ~EglHelper();
        int initEgl(EGLNativeWindowType win);
        int swapBuffers();
        void destroyEgl();
    };
    
    
    #endif
    

    EglHelper.cpp主要实现如下,EGL的主要创建过程在函数initEgl中,具体看注释:

    #include "EglHelper.h"
    #include "../utils/Log.h"
    
    EglHelper::EglHelper() {
    
        mEglDisplay = EGL_NO_DISPLAY;
        mEglSurface = EGL_NO_SURFACE;
        mEglContext = EGL_NO_CONTEXT;
        mEglConfig = NULL;
    }
    
    EglHelper::~EglHelper() {
        destroyEgl();
    }
    
    int EglHelper::initEgl(EGLNativeWindowType window) {
    
        //1、获取显示设备
        mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        if(mEglDisplay == EGL_NO_DISPLAY)
        {
            LOGE("eglGetDisplay error");
            return -1;
        }
        // 2、 EGL初始化
        EGLint *version = new EGLint[2];
        if(!eglInitialize(mEglDisplay, &version[0], &version[1]))
        {
            LOGE("eglInitialize error");
            return -1;
        }
    
        //3、 资源配置,例如颜色位数等
        const EGLint attribs[] = {
                EGL_RED_SIZE, 8,
                EGL_GREEN_SIZE, 8,
                EGL_BLUE_SIZE, 8,
                EGL_ALPHA_SIZE, 8,
                EGL_DEPTH_SIZE, 8,
                EGL_STENCIL_SIZE, 8,
                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
                EGL_NONE
        };
    
        EGLint num_config;
        if(!eglChooseConfig(mEglDisplay, attribs, NULL, 1, &num_config))
        {
            LOGE("eglChooseConfig  error 1");
            return -1;
        }
    
        //4、ChooseConfig
        if(!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_config, &num_config))
        {
            LOGE("eglChooseConfig  error 2");
            return -1;
        }
    
        // 5、创建上下文
        int attrib_list[] = {
                EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL_NONE
        };
    
        mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list);
    
        if(mEglContext == EGL_NO_CONTEXT)
        {
            LOGE("eglCreateContext  error");
            return -1;
        }
    
        //6、创建渲染的Surface
        mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, NULL);
        if(mEglSurface == EGL_NO_SURFACE)
        {
            LOGE("eglCreateWindowSurface  error");
            return -1;
        }
    
        // 7、使用
        if(!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext))
        {
            LOGE("eglMakeCurrent  error");
            return -1;
        }
        LOGD("egl init success! ");
        return 0;
    }
    
    int EglHelper::swapBuffers() {
    
        if(mEglDisplay != EGL_NO_DISPLAY && mEglSurface != EGL_NO_SURFACE)
        {
            if(eglSwapBuffers(mEglDisplay, mEglSurface))
            {
                return 0;
            }
        }
        return -1;
    }
    
    void EglHelper::destroyEgl() {
    
        if(mEglDisplay != EGL_NO_DISPLAY)
        {
            eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        }
        if(mEglDisplay != EGL_NO_DISPLAY && mEglSurface != EGL_NO_SURFACE)
        {
            eglDestroySurface(mEglDisplay, mEglSurface);
            mEglSurface = EGL_NO_SURFACE;
        }
        if(mEglDisplay != EGL_NO_DISPLAY && mEglContext != EGL_NO_CONTEXT){
            eglDestroyContext(mEglDisplay, mEglContext);
            mEglContext = EGL_NO_CONTEXT;
        }
        if(mEglDisplay != EGL_NO_DISPLAY)
        {
            eglTerminate(mEglDisplay);
            mEglDisplay = EGL_NO_DISPLAY;
        }
    }
    

    自己EGL环境创建完毕,我们通过JNI调用起来看看效果,native-lib.cpp:

    #include <jni.h>
    #include <string>
    #include "eglhelper/EglHelper.h"
    #include <cstdint>
    #include "android/native_window.h"
    #include "android/native_window_jni.h"
    #include "GLES3/gl3.h"
    
    jlong eglHelperNativePtrInit(JNIEnv *env, jobject thiz) {
        EglHelper *eglHelper = new EglHelper();
        return reinterpret_cast<uintptr_t>(eglHelper);
    }
    
    void eglSurfaceCreated(JNIEnv *env, jobject thiz,jlong native_ptr, jobject surface) {
        if(native_ptr != 0){
            EglHelper *eglHelper = reinterpret_cast<EglHelper *>(native_ptr);
            ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
            eglHelper->initEgl(nativeWindow);
        }
    }
    
    void eglSurfaceChanged(JNIEnv *env, jobject thiz,jlong native_ptr, jint width,jint height) {
    
        if(native_ptr != 0){
            //设置视口大小
            glViewport(0, 0, width, height);
            // 绿色清屏
    //        glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
            // 蓝色清屏
            glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
            EglHelper *eglHelper = reinterpret_cast<EglHelper *>(native_ptr);
            eglHelper->swapBuffers();
        }
    }
    
    void eglSurfaceDestroyed(JNIEnv *env, jobject thiz,jlong native_ptr) {
        if(native_ptr != 0){
            EglHelper *eglHelper = reinterpret_cast<EglHelper *>(native_ptr);
            delete eglHelper;
        }
    }
    
    static JNINativeMethod nativeMethod_EGLHelper[] = {
            // Java中的函数名
            {"n_nativePtrInit",
                    // 函数签名信息
             "()J",
                    // native的函数指针
             (jlong *) (eglHelperNativePtrInit)},
    
            {"n_surfaceCreated",
                    // 函数签名信息
             "(JLandroid/view/Surface;)V",
                    // native的函数指针
             (void *) (eglSurfaceCreated)},
    
            {"n_surfaceChanged",
                    // 函数签名信息
             "(JII)V",
                    // native的函数指针
             (void *) (eglSurfaceChanged)},
    
            {"n_surfaceDestroyed",
                    // 函数签名信息
             "(J)V",
                    // native的函数指针
             (void *) (eglSurfaceDestroyed)},
    };
    
    static int RegisterNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int methodNum)
    {
        jclass clazz = env->FindClass(className);
        if (clazz == NULL)
        {
            return JNI_FALSE;
        }
        if (env->RegisterNatives(clazz, methods, methodNum) < 0)
        {
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }
    
    // 类库加载时自动调用
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reversed)
    {
        JNIEnv *env = NULL;
        // 初始化JNIEnv
        if(vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK){
            return JNI_FALSE;
        }
        // 动态注册
        RegisterNativeMethods(env,"com/fly/opengles/learn/egl/EGLHelper",nativeMethod_EGLHelper,sizeof(nativeMethod_EGLHelper) / sizeof(JNINativeMethod) );
        // 返回JNI使用的版本
        return JNI_VERSION_1_6;
    }
    

    上述native-lib.cpp涉及到了之前介绍过的JNI函数签名、动态注册等相关知识点,忘记了的童鞋可往回看之前的记录。

    如无意外,运行看到的是一个蓝屏画面则说明EGL环境搭建成功了,后续开启你的Opengl炫酷之旅吧!!!

    推荐阅读

    JNI基础简介
    JNI之数组与字符串的使用
    JNI之动态注册与静态注册
    JNI之访问java属性和方法
    JNI之缓存与引用
    JNI之异常处理
    JNI之常用技巧与陷阱

    关注我,一起进步,人生不止coding!!!

    相关文章

      网友评论

        本文标题:Opengl ES之EGL

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