ASGP's Android Port Part III: from OpenGL Direct Mode to GLES
By Julien on Tuesday 8 October 2013, 09:45 - Permalink
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, y, z); glVertex3f(x, y, z); glVertex3f(x, y, z); glVertex3f(x, y, z); glEnd();
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
glBegin()/glEnd() sections with calls to
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
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
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:
glDisableClientState()to activate/deactivate the OpenGL elements we need (colors, vertices, texture coordinates),
glColorPointer()to provide the colors of the shape,
glVertexPointer()to provide the vertices of the shape,
glTexCoordPointer()to provide the coordinates in the texture,
glDrawArrays()to effectively draw the previously provided elements.
All of the
glSomethingPointer(size, type, stride, pointer)
functions work in the same way. The idea is to provide a
to a memory area containing several elements ("something") represented by
size primitive values of a given
elements are expected to be separated in memory by
Before calling a
glSomethingPointer() function, we have to
activate the element by a call to
the right argument (in our situation, one of
Then, when everything is well described, we just have to render them by a
glDrawArrays(mode, first, count). The first argument tells
which primitives to build with the vertices (in our situation, one of
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
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
and related functions by an alias to
GLfloat and their functions.
Fortunately for me, there was not a lot of them in my code.
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.