234 MISCELLANEOUS OPENGL ES FEATURES CHAPTER 10 10.4 EXTENSIONS OpenGL ES inherits the extension mechanism of the desktop OpenGL. Any vendor can create their own extensions to the basic behavior. Additionally, the OpenGL ES specifica- tion defines a few optional extensions that are likely to be implemented by several vendors, as it would not be very useful if the vendors implemented them in slightly different ways. We first explain the mechanism for querying which extensions are present and obtaining pointers to the extension functions. We continue by describing three extensions: query matrix, matrix palette, and draw texture. 10.4.1 QUERYING EXTENSIONS The list of supported extensions can be queried by calling glGetString with the argu- ment GL_EXTENSIONS. This call returns a space-separated string containing the list of all supported extensions. The application can thenparse this string and use an OS-specific mechanism for obtaining access to the extension functions. If the platform supports EGL, then the function eglGetProcAddress can be used for receiving the address of an extension function: /* define a function pointer of the right type, set to NULL */ void (*_glDrawTexx)(GLfixed, GLfixed, GLfixed, GLfixed, GLfixed) = NULL; if( strstr( glGetString( GL_EXTENSIONS ), "GL_OES_draw_texture" ) ) { _glDrawTexx = (void (*)( GLfixed, GLfixed, GLfixed, GLfixed, GLfixed )) eglGetProcAddress( "glDrawTexxOES" ); } In the example the return value from eglGetProcAddress is cast to a function pointer that matches the extension function prototype. If your implementation has the glext.h header file that contains a ready-made prototype for the extension, you can use it instead. Note that eglGetProcAddress does not work for the core OpenGL ES func- tions. When extensions are folded into the core in newer versions, the extensions for the same functionality are also left in place so that they can still be queried with eglGetProcAddress. 10.4.2 QUERY MATRIX The OES_query_matrix extension introduces the function glQueryMatrixxOES that can be used for reading back the top of the current matrix stack. This somewhat surprising function was introduced in OpenGL ES 1.0 as there was no support for any dynamic state queries, yet the working group felt that mat rix read-back would be useful at least for debugging purposes. The function returns the matrix components’ mantissas and exponents separately, thus providing a representation that is independent of the actual internal implementation of the matrix stack. SECTION 10.4 EXTENSIONS 235 GLbitfield glQueryMatrixxOES(GLfixed mantissa[16], GLint expone nt[16]) queries the matrix at the top of the current matrix stack. The mantissa array will contain the signed 16.16 mantissas of the 4×4 matrix, and the exponent array the exponents. Each entry is then mantissa ∗ 2 exponent . The function returns status, which is a bitfield, which is zero if all the components are valid. If status & (1<<i) != 0, then component i is invalid (e.g., NaN or +−infinity). The following example queries the elements of the current matrix and converts them to floats. The mantissa is first converted to a float, then, depending on the sign of the exponent, it is either multiplied or divided by a suitable poweroftwo. int i,j; GLfixed mantissa[16]; GLint exponent[16]; GLfloat matrix[16]; GLbitfield status; status = glQueryMatrixxOES( mantissa, exponent ); if( 0 == status ) { for(i=0;i<16;i++) { float t = (float)mantissa[i] / 65536.0f; matrix[i]=t*pow( 2, exponent[i] ); } } Note that this extension has been deprecated in OpenGL ES 1.1. A new extension, OES_matrix_get is provided instead. This allows querying the internal floating-point matrices as integer bit patterns. 10.4.3 MATRIX PALETTE Vertex skinning is brought into OpenGL ES by the optional OES_matrix_palette extension. This is a somewhat simplified version of the ARB_matrix_palette exten- sion from the desktop world. Matrix palettes were first introduced to OpenGL ES in version 1.1. Here is a short example code that first checks whether the optional extension is supported, then queries the extension function pointers, sets up the required OpenGL ES state to use the matrix palette, and finally sets up a few matrices in the matrix palette: /* Check if extension is supported, bail out if not */ if( !strstr( glGetString(GL_EXTENSIONS), "GL_OES_matrix_palette" ) ) { return NULL; } 236 MISCELLANEOUS OPENGL ES FEATURES CHAPTER 10 /* Get the extension function pointers and store to global store */ _glCurrentPaletteMatrix = (void (*)(GLuint)) eglGetProcAddress( "glCurrentPaletteMatrixOES" ); _glLoadPaletteFromModelViewMatrix = (void (*)(void)) eglGetProcAddress( "glLoadPaletteFromModelViewMatrixOES" ); _glMatrixIndexPointer = (void (*)( GLint, GLenum, GLsizei, const GLvoid * )) eglGetProcAddress( "glMatrixIndexPointerOES" ); _glWeightPointer = (void (*)( GLint, GLenum, GLsizei, const GLvoid * )) eglGetProcAddress( "glWeightPointerOES" ); _glWeightPointer( 3, GL_FLOAT, 0, mtxweights ); _glMatrixIndexPointer( 3, GL_UNSIGNED_BYTE, 0, mtxindices ); glEnableClientState( GL_MATRIX_INDEX_ARRAY_OES ); glEnableClientState( GL_WEIGHT_ARRAY_OES ); glEnable( GL_MATRIX_PALETTE_OES ); /* set up basic modelview matrix */ glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glTranslatef( 0, 0, -4.f ); glScalef( 0.2f, 0.2f, 0.2f); /* set up matrices in palette indices 0 and 1 */ glMatrixMode( GL_MATRIX_PALETTE_OES ); _glCurrentPaletteMatrix( 0 ); _glLoadPaletteFromModelViewMatrix(); glTranslatef( 0.7f, 0, 0 ); _glCurrentPaletteMatrix( 0 ); _glLoadPaletteFromModelViewMatrix(); glTranslatef( -0.2f, 0, 0 ); void glCurrentPaletteMatrixOES(GLuint matrixpaletteindex) defines which matrix is affected by future matrix manipulation calls. void glLoadPaletteFromModelViewMatrixOES(void) copies the top of the modelview matrix stack to the current matrix palette. void glMatrixIndexPointerOES(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) defines an array of matrix indices. The parameter size determines the number of indices per vertex, type is the data type of the indices (only GL_UNSIGNED_BYTE accepted), SECTION 10.4 EXTENSIONS 237 stride is the stride in bytes between consecutive matrix indices, and pointer points to the matrix index of the first vertex in the array. This vertex array is enabled by calling glEnableClientState with the argument GL_MATRIX_INDEX_ARRAY_OES. void glWeightPointerOES(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) defines an array of matrix weights. The parameter size defines the number of weights per vertex, type is the data type used for the weights (GL_FIXED and GL_FLOAT are sup- ported), str ide is the stride in bytes between consecutive weights, and pointer points to the first weight of the first vertex. This vertex array is enabled by calling glEnableClient State with the argument GL_WEIGHT_ARRAY_OES. There are several state queries that become possible if the matrix palette extension is supported. They are GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES the buffer object name bound to the matrix index array. GL_MATRIX_INDEX_ARRAY_SIZE_OES the number of matrix indices per vertex. GL_MATRIX_INDEX_ARRAY_STRIDE_OES the by te offset between elements. GL_MATRIX_INDEX_ARRAY_TYPE_OES the type of matrix indices. GL_MAX_PALETTE_MATRICES_OES the number of supported matrix palettes (≥ 9). GL_MAX_VERTEX_UNITS_OES the number of supported matrices per vertex (≥ 3). GL_WEIGHT_ARRAY_BUFFER_BINDING_OES the buffer object name bound to weight array. GL_WEIGHT_ARRAY_SIZE_OES the number of weights per vertex. GL_WEIGHT_ARRAY_STRIDE_OES byte offset between weights. GL_WEIGHT_ARRAY_TYPE_OES type of weights. void glGetPointerv(GLenum pname, void ** params) supports two additional tokens: GL_MATRIX_INDEX_ARRAY_POINTER_OES GL_WEIGHT_ARRAY_POINTER_OES GLboolean glIsEnabled(GLenum cap) supports these additional capabilities: GL_MATRIX_PALETTE_OES GL_MATRIX_INDEX_ARRAY_OES GL_WEIGHT_ARRAY_OES 238 MISCELLANEOUS OPENGL ES FEATURES CHAPTER 10 Pitfall: Implementations only need to support 9 bones for a single vertex array. How- ever, many models use more bones than 9, for example, a human character typi- cally requires at least 15, even over 40 bones. If the model uses more bones than the OpenGL ES implementation can handle, you have to split the model into smaller par- titions and render the mesh with several glDrawElements calls. 10.4.4 DRAW TEXTURE The OES_draw_texture extension introduced in OpenGL ES 1.1 provides a mech- anism for rendering a two-dimensional texture-mapped pixel rectangle to a rectangular portion of the screen. void glDrawTex{sifx}OES(T x, T y, T z, T width, T height ) renders a texture-mapped pixel block to the screen. Here x and y define the window coor- dinates of the lower-left corner of the rectangle, and z is a value between 0 and 1 where 0 maps to the near plane and 1 maps to the far plane of the current depth range. The parameters width and height give the size of the screen rectangle in pixels. void glDrawTex{sifx}vOES(const T * coords) provides variants that take the five input coordinates as an array. 10.4.5 USING EXTENSIONS When using an extension, you should first implement a generic version, and switch to the faster-executing extension only if it is available. The following example shows how this is done. Here point sprites are the preferred method, draw texture comes next, and the ultimate fall-back is to render two texture-mapped tr iangles. For the complete drawing code, see the full example on the accompanying web site. { /* initial values for decision variables */ int oes11 = 0; int drawtexture = 0; int pot = 0; int pointsize = 0; /* check GL version */ ver = glGetString( GL_VERSION ); major = ver[strlen(ver)-3]; minor = ver[strlen(ver)-1]; if(minor > ’0’) oes11 = 1; /* Check drawtexture extension */ if( strstr( glGetString(GL_EXTENSIONS), "GL_OES_draw_texture" ) ) SECTION 10.4 EXTENSIONS 239 { drawtexture = 1; _glDrawTexx = (void (*)( GLfixed, GLfixed, GLfixed, GLfixed, GLfixed )) eglGetProcAddress( "glDrawTexxOES" ); } Next, we check whether the dimensions of the source are powers of two and whether the source region size is inside the supported point size range. /* check if dimensions are power-of-two */ if(( getnextpow2( image_width ) == image_width ) && ( getnextpow2( image_height ) == image_height )) { pot=1; } /* is the size supported? Supported point sprite range is the same as aliased point size range */ { GLfloat pointsizerange[2]; glGetFloatv( GL_ALIASED_POINT_SIZE_RANGE, pointsizerange ); if(( image_width >= pointsizerange[0] ) && ( image_height <= pointsizerange[1] )) { pointsize = 1; } } Now the decision variables are ready for a final decision on which method is going to be used for rendering. The basic fall-back is to draw the region using two triangles. /* if everything else fails, use two triangles */ method = BLIT_DRAW_METHOD_TRIANGLES; /* if width == height AND power of two AND we have OpenGL ES 1.1 AND the point size is inside the supported range we use point sprites */ if( ( BLIT_WIDTH == BLIT_HEIGHT ) && oes11 && pot && pointsize ) { method = BLIT_DRAW_METHOD_POINTSPRITE; } else if(drawtexture) { /* if draw_texture extension is supported, use it */ method = BLIT_DRAW_METHOD_DRAWTEXTURE; } Each of these methods has different setup and drawing codes. Refer to the blit example on the companion web site for a fully working example that also does the setup of the 240 MISCELLANEOUS OPENGL ES FEATURES CHAPTER 10 methods and actual rendering. The example also shows the correct handling of point clipping for point sprites. If you end up using a pair of texture-mapped triangles, the easiest approach is to set the modelview matrix, projection matrix, and v iewport so that a single step in the x or y direction in vertex data is equal to a step of a single pixel on the display. 11 CHAPTER EGL When the desktop OpenGL API was first specified, the windowing system and operating system dependent parts were left out of the core specification. Different windowing sys- tems have their own ways to handle displays, windows, graphics devices, and contexts, and different operating systems developed their own companion APIs for initializing graphics resources. For X11 there is GLX, Mac has AGL, and Windows uses WGL. Even though all of these APIs differ from each other, it is possible to create a “template API” that mostly abstracts out platform differences, but allows platform-dependent data types to be used where absolutely necessary. EGL unifies the OpenGL ES–related resource management across platforms, and defines standard function names and tokens for the implementations and applications to use. This increases source-level portability for OpenGL ES applications across the many operating systems in the mobile domain, e.g., Symbian, BREW, Linux, and Palm OS. Some parameter types in EGL are really placeholders for OS-specific types. For example EGLDisplay eglGetDisplay(NativeDisplayType display_id) takes in an OS-dependent display type, initializes EGL to use that display, and returns an OS-independent type EGLDisplay. NativeDisplayType is usually typedef ’d, as the name implies, to a handle to the native display type. With this approach application developers ideally need only to change a few lines in their EGL initialization code when porting from one platform to another. Typically, an appli- cation developer only needs to take care of initializing the platform-dependent window, 241 242 EGL CHAPTER 11 and to provide it as a parameter to eglCreateWindowSurface. All other EGL calls are portable across the different platforms. One thing to note is that EGL is an optional API—platforms are free to choose how they bind their windowing with OpenGL ES. Luckily most platforms that have adop- ted OpenGL ES have also chosen to suppor t EGL. This is good news for application portability. This chapter begins w ith an example and an overview of the EGL functionality, then pro- ceeds to describe EGL configuration, the different surface types supported by EGL, and the OpenGL ES/EGL context. We cover EGL extensions and describe how the surfaces can be used to mix OpenGL ES and other graphics library calls. We also cover additions introduced by EGL 1.1: optional support for rendering directly into texture maps, and better support for power events and power optimization. 11.1 API OVERVIEW Here is a walk-through of a simplified EGL initialization code without er ror checking. #include <GLES/egl.h> const EGLint attrib_list[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 16, EGL_NONE }; EGLDisplay display; EGLConfig config; EGLContext context; EGLSurface surface; void initEgl( void ) { int numofconfigs; display = eglGetDisplay( EGL_DEFAULT_DISPLAY ); eglInitialize( display, NULL, NULL ); eglChooseConfig( display, attrib_list, &config, 1, &numofconfigs ); context = eglCreateContext( display, config, EGL_NO_CONTEXT, NULL ); /* replace WINDOW() with the OS dependent window type */ surface = eglCreateWindowSurface( display, config, WINDOW(), NULL ); SECTION 11.1 API OVERVIEW 243 eglMakeCurrent( display, surface, surface, context ); } void renderOneFrame( void ) { /* some GL rendering calls */ eglSwapBuffers( display, surface ); } void terminateEgl( void ) { eglMakeCurrent( display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT ); eglDestroySurface( display, surface ); eglDestroyContext( display, context ); eglTerminate( display ); } First, we need to acquire a display. Some devices may support multiple displays, but in this example we simply use the default display, which we can do in a source-level portable way using the token EGL_DEFAULT_DISPLAY. Other than the default display, the display handling is platform-dependent, and you need to consult platform documentation to find out how displays are controlled. On some systems the display control may partially take place even outside EGL. EGLDisplay eglGetDisplay void can be used to get the currently active display that is associated with the current context. After the display handle has been acquired, EGL is initialized with EGLBoolean eglInitialize(EGLDisplay dpy, EGLint * major, EGLint * minor) which returns EGL_TRUE on success, and if major and minor are not NULL they are filled with the EGL version number. If initialization fails, the function returns EGL_FALSE and sets up an error flag, w hich can be retrieved with EGLint eglGetError(void) Possible errors returned by eglGetError are listed in Table 11.1. After EGL has been initialized, you need to select a buffer configuration. Either eglChooseConfig or eglGetConfigs may be used to choose the configuration that best matches the given attributes. In the example code we simply retrieve a configu- ration that has at least 8 bits for each of the red, green, and blue color channels, at least 16 bits for the depth buffer, and that supports window surfaces (different surface types are covered later). After a configuration is chosen, a surface and a context can be created. Contexts are con- tainers that carry the whole internal state of OpenGL ES. You can use several contexts for each surface or use the same context for different surfaces. Surfaces represent containers where the actual rendered pixels will end up. In this example, we create a window surface, so the pixels will end up inside a window on the device display. . differences, but allows platform-dependent data types to be used where absolutely necessary. EGL uni es the OpenGL ES related resource management across platforms, and defines standard function names and. to describe EGL configuration, the different surface types supported by EGL, and the OpenGL ES/ EGL context. We cover EGL extensions and describe how the surfaces can be used to mix OpenGL ES and. requires at least 15, even over 40 bones. If the model uses more bones than the OpenGL ES implementation can handle, you have to split the model into smaller par- titions and render the mesh with