3D Graphics Made Easier with OpenGL ES for BREW

3D Graphics Made Easier with OpenGL ES for BREW

penGL has long been a standard interface for 3D graphics development. Developed by SGI, its only real competitor today is Microsoft’s Direct3D. Unfortunately, until recently there’s been no mobile analogue to OpenGL. Developers have had to either port their own 3D engines from one platform to another, or improvise with platform-specific interfaces and pay the price while porting applications. OpenGL ES, a version of the OpenGL standard for embedded devices, stands to change all this. As an open standard supported by the Khronos Group, it is already available for BREW, Symbian, and Linux. This article describes how to get your environment set up for OpenGL ES development in BREW, as well as introduces you to the basic concepts you need in order to get started.

Setup and Installation
If you’re new to BREW, the first thing you should do is peruse the articles in Related Resources to come up to speed on the programming environment. BREW is an event-driven, single-threaded, component-oriented environment in which you write code in C or C++ against system interfaces; it’s similar in many ways to platforms such as Palm OS, although the component orientation reminds some of Windows COM development. If you’re new to OpenGL, you should check out a brief tutorial, or better yet, OpenGL ES.

There’s no support for OpenGL in the base BREW emulator. Instead, you need to download the BREW SDK Extension for OpenGL ES for the BREWSDK. You’ll get a .zip file with files you need to manually install as follows:

  • Files in the inc directory need to go into your BREW SDK’s inc directory (or else you will need to include a reference to this directory in your project’s include paths).
  • Files in the src directory need to go into your BREW SDK’s src directory, or to another common location.
  • The DLL from the BREW 3.x directory will need to go into the SDK’s binmodules directory.
  • You can put the phone skin wherever you like.

In a similar vein, it’s important to realize that not every handset presently has support for OpenGL ES. Generally, handsets will need to be built upon the MSM6550 chipset or later, and must also have the appropriate extensions to the BREW runtime in ROM. To see if a specific handset has support for OpenGL ES, check the data sheet for that handset.

OpenGL ES for BREW Interfaces
Like other BREW components, you gain access to OpenGL ES through BREW extensions. Two extensions comprise support for OpenGL ES: the IGL interface and IEGL interface. The IGL interface provides the actual 3D graphics engine, while the IEGL interface provides the window manager engine. Thus, to get started, it suffices to write:

ISHELL_CreateInstance(pMe->a.m_pIShell, AEECLSID_GL, (void **)&pIGL)

However, this isn’t truly portable. While there’s nothing in how BREW interfaces are written that prevents porting the extension concept over to other platforms, doing so would be at odds with many other platforms, such as the traditional “function-call-to-a-library” approach of Linux, or the C++ framework used by Symbian. Instead, Qualcomm provides an interface file, GL.c, that contains the actual OpenGL ES interfaces written as C calls to the IGL and IEGL interfaces. While you don’t have to use these calls directly, it’s better that you do, because your code will be more portable, and match the OpenGL ES specifications and documentation.

Starting with OpenGL ES
As suggested in the last section, you should use the functions in GL.c rather than the native extension interfaces for maximum portability. To do this, you need to initialize the OpenGL ES functions with the extension interfaces you create, typically at application startup:

if( ISHELL_CreateInstance(pMe->a.m_pIShell, AEECLSID_GL, (void **)&pMe->pIGL) != SUCCESS ){  return FALSE;}else {  // To use the standard OpenGL|ES APIs must call IGL_Init()  IGL_Init(pMe->pIGL);}if( ISHELL_CreateInstance(pMe->a.m_pIShell, AEECLSID_EGL, (void **)&pMe->pIEGL) != SUCCESS ){  return FALSE;}else{  // To use the standard EGL APIs must call IEGL_Init() IEGL_Init(pMe->pIEGL);}

You will need to keep these interfaces around, because when it’s time to clean up, you’re responsible for freeing them.

Next, obtain your display bitmap, so you can share the bitmap with the OpenGL ES interfaces:

if( IDISPLAY_GetDeviceBitmap(pMe->a.m_pIDisplay, &pMe->pDDBitmap) != SUCCESS ){  CleanUp(pMe);		  return FALSE;}if( IBITMAP_GetInfo(pMe->pDDBitmap, &pMe->sDDBitmapInfo, sizeof(AEEBitmapInfo)) != SUCCESS ){  CleanUp(pMe);  return FALSE;}

By getting the device bitmap from the display, you can also use OpenGL on other displays on the handset where the manufacturer has enabled external display access through IDisplay.

Finally, it’s time to initialize the window manager and graphics engine. This involves setting the main display, window surface, and context for the window manager, followed by specifying various parameters for the graphics engine itself:

EGLConfig cfg;EGLint ncfg = 1;EGLint params[5] = {EGL_NONE,EGL_NONE,EGL_NONE,EGL_NONE,EGL_NONE};IDIB *pDIB;pMe->eglDisplay		= EGL_NO_DISPLAY;pMe->eglSurface		= EGL_NO_SURFACE;pMe->eglContext		= EGL_NO_CONTEXT;// Set the display to the main displaypMe->eglDisplay = eglGetDisplay( pMe->a.m_pIDisplay );if( pMe->eglDisplay == EGL_NO_DISPLAY ||     eglGetError() != EGL_SUCCESS )   return FALSE;// Initialize the displayif( eglInitialize( pMe->eglDisplay, NULL, NULL ) == EGL_FALSE ||     eglGetError() != EGL_SUCCESS )	return FALSE;	// Get display configuration	eglGetConfigs( pMe->eglDisplay, &cfg, 1, &ncfg );// Create the window surface using our display’s bitmap.if( IBITMAP_QueryInterface(pMe->pDDBitmap,AEECLSID_DIB,(void**)&pDIB)!=SUCCESS)  return EFAILED;pMe->eglSurface = eglCreateWindowSurface( pMe->eglDisplay, cfg, pDIB, params );IDIB_Release( pDIB );if( pMe->eglSurface == EGL_NO_SURFACE || eglGetError() != EGL_SUCCESS )  return FALSE;// Create the context used to render on the new window surfacepMe->eglContext = eglCreateContext( pMe->eglDisplay, cfg, 0, 0 );if( pMe->eglContext == EGL_NO_CONTEXT || eglGetError() != EGL_SUCCESS )  return FALSE;// Make it the current context with the newly-created display and surface.if( eglMakeCurrent( pMe->eglDisplay, pMe->eglSurface, pMe->eglSurface, pMe->eglContext ) == EGL_FALSE ||eglGetError() != EGL_SUCCESS )	return FALSE;

This code is straightforward, but experienced BREW developers should recognize the introduction of new types like EGLint. For portability, you should always use the OpenGL ES types when working with the OpenGL interfaces. These include EGLBoolean and EGLInt for working with the EGL, and GLboolean, GLfixed, GLfloat, Glint, GLshort, GLuint, GLushort, and GLvoid for the graphics engine.

With the window manager set up, it’s time to set up the graphics engine. These are values you’re likely to change depending on your application, but are reasonable defaults:

// Select smooth shading & depth testingglShadeModel( GL_SMOOTH );glEnable( GL_DEPTH_TEST );// Disable lighting and blending for performance.glDisable(GL_LIGHTING);glDisable(GL_BLEND);// Request nicest perspective correction; use GL_FASTEST for better performanceglHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );		// Initialize the viewport glViewport( 0, 0, pMe->, pMe-> );	// Initialize the Projection MatrixglMatrixMode( GL_PROJECTION );glLoadIdentity();	glFrustumx( ITOX(-5),  ITOX(5),            ITOX(-5),  ITOX(5),            ITOX(10),  ITOX(100)	);	// Initialize the model view matrixglMatrixMode( GL_MODELVIEW );glLoadIdentity();	if (glGetError() != GL_NO_ERROR) return FALSE;	return TRUE;

These APIs are all well documented by Khronos, and should be familiar to OpenGL developers. The code begins by enabling smooth shading and depth testing, disabling lighting and blending, and suggesting that perspective correction should use the most accurate means instead of the fastest. Next, the code sets up the viewport, specifying the entire bitmap as obtained from the main display. Finally, the code sets up the projection and model view matrices, beginning each with the identity matrix and applying a transformation to the projection matrix. The use of the ITOX macro converts an integer to the fixed-point representation used by OpenGL?fixed-point math is typically significantly faster than floating-point math on OpenGL ES supported platforms.

With the engines initialized, it’s time to define some objects. How you do this depends on the kinds of objects you want to draw, of course, but you generally follow these steps:

  • Define shapes as a series of vertexes.
  • Apply textures to specific surfaces in the scene.

A simple example, drawn from Qualcomm sample code, illustrates this by defining a simple triangle:

GLfixed face[9] ={  -ITOX(2),	-ITOX(2),  ITOX(2),   ITOX(2),	-ITOX(2),	 ITOX(2),  -ITOX(2),	 ITOX(2),	 ITOX(2)		};GLfixed color[12] ={  0,       ITOX(1), ITOX(1), 0,  ITOX(1), 0,       ITOX(1), 0,  ITOX(1), 0,       0,       0};uint8 index[3] = {0,1,2};// Clear everythingglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );// Triangle		glEnableClientState(GL_VERTEX_ARRAY);glEnableClientState(GL_COLOR_ARRAY);	glVertexPointer(3, GL_FIXED, 0, face);		glColorPointer(4, GL_FIXED, 0, color);	glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, index);

The face array contains an array of vertices for the specific shape to be drawn. For each vertex, there’s a corresponding color in the color array. Note that colors are represented as RGBA, although instead of the byte RGBA values you’re used to in doing BREW color specifications, they’re again represented as fixed-point numbers. The glEnableClientState calls enable drawing by vertex and color arrays, which are subsequently passed to the OpenGL engine using glVertexPointer and glColorPointer. Finally, the engine draws the three vertexes as directed by the index array when you call glDrawElements (note that glDrawElements determines which vertexes to draw in the vertex array, and in what order they’re drawn?the counts passed to glVertexPointer and glColorPointer indicate the number of items per vertex or color).

Most of the time, you won’t want to draw just a static image, you’ll want to animate things. To do this in BREW, you need to periodically update your model (by shifting perspective, objects in the scene, or both), and then redraw the scene using glDrawElements. This is an excellent opportunity to use IThread (see my previous article), because you can then write your animation and update code using a simple set of loops and conditionals, and simply yield the processor using IThread_Sleep in AEETU_Sleep.c between each frame.

When you’re done using the OpenGL ES interfaces, it’s important to clean up everything you’ve allocated to avoid leaks. This includes the display bitmap, EGL display, context, and surface, as well as the actual interfaces:

if (m_pDDBitmap)  IBITMAP_Release( m_pDDBitmap );eglMakeCurrent( EGL_NO_DISPLAY,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT );eglDestroyContext( pMe->eglDisplay, pMe->eglContext );eglDestroySurface( pMe->eglDisplay, pMe->eglSurface );eglTerminate( pMe->eglDisplay );if (pMe->pIEGL) IEGL_Release( pMe->pIEGL );if (pMe->pIGL)  IGL_Release( pMe->pIGL );

Be sure you release the EGL and GL extensions last, as no gl or egl APIs work once they’re released. This is obvious, as these interfaces rely on having instances of the extension, but it’s an easy mistake to make.

A 3D Interface Wizard
Whether you’re writing just for BREW, or looking at cross-platform development, if you need 3D interfaces, OpenGL ES should be your first choice. Although it’s not yet supported by all handsets, Qualcomm was a major participant in the OpenGL ES development process, and you can expect OpenGL ES to be the definitive 3D API for BREW going forward.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist