An Introduction to EGL - OpenGL ES 3.0: Programming Guide, Second Edition (2014)

OpenGL ES 3.0: Programming Guide, Second Edition (2014)

Chapter 3. An Introduction to EGL

In Chapter 2, “Hello Triangle: An OpenGL ES 3.0 Example,” we drew a triangle into a window using OpenGL ES 3.0, but we used some custom functions of our own design to open and manage the window. Although that technique simplifies our examples, it obscures how you might need to work with OpenGL ES 3.0 on your own systems.

As part of the family of APIs provided by the Khronos Group for developing content, a (mostly) platform-independent API, EGL, is available for managing drawing surfaces (windows are just one type; we will talk about others later). EGL provides the mechanisms for the following:

• Communicating with the native windowing system of your device

• Querying the available types and configurations of drawing surfaces

• Creating drawing surfaces

• Synchronizing rendering between OpenGL ES 3.0 and other graphics-rendering APIs (such as desktop OpenGL and OpenVG, a cross-platform API for hardware-accelerated vector graphics, or the native drawing commands of your windowing system)

• Managing rendering resources such as texture maps

This chapter introduces the fundamentals required to open a window. As we describe other operations, such as creating texture maps, we discuss the necessary EGL commands.

Communicating with the Windowing System

EGL provides a “glue” layer between OpenGL ES 3.0 (and other Khronos graphics APIs) and the native windowing system running on your computer, like the X Window System commonly found on GNU/Linux systems, Microsoft Windows, or Mac OS X’s Quartz. Before EGL can determine which types of drawing surfaces are available—or any other characteristics of the underlying system, for that matter—it needs to open a communications channel with the windowing system. Note that Apple provides its own iOS implementation of the EGL API called EAGL.

Because every windowing system has different semantics, EGL provides a basic opaque type—the EGLDisplay—that encapsulates all of the system dependencies for interfacing with the native windowing system. The first operation that any application using EGL will need to perform is to create and initialize a connection with the local EGL display. This is done in a two-call sequence, as shown in Example 3-1.

Example 3-1 Initializing EGL


EGLint majorVersion;
EGLint minorVersion;

EGLDisplay 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
}


To open a connection to the EGL display server, you call the following function:

Image

EGLNativeDisplayType is defined to match the native window system’s display type. On Microsoft Windows, for example, an EGLNativeDisplayType would be defined to be an HDC—a handle to the Microsoft Windows device context. However, to make it easy to move your code to different operating systems and platforms, the token EGL_DEFAULT_DISPLAY is accepted and will return a connection to the default native display, as we did.

If a display connection isn’t available, eglGetDisplay will return EGL_NO_DISPLAY. This error indicates that EGL isn’t available, and you won’t be able to use OpenGL ES 3.0.

Before we continue by discussing more EGL operations, we need to briefly describe how EGL processes and reports errors to your application.

Checking for Errors

Most functions in EGL return EGL_TRUE when successful and EGL_FALSE otherwise. However, EGL will do more than just tell you if the call failed—it will record an error to indicate the reason for failure. However, that error code is not returned to you directly; you need to query EGL explicitly for the error code, which you can do by calling the following function:

Image

This function returns the error code of the most recent EGL function called in a specific thread. If EGL_SUCCESS is returned, then there is no status to return.

You might wonder why this is a prudent approach, as compared to directly returning the error code when the call completes. Although we never encourage anyone to ignore function return codes, allowing optional error code recovery reduces redundant code in applications verified to work properly. You should certainly check for errors during development and debugging, and on an ongoing basis in critical applications, but once you are convinced your application is working as expected, you can likely reduce your error checking.

Initializing EGL

Once you have successfully opened a connection, EGL needs to be initialized, which is done by calling the following function:

Image

This function initializes EGL’s internal data structures and returns the major and minor version numbers of the EGL implementation. If EGL is unable to be initialized, this call will return EGL_FALSE, and set EGL’s error code to

• EGL_BAD_DISPLAY if display doesn’t specify a valid EGLDisplay.

• EGL_NOT_INITIALIZED if the EGL cannot be initialized.

Determining the Available Surface Configurations

Once we have initialized EGL, we are able to determine which types and configurations of rendering surfaces are available to us. There are two ways to go about this:

• Query every surface configuration and find the best choice ourselves.

• Specify a set of requirements and let EGL make a recommendation for the best match.

In many situations, the second option is simpler to implement, and most likely yields what you would have found using the first option. In either case, EGL will return an EGLConfig, which is an identifier to an EGL-internal data structure that contains information about a particular surface and its characteristics, such as the number of bits for each color component, or the depth buffer (if any) associated with that EGLConfig. You can query any of the attributes of an EGLConfig, using the eglGetConfigAttrib function, which we describe later.

To query all EGL surface configurations supported by the underlying windowing system, call this function:

Image

This function returns EGL_TRUE if the call succeeded. On failure, this call will return EGL_FALSE and set EGL’s error code to

• EGL_NOT_INITIALIZED if display is not initialized.

• EGL_BAD_PARAMETER if numConfigs is NULL.

There are two ways to call eglGetConfigs. First, if you specify NULL for the value of configs, the system will return EGL_TRUE and set numConfigs to the number of available EGLConfigs. No additional information about any of the EGLConfigs in the system is returned, but knowing the number of available configurations allows you to allocate enough memory to get the entire set of EGLConfigs, should you care to do so.

Alternatively, and perhaps more usefully, you can allocate an array of uninitialized EGLConfig values and pass them into eglGetConfigs as the configs parameter. Set maxReturnConfigs to the size of the array you allocated, which will also specify the maximum number of configurations that will be returned. When the call completes, numConfigs will be updated with the number of entries in configs that were modified. You can then begin processing the list of returned values, querying the characteristics of the various configurations to determine which one best matches your needs.

Querying EGLConfig Attributes

We now describe the values that EGL associates with an EGLConfig and explain how you can retrieve those values.

An EGLConfig contains all of the information about a surface made available by EGL. This includes information about the number of available colors, additional buffers associated with the configuration (such as depth and stencil buffers, which we discuss later), the type of surfaces, and numerous other characteristics. The following is a list of the attributes that can be queried from an EGLConfig. We discuss only a subset of these in this chapter, but the entire list appears in Table 3-1 as a reference.

Image

Image

Note: Various tokens do not have a default value mandated in the EGL specification, as indicated by the dash (—) for their default value.

Table 3-1 EGLConfig Attributes

To query a particular attribute associated with an EGLConfig, use the following function:

Image

This function returns EGL_TRUE if the call succeeded. On failure, EGL_FALSE is returned, and an EGL_BAD_ATTRIBUTE error is posted if attribute is not a valid attribute.

This call will return the value for the specific attribute of the associated EGLConfig. This allows you total control over which configuration you choose for ultimately creating rendering surfaces. However, when looking at Table 3-1, you might be somewhat intimidated by the large number of options. EGL provides another routine, eglChooseConfig, that allows you to specify what is important for your application, and will return the best matching configuration given your requests.

Letting EGL Choose the Configuration

To have EGL make the choice of matching EGLConfigs, use this function:

Image

This function returns EGL_TRUE if the call succeeded. On failure, EGL_FALSE is returned, and an EGL_BAD_ATTRIBUTE error is posted if attribList contains an undefined EGL attribute or an attribute value that is unrecognized or out of range.

You need to provide a list of the attributes, with associated preferred values for all the attributes that are important for the correct operation of your application. For example, if you need an EGLConfig that supports a rendering surface having five bits red and blue, and six bits green (the commonly used “RGB 565” format); a depth buffer; and OpenGL ES 3.0, you might declare the array shown in Example 3-2.

Example 3-2 Specifying EGL Attributes


EGLint attribList[] =
{
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_DEPTH_SIZE, 1,
EGL_NONE
};


For values that are not explicitly specified in the attribute list, EGL will use the default values shown in Table 3-1. Additionally, when specifying a numeric value for an attribute, EGL will guarantee the returned configuration has at least that value at a minimum if there is a matchingEGLConfig available.


Note

Using the EGL_OPENGL_ES3_BIT_KHR attribute requires the EGL_KHR_create_context extension. This attribute is defined in eglext.h (EGL v1.4). It is also worth noting that some implementations will always promote OpenGL ES 2.0 contexts to OpenGL ES 3.0 contexts, as OpenGL ES 3.0 is backward compatible with OpenGL ES 2.0.


To use this set of attributes as a selection criteria, follow Example 3-3.

Example 3-3 Querying EGL Surface Configurations


const EGLint MaxConfigs = 10;
EGLConfig configs[MaxConfigs]; // We'll accept only 10 configs
EGLint numConfigs;
if ( !eglChooseConfig( display, attribList, configs, MaxConfigs,
&numConfigs ) )
{
// Something did not work ... handle error situation
}
else
{
// Everything is okay; continue to create a rendering surface
}


If eglChooseConfig returns successfully, a set of EGLConfigs matching your criteria will be returned. If more than one EGLConfig matches (with at most the maximum number of configurations you specify), eglChooseConfig will sort the configurations using the following ordering:

1. By the value of EGL_CONFIG_CAVEAT. Precedence is given to configurations where there are no configuration caveats (when the value of EGL_CONFIG_CAVEAT is EGL_NONE), then slow rendering configurations (EGL_SLOW_CONFIG), and finally nonconformant configurations (EGL_NON_CONFORMANT_CONFIG).

2. By the type of buffer as specified by EGL_COLOR_BUFFER_TYPE.

3. By the number of bits in the color buffer in descending sizes. The number of bits in a buffer depends on the EGL_COLOR_BUFFER_TYPE, and will be at least the value specified for a particular color channel. When the buffer type is EGL_RGB_BUFFER, the number of bits is computed as the total of EGL_RED_SIZE, EGL_GREEN_SIZE, and EGL_BLUE_SIZE. When the color buffer type is EGL_LUMINANCE_BUFFER, the number of bits is the sum of EGL_LUMINANCE_SIZE and EGL_ALPHA_SIZE.

4. By the value of EGL_BUFFER_SIZE in ascending order.

5. By the value of EGL_SAMPLE_BUFFERS in ascending order.

6. By the number of EGL_SAMPLES in ascending order.

7. By the value of EGL_DEPTH_SIZE in ascending order.

8. By the value of the EGL_STENCIL_SIZE in ascending order.

9. By the value of the EGL_ALPHA_MASK_SIZE (which is applicable only to OpenVG surfaces).

10. By the EGL_NATIVE_VISUAL_TYPE in an implementation-dependent manner.

11. By the value of the EGL_CONFIG_ID in ascending order.

Parameters not mentioned in this list are not used in the sorting process.


Note

Because of the third sorting rule, to get the best format that matches what you specified, you will need to add extra logic to go through the returned results. For example, if you ask for “565” RGB format, then the “888” format will appear in the returned results first.


As mentioned in Example 3-3, if eglChooseConfig returns successfully, we have enough information to continue to create something to draw into. By default, if you do not specify which type of rendering surface type you would like (by specifying the EGL_SURFACE_TYPE attribute), EGL assumes you want an on-screen window.

Creating an On-Screen Rendering Area: The EGL Window

Once we have a suitable EGLConfig that meets our requirements for rendering, we are ready to create our window. To create a window, call the following function:

Image

This function takes as arguments our connection to the native display manager and the EGLConfig that we obtained in the previous step. Additionally, it requires a window from the native windowing system that was created previously. Because EGL is a software layer between many different windowing systems and OpenGL ES 3.0, demonstrating how to create a native window is outside the scope of this guide. Please refer to the documentation for your native windowing system to determine what is required to create a window in that environment.

Finally, this call takes a list of attributes; however, this list differs from the attributes shown in Table 3-1. Because EGL supports other rendering APIs (notably OpenVG), some attributes accepted by eglCreateWindowSurface do not apply when working with OpenGL ES 3.0 (see Table 3-2). For our purposes, eglCreateWindowSurface accepts a single attribute, which is used to specify the buffer of the front- or back-buffer we would like to render into.

Image

Table 3-2 Attributes for Window Creation Using eglCreateWindowSurface


Note

For OpenGL ES 3.0 window rendering surfaces, only double-buffered windows are supported.


The attribute list might be empty (i.e., passing a NULL pointer as the value for attribList), or it might be a list populated with an EGL_NONE token as the first element. In such cases, all of the relevant attributes use their default values.

There are a number of ways in which eglCreateWindowSurface could fail, and if any of them occur, EGL_NO_SURFACE is returned from the call and the particular error is set. If this situation occurs, we can determine the reason for the failure by calling eglGetError, which will return one of the reasons shown in Table 3-3.

Image

Table 3-3 Possible Errors When eglCreateWindowSurface Fails

Putting this all together, our code for creating a window is shown in Example 3-4.

Example 3-4 Creating an EGL Window Surface


EGLint attribList[] =
{
EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
EGL_NONE
);

EGLSurface window = eglCreateWindowSurface ( display, config,
nativeWindow,
attribList );
if ( window == EGL_NO_SURFACE )
{
switch ( eglGetError ( ) )
{
case EGL_BAD_MATCH:
// Check window and EGLConfig attributes to determine
// compatibility, or verify that the EGLConfig
// supports rendering to a window
break;

case EGL_BAD_CONFIG:
// Verify that provided EGLConfig is valid
break;

case EGL_BAD_NATIVE_WINDOW:
// Verify that provided EGLNativeWindow is valid
break;

case EGL_BAD_ALLOC:
// Not enough resources available; handle and recover
break;
}
}


This creates a place for us to draw into, but we still have two more steps that must be completed before we can successfully use OpenGL ES 3.0 with our window. Windows, however, are not the only rendering surfaces that you might find useful. We introduce another type of rendering surface next before completing our discussion.

Creating an Off-Screen Rendering Area: EGL Pbuffers

In addition to being able to render into an on-screen window using OpenGL ES 3.0, you can render into nonvisible off-screen surfaces called pbuffers (short for pixel buffer). Pbuffers can take full advantage of any hardware acceleration available to OpenGL ES 3.0, just as a window does. Pbuffers are most often used for generating texture maps. If all you want to do is render to a texture, we recommend using framebuffer objects (covered in Chapter 12, “Framebuffer Objects”) instead of pbuffers because they are more efficient. However, pbuffers can still be useful in some cases where framebuffer objects cannot be used, such as when rendering an off-screen surface with OpenGL ES and then using it as a texture in another API such as OpenVG.

Creating a pbuffer is very similar to creating an EGL window, with a few minor differences. To create a pbuffer, we need to find an EGLConfig just as we did for a window, with one modification: We need to augment the value of EGL_SURFACE_TYPE to include EGL_PBUFFER_BIT. Once we have a suitable EGLConfig, we can create a pbuffer using the function

Image

As with window creation, this function takes our connection to the native display manager and the EGLConfig that we selected. This call also takes a list of attributes, as described in Table 3-4.

Image

Image

Table 3-4 EGL Pixel Buffer Attributes

There are a number of ways that eglCreatePbufferSurface could fail. Just as with window creation, if any of these failures occur, EGL_NO_SURFACE is returned from the call and the particular error is set. In this situation, eglGetError will return one of the errors listed in Table 3-5.

Image

Table 3-5 Possible Errors When eglCreatePbufferSurface Fails

Putting this all together, we create a pbuffer, as shown in Example 3-5.

Example 3-5 Creating an EGL Pixel Buffer


EGLint attribList[] =
{
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_DEPTH_SIZE, 1,
EGL_NONE
};

const EGLint MaxConfigs = 10;
EGLConfig configs[MaxConfigs]; // We'll accept only 10 configs
EGLint numConfigs;
if ( !eglChooseConfig( display, attribList, configs, MaxConfigs,
&numConfigs ) )
{
// Something did not work ... handle error situation
}
else
{
// We have found a pbuffer-capable EGLConfig
}

// Proceed to create a 512 x 512 pbuffer
// (or the largest available)
EGLSurface pbuffer;
EGLint attribList[] =
{
EGL_WIDTH, 512,
EGL_HEIGHT, 512,
EGL_LARGEST_PBUFFER, EGL_TRUE,
EGL_NONE
);

pbuffer = eglCreatePbufferSurface( display, config, attribList);
if ( pbuffer == EGL_NO_SURFACE )
{
switch ( eglGetError ( ) )
{
case EGL_BAD_ALLOC:
// Not enough resources available; handle and recover
break;

case EGL_BAD_CONFIG:
// Verify that provided EGLConfig is valid
break;

case EGL_BAD_PARAMETER:
// Verify that EGL_WIDTH and EGL_HEIGHT are
// non-negative values
break;

case EGL_BAD_MATCH:
// Check window and EGLConfig attributes to determine
// compatibility and pbuffer-texture parameters
break;
}
}

// Check the size of pbuffer that was allocated
EGLint width;
EGLint height;

if ( !eglQuerySurface ( display, pbuffer, EGL_WIDTH, &width ) ||
!eglQuerySurface ( display, pbuffer, EGL_HEIGHT, &height ))
{
// Unable to query surface information
}


Pbuffers support all OpenGL ES 3.0 rendering facilities, just as windows do. The major difference—aside from the fact that you cannot display a pbuffer on the screen—is that instead of swapping buffers when you are finished rendering as you do with a window, you either copy the values from a pbuffer to your application or modify the binding of the pbuffer as a texture.

Creating a Rendering Context

A rendering context is a data structure internal to OpenGL ES 3.0 that contains all of the state information required for operation. For example, it contains references to the vertex and fragment shaders and the array of vertex data used in the example program in Chapter 2, “Hello Triangle: An OpenGL ES 3.0 Example.” Before OpenGL ES 3.0 can draw, it needs to have a context available for its use.

To create a context, use the following function:

Image

Once again, you will need the display connection and the EGLConfig that best represents your application’s requirements. The third parameter, shareContext, allows multiple EGLContexts to share specific types of data, such as shader programs and texture maps. For the time being, we pass EGL_NO_CONTEXT in as the value for shareContext, indicating that we are not sharing resources with any other contexts.

Finally, as with many EGL calls, a list of attributes specific to eglCreateContext’s operation is specified. In this case, a single attribute is accepted, EGL_CONTEXT_CLIENT_VERSION, which is discussed in Table 3-6.

Image

Table 3-6 Attributes for Context Creation Using eglCreateContext

Because we want to use OpenGL ES 3.0, we will always have to specify this attribute to obtain the right type of context.

When eglCreateContext succeeds, it returns a handle to the newly created context. If a context cannot be created, then eglCreateContext returns EGL_NO_CONTEXT, and the reason for the failure is set and can be obtained by calling eglGetError. With our current knowledge, the only reason that eglCreateContext would fail is if the EGLConfig we provide is not valid, in which case the error returned by eglGetError is EGL_BAD_CONFIG.

Example 3-6 shows how to create a context after selecting an appropriate EGLConfig.

Example 3-6 Creating an EGL Context


const EGLint attribList[] =
{
// EGL_KHR_create_context is required
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};

EGLContext context = eglCreateContext ( display, config,
EGL_NO_CONTEXT,
attribList );

if ( context == EGL_NO_CONTEXT )
{
EGLError error = eglGetError ( );

if ( error == EGL_BAD_CONFIG )
{
// Handle error and recover
}
}


Other errors may be generated by eglCreateContext, but for the moment we will check for only bad EGLConfig errors.

After successfully creating an EGLContext, we need to complete one final step before we can render.

Making an EGLContext Current

As an application might have created multiple EGLContexts for various purposes, we need a way to associate a particular EGLContext with our rendering surface—a process commonly called “make current.”

To associate a particular EGLContext with an EGLSurface, use the call

Image

This function returns EGL_TRUE if the call succeeded. On failure, it returns EGL_FALSE.

You probably noticed that this call takes two EGLSurfaces. Although this approach allows flexibility that we will exploit in our discussion of advanced EGL usage, we set both read and draw to the same value, the window that we created previously.


Note

Because the EGL specification requires a flush for eglMakeCurrent implementation, this call is expensive for tile-based architectures.


Putting All Our EGL Knowledge Together

This chapter concludes with a complete example showing the entire process starting with the initialization of the EGL through binding an EGLContext to an EGLSurface. We will assume that a native window has already been created, and that if any errors occur, the application will terminate.

In fact, Example 3-7 is similar to what is done in esCreateWindow, our homegrown function that wraps the required EGL window creation code, as shown in Chapter 2, “Hello Triangle: An OpenGL ES 3.0 Example,” except for those routines that separate the creation of the window and the context (for reasons that we discuss later).

Example 3-7 A Complete Routine for Creating an EGL Window


EGLBoolean initializeWindow ( EGLNativeWindow nativeWindow )
{
const EGLint configAttribs[] =
{
EGL_RENDER_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_NONE
};

const EGLint contextAttribs[] =
{
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};

EGLDisplay display = eglGetDisplay ( EGL_DEFAULT_DISPLAY )
if ( display == EGL_NO_DISPLAY )
{
return EGL_FALSE;
}

EGLint major, minor;
if ( !eglInitialize ( display, &major, &minor ) )
{
return EGL_FALSE;
}

EGLConfig config;
EGLint numConfigs;
if ( !eglChooseConfig ( display, configAttribs, &config, 1,
&numConfigs ) )
{
return EGL_FALSE;
}

EGLSurface window = eglCreateWindowSurface ( display, config,
nativeWindow, NULL );
if (window == EGL_NO_SURFACE)
{
return EGL_FALSE;
}

EGLContext context = eglCreateContext ( display, config,
EGL_NO_CONTEXT,
contextAttribs);
if ( context == EGL_NO_CONTEXT )
{
return EGL_FALSE;
}

if ( !eglMakeCurrent ( display, window, window, context ) )
{
return EGL_FALSE;
}
return EGL_TRUE;
}


This code would be similar if an application made the call in Example 3-8 to open a 512 × 512 window.

Example 3-8 Creating a Window Using the esUtil Library


ESContext esContext;
const char* title = "OpenGL ES Application Window Title";

if (esCreateWindow(&esContext, title, 512, 512,
ES_WINDOW_RGB | ES_WINDOW_DEPTH))
{
// Window creation failed
}


The last parameter to esCreateWindow specifies the characteristics we want in our window, and specifies as a bitmask of the following values:

• ES_WINDOW_RGB—Specify an RGB-based color buffer.

• ES_WINDOW_ALPHA—Allocate a destination alpha buffer.

• ES_WINDOW_DEPTH—Allocate a depth buffer.

• ES_WINDOW_STENCIL—Allocate a stencil buffer.

• ES_WINDOW_MULTISAMPLE—Allocate a multisample buffer.

Specifying these values in the window configuration bitmask will add the appropriate tokens and values into the EGLConfig attributes list (i.e., configAttribs in the preceding example).

Synchronizing Rendering

You might encounter situations in which you need to coordinate the rendering of multiple graphics APIs into a single window. For example, you might find it easier to use OpenVG or find the native windowing system’s font rendering functions better suited for drawing characters into a window than OpenGL ES 3.0. In such cases, you will need to have your application allow the various libraries to render into the shared window. EGL has a few functions to help with your synchronization tasks.

If your application is rendering only with OpenGL ES 3.0, then you can guarantee that all rendering has occurred by simply calling glFinish (or more efficient sync objects and fences, which are discussed in Chapter 13, “Sync Objects and Fences”).

However, if you are using more than one Khronos API for rendering (such as OpenVG) and you might not know which API is used before switching to the window system’s native rendering API, you can call this function:

Image

This function’s operation is similar to that of glFinish, but it works regardless of which Khronos API is currently in operation.

Likewise, if you need to guarantee that the native windowing system’s rendering is completed, call this function:

Image

EGL_CORE_NATIVE_ENGINE is always accepted, and represents the most common engine supported; other engines are implementation specific, and are specified through EGL extensions. EGL_TRUE is returned on success. On failure, EGL_FALSE is returned and anEGL_BAD_PARAMETER error is posted.

Summary

In this chapter, you learned about EGL, the API for creating surfaces and rendering contexts for OpenGL ES 3.0. Now, you know how to initialize EGL; query various EGL attributes; and create an on-screen, off-screen rendering area and rendering context using EGL. You have learned enough EGL to do everything you will need for rendering with OpenGL ES 3.0. In the next chapter, we show you how to create OpenGL ES shaders and programs.