Hello everyone, this is my OpenGL ES advanced advanced series of articles, there is a corresponding project on my Github, welcome to follow, link: github.com/kenneycode/…

EGL is a very important part of OpenGL ES development, especially when you want to implement some more complex functions, it is necessary to understand EGL, in addition, to understand EGL is also important to master the basic principles of rendering. I think it’s something OpenGL ES developers need to master to take it to the next level, and GL thread is a thread that is bound to an EGL environment and can perform GL operations in that thread.

EGL what is it? EGL is the bridge between OpenGL ES and native window systems.

How to understand this sentence? We know that OpenGL is cross-platform, but Windows systems on different platforms are different, and it needs something to help OpenGL interconnect with local Windows systems, manage them, execute GL commands, etc.

That sounds pretty basic. Why do we need to know about this? I’ll give you a few examples. If you want to multithread your GL logic to improve efficiency, if you don’t know EGL and simply split GL operations into multiple threads, you will find problems, which will be covered later. If you want to use MediaCodec for video codec, you will find that EGL is also often needed. Especially if you want to do OpenGL effects before encoding and after decoding, like rendering the original video in OpenGL ES and encoding it, or decoding the original video and rendering it in OpenGL ES and displaying it. When you encode, you need to render the frame to be encoded on a surface that MediaCodec gives you, and you need EGL to do that. When you decode, you need to decode it on a surface that you specify, and you don’t have a ready-made EGL environment. If you want to decode it and do some special effects with OpenGL ES and then display it, then you need an EGL environment as well.

To give you a first hand feel of what EGL does, let’s try some code. One is the familiar GLSurfaceView Renderer. We can do GL in the callback.

glSurfaceView.setRenderer(object : GLSurfaceView.Renderer {
            
    override fun onDrawFrame(gl: GL10?).{}override fun onSurfaceChanged(gl: GL10? , width:Int, height: Int){}override fun onSurfaceCreated(gl: GL10? , config:EGLConfig?). {
        
        val textures = IntArray(1)
        GLES30.glGenTextures(textures.size, textures, 0)
        val imageTexture = textures[0]}})Copy the code

If you look at the value of the texture, you will see that it is greater than 0, so it is successfully created. You can also use the OpenGL ES method gles30.glistexture (imageTexture) to determine if the texture is legitimate.

What if we put the creation of texture above into the main thread?

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)

        val textures = IntArray(1)
        GLES30.glGenTextures(textures.size, textures, 0)
        val imageTexture = textures[0]}}Copy the code

We will see that the created texture is 0, which means the created texture failed. Not only the created texture failed, but all GL operations will fail.

What if you put it in a child thread?

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)

        Thread {

            val textures = IntArray(1)
            GLES30.glGenTextures(textures.size, textures, 0)
            val imageTexture = textures[0]
            
        }.start()
    }

}
Copy the code

The effect is the same. It also fails. Why? The OpenGL ES API is called in a thread that doesn’t have an EGL environment. How do you make a thread have an EGL environment? There are several steps to create an EGL environment, including the following:

  • Obtain display device

    eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
    Copy the code

    Here we get the display device of default, which we do most of the time, because most of the time the device has only one screen, and this article will only discuss that case.

  • Initialize the display device

    val version = IntArray(2)
    EGL14.eglInitialize(eglDisplay, version, 0, version, 1)
    Copy the code

    Here, when initialization is complete, the maximum and minimum version numbers of EGL that we support are returned.

  • Select the config

    val attribList = intArrayOf(
        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 or EGLExt.EGL_OPENGL_ES3_BIT_KHR, 
        EGL14.EGL_NONE
    )
    val eglConfig = arrayOfNulls<EGLConfig>(1)
    val numConfigs = IntArray(1)
    EGL14.eglChooseConfig(
        eglDisplay, 
        attribList, 
        0, 
        eglConfig, 
        0, 
        eglConfig.size,
        numConfigs, 
        0
    )
    Copy the code

    This step tells the system the EGL configuration we expect, and the system returns us a list, sorted by the degree to which the configuration matches. Since the system may not have the configuration we expect, we query the system to return the configuration as close as possible.

    AttribList is our desired configuration, our configuration here is to set RGBA color depth to 8 bits, and OpenGL ES versions to 2 and 3, indicating that both OpenGL 2 and OpenGL 3 are supported, and finally ending with an eGL14.egl_NONE.

    EglConfig is returned as close as possible to the list of configurations that we expect, and we usually use the 0th one that best fits our expectations.

  • Create EGL Context

    eglContext = EGL14.eglCreateContext(
        eglDisplay, 
        eglConfig[0], 
        EGL14.EGL_NO_CONTEXT,
        intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE), 
        0
    )
    Copy the code

    EglDisplay is the display device created earlier, notice the third parameter, which specifies a shared EGL Context. Once shared, the two EGL contexts can use each other’s created resources such as texture. By default, they are not shared, but not all resources are shared. Programs, for example, are not shared.

  • Create EGL Surface

    val surfaceAttribs = intArrayOf(EGL14.EGL_NONE)
    eglSurface = EGL14.eglCreatePbufferSurface(
        eglDisplay, 
        eglConfig[0], 
        surfaceAttribs, 
        0
    )
    Copy the code

    What is an EGL Surface? It can be understood as a thing used to carry display contents. There are two kinds of EGL Surface to choose from, one is window Surface and the other is Pbuffer Surface. If we create this EGL environment to bind to a Surface, For example, if you want to create an EGL environment for the Surface View and use OpenGL ES to render on the Surface View, you should select Window Surface. The corresponding creation method is as follows:

    EGL14.eglCreateWindowSurface(
        eglDisplay, 
        eglConfig[0], 
        surface, 
        surfaceAttribs, 
        0
    )
    
    Copy the code

    Surface is the surface corresponding to the Surface View. If we do not need to render it, we can create a Pbuffer surface that is not bound to the surface and does not need to be passed to it. This is also called off-screen rendering. In this article, we will create a Pbuffer surface.

    eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig[0], surfaceAttribs, 0)
    Copy the code

Here is a detail. The current surface buffers are usually double buffers, so that while one buffer is being displayed, the other buffer can be used for rendering. The displayed buffer is called the front buffer. EglSwapBuffers (eglDisplay, eglSurface) ¶ The buffer that is being rendered is called the back buffer. If you want to render it to the surface, you must call EGL14 after rendering. EglSwapBuffers (eglDisplay, eglSurface) will show that the buffers and the rendered buffer will be swapped. Otherwise, it will render to the back buffer, which will not be displayed as the front buffer on the surface.

  • Binding EGL

    In the previous steps, we have created something that needs to be created. Now we need to bind EGL to the thread so that it has the EGL environment:

    EGL14.eglMakeCurrent(
        eglDisplay, 
        eglSurface, 
        eglSurface, 
        eglContext
    )
    
    Copy the code

    Note that only one EGL environment can be bound to a thread, and if other EGL environments have been bound before, then another one will be tied last.

    At this point, a thread has an EGL environment and can do GL operations without any problems.

Ok, let’s take a look at our sample code:

Thread {

    val egl = EGL()
    egl.init()

    egl.bind()

    val textures = IntArray(1)
    GLES30.glGenTextures(textures.size, textures, 0)
    val imageTexture = textures[0]
    assert(GLES30.glIsTexture(imageTexture))

    egl.release()

}.start()

Copy the code

EGL is a wrapper class that encapsulates the operations described above. The init() method corresponds to getting the display device, initializing the display device, selecting config, creating an EGL Context, and creating an EGL Surface. The bind() method corresponds to eglMakeCurrent().

EGL can actually do a lot of things. I’ve packaged a library for EGL and GL threads: GLKit(github.com/kenneycode/… ES rendering to SurfaceView, TextureView, as well as the implementation of OpenGL ES multithreaded programming, welcome to the need for friends to use, I also wrote a demo for this library, interested friends can go to see:

Okay, so if you look at this, maybe some of you are wondering, why is it that when we use GLSurfaceView, we don’t have to worry about all this stuff? That’s because the GLSurfaceView package well for you, if everyone go to see the GLSurfaceView source code, you will find it is step by step according to the previously mentioned steps to create good environment of EGL, it have a GLThread class, is a thread binding the EGL environment, The source logic looks like this:

while(...) {
    ...
    mEglHelper.start();    // Get display device, initialize display device, select config, create EGL Context. mEglHelper.createSurface();// Create the EGL Surface and bind the EGL Context. Call Renderer onSurfaceCreated()... Call Renderer onSurfaceChanged ()... Call Renderer onDrawFrame()... mEglHelper.swap();// eglSwapBuffer. }Copy the code

See why we can use the OpenGL ES API in the GLSurfaceView Renderer callback method? It creates the EGL environment for you and binds it before the callback.

The code is in my Github OpenGLESPro project, this article corresponds to SampleEGL, project link: github.com/kenneycode/…

Thanks for reading!