Let’s talk about eglMakeCurrent, eglSwapBuffers, glFlush, glFinish

OpenGL_ES_logo

I have been interested in OpenGL ES 2.0 for years and today I want to share some of my experience.

eglMakeCurrent

First of all, do you remember the function eglMakeCurrent? Its prototype is:

EGLBoolean eglMakeCurrent(
    EGLDisplay display,
    EGLSurface draw,
    EGLSurface read,
    EGLContext context);

This prototype would force you to understand what are Display, Surface, and Context.

Display

As you have seen, most EGL functions require a Display as the first parameter. A Display is just a connection to the native windowing system running on your computer, like the X Window System on GNU/Linux systems, or Microsoft Windows.

Therefore, before you can use EGL functions, you must create and initialize a Display connection. This is easily done in a two-call sequence, as shown below:

EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display; // EGLDisplay is just a void* type
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) {
    // Unable to open connection to local windowing system
}
if (!eglInitialize(display, &majorVersion, &minorVersion)) {
    // Unable to initialize EGL. Handle and recover
}

You usually (always) pass EGL_DEFAULT_DISPLAY to the function eglGetDisplay so EGL will select the default Display automatically.

Context

Context is nothing but a container that contains two things:

  • Internal state machine (view port, depth range, clear color, textures, VBO, FBO …).
  • A command buffer to hold GL commands that have been called in this context.

In general, Context‘s purpose is storing input data of rendering.

Surface

You can guess that Surface‘s purpose is storing output of rendering. Indeed, a Surface extends a native window or pixmap with additional auxiliary buffers. These buffers include a color buffer, a depth buffer, and a stencil buffer.

Back to eglMakeCurrent

So what does the function eglMakeCurrent do? If you are wondering: when I call a GL command (e.g. glDrawElements), which context and which surface is affected by that GL command; then the answer is eglMakeCurrent:

EGLBoolean eglMakeCurrent(
    EGLDisplay display,
    EGLSurface draw,
    EGLSurface read,
    EGLContext context);

eglMakeCurrent binds context to the current rendering thread AND TO the draw and read surfaces. draw is used for all GL operations except for any pixel data read back (glReadPixels, glCopyTexImage2D, and glCopyTexSubImage2D), which is taken from the frame buffer values of read.

Therefore, when you call the GL command on a thread T, OpenGL ES will detect which context C was bound to T, and which surface S[draw] and S[read] were bound to C.

Multiple contexts

Sometimes, you want to create and use more than one context. Here is the rules for that case:

  • Binding the same context in 2 threads is NEVER allowed.
  • Binding 2 different contexts in 2 different threads to the same surface is NEVER allowed.
  • Binding 2 different contexts in 2 different threads to 2 different surfaces MAY be allowed, but MAY fail (depending on the GPU implementation you are using).

Shared contexts

Shared contexts are useful in the loading phase of video games. Because of the heavy of uploading data (especially textures) to GPU; if you want your game’s frame rate to be stable, you should run the uploading on another thread. Due to the three rules above, you must create a secondary context that uses the same internal state machine with the primary context. These primary and secondary contexts are called shared contexts.

Please note that, these two contexts share internal state machine only, they DO NOT share their command buffers.

To create the secondary context, you call:

EGLContext eglCreateContext(
    EGLDisplay display,
    EGLConfig config,
    EGLContext share_context,
    EGLint const * attrib_list);

The 3rd parameter share_context is important here, it is the primary context.

In the secondary thread, you should not draw anything, just upload data to GPU. Hence the surface that you use for this context should be a pixel buffer surface:

EGLSurface eglCreatePbufferSurface(
    EGLDisplay display,
    EGLConfig config,
    EGLint const * attrib_list);

eglSwapBuffers

EGLBoolean eglSwapBuffers(
    EGLDisplay display,
    EGLSurface surface);

At the first time I learned this function, I was thinking that its purpose is swapping the display and the surface :)) very silly.

Actually, the only thing that you need to focus here is the surface. If the surface is a pixel buffer surface, then nothing to do, the function returns without any error.

If the surface is a double-buffer surface (you often use this), the function will swap the front-buffer and the back-buffer inside the surface. The back-buffer is used to store output of rendering, while front-buffer is used by the native window to show color on your monitors.

glFlush and glFinish

The OpenGL ES driver and GPU run in a parallel/asynchronous fashion. When you call a GL command, of course, the driver will try to send the command to GPU as soon as possible for best performance. However, the command will not be immediately executed by GPU, it is just added to a queue inside GPU. Hence, if you ask the driver to send too many GL commands in a short time, the GPU queue might be full so the driver has to keep those commands in the command buffer of the current context. (Do you remember the command buffer of a context?)

That when these pending commands will be sent to the GPU is a question. In general, most OpenGL ES driver implementation may send these commands when there’s a new (next) GL command being called. But if you need the sending to be explicitly happened, please call glFlush, this will block current thread until all commands to be sent to the GPU. glFinish is more powerful, it will block current thread until all commands to be sent to GPU and completely executed by GPU. Be careful as your application’s performance will be declined.

glFlush and glFinish are called explicit synchronization. Sometimes, implicit synchronization might be happened. That is when the eglSwapBuffers command is called. Because this command is directly executed by the driver, there’s a chance so that the GPU will draw pending glDraw* commands onto an unexpected surface buffer, since the front-buffer and back-buffer were swapped before. So, yes, as you are thinking, before swapping, the driver must block current thread to wait for all pending glDraw* commands that affect the current surface being finished.

Of course, with double-buffer surfaces, you never need to call glFlush or glFinish because eglSwapBuffers performs an implicit synchronization. But with single-buffer surfaces (i.e. in the secondary thread above), you MUST call glFlush on-time. For example, before the thread is exited, a call to glFlush is a MUST; otherwise, your GL commands might be NEVER sent to GPU.

Okay, now I can call glFinish :)

Advertisements

3 thoughts on “Let’s talk about eglMakeCurrent, eglSwapBuffers, glFlush, glFinish

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s