AndroidOpenGL開発-EGLの詳細

序文

OpenGlを使用してカメラをカスタマイズする場合でも、EGLを理解する必要があります。

おそらくほとんどの開発者はOpengGLを使用していますが、EGLが何であるかわかりませんか?EGLの役割は何ですか?AndroidのGlSurfaceViewはすでにEGL環境を構成しているので、これは実際にはまったく驚くべきことではありません。あなたはそれを使用していますが、その存在を知らないだけです。

OpenGl ESを使用してデータをレンダリングするときに、多くの人が質問をする可能性があります。レンダリングされたデータはどこに送られますか?キャンバスが表示されない場合、Androidのすべてのカスタムビューにキャンバスがありませんか?

この記事は、この一連の問題を解決するのに役立ちます。もちろん、カスタムカメラ、オーディオ、ビデオコーディングの開発に興味がある場合は、私のオープンソースプロジェクトAndroidCameraについて学ぶことができます。これには、ビデオ顔認識ステッカー、美容、セグメント化された記録、ビデオクロッピング、ビデオフレーム処理、ビデオキーの取得が含まれます。フレーム、ビデオの回転、フィルターの追加、透かしの追加、ビデオへのGifの合成、およびその他の多くの機能を使用して、一緒に学習および通信できます。

OpenGLについてよく知らない場合は、前の記事を参照してください。

目次

この記事を通して、あなたは以下を学ぶことができます:

  1. EGLとは何ですか。
  2. EGLとOpenGLESの関係
  3. EGLで描画するための基本的な手順。
  4. GlSurfaceViewソースコードでのEGLの分析
  5. 要約する

1. EGLとは何ですか?

EGLとは何ですか?EGLは、レンダリングAPI(OpenGL、OpenGL ES、OpenVGなど)とネイティブウィンドウシステム間のインターフェイスです。グラフィックコンテキスト管理、サーフェス/バッファの作成、バインディング、レンダリングの同期を処理し、他のKhronos APIを使用して、高性能、高速化、混合モードの2Dおよび3DレンダリングOpenGL /OpenGLESレンダリングクライアントAPIOpenVGレンダリングクライアントAPIネイティブプラットフォームウィンドウシステムを実現します。これを少し理解しても問題ありません。OpenGlESの描画インターフェイスを提供するために使用されるインターフェイスであることを知っておく必要があります。

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绘图的基本步骤

image.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

ナゲッツテクノロジーコミュニティのクリエイター署名プログラムの募集に参加しています。リンクをクリックして登録し、送信してください。

おすすめ

転載: juejin.im/post/7119673311792988168