OpenGL is a cross-platform API, and different operating systems (Windows,Android,IOS) have their own screen rendering implementation. So OpenGL defines an intermediate interface layer EGL (Embedded Graphics Library) standard, the specific implementation of each operating system itself

EGL

EGL is an intermediate interface layer and a specification. Because of the cross-platform nature of OpenGL, this specification is particularly important. No matter how different operating systems hop, they cannot be separated from the specification I defined.

Some basics of EGL

  • EGLDisplay

EGL defines an abstract system display class for manipulating device Windows.

  • EGLConfig

EGL configurations, such as RGBA bits

  • EGLSurface

Render cache, a piece of memory where all the image data to be rendered on the screen is cached on the EGLSurface.

  • EGLContext

OpenGL context, used to store OpenGL drawing state information, data.

The process of initializing EGL can be described as the process of configuring the above information.

OpenGL ES drawing complete process

Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render (GLSurfaceView) Render Android actually saves me a lot of those steps. So let’s look at the complete process here (1). Get the display device (corresponding to the EGLDisplay above)

/*
 * Get an EGL instance */
 mEgl = (EGL10) EGLContext.getEGL();
 
/*
 * Get to the default display. */
 mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

Copy the code

(2). Initialize EGL

int[] version = new int[2]; // Initialize the screen if(! mEgl.eglInitialize(mEglDisplay, version)) { throw new RuntimeException("eglInitialize failed"); }Copy the code

(3). Select Config(set parameters with EGLConfig)

// This code is used to select the EGL configuration, that is, you can first set up a desired EGL configuration, such as RGB three colors of the number of bits, you can choose, but EGL may not meet all your requirements, so it will return some of your requirements closest to the configuration for you to choose. if (! egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, num_config)) { throw new IllegalArgumentException("eglChooseConfig#2 failed"); }Copy the code

(4). Create EGLContext

// Select a configuration from the list of configurations returned by EGL in the previous step to create an EGL Context. egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, mEGLContextClientVersion ! = 0? attrib_list : null);Copy the code

(5). To obtain EGLSurface

/ / create a window Surface, can be seen as a screen for memory. Egl eglCreateWindowSurface (display, config, nativeWindow, null)Copy the code

The nativeWindow here is the surfaceHolder of the GLSurfaceView

(6). Bind render environment to current thread

/* * Before we can issue GL commands, we need to make sure * the context is current and bound to a surface. */ if (! mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { /* * Could not make the context current, probably because the underlying * SurfaceView surface has been destroyed. */ logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); return false; }Copy the code

Loop drawing

Loop :{// drawing.... //(7). Swap buffer meglhelper.swap (); } public int swap() { if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { return mEgl.eglGetError(); } return EGL10.EGL_SUCCESS; }Copy the code

Java – GLSurfaceView/GLTextureView

EGL above we introduced some basic knowledge, then we see in the GLSurfaceView/GLTextureView EGL concrete implementation, we from the source to analyze the Android EGL and GL thread.

GLThread

Let’s take a look at GLThread, GLThread is also inherited from the common Thread class, theoretically a common Thread, why does it have OpenGL drawing capabilities? Moving on, the most important part is the guardedRun() method.

static class GLThread extends Thread { ... @Override public void run() { try { guardedRun(); } catch (InterruptedException e) { // fall thru and exit normally } finally { sGLThreadManager.threadExiting(this); }}}Copy the code

Let’s take a look at what’s in the guardedRun() method, and what guardedRun() does roughly:

private void guardedRun() throws InterruptedException { while(true){ //if ready to draw ... mEglHelper.start(); // Corresponding to the above complete process (1)(2)(3)(4)... Meglhelper.createsurface ()// corresponds to (5)(6) of the complete process above... Call glsurfaceView.renderer onSurfaceCreated(); . Callback glsurfaceView.renderer onSurfaceChanged(); . Call glsurfaceView.renderer onDrawFrame(); . mEglHelper.swap(); // Corresponding to (5)(7)}} in the complete process aboveCopy the code

From the above analysis, we know that GLThread in GLSurfaceView is an ordinary thread, but it operates correctly according to the complete process of OpenGL drawing, so it has the drawing ability of OpenGL. So, if we create our own thread and do the same, can we also draw in our own thread? The answer is yes (this is not exactly what EGL’s interface is about), and I’ll show you the implementation of EGL in Native C/C++ below.

Native – EGL

There is no ready-made EGL environment in Android Native environment, so we have to implement the EGL environment ourselves when developing OpenGL NDK. Then how to achieve it? We only need to refer to the writing method of GLThread in GLSurfaceView to realize EGL in Native

PS

The following may require some familiarity with C/C++ and the NDK

Step 1 implements functionality similar to GLThread in Java GLSurfaceView

gl_render.h

class GLRender { private: const char *TAG = "GLRender"; OpenGL RENDERING STATE enum STATE {NO_SURFACE, // There is no valid surface RENDERING, // Hold a new surface RENDERING that is initialized, Start rendering SURFACE_DESTROY, //surface destroy STOP // STOP rendering}; JNIEnv *m_env = NULL; JavaVM * m_jVM_for_thread = NULL; Jobject M_surface_ref = NULL; // Local screen ANativeWindow *m_native_window = NULL; EglSurface *m_egl_surface = NULL; int m_window_width = 0; int m_window_height = 0; // Draw agent ImageRender *pImageRender; //OpenGL render STATE m_state = NO_SURFACE; // Initialize the related method void InitRenderThread(); bool InitEGL(); void InitDspWindow(JNIEnv *env); // Create/destroy Surface void CreateSurface(); void DestroySurface(); // Render method void Render(); void ReleaseSurface(); void ReleaseWindow(); Static void sRenderThread(STD ::shared_ptr<GLRender> that); public: GLRender(JNIEnv *env); ~GLRender(); Void SetSurface(jobject Surface); void Stop(); void SetBitmapRender(ImageRender *bitmapRender); Void ReleaseRender(); ImageRender *GetImageRender(); };Copy the code

gl_render.cpp

// constructor GLRender::GLRender(JNIEnv *env) {this->m_env = env; Env ->GetJavaVM(&m_jVM_for_thread); InitRenderThread(); } // GLRender::~GLRender() {delete m_egl_surface; } / / initializes the rendering thread void GLRender: : InitRenderThread () {/ / using smart Pointers, and at the end of the thread, automatically deleted this pointer STD: : from < GLRender > that (this); std::thread t(sRenderThread, that); t.detach(); Void GLRender::sRenderThread(STD ::shared_ptr<GLRender> that) {JNIEnv *env; If (that-> m_jVM_for_thread ->AttachCurrentThread(&env, NULL)! = JNI_OK) {LOGE(that->TAG, "thread initialization exception "); return; } // (2) initialize EGL if (! That ->InitEGL()) {that-> m_jVM_for_thread ->DetachCurrentThread(); return; Switch (that->m_state) {// Refresh the Surface, set the Surface from outside m_state to this state, Case FRESH_SURFACE: LOGI(that->TAG, "Loop Render FRESH_SURFACE") // (3) initialize Window that->InitDspWindow(env); // (4) create EglSurface that->CreateSurface(); That ->m_state = RENDERING; break; Case RENDERING: LOGI(that->TAG, "Loop Render RENDERING") // (5) Render that->Render(); break; Case STOP: LOGI(that->TAG, "Loop Render STOP") //(6) that->m_jvm_for_thread->DetachCurrentThread(); return; Case SURFACE_DESTROY: LOGI(that->TAG, "Loop Render SURFACE_DESTROY") //(7) DestroySurface(); that->m_state = NO_SURFACE; break; case NO_SURFACE: default: break; } usleep(20000); }}Copy the code

Steps have been marked in the GLRender process code defined by us. Although there is a large amount of code, our c++ class analysis is similar to Java.

PS Steps (3) and (4) in the figure above correspond to step comments in the code

(1)Attach the thread to the virtual machine and get the ENV

This is pretty straightforward. Let’s move on

Prepare for EGL encapsulation

In the last, we knew that some basic knowledge, EGL EGLDiaplay, EGLConfig, EGLSurface, EGLContext, we need to get these base classes encapsulate, so how to encapsulate? Let’s first look at what gl_render.h is needed for our custom GLRender class from the previous article

Jobject M_surface_ref = NULL; // Local screen ANativeWindow *m_native_window = NULL; EglSurface = NULL; EglSurface *m_egl_surface = NULL;Copy the code

For gl_render the input is the external Surface object, in our case jobject M_surface_ref, so the output needs to be ANativeWindow,EglSurface.

See the official document ANativeWindow for details about ANativeWindow

What about the EglSurface,

egl_surface.h

class EglSurface { private: const char *TAG = "EglSurface"; // Local screen ANativeWindow *m_native_window = NULL; EglCore *m_core encapsulates the EGLDisplay EGLConfig EGLContext custom class; //EGL API provides EGLSurface EGLSurface m_surface; }Copy the code

You can see that the idea of our definition above is also to separate V(View) from C(Controller).

egl_core.h

class EglCore { private: const char *TAG = "EglCore"; //EGL display window EGLDisplay m_egl_DSP = EGL_NO_DISPLAY; //EGL context EGLContext m_egl_context = EGL_NO_CONTEXT; //EGL config EGLConfig m_egl_config; }Copy the code

With the above preparation, let’s follow the steps of the flowchart step by step.

(2) Initialize EGL

gl_render.cpp

Bool GLRender::InitEGL() {// create EglSurface object m_egl_surface = new EglSurface(); Return m_egl_surface-> init (); }Copy the code

egl_surface.cpp

PS We also mentioned above that EGL initialization is mainly on EGLDisplay, EGLConfig, EGLContext, so now it’s on EGLCore.

EglSurface::EglSurface() {// create EGLCore m_core = new EGLCore (); } bool EglSurface::Init() {return m_core->Init(NULL); }Copy the code

egl_core.cpp

EglCore::EglCore() { } bool EglCore::Init(EGLContext share_ctx) { if (m_egl_dsp ! = EGL_NO_DISPLAY) { LOGE(TAG, "EGL already set up") return true; } if (share_ctx == NULL) { share_ctx = EGL_NO_CONTEXT; } // get Dispaly m_egl_DSP = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (m_egl_dsp == EGL_NO_DISPLAY || eglGetError() ! = EGL_SUCCESS) { LOGE(TAG, "EGL init display fail") return false; } EGLint major_ver, minor_ver; EGLBoolean success = eglInitialize(m_egl_DSP, &major_ver, &minor_ver); if (success ! = EGL_TRUE || eglGetError() ! = EGL_SUCCESS) { LOGE(TAG, "EGL init fail") return false; } LOGI(TAG, "EGL version: %d.%d", major_ver, minor_ver) // GetEGLConfig m_egl_config = GetEGLConfig(); const EGLint attr[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; // create EGLContext m_eGL_context = eglCreateContext(m_egl_DSP, m_eGL_config, share_ctx, attr); if (m_egl_context == EGL_NO_CONTEXT) { LOGE(TAG, "EGL create fail, error is %x", eglGetError()); return false; } EGLint egl_format; success = eglGetConfigAttrib(m_egl_dsp, m_egl_config, EGL_NATIVE_VISUAL_ID, &egl_format); if (success ! = EGL_TRUE || eglGetError() ! = EGL_SUCCESS) { LOGE(TAG, "EGL get config fail, error is %x", eglGetError()) return false; } LOGI(TAG, "EGL init success") return true; } EGLConfig EglCore::GetEGLConfig() { EGLint numConfigs; EGLConfig config; // The desired minimum configuration, Static const EGLint CONFIG_ATTRIBS[] = {EGL_BUFFER_SIZE, EGL_DONT_CARE, EGL_RED_SIZE, 8,//R bits EGL_GREEN_SIZE, 8,//G bits EGL_BLUE_SIZE, 8,//B bits EGL_ALPHA_SIZE, 8,//A bits EGL_DEPTH_SIZE, 16, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE // The end flag}; // Depending on the minimum configuration you set, the system will select a configuration that meets your minimum requirements. EGLBoolean SUCCESS = eglChooseConfig(M_eGL_DSP, CONFIG_ATTRIBS, &config, 1, &numConfigs); if (! success || eglGetError() ! = EGL_SUCCESS) { LOGE(TAG, "EGL config fail") return NULL; } return config; }Copy the code

(3) Create Window

gl_render.cpp

Void GLRender::InitDspWindow(JNIEnv *env) {// If (m_surface_ref! // initialize window m_native_window = ANativeWindow_fromSurface(env, m_surface_ref); M_window_width = ANativeWindow_getWidth(m_native_window); m_window_height = ANativeWindow_getHeight(m_native_window); ANativeWindow_setBuffersGeometry(m_native_window, M_WINDOW_width, m_WINDOW_height, WINDOW_FORMAT_RGBA_8888); LOGD(TAG, "View Port width: %d, height: %d", m_window_width, m_window_height) } }Copy the code

(4) Create the EglSurface and bind it to the thread

gl_render.cpp

void GLRender::CreateSurface() {
    m_egl_surface->CreateEglSurface(m_native_window, m_window_width, m_window_height);
    glViewport(0, 0, m_window_width, m_window_height);
}

Copy the code

egl_surface.cpp

/** ** @param native_window passes the ANativeWindow * @param width * @param height */ void created in the previous step EglSurface::CreateEglSurface(ANativeWindow *native_window, int width, int height) { if (native_window ! = NULL) { this->m_native_window = native_window; m_surface = m_core->CreateWindSurface(m_native_window); } else { m_surface = m_core->CreateOffScreenSurface(width, height); } if (m_surface == NULL) { LOGE(TAG, "EGL create window surface fail") Release(); } MakeCurrent(); } void EglSurface::MakeCurrent() { m_core->MakeCurrent(m_surface); }Copy the code

egl_core.cpp

EGLSurface EglCore: : CreateWindSurface (ANativeWindow * window) {/ / invoke EGL Native API to create the window Surface EGLSurface Surface = eglCreateWindowSurface(m_egl_dsp, m_egl_config, window, 0); if (eglGetError() ! = EGL_SUCCESS) { LOGI(TAG, "EGL create window surface fail") return NULL; } return surface; } void EglCore::MakeCurrent(EGLSurface egl_surface) {// Call EGL Native API to bind render environment to current thread if (! eglMakeCurrent(m_egl_dsp, egl_surface, egl_surface, m_egl_context)) { LOGE(TAG, "EGL make current fail"); }}Copy the code

(5) rendering

gl_render.cpp

void GLRender::Render() { if (RENDERING == m_state) { pImageRender->DoDraw(); // Draw pictures.... m_egl_surface->SwapBuffers(); }}Copy the code

egl_surface.cpp

void EglSurface::SwapBuffers() {
    m_core->SwapBuffer(m_surface);
}
Copy the code

egl_core.cpp

Void EglCore::SwapBuffer(EGLSurface egl_surface) {// Call EGL Native API eglSwapBuffers(m_egl_DSP, egl_surface); }Copy the code

The following stop and destroy will be left to the reader to study.

code

EGLDemoActivity.java

EGL Native