OpenGL ES command queue glFinish/glFlush

As we know, the OpenGL ES methods we call are all called on THE CPU, and these calls will eventually be converted into GPU driver instructions and executed on the GPU. As CPU and GPU are two different processors, they can naturally execute their instructions in parallel. OpenGL ES has a command queue that’s used to hold commands that haven’t been sent to the GPU yet, and in fact most of the OpenGL ES methods that we call, they just insert commands into the command queue, they don’t finish executing commands on the CPU or whatever, so if you measure the time, you’ll find that most of the OpenGL ES methods, It takes almost no time, no matter how complex the rendering is.

I drew a graph to show it:

Note that the command queue does not correspond to all threads. The command queue corresponds to an EGL Context, and a thread can only be bound to one EGL Context at the same time. See my other article “OpenGL ES Advanced: EGL and GL Threads”), so it can be understood that the command queue corresponds to the thread of the bound EGL Context.

Sometimes we want to wait on the CPU for the OpenGL ES command to complete. For example, we want to do multi-threading optimization, rendering in one thread and doing other operations with the rendered texture in the other thread in two threads sharing the EGL Context. In this case, We can’t sync on a CPU like we can on a CPU.

// thread0:
fun run(a){...Call glDrawXXX() to render to the texture
    lock.notify()
    ...
}
// thread1:
fun run(a){... lock.wait()// Get texture to use. }Copy the code

In the code, we want to read it into the bitmap in Thread1 after thread0 has finished rendering. What result does that read? Thread0 does not wait for the GPU to actually render after executing glDrawXXX(), so the texture of thread1’s texture is undefined. It may not have started rendering yet, or it may be halfway done. Or it’s already rendered. To get the right result, in OpenGL ES 2.0 we can use glFinsh(), in OpenGL ES 3.0 we can use fence, I’ll write about fence later, now we use glFinish(), GlFinish (); glFinish(); glFinish(); glFinish();

// thread0:
fun run(a){...Call glDrawXXX() to render to the texture
    glFinish()
    lock.notify()
    ...
}
// thread1:
fun run(a){... lock.wait()// Get texture to use. }Copy the code

I drew a picture to show it visually:

Since glFinish() waits on the CPU, there is some performance impact. If thread0 is a main render thread, it will affect the frame rate, so it is better to put the wait in a less important thread1, but can we put glFinish() in thread1? Take a look at the chart below:

As mentioned earlier, command queues are separate for each thread bound to the EGL Context. GlFinish () will only wait for the command queue of the current thread to complete, i.e., for the command queue of thread1, so it will not have the desired effect. In OpenGL ES 2.0, there is no way to wait for an OpenGL command from one thread to another. In OpenGL ES 3.0, fence is implemented.

We said that most OpenGL ES methods don’t wait, but what method does? GlFinish () ¶ glReadPixels() ¶ glFinish() ¶ glReadPixels() ¶ glFinish() ¶ glreadBuffers () ¶ GlReadPixels () takes time, so some people might think that reading out the texture is time-consuming. In fact, it doesn’t take much time. It’s waiting for all the commands in the command queue to complete.

GlReadPixels () doesn’t take as long as you’d expect if you empty the command queue by calling glFinish() and then execute glReadPixels().

GlReadPixels () why call glFinish() implicitly? GlReadPixels () is intended to read pixels out of the texture. If you don’t ensure that the previous render command is run out, the result is uncertain. GlFinish () is also used for buffers. Similarly, since eglSwapBuffers() swaps both buffers so that the back buffer being rendered can be displayed, is it possible to display incomplete buffers without ensuring that all render commands have been executed?

Since most OpenGL ES methods simply insert commands into the command queue without waiting for execution to complete, to measure the actual elapsed time of an OpenGL ES operation, add glFinish() before and after it:

glFinish()
val startTime = System.currentTimeMillis()
// OpenGL ES
glFinish()
val duration = System.currentTimeMillis() - startTime
Copy the code

We also added glFinish() to finish the previous command without interfering with our measurements.

Another method similar to glFinish() is glFlush(), which flusits the entire command queue to the GPU without waiting for it to complete, so you can use glFlush() when you want it to execute quickly but not immediately.

Thanks for reading!