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/…

Fence is a feature of OpenGL ES 3.0 that allows you to synchronize OpenGL commands, which is useful for multithreaded programming, as I described in my previous article, “OpenGL ES command queues and glFlush/glFinish.” OpenGL command execution is performed on GPU. When we call OpenGL method, we actually insert commands into the command queue of OpenGL, and GPU takes out commands from the command to execute.

So, in general, when we call OpenGL, it doesn’t work immediately. If we want to make sure that the previous OpenGL is finished, we need to use glFinish, but glFinish can only make sure that the command queue of this thread is finished. This means that you can’t wait in one thread for OpenGL to finish executing in another thread. This is very limited. Think back to the synchronous operations we had on the CPU, such as waiting in one thread and notifying in another thread. This is a common operation, but similar logic cannot be implemented with glFinish on gpus. In fact, it was not possible before OpenGL ES 3.0.

In OpenGL ES 3.0, we can implement a fence, which is very easy to use, just insert a fence in one thread, and then wait for it in another thread.

For example, if we have logic that renders a texture in GLThread 0 and uses it in another thread, GLThread 1, we need to make sure that by the time GLThread 1 uses the texture, GLThread 0 has already rendered the texture. After the fence, We can insert a fence after the GLThread 0 render operation, and then wait for the fence when GLThread 1 wants to use the texture.

Let’s look at the code, starting with the code that inserts the fence:

val fenceSyncObject = GLES30.glFenceSync(GLES30.GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
Copy the code

This method call inserts a fence into the command queue of the current thread and returns a long variable to code the fence synchronization object so that other places can wait for it. There are two methods for waiting, glWaitSync and glClientWaitSync. The difference is that glWaitSync waits on the GPU and glClientWaitSync waits on the CPU.

In this example, we render an image to a texture on the screen in one thread, and read the texture to an ImageView in the bottom right corner of the screen in another thread:

override fun onDrawFrame(gl: GL10?).{...// Render to texture
    // Render to texture
    GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, textureCoordinateData.size / VERTEX_COMPONENT_COUNT)

    // Insert a fence into OpenGL's Command Buffer
    // Insert a fence into the OpenGL command buffer
    val fenceSyncObject = GLES30.glFenceSync(GLES30.GL_SYNC_GPU_COMMANDS_COMPLETE, 0)

    // Read the render result of the current thread in another thread
    // Read the render result in the other thread
    otherThreadHandler.post {
        if(! flag) {// Wait for the OpenGL command on the fence to complete
            // Waiting for completion of the OpenGL commands before our fence
            GLES30.glWaitSync(fenceSyncObject, 0, GLES30.GL_TIMEOUT_IGNORED)

            // Delete the fence synchronization object
            // Delete the fence sync object
            GLES30.glDeleteSync(fenceSyncObject)

            val frameBuffers = IntArray(1)
            GLES30.glGenFramebuffers(frameBuffers.size, frameBuffers, 0)
            GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, sharedTexture)
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffers[0])
            GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, sharedTexture, 0)
            val buffer = ByteBuffer.wrap(ByteArray(glSurfaceViewWidth * glSurfaceViewHeight * 4))
            GLES30.glReadPixels(0.0, glSurfaceViewWidth, glSurfaceViewHeight, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, buffer)
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
            val bitmap = Bitmap.createBitmap(glSurfaceViewWidth, glSurfaceViewHeight, Bitmap.Config.ARGB_8888)
            buffer.position(0)
            bitmap.copyPixelsFromBuffer(buffer)
            flag = true

            // Display the read render results to an ImageView
            // Display the read render result on a ImageView
            imageView.post {
                imageView.setImageBitmap(bitmap)
            }
        }

    }

    // Bind the frame buffer back to 0 to render the result on screen at the same time
    // Bind frame buffer to 0# and also render the result on screen
    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)

    GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, imageTexture)

    GLES30.glClearColor(0.9f, 0.9f, 0.9f, 1f)

    / / clear screen
    // Clear the screen
    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

    // Set the viewport to the entire GLSurfaceView area
    // Set the viewport to the full GLSurfaceView
    GLES30.glViewport(0.0, glSurfaceViewWidth, glSurfaceViewHeight)

    GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexData.size / VERTEX_COMPONENT_COUNT)
}
Copy the code

After add the fence can guarantee in another thread to read, image texture rendering completed, if not add a fence, may observe the texture is mutilated, read rendering the more complicated to operate, the more likely to observe, because when reading could render out, in this case, I made a fuzzy operation, Make rendering a little more complicated.

Here’s what this example looks like:

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

Thanks for reading!