Porting to GLES from GL
Contents
Forwords
This guide is based on my limited knowledge (I do have barely the requierement) on the subject. I'm open to get edits on it, in fact it is encouraged :) --Sebt3 17:05, 24 February 2011 (MET)
Requierement
- a working toolchain
- average C/C++ knowledge
- basic knowledge on how GL or GLES works
- read OpenGL_ES_1.1_Tutorial
You might also want to have a look at others work in that matter :
- http://pickle.gp2x.de/source/kenlabes-src.zip
- http://pickle.gp2x.de/gish_src.zip
- http://sebt3.openpandora.org/src/briquolo-0.5.7-pandora.tar.gz
- http://sebt3.openpandora.org/src/zaz-1.0.0-pandora.tar.gz
- http://paeryn.myby.co.uk/pandora/sdl-ball.tar.bz2
- ...
For more references, have a look to :
Before starting
Choose a simple GL game for your first experience. Working with a large and complexe codebase is not what you want to do at first.
To have a good view of what you'll need to do, here a simple (and incomplete) bash script that will list you all the code lines you'll need to work with :
#!/bin/sh
search() {
find . -name "*.h" -o -name "*.c" -o -name "*.cpp" -exec grep -Hn "$*" {} \;
}
listIssues() {
search SDL_WM_GrabInput
search SDL_WarpMouse
search SDL_GL
search GL_CLAMP
search glColor|grep -v glColor4f
search glPushAttrib
search glPolygonMode
search GL_QUADS |grep glDrawArrays
search SDL_Init
search SDL_OPENGL
search SDL_ShowCursor
search glClearDepth
search SDL_Quit
search gluBuild2DMipmaps
search glTexImage2D
search glBegin
}
listIssues|sort -u
Run that from the source directory. The less lines displayed the better.
If your project use some GL libraries, get that to build first. Here are some libraries ported to gles :
- GLU : http://code.google.com/p/glues/ (you might want to grab a binary version with headers in zaz or briquolo pnd)
- FTGL : http://code.google.com/p/ftgles/ (I do have fixed version here, if you prefer a binary version, get that in the zaz PND)
Getting it to build
You first goal is to have it built. You'll probably need to hack the build system.
- If it's just Makefiles, replace -lGL with -lGLES_CM -lEGL -lX11.
- If it's an autotools project, have a look at how it's done in briquolo for references. (take a look at configure.ac, and follow the enable_gles bits ;) Dont forget the Makefile.am too. Once your edit are in, autoreconf is your friend.)
Make sure your build system (or your toolchain) will also set defines some marker to know if it need to build GL or GLES code. Having one too for pandora specific hack might show itself usefull too. I'm using "-DPANDORA -DHAVE_GLES" myself.
Now try to build it. You'll have error in the build process. to fix them, for now, just #ifdef out the offending lines. Here is what to do with the include instructions :
#if !defined(HAVE_GLES)
#include <GL/gl.h>
#else
#include <GLES/gl.h>
#endif
Other offending code, should be #ifdef out like this :
#if !defined(HAVE_GLES)
glClearDepth(1.0f);
#endif
At the end of this process you'll end up with a broken pandora binary. Let's now fix it :D
Adding the EGL context
Grab the eglport package pickle have built here. You will need to add call to it's 4 functions in the sources (make also sure that eglport.c is built and linked in the build process). Here is how your sources should look at the end :
#include <GL/gl.h>
#else
#include <GLES/gl.h>
#include "eglport.h"
#endif
int main( void )
{
// other stuff here
SDL_Init( SDL_INIT_VIDEO );
#if !defined(HAVE_GLES)
if (!EGL_Open())
exit(1);
#endif
// some more initialisations
#if !defined(HAVE_GLES)
screen = SDL_SetVideoMode( 800, 600, 0, SDL_HWSURFACE | SDL_OPENGL | SDL_FULLSCREEN);
#else
screen = SDL_SetVideoMode( 800, 480, 0, SDL_SWSURFACE | SDL_FULLSCREEN );
EGL_Init();
#endif
// the event loop
while( quit == 0 ) {
// management of the even and the game
#if !defined(HAVE_GLES)
SDL_GL_SwapBuffers();
#else
EGL_SwapBuffers();
#endif
}
// probably some more clean-ups here
#if defined(HAVE_GLES)
EGL_Destroy();
#endif
SDL_Quit();
return 0;
}
Please note that :
- EGL_Init have to called just after the SDL initialisation of the VIDEO layer
- SDL_SetVideoMode arguments have to be those fiven here (aka good resolution, and no SDL_OPENGL flag)
- EGL_Init goes just after SDL_SetVideoMode
- All SDL_GL_SwapBuffers call have to be converted to EGL_SwapBuffers calls.
- You have to call EGL_Destroy before quitting SDL (or closing the video if that's splitted in your original source).
When built, your source should run on the pandora cleanly (ok you'll have a black screen, but that's expected as we havent converted the drawing yet.
Converting the easy stuff
This part could have been done while getting the game built, but I prefered splitting the 2 process for readability. Fell free to work as you please :D. Remember that all you have #ifdef out in the building part have to be converted, dont forget anything.
GLES only support float, double (aka GLdouble) dont work. So :
- any GLdouble should be converted to GLfloat
- all GL function call ending with a "d" should have the d remplaced with f.
Here are others know conversions :
- GL_CLAMP -> GL_CLAMP_TO_EDGE
- glClearDepth -> glClearDepthf
- glOrtho -> glOrthof
Colors
GLES only know glColor4f as unction to create a color, all the glColor3f, glColor3d and glColor4d have to be converted to glColor4f. If you only have 3 arguments, the last have to be 1.0f (as no transparency).
Converting the immediate mode
If you have read OpenGL_ES_1.1_Tutorial, you already have a good idea of what need to be changed here. Here are a few others exemples to help you in your process :
1) a simple quad with no texture or color :
#if !defined(HAVE_GLES)
glBegin(GL_QUADS);
glVertex2f(-10,-10);
glVertex2f(10,-10);
glVertex2f(10,10);
glVertex2f(-10,10);
glEnd();
#else
GLfloat q3[] = {
-10,-10,
10,-10,
10,10,
-10,10
};
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, q3);
glDrawArrays(GL_TRIANGLE_FAN,0,4);
glDisableClientState(GL_VERTEX_ARRAY);
#endif
2) a textured quad :
glBindTexture(GL_TEXTURE_2D, carac->TextureName);
#if !defined(HAVE_GLES)
glBegin(GL_QUADS);
glTexCoord2f(0,0);
glVertex3f(pos[0]-tailleX/2, pos[1]-tailleY/2, 0);
glTexCoord2f(1,0);
glVertex3f(pos[0]+tailleX/2, pos[1]-tailleY/2, 0);
glTexCoord2f(1,1);
glVertex3f(pos[0]+tailleX/2, pos[1]+tailleY/2, 0);
glTexCoord2f(0,1);
glVertex3f(pos[0]-tailleX/2, pos[1]+tailleY/2, 0);
glEnd();
#else
GLfloat vtx1[] = {
pos[0]-tailleX/2, pos[1]-tailleY/2, 0,
pos[0]+tailleX/2, pos[1]-tailleY/2, 0,
pos[0]+tailleX/2, pos[1]+tailleY/2, 0,
pos[0]-tailleX/2, pos[1]+tailleY/2, 0
};
GLfloat tex1[] = {
0,0,
1,0,
1,1,
0,1
};
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vtx1);
glTexCoordPointer(2, GL_FLOAT, 0, tex1);
glDrawArrays(GL_TRIANGLE_FAN,0,4);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
#endif
3) a colored quad (note that, as GL is a state machine, the color is only showed once in the GL code, while it is copied for every points on gles) :
#if !defined(HAVE_GLES)
glBegin(GL_QUADS);
glColor3d( .1, .1, .7);
glVertex3d(0, 0, 0);
glVertex3d(0, -pbarheight, 0);
glColor3d( 0, 0, 0.5);
glVertex3d((startupProgress / startupProgressSteps) * pbarwidth, -pbarheight, 0);
glVertex3d((startupProgress / startupProgressSteps) * pbarwidth, 0, 0);
glEnd();
#else
GLfloat vtx1[] = {
0, 0, 0,
0, -pbarheight, 0,
(startupProgress / startupProgressSteps) * pbarwidth, -pbarheight, 0,
(startupProgress / startupProgressSteps) * pbarwidth, 0, 0
};
GLfloat col1[] = {
.1, .1, .7, 1.0f,
.1, .1, .7, 1.0f,
0, 0, 0.5, 1.0f,
0, 0, 0.5, 1.0f
};
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vtx1);
glColorPointer(4, GL_FLOAT, 0, col1);
glDrawArrays(GL_TRIANGLE_FAN,0,4);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
#endif
Note that the last argument of glDrawArrays is the number of points. In these example that's always 4 because these all have 4 points. You might want to change that if your object have more (or less) than these 4 points.
Here is a list of conversion between GL to GLES draw modes :
- GL_POINTS -> GL_POINTS
- GL_TRIANGLES -> GL_TRIANGLES (or GL_LINE_LOOP if glPolygonMode is set to GL_LINE)
- GL_QUADS -> GL_TRIANGLE_FAN
- GL_LINE_STRIP -> GL_LINE_STRIP
- GL_LINE -> GL_LINE
- GL_POLYGON -> GL_TRIANGLE_FAN
Textures
All textures have to be loaded with with dimentions in power of 2. See GLES2D sources and GLES2D_p2 function for a easy method of doing to work around that. Briquolo had a more advanced method to do so thanks to Paeryn (it also actually scale the texture to that given size).
When loading textures with glTexImage2D only supported formats are GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, or GL_LUMINANCE_ALPHA. Arguments for internal format and format(3 and 7) must be the same. Note: one common situation is where the original program uses 3 or 4 (which is valid for opengl) for internal format and GL_RGB or GL_RGBA for format. GLES requires the 3 to be GL_RGB or 4 to be GL_RGBA. GLES Example:
glTexImage2D( target, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
Known issues
- glPushAttrib, glPopAttrib and their clients version dont exist. You'll have to manage these state yourself. An other good way is to have all disabled at all time and only enable what needed at the right time.
- SDL_WM_GrabInput and SDL_WarpMouse might cause soft freeze of your game. If it does, #ifndef PANDORA them :)