In Chapter 2, “Hello, Triangle: An OpenGL ES 2.0 Example,” we drew a triangle into a window using OpenGL ES 2.0, but we used some custom functions of our own design to open and manage the window. Although that simplifies our examples, it obscures how you might need to work with OpenGL ES 2.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’ll talk about others later). EGL provides the mechanisms for the following:
Communicating with the native windowing system of your system.
Querying the available types and configurations of drawing surfaces.
Creating drawing surfaces.
Synchronizing rendering between OpenGL ES 2.0 and other graphics-rendering APIs (like OpenVG, or the native drawing commands of your windowing system).
Managing rendering resources such as texture maps.
We introduce the fundamentals required to open a window in this chapter. As we describe other operations, such as creating a texture map, we discuss the necessary EGL commands.
EGL provides a “glue” layer between OpenGL ES 2.0 (and other Khronos graphics APIs) and the native windowing system running on your computer, like the X Window System common on GNU/Linux systems, Microsoft Windows, or Mac OS X’s Quartz. Before EGL can determine what types of drawing surfaces, or any other characteristics of the underlying system for that matter, it needs to open a communications channel with the windowing system.
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 do is 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; 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, call
|
|
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 2.0.
Before we continue discussing more EGL operation, we need to briefly describe how EGL processes and reports errors to your application.
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 isn’t returned to you directly; you need to query EGL explicitly for the error code, which you can do by calling
|
|
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 ignoring 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 all the time in critical applications, but once you’re convinced your application is working as expected, you can likely reduce your error checking.
Once you’ve successfully opened a connection, EGL needs to be initialized, which is done by calling
|
|
This 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.
Once we’ve initialized EGL, we’re able to determine what 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 number of bits for each color component, or if there’s a depth buffer associated with that EGLConfig
. You can query any of the attributes of an EGLConfig
, using the eglGetConfigAttribute
function, which we describe later.
To query all EGL surface configurations supported by the underlying windowing system, call
|
|
which returns EGL_TRUE
if the call succeeded.
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 EGLConfig
s. No additional information about any of the EGLConfig
s in the system is returned, but knowing the number of available configurations allows you to allocate enough memory to get the entire set of EGLConfig
s, should you care to.
Alternatively, and perhaps more useful, is that you can allocate an array of uninitialized EGLConfig
values, and pass those into eglGetConfigs
as the configs
parameter. Set maxReturnConfigs
to the size of the array you allocated, which will also specify the maximum number of configs 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 returns, querying the characteristics of the configurations to determine which one matches our needs the best.
We now describe the values that EGL associates with an EGLConfig
, and 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 (like depth and stencil buffers, which we discuss later), the type of surfaces, and numerous other characteristics. What follows is a list of all of the attributes that can be queried from an EGLConfig
. We only discuss a subset of these in this chapter, but we provide the entire list in Table 3-1 as a reference.
Table 3-1. EGLConfig
Attributes
Description | Default Value | |
---|---|---|
| Number of bits for all color components in the color buffer | 0 |
| Number of red bits in the color buffer | 0 |
| Number of green bits in the color buffer | 0 |
| Number of blue bits in the color buffer | 0 |
| Number of luminance bits in the color buffer | 0 |
| Number of alpha bits in the color buffer | 0 |
| Number of alpha-mask bits in the mask buffer | 0 |
| True if bindable to RGB textures |
|
| True if bindable to RGBA textures |
|
| Type of the color buffer: either |
|
| Any caveats associated with the configuration |
|
| The unique |
|
| True if contexts created with this | — |
| Number of bits in the depth buffer | 0 |
| Frame buffer level | 0 |
| Maximum width for a | — |
| Maximum height for a | — |
Maximum size of a | — | |
| Maximum buffer swap interval |
|
| Minimum buffer swap interval |
|
| True if native rendering libraries can render into a surface created with |
|
| Handle of corresponding native window system visual ID |
|
| Type of corresponding native window system visual |
|
| A bitmask composed of the tokens |
|
| Number of available multisample buffers | 0 |
| Number of samples per pixel | 0 |
| Number of bits in the stencil buffer | 0 |
| Type of EGL surfaces supported. Can be any of |
|
| Type of transparency supported |
|
| Red color value interpreted as transparent |
|
| Green color value interpreted as transparent |
|
| Blue color value interpreted as transparent. |
|
Note: Various tokens do not have a default value mandated in the EGL specification, which are indicated by—for their default value. |
To query a particular attribute associated with an EGLConfig
, use
|
|
which 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, looking at Table 3.1, you might be somewhat intimidated given the number of options. EGL provides another routine, eglChooseConfig
, that allows you to specify what’s important for your application, and will return the best matching configuration to your requests.
To have EGL make the choice of matching EGLConfig
s, use
|
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, six bits green (the common “RGB 565” format), a depth buffer, and supporting OpenGL ES 2.0, you might declare the array shown in Example 3-2.
For values that aren’t explicitly specified in the attribute list, EGL will use their default value as specified in Table 3-1. Additionally, when specifying a numeric value for an attribute, EGL will guarantee the returned configuration will have at least that value as a minimum if there’s a matching EGLConfig
available.
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 only accept 10 configs EGLint numConfigs; if(!eglChooseConfig(dpy, attribList, configs, MaxConfigs, &numConfigs)) { // Something didn't work ... handle error situation } else { // Everything's okay. Continue to create a rendering surface }
If eglChooseConfig
returns successfully, a set of EGLConfig
s 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:
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 GL_NONE
), then slow rendering configurations (EGL_SLOW_CONFIG
), and finally nonconformant configurations (EGL_NON_CONFORMANT_CONFIG
).
By the type of buffer as specified by EGL_COLOR_BUFFER_TYPE
.
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
.
By the EGL_BUFFER_SIZE
in ascending order.
By the value of EGL_SAMPLE_BUFFERS
in ascending order.
By the number of EGL_SAMPLES
in ascending order.
By the value of EGL_DEPTH_SIZE
in ascending order.
By the value of the EGL_STENCIL_SIZE
in ascending order.
By the value of the EGL_ALHPA_MASK_SIZE
(which is applicable only to OpenVG surfaces).
By the EGL_NATIVE_VISUAL_TYPE
in an implementation-dependent manner.
By the value of the EGL_CONFIG_ID
in ascending order.
Parameters not mentioned in this list are not used in the sorting process.
As mentioned in the example, if eglChooseConfig
returns successfully, we have enough information to continue to create something to draw into. By default, if you don’t specify what type of rendering surface type you would like (by specifying the EGL_SURFACE_TYPE
attribute), EGL assumes you want an on-screen window.
Once we have a suitable EGLConfig
that meets our requirements for rendering, we’re set to create our window. To create a window, call
|
|
This function takes 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 2.0, demonstrating how to create a native window is outside the scope of this guide. Please reference the documentation for your native windowing system to determine what’s required to create a window in that environment.
Finally, this call also takes a list of attributes; however this list differs from those shown in Table 3-1. Because EGL supports other rendering APIs (notably OpenVG), there are attributes accepted by eglCreateWindowSurface
that don’t apply when working with OpenGL ES 2.0 (see Table 3-2). For our purposes, there is a single attribute that’s accepted by eglCreateWindowSurface
, and it’s used to specify which buffer of the front- or back-buffer we’d like to render into.
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 that 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 following reasons shown in Table 3-3.
Table 3-3. Possible Errors When eglCreateWindowSurface
Fails
Description | |
---|---|
| This situation occurs when: |
| |
| This error is flagged if the provided |
| This error is specified if the provided native window handle is not valid. |
| This error occurs if |
Putting this all together, our code for creating a window is shown in Example 3-4.
Example 3-4. Creating an EGL Window Surface
EGLRenderSurface window; EGLint attribList[] = { EGL_RENDER_BUFFER, EGL_BACK_BUFFER, EGL_NONE ); window = eglCreateWindowSurface(dpy, window, config, 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 before we’ll be able to successfully use OpenGL ES 2.0 with our window. Windows, however, aren’t the only rendering surfaces that you might find useful. We introduce another type of rendering surface next before completing our discussion.
In addition to being able to render into an on-screen window using OpenGL ES 2.0, you can also 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 2.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 for 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
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 described in Table 3-4.
Table 3-4. EGL Pixel Buffer Attributes
Token | Description | Default Value |
---|---|---|
| Specifies the desired width (in pixels) of the pbuffer. | 0 |
| Specifies the desired height (in pixels) of the pbuffer. | 0 |
| Select the largest available pbuffer if one of the requested size isn’t available. Values can be |
|
| Specifies the type of texture format (see Chapter 9) if the pbuffer is bound to a texture map. Valid values are |
|
| Specifies the associated texture target that the pbuffer should be attached to if used as a texture map (see Chapter 9). Valid values are |
|
| Specifies whether storage for texture mipmap levels (see Chapter 9) should be additionally allocated. Valid values are |
|
There are a number of ways that eglCreatePbufferSurface
could fail, and just as with window creation, if any of them 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.
Table 3-5. Possible Errors When eglCreatePbufferSurface
Fails
Description | |
---|---|
| This error occurs when the pbuffer is unable to be allocated due to a lack of resources. |
| This error is flagged if the provided |
| This error is generated if either the |
| This error is generated if any of the following situations occur: if the |
| This error occurs if any of |
Putting this all together, we would 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_ES2_BIT, 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 only accept 10 configs EGLint numConfigs; if(!eglChooseConfig(dpy, attribList, configs, MaxConfigs, &numConfigs)) { // Something didn't work ... handle error situation } else { // We've found a pbuffer-capable EGLConfig } // Proceed to create a 512 x 512 pbuffer (or the largest available) EGLRenderSurface pbuffer; EGLint attribList[] = { EGL_WIDTH, 512, EGL_HEIGHT, 512, EGL_LARGEST_PBUFFER, EGL_TRUE, EGL_NONE ); pbuffer = eglCreatePbufferSurface(dpy, 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 the 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 to see what size pbuffer we were allocated EGLint width; EGLint height; if(!eglQuerySurface(dpy, pbuffer, EGL_WIDTH, &width) || !eglQuerySurface(dpy, pbuffer, EGL_HEIGHT, &height)) { // Unable to query surface information. }
Pbuffers support all OpenGL ES 2.0 rendering facilities just as windows do. The major difference—aside from you can’t display a pbuffer on the screen—is that instead of swapping buffers when you’re finished rendering as you do with a window, you will either copy the values from a pbuffer to your application, or modify the binding of the pbuffer as a texture.
A rendering context is a data structure internal to OpenGL ES 2.0 that contains all of the state 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. Before OpenGL ES 2.0 can draw it needs to have a context available for its use.
To create a context, use
|
|
Once again, you’ll need the display connection as well as the EGLConfig
best representing your application’s requirements. The third parameter, shareContext
, allows multiple EGLContext
s to share specific types of data, like shader programs and texture maps. Sharing resources among contexts is an advanced concept that we discuss in Chapter 13, “Advanced Programming with OpenGL ES 2.0.” For the time being, we pass EGL_NO_CONTEXT
in as the value for shareContext
, indicating that we’re 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, there’s a single attribute that’s accepted, EGL_CONTEXT_CLIENT_VERSION
, discussed in Table 3-6.
As we want to use OpenGL ES 2.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 is not able to 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 isn’t 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_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLContext context; context = eglCreateContext(dpy, 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’ll only check for bad EGLConfig
errors.
After successfully creating an EGLContext
, we need to complete one final step before we can render.
As an application might have created multiple EGLContext
s 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
|
|
You probably noticed that this call takes two EGLSurface
s. Although this allows flexibility that we exploit in our discussion of advanced EGL usage, we set both read and draw to the same value, the window that we created previously.
We conclude this chapter with a complete example showing the entire process starting with the initialization of the EGL through binding an EGLContext
to an EGLRenderSurface
. We’ll 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 very similar to what is done in esCreateWindow
as shown in Chapter 2, except those routines 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, 2, EGL_NONE }; EGLDisplay dpy; dpy = eglGetNativeDispay(EGL_DEFAULT_DISPLAY); if(dpy == EGL_NO_DISPLAY) { return EGL_FALSE; } EGLint major, minor; if(!eglInitialize(dpy, &major, &minor)) { return EGL_FALSE; } EGLConfig config; EGLint numConfigs; if(!eglChooseConfig(dpy, configAttribs, &config, 1, &numConfigs)) { return EGL_FALSE; } EGLSurface window; window = eglCreateWindowSurface(dpy, config, nativeWindow, NULL); if(window == EGL_NO_SURFACE) { return EGL_FALSE; } EGLContext context; context = eglCreateContext(dpy, config, EGL_NO_CONTEXT, contextAttribs); if(context == EGL_NO_CONTEXT) { return EGL_FALSE; } if(!eglMakeCurrent(dpy, window, window, context)) { return EGL_FALSE; } return EGL_TRUE; }
This code would be very similar if an application made the call in Example 3-8 to open a 512 × 512 window.
The last parameter to esCreateWindow
specifies the characteristics we want in our window, and specified 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
attribute list (i.e., configAttribs
in the preceding example).
You might find 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 the native windowing system’s font rendering functions more suited for drawing characters into a window than OpenGL ES 2.0. In such cases, you’ll 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 only rendering with OpenGL ES 2.0, then you can guarantee that all rendering has occurred by simply calling glFinish
.
However, if you’re 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 native rendering API, you can call the following.
|
|
Delays execution of the client until all rendering through a Khronos API (like OpenGL ES 2.0, or OpenVG) is completed. On success, it will return |
Its operation is similar in operation to glFinish
, but works regardless of which Khronos API is currently in operation.
Likewise, if you need to guarantee that the native windowing system rendering is completed, call eglWaitNative
.
|
|
| specifies the renderer to wait for rendering completion. |