Android OpenGL 개발 - EGL 상세

머리말

OpenGl을 사용하여 카메라를 사용자 정의하려면 EGL을 여전히 이해해야 합니다.

아마도 대부분의 개발자는 OpengGL을 사용했지만 EGL이 무엇인지 모르십니까? EGL의 역할은 무엇입니까? Android의 GlSurfaceView가 이미 EGL 환경을 구성했기 때문에 이것은 실제로 전혀 놀라운 일이 아닙니다. 여러분은 이미 EGL 환경을 사용하고 있지만 그 존재를 모를 뿐입니다.

많은 사람들이 OpenGl ES를 사용하여 데이터를 렌더링할 때 렌더링된 데이터가 어디로 가는지 질문할 수 있습니다. 캔버스를 보지 않고 Android의 모든 사용자 정의 보기에 캔버스가 있지 않습니까?

이 기사는 이러한 일련의 문제를 해결하는 데 도움이 됩니다. 물론 커스텀 카메라, 오디오 및 비디오 코딩 개발에 관심이 있다면 비디오 얼굴 인식 스티커, 뷰티, 분할 녹화, 비디오 자르기, 비디오 프레임 처리 및 비디오 키 획득을 포함하는 내 오픈 소스 프로젝트 AndroidCamera 에 대해 배울 수 있습니다. 프레임, 동영상 회전, 필터 추가, 워터마크 추가, 동영상에 GIF 합성 등 다양한 기능을 함께 배우고 소통할 수 있습니다.

OpenGL에 대해 잘 모른다면 이전 기사를 살펴보십시오.

목차

이 기사를 통해 다음을 배울 수 있습니다.

  1. EGL이란 무엇입니까?
  2. EGL과 OpenGL ES의 관계
  3. EGL로 그리기 위한 기본 단계입니다.
  4. GlSurfaceView 소스 코드의 EGL 분석
  5. 요약하다

1. EGL이 무엇인지 이해합니까?

EGL이란 무엇입니까? EGL은 렌더링 API(예: OpenGL, OpenGL ES, OpenVG)와 기본 윈도우 시스템 간의 인터페이스입니다. 그래픽 컨텍스트 관리, 표면/버퍼 생성, 바인딩 및 렌더링 동기화를 처리하고 고성능, 가속, 혼합 모드 2D 및 3D 렌더링 OpenGL/OpenGL ES 렌더링 클라이언트 API OpenVG 렌더링 클라이언트 API 네이티브 플랫폼 창 시스템을 위해 다른 Khronos API를 사용합니다. 이것에 대한 약간의 이해는 OK입니다. OpenGl ES에 대한 그리기 인터페이스를 제공하는 데 사용되는 인터페이스라는 것만 알면 됩니다.

EGL의 역할:

  1. 장치의 기본 윈도우 시스템과 통신합니다.
  2. 도면 표면의 사용 가능한 유형 및 구성을 쿼리합니다.
  3. 그리기 표면을 만듭니다.
  4. 在OpenGL ES 和其他图形渲染API之间同步渲染。
  5. 管理纹理贴图等渲染资源。

这里关于EGL的介绍就讲这么多,如果你有兴趣的话你可以继续到这里去see yi seeUnderstanding Android EGL

2. EGL和OpenGl ES的关系

从上面的讲解我们基本上可以知道,EGL是为OpenGl提供绘制表面的。

对的,这就是OpenGl ES数据渲染画布所在了。想必到这里大家也清楚了渲染数据去处的问题了。

EGL还有什么用呢?EGL可以理解为OpenGl ES ES和设备之间的桥梁。完全可以这么理解。

3. EGL绘图的基本步骤

이미지.png 先上图,再说话。

简单讲解下各部分的作用:

  1. Display(EGLDisplay) 是对实际显示设备的抽象。
  2. Surface(EGLSurface)是对用来存储图像的内存区FrameBuffer 的抽象,包括Color Buffer,Stencil Buffer,Depth Buffer。
  3. Context (EGLContext) 存储 OpenGL ES绘图的一些状态信息。

EGL的基本使用步骤:

  1. 首先我们需要知道绘制内容的目标在哪里,EGLDisplayer是一个封装系统屏幕的数据类型,通常通过eglGetDisplay方法来返回EGLDisplay作为OpenGl ES的渲染目标,eglGetDisplay()
 if ( (mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)) == EGL14.EGL_NO_DISPLAY) {
                throw new RuntimeException("unable to get EGL14 display");
            }
复制代码
  1. 初始化显示设备,第一参数代表Major版本,第二个代表Minor版本。如果不关心版本号,传0或者null就可以了。初始化与 EGLDisplay 之间的连接:eglInitialize()
            if (!EGL14.eglInitialize(mEGLDisplay, 0, 0)) {
                throw new RuntimeException("unable to initialize EGL14");
            }
复制代码
  1. 下面我们进行配置选项,使用eglChooseConfig()方法,Android平台的配置代码如下:
int[] attribList = {
                    EGL14.EGL_RED_SIZE, 8,
                    EGL14.EGL_GREEN_SIZE, 8,
                    EGL14.EGL_BLUE_SIZE, 8,
                    EGL14.EGL_ALPHA_SIZE, 8,
                    EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
                    EGL_RECORDABLE_ANDROID, 1,
                    EGL14.EGL_NONE
            };
            EGLConfig[] configs = new EGLConfig[1];
            int[] numConfigs = new int[1];
            EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
                    numConfigs, 0);
复制代码
  1. 接下来我们需要创建OpenGl的上下文环境 EGLContext 实例,这里值得留意的是,OpenGl的任何一条指令都是必须在自己的OpenGl上下文环境中运行,我们可以通过eglCreateContext()方法来构建上下文环境:
    int[] attrib_list = {
                    EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
                    EGL14.EGL_NONE
            };
            mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
                    attrib_list, 0);
复制代码

eglCreateContext中的第三个参数可以传入一个EGLContext类型的变量,改变量的意义是可以与正在创建的上下文环境共享OpenGl资源,包括纹理ID,FrameBuffer以及其他Buffer资源。如果没有的话可以填写Null.

  1. 通过上面四步,获取OpenGl 上下文之后,说明EGL和OpenGl ES端的环境已经搭建完毕,也就是说OpengGl的输出我们可以获取到了。下面的步骤我们讲如何将EGl和设备屏幕连接起来。如果连接呢?当然,这时候我们就要使用EGLSurface了,我们通过EGL库提供eglCreateWindowSurface可以创建一个实际可以显示的surface.当然,如果需要离线的surface,我们可以通过eglCreatePbufferSurface创建。eglCreateWindowSurface()
     private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
      int[] surfaceAttribs = {
                    EGL14.EGL_NONE
            };
            mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface,
                    surfaceAttribs, 0);
复制代码
  1. 通过上面的步骤,EGL的准备工作做好了,一方面我们为OpenGl ES渲染提供了目标及上下文环境,可以接收到OpenGl ES渲染出来的纹理,另一方面我们连接好了设备显示屏(这里指SurfaceView或者TextureView),接下来我们讲解如何在创建好的EGL环境下工作的。首先我们有一点必须要明确,OpenGl ES 的渲染必须新开一个线程,并为该线程绑定显示设备及上下文环境(Context)。因为前面有说过OpenGl指令必须要在其上下文环境中才能执行。所以我们首先要通过 eglMakeCurrent()方法来绑定该线程的显示设备及上下文。
EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
复制代码
  1. 当我们绑定完成之后,我们就可以进行RenderLoop循环了。这里简单说一下,EGL的工作模式是双缓冲模式,其内部有两个FrameBuffer(帧缓冲区,可以理解为一个图像存储区域),当EGL将一个FrameBuffer显示到屏幕上的时候,另一个FrameBuffer就在后台等待OpenGl ES进行渲染输出。知道调用了eglSwapBuffers这条指令的时候,才会把前台的FrameBuffers和后台的FrameBuffer进行交换,这样界面呈现的就是OpenGl ES刚刚渲染的结构了。
mInputSurface.swapBuffers();
复制代码
  1. 当然,在所有的操作都执行完之后,我们要销毁资源。特别注意,销毁资源必须在当前线程中进行,不然会报错滴。首先我们销毁显示设备(EGLSurface),然后销毁上下文(EGLContext),停止并释放线程,最后终止与EGLDisplay之间的链接,
 EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
                EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
                EGL14.eglReleaseThread();
                EGL14.eglTerminate(mEGLDisplay);
复制代码

4. GlSurfaceView源码中分析EGL

上面我们有提到过Android GlSurfaceView中已经帮忙配置好了EGL,下面我们来看下EGL在GLSurfaceView中的具体实现过程:我们平时使用GlSurfaceView怎么使用的呢?xml中布置,然后setRenderer(this),然后调用下setRenderMode()就OK了。特比简单。但是GlSurfaceView内部原理是什么呢?我们现在来一探究竟:

特别声明:下面所有代码皆为GlSurfaView中的源码。

  public void setRenderer(Renderer renderer) {
        checkRenderThreadState();
        if (mEGLConfigChooser == null) {
            mEGLConfigChooser = new SimpleEGLConfigChooser(true);
        }
        if (mEGLContextFactory == null) {
            mEGLContextFactory = new DefaultContextFactory();
        }
        if (mEGLWindowSurfaceFactory == null) {
            mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
        }
        mRenderer = renderer;//为当前的View设置一个渲染器
        mGLThread = new GLThread(mThisWeakRef);
        //创建线程并开启
        mGLThread.start();
    }
复制代码

setRenderer函数主要干了2件事,一个是给View设置一个渲染器对象二是创建并开启渲染线程。线程start后执行guardedRun,run函数一个while(true)循环,渲染数据。

 public void setRenderer(Renderer renderer) {
        checkRenderThreadState();
        if (mEGLConfigChooser == null) {
            mEGLConfigChooser = new SimpleEGLConfigChooser(true);
        }
        if (mEGLContextFactory == null) {
            mEGLContextFactory = new DefaultContextFactory();
        }
        if (mEGLWindowSurfaceFactory == null) {
            mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
        }
        mRenderer = renderer;//为当前的View设置一个渲染器
        mGLThread = new GLThread(mThisWeakRef);
        //创建线程并开启
        mGLThread.start();
    }
复制代码

到这里为止,是不是感觉特别熟悉?我们已经看到了onSurfaceCreated(),onSurfaceChanged(),onDrawFrame()的回调了。在这些方法之前有个EglHelper类的创建,接下来进入EGL的配置环节。请系好安全带:

public void start(){
		……
	 mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);//1. 获取 EGL Display 对象
		……
		//2. 初始化与 EGLDisplay 之间的连接
		 if(!mEgl.eglInitialize(mEglDisplay, version)) {
                throw new RuntimeException("eglInitialize failed");
            }
	     ……
	       mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);//3. 获取 EGLConfig 对象
	        /*
                * Create an EGL context. We want to do this as rarely as we can, because an
                * EGL context is a somewhat heavy object.
                */
                mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);//4 创建 EGLContext 实例
               ……
 }
复制代码

글쎄, 위의 EGL 그리기 단계를 따르고 GlSurfaceView의 소스 코드를 비교하십시오. 노인은 여기서 그것에 대해 이야기하지 않을 것입니다. 나에게 왜 냐고 묻지마?

요약하다

요약하자면, 위의 내용을 읽고 난 후에는 처음에 언급한 질문에 대한 명확한 이해가 있어야 하며, 이미 마음속에 답이 있다고 생각합니다. 요약하자면? 감정에 대해 이야기해 봅시다 프로그래밍을 배우는 가장 좋은 방법은 소스 코드를 보는 것입니다 학습을 위해 소스 코드를 읽는 것보다 효과적인 것은 없습니다. 오늘 GlSurfaceView를 다시 보니 이전과 다른 이해가 되었습니다. 마지막으로 커스텀 카메라 학습에 대해 궁금한 점이 있으시면 함께 소통하고 배우라는 메시지를 남겨주세요. WeChat 공개 계정: aserbao.

Android 사용자 정의 카메라 개발을 배우고 있다면 내 오픈 소스 프로젝트 AndroidCamera 에 오신 것을 환영합니다.

글이 잘 작성되었다고 생각되시면 손가락을 눌러주시고, 글이 잘 못 작성되었다고 생각되시면 제때에 검토하고 수정할 수 있도록 비판과 수정의 글을 남겨주세요.

페이슈 20220713-100110.gif

Nuggets Technology Community의 작성자 서명 프로그램 모집에 참여하고 있습니다. 링크를 클릭하여 등록하고 제출 하십시오.

рекомендация

отjuejin.im/post/7119673311792988168