Julien Jorge's Personal Website

ASGP's Android Port Part III: from OpenGL Direct Mode to GLES

Tue Oct 8, 2013

Following the critically acclaimed Part I and the not-as-good-as-the-first-part-but-still-good-enough Part II, here comes the back-to-the-origins third act of the Android port of our C++ game Andy’s Super Great Park!

Today’s topic is the migration from OpenGL direct mode to GLES. This is the last blocking element in the compilation of the engine and the game. Consequently, this is a very exciting step. As you will see, the changes are quite straightforward, as soon as we know where to go.

After a short reminder of the original OpenGL direct mode API, I will define the target GL API and the acceptable compromises in the engine’s functionalities. Then I will describe the conversion of the existing rendering code using GLES and provide a workaround for the disabled functionalities of the engine.

About OpenGL direct mode and GLES

The direct mode is the old way to describe a scene in OpenGL. It was the only way to render things for a long time and it was deprecated starting with OpenGL 3.0. Still, you may find code samples using the direct mode all over the web, especially in old tutorials and even in recent forum questions.

Due to this wide visibility, it is still considered as the easiest way to start OpenGL programming (unfortunately), especially when you are in the prototyping phase (i.e. when you want to render things, not to render efficiently).

You may recognize this approach by the use of the glBegin()/glEnd() directives, as in the following code sample:

glBegin( GL_QUADS );
    glColor4f(red, green, blue, alpha);
    glVertex3f(x[0], y[0], z[0]);
    glVertex3f(x[1], y[1], z[1]);
    glVertex3f(x[2], y[2], z[2]);
    glVertex3f(x[3], y[3], z[3]);

So, this API is now deprecated and even if it still works on desktop applications, it is not available on newer phones and tablets. For them, we have to switch to OpenGL for embedded systems, also known as GLES.

Picking the right API

As I write, there are three versions of GLES. The third version is quite recent and still not widely available, so we will not use it. Thus we have to choose between GLES 1 and GLES 2.

GLES 1 is interesting in the sense that it is widely available and requires minimum changes in the rendering process. The changes mostly consist into replacing the glBegin()/glEnd() sections with calls to glEnableClientState() and glVertexPointer() and similar. On the other hand, there is no shader support in GLES 1. Thus we’ll have to disable this part of the engine and find a way to work without it.

GLES 2 is most recent and also widely available. Consequently this is the most interesting candidate to update the game engine. Shaders are supported, which is great, but on the other hand you have to use them every time (i.e. there is no default vertex nor fragment shader). Also, the calls to render primitives are a bit more complex (see glVertexAttribPointer() to provide the coordinates of your vertices).

As you may have observed in the previous posts, I am a partisan of small changes and incremental progression. It comes to no surprise, then, that I chose to switch to GLES 1 and to leave the shaders out for now. Indeed, the support for shaders in the engine were added for Tunnel and are absolutely not required for Andy’s Super Great Park. Consequently I will be able to focus on the port of the game rather than on the upgrade of the engine, which will be done later.

Converting OpenGL calls

Let’s code! The engine supports three graphical objects: colored lines, colored polygons and sprites. To render them, we will need the following OpenGL functions:

All of the glSomethingPointer(size, type, stride, pointer) functions work in the same way. The idea is to provide a pointer to a memory area containing several elements (“something”) represented by size primitive values of a given type. Adjacent elements are expected to be separated in memory by stride bytes.

Before calling a glSomethingPointer() function, we have to activate the element by a call to glEnableClientState(cap) with the right argument (in our case, one of GL_COLOR_ARRAY, GL_VERTEX_ARRAY and GL_TEXTURE_COORD_ARRAY).

Then, when everything is well described, we just have to render them by a call to glDrawArrays(mode, first, count). The first argument tells which primitives to build with the vertices (in our case, one of GL_LINE_STRIP or GL_TRIANGLE_FAN), the second one is the index of the first element to render in the array and the last one is the number of elements to render. Obviously the size of the memory area passed to each of the previous calls to glSomethingPointer() must be greater or equal to (first + count) × size.

Disabling the shaders and other adaptations

In order to have the code to compile without completely dropping the shaders, I have declared a set of macros to fill the nonexistent shader interface. For example, the compiler complains about glCreateShader() not being defined, thus I put in my code:

#define glCreateShader( a ) 0

And so on for each missing function, until I obtain a dummy shader API.

Also, an important constraint of GLES compared with OpenGL is that GLdouble is not available, so are the related functions of the API. The single floating point type available is thus GLfloat. In order to pass easily from the latter to the former, I had to put these two lines in my code:

typedef GLfloat GLdouble;
#define glOrtho glOrthof

The idea is to replace the nonexistent declaration of GLdouble and related functions by an alias to GLfloat and their functions. Fortunately for me, there was not a lot of them in my code.

What’s next?

All these changes can be seen in details in the commit of the conversion to GLES. As stated before, it was the last blocking step before being able to compile the game. Once these modifications were done, the engine and the game compiled well.

Before being able to run the game on my phone, a launcher and an Android application must be written, along with a new procedure to load the game’s resources. Several other adjustments will have to be done too, like the handling of the touch events or the translations. Also, the initial performances will be quite low and I will have to do some optimizations in various parts of the code in order to reach an acceptable frame rate. These optimizations will be also described in future notes.