草庐IT

Opengl ES之EGL

FlyerGo 2023-03-28 原文

前言

前面我们发布了一系列的入门教程,例如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的更多相关文章

  1. java - OpenGL 和 OpenGLES 中的 glVertexAttribPointer - 2

    我正在阅读有关OpenGL和OpenGLES的教程,我对函数glVertexAttribPointer在这两个API中的使用有点困惑。在OpenGL教程中,此函数将数字偏移量用作最后一个参数(转换为constGLVoid*),我假设顶点直接取自当前数组缓冲区。glVertexAttribPointer(vs_position,2,GL_FLOAT,GL_TRUE,5*sizeof(GLfloat),(constGLvoid*)(3*sizeof(GLfloat)));在OpenGLES教程中,最后一个参数直接指向表示顶点的结构:GLFloatvertices[]={...definit

  2. c++ - Android ndk 上 OpenGLES 1.1 中具有 GLSurfaceView 模式的 FrameBuffers - 2

    在AndroidNDK中,是否可以使OpenGLES1.1使用典型的Java端GLSurfaceView模式(覆盖GLSurfaceView.RendereronDrawFrame、onSurfaceCreated等方法),同时在C++端使用框架、颜色和深度缓冲区和VBO?我正在尝试使用这个创建它们:voidES1Renderer::on_surface_created(){//Createdefaultframebufferobject.Thebackingwillbeallocatedforthecurrentlayerin-resizeFromLayerglGenFramebuf

  3. c++ - 我可以在 OSX 中使用 EGL 吗? - 2

    我正在尝试使用CairoC++应用程序中的库利用其在Mac中的GL加速。(我对其Quartz后端进行了相同的测试,但性能令人失望。)它说它支持EGL和GLX。使用GLX需要(外部安装)XQuartz并打开一个XWindow,所以我倾向于使用EGL:Apple的programmingguidepages告诉我们使用NSOpenGL*,即this页面和其他人说它使用CGL。This(2012)页面说Mac有EAGL,它只类似于EGL(我想它指的是IOS,而不是MAC,因为它的EAGL引用链接指向IOS帮助页面)。Angle说它支持EGL,但据我所知,它适用于Windows中的Direct3

  4. c++ - 为什么 eglMakeCurrent 因 EGL_BAD_ALLOC 而失败? - 2

    我正在使用OpenGLES2.0和AndroidNDKr8b。我有一个用于工作线程的共享上下文。当我尝试使用eglMakeCurrent将共享上下文绑定(bind)到工作线程时,我收到错误EGL_BAD_ALLOC。现在让我感到困惑的是这段代码之前工作得很好......我不确定我做了什么来破坏它......EGL文档说这个错误与资源不可用有关,但我正在运行曾经在这个完全相同的设备上完美运行的同一个应用程序,并且所有纹理都可以从主线程正常加载。那么可能导致此错误的原因是什么?这是我的egl初始化:boolInitialize(void*displaySurface){assert(dis

  5. c++ - 如何通过 EGL 创建 OpenGL 3.3 或 4.x 上下文 - 2

    我有兴趣制作一个不依赖于X11的OpenGL应用程序。如我所见,这应该可以通过EGL实现。网上什至有例子。但是我怎样才能控制上下文版本呢?下面的示例代码创建了一个版本为2.1的OpenGL上下文(在wayland上),但在我的计算机上它显示支持的最高OpenGL版本是3.3(这样的上下文可以使用glXCreateContextAttribsARB在X服务器中使用GLX和xlib创建)。所以我的问题是:我可以通过EGL以某种方式创建更高版本的OpenGL上下文吗?如果可以,如何创建?示例代码:#include#include#include#include#include#include

  6. opengl-es - 在带有 OpenGLES 的 iOS 上如何拥有多个 View ? - 2

    在iOS上,如果我想叠加两个View,然后从一个场景混合到另一个场景。例如:从游戏菜单到实际游戏。首先有菜单,然后单击(“开始游戏”)加载游戏View,并从菜单到游戏阶段进行很好的混合/转换。这不是混合的具体问题,而是关于如何在OpenGL应用程序中处理多个View的问题。我已经阅读并尝试了一些关于如何绘制线条、对象、闪电、颜色和类似内容的基础知识,但是当涉及到将它们全部放在一个真实的(不仅仅是一个静态View)OpenGLES应用程序中时,我完全是绿色的。我的意思是你必须使用不同的观点,对吧?或者我们在日常UIKit编程中都习惯的具有不同ViewController和View的设计模

  7. iphone - 使用多个顶点和索引缓冲区对象进行渲染(ios - OpenGLES 2.0) - 2

    我无法使用一对以上的顶点和索引缓冲区对象来渲染我的所有对象。为了检查所有内容,我只初始化了3个对象并渲染它们。这会导致前两个对象的几何形状变形,而第三个对象的几何形状渲染得很好(不完美)。当我刚刚初始化所有3个但只是首先渲染时,它再次显示扭曲的几何体并且第三个几何体以某种方式更加可见(即使我没有渲染它)。但是,如果我正在初始化和渲染它们中的任何一个,它就会渲染得很好(完美)。这是我的代码:floattempAngles[4]={0,60,180,360};pieOne=[[IVNodealloc]initWithPieGeometry:0.75thickness:0.20startAn

  8. ios - 从父类中混合 OpenGLES 和 UIKIT 获取 iOS 屏幕截图 - 2

    我知道这个问题得到了很多回答,但我的情况似乎有所不同。我正在尝试编写一个顶层函数,我可以随时截取我的应用程序的屏幕截图,无论是openGLES还是UIKit,我都无法访问底层类来进行任何更改。我一直在尝试的代码适用于UIKit,但返回OpenGLES部分的黑屏CGSizeimageSize=[[UIScreenmainScreen]bounds].size;if(NULL!=UIGraphicsBeginImageContextWithOptions)UIGraphicsBeginImageContextWithOptions(imageSize,NO,0);elseUIGraphic

  9. ios - 适用于 iOS 的 OpenGLES - glBufferData() - GLuint 不适用于数据字段 - 2

    我花了半天时间试图解决这个问题,但到目前为止我没有运气,所以我正在寻找一些建议和指导。我正在我的32位iPad3(iOS8)上渲染一个球体。-(void)bufferVBO:(objectVertex[])objVertices:(int)objectVerticesSize:(GLubyte[])vertexIndices:(int)vertexIndicesSize{...glBufferData(GL_ELEMENT_ARRAY_BUFFER,vertexIndicesSize,vertexIndices,GL_STATIC_DRAW);...}这很好用。但是,这最多只能渲染25

  10. ios - 在 iOS 上使用具有多个 VBO 和 IBO(多个对象)OpenGLES 2 的 VAO 进行绘制 - 2

    我对多个对象(大约200个对象,其中15个对象一次可见,每个对象都有自己的顶点和索引缓冲区)使用VAO(顶点数组对象)感到有些困惑。下面是我的渲染函数-(void)glkView:(GLKView*)viewdrawInRect:(CGRect)rect{glClearColor(0.50f,0.50f,0.50f,1.0f);glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);[superdrawLines];for(inti=0;i0){self.paused=NO;}else{self.paused=YES;}}这工作得很好。在Ope

随机推荐