SDL/GLES/Android: We need to go deeper!

I’ve decided to drop down to a lower level: instead of a nice, simple SDL_Renderer we’ll be using Open GL ES 1.1 directly…

There are two reasons for doing things this way. First up, SDL’s Android renderer seems incapable of drawing more than one image to the screen at a time. All subsequent calls are rendered as untextured black squares. But never mind: rendering through SDL doesn’t support sprite rotation and I need it for this project, so I’d have had to have gone with direct Open GL anyway.

Guess it’s time for yet another rewrite… and another numbered list, because everyone loves numbered lists (including me). Once again all credit goes to the SDL community :)

1. Surfaces & Textures: know the difference

As far as I can understand, “textures” are a more efficient structure stored in video memory rather than on the heap. Needless to say they are used for hardware rendering rather than software rendering, so are not supported by really old systems.

Last week was actually the first time I explicitly used textures. One must be explicit with SDL since, unlike SFML, those bleeding-hearts actually care about people who don’t have graphics cards. I know, absurd right? Even phones support hardware-acceleration these days: get with the times grandpa!

Seriously though (yeah, I was being sarcastic), there’s been some debate about this lately: should SDL keep supporting software-rendering in today’s world? It would be a mistake to spread one’s limited resources too thinly, and everyone else seems to be abandoning ship and hoping aboard the HMS Hardware-Graphics. I don’t know about this stuff enough to have an educated opinion, but I reckon there must be some merit in looking out for the little guy. And offering something no one else can be bothered supporting is a good way of surviving unassailable on Pareto frontier.

2. Load an OpenGL texture

Using textures in SDL 1.3 is very easy if you’re familiar with software-rendering using the SDL_Surface structure. You simply take a surface you’ve loaded into memory using SDL_LoadBMP or IMG_Load and move it to video memory using SDL_CreateTextureFromSurface in one line of code.

Things aren’t so simple with OpenGL. We still create a surface using SDL or SDL_image (thank God), but converting it to a texture is a little different. First up we want our textures’ sizes to be powers of two (32, 64, 128, etc), which is pretty much the convention anyway. An efficient way of checking is with a binary and (‘&‘ in C), like so:

#define ISPWR2(n) !(n&(n-1))

Now the preprocessor will replace ISPWR2(n) with !(n&(n-1)) just before compilation, allowing us to have a legible cake and eat it efficiently:

ASSERT(ISPWR2(surface->w), “Check width is a power of 2″); ASSERT(ISPWR2(surface->h), “Check height is a power of 2″);

How cool is that? ASSERT, by the way, is another macro of mine: I won’t insult your intelligence by explaining what it does.

We now need to figure out what the colour format of our image is. This is usually as simple as looking at the number of colours. Three is generally arranged RGB (or BGR if you’ve had too many magic mushrooms), while four is RGBA (or BGRA if you want, but you really need to kick that drug habit of yours):

n_colours = surface->format->BytesPerPixel;
switch(n_colours){ case 4: format = GL_RGBA; break; case 3: format = GL_RGB; break;

default:

LOG(“Load texture failed”, “image not true-colour”);

return 0;

break;

}

LOG(x,y) coresponds to printf for Desktop platforms and __android_log_print(ANDROID_LOG_ERROR, “APPLICATION”, “%s: %s”, what, why) for Android. Screw writing that out each time :P

Anyway, now we can finally request a new texture handle (an unused number to identify our texture) and pass the surface data across to it:

Gluint texture; 
 
// Request an OpenGL texture handleglGenTextures(1, &texture);// Bind the texture object to the current block
glBindTexture(GL_TEXTURE_2D, texture);// Set the texture’s propertiesglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

glTexImage2D( GL_TEXTURE_2D, 0, n_colours, surface->w, surface->h, 0,

format, GL_UNSIGNED_BYTE, surface->pixels );

The original surface is no longer needed, so the last thing we should should be:

SDL_FreeSurface(surface);

3. Rotate and draw your texture

The logic behind OpenGL is quite different from most higher-level graphics libraries, which tend to employ functions of the form:

void draw(Image* image, int x, int y, float xscale, float yscale, float angle);

Instead OpenGL has a sort of global cursor (a transformation matrix) which is moved into position, scaled and rotated before any drawing takes place. This means a number of similar images can be drawn very efficiently. It’s a bit of a pain for just one image though:

// Clear the entire screenglClear(GL_COLOR_BUFFER_BIT);// Translate and rotate the global “cursor”glTranslatef(window_size.x/2, window_size.y/2, 0.0);

glRotatef(45, 0.0, 0.0, 1.0 );

// Select the texture to which subsequent calls refer to

glBindTexture(GL_TEXTURE_2D, texture);

// Draw the texture at the position we’ve moved to

glBegin(GL_QUADS);

glTexCoord2i(0, 0); glVertex3f(-16, -16, 0 ); // Top-left vertex

glTexCoord2i(1, 0); glVertex3f(16, -16, 0 ); // Bottom-left vertex

glTexCoord2i(1, 1); glVertex3f(16, 16, 0 ); // Bottom-right vertex

glTexCoord2i(0, 1); glVertex3f(-16, 16, 0); // Top-right vertex

glEnd();

// Reset back to the origin

glLoadIdentity();

Phew! That was a lot easier than I thought it would be. But wait! There’s more…

4. OpenGL 1.5 & OpenGL ES 1.1: know the difference

Having rewritten and recompiled my project for Ubuntu, I was surprised to find that it wouldn’t compile for Android. A number of names seemed to be missing, specifically:

  • glOrtho
  • GL_BGR
  • GL_BGRA
  • GL_QUADS
  • glBegin
  • glTexCoord2i
  • glVertex3f
  • glEnd

The only real difference between the two platforms is that Desktop Linux uses standard OpenGL while Android and other mobile platforms use a special OpenGL for embedded system called -you’ll never guess- OpenGL Embedded Systems. Or OpenGL ES for short. Or GLES for even shorter.

Trouble with GLES is that it’s even lower-level that the standard OpenGL. It’s basically OpenGL minus the bells and whistles, so if you want to draw square textures (GL_QUADS) you’ll have to cussing well draw a pair of GL_TRIANGLES like a big boy!

Luckily everything that works in GLES works in GL (just not the other way around), so we can modify the code once again and this time deploy it everywhere. The main change happens for the draw-call, here:

// Clear the entire screenglClear(GL_COLOR_BUFFER_BIT);// Set up position and rotationglTranslatef(WINDOW_W/2, WINDOW_H/2, 0.0);

glRotatef(45, 0.0, 0.0, 1.0 );// Bind the texture to which subsequent calls refer toglBindTexture(GL_TEXTURE_2D, texture);// Start drawing texture

glEnableClientState(GL_VERTEX_ARRAY);

glEnableClientState(GL_TEXTURE_COORD_ARRAY);

GLfloat size[9], coords[9];

glVertexPointer(3, GL_FLOAT, 0, size);

glTexCoordPointer(2, GL_FLOAT, 0, coords);

// Top-left triangle

size = {-16,-16,0, 16,-16,0, 16,16,0}; ///FIXME

coords = {0,0, 1,0, 1,1}; ///FIXME

glDrawArrays(GL_TRIANGLE_FAN, 0, 3);

// Bottom-right triangle

size = {-16,-16,0, -16,16,0, 16,16,0}; ///FIXME

coords = {0,0, 0,1, 1,1}; ///FIXME

glDrawArrays(GL_TRIANGLES, 0, 3);

// Stop drawing texture

glDisableClientState(GL_VERTEX_ARRAY);

glDisableClientState(GL_TEXTURE_COORD_ARRAY);

// Reset back to the origin

glLoadIdentity();

// Flip the buffers to update the screen

SDL_GL_SwapWindow(window);

Note the lack of glBegin/glEnd pairs, and the reliance on vertex arrays: this is actually a far more efficient way of doing things, so I’m more than happy to use it. I’ll probably just wrap it myself anyway, so it only needs to be written once.

5. Pull out hair

You can find the minimal code here on pastebin, though you’ll need to set up a blend function during configuration if you want to correctly display images with transparency. For instance:

glEnable(GL_BLEND);glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Unfortunately both my Android hardware and emulator resolutely refuse to texture any of the triangles, so even after all this work I’m still getting a little black square:

Fuuuu!! Android

Untextured polygons are like unbuttered toast :'(

Well, actually it’s now a little white square. I guess that qualifies of progress in a sense.

Don’t despair. “Success,” as Winston Churchill put it “is the ability to go from one failure to the next without loss of enthusiasm.” With all the help I’m getting from the SDL community, I’m sure I’ll figure it out eventually. We just need to go deeper. Even deeper…

 

—————–edit—————–

6. Use glGetError, you tool

OpenGL and I have a lot in common: we keep calm and carry on. No, I’m not going insane and personifying graphics libraries. Nor am I being cocky. I’m simply pointing out that when something happens, OpenGL won’t just crash. In fact, it won’t tell you about the problem, it’ll just sulk and refuse to texture your polygons. To find out what’s wrong you must insist, with the help of a little something called glGetError.

This function, my nifty little LOG marco and Android’s Logcat allowed me to pinpoint the problem to this exact line of code:

glTexImage2D(GL_TEXTURE_2D, 0, n_colours, surface->w, surface->h, 0,
format, GL_UNSIGNED_BYTE, surface->pixels);

The error being generated was GL_INVALID_OPERATION. This, unfortunately, wasn’t very helpful at all: according to the OpenGL documentation a whole 10 different configurations might be causing the problem. Some could be ruled out, but others were more difficult: I know, for instance, that n_colours is 4 for my test, but what GLenum does 4 correspond to?

7. Read the f**king manual

I figured it would be a safer bet to use GL_RGBA (or GL_RGB as the case may be) everywhere rather than some random constant mapped to the number 4. I checked the documentation though, and it seems that numeric values (the number of colours) can be used instead of a GLenum:

internalFormat:
Specifies the number of color components in the texture. Must be 1, 2, 3, or 4, OR one of the following symbolic constants (…)

So what on earth is causing the issue?

8. Make sure it’s the right f**king manual

Wait a minute though: that’s GL and GL is working fine. The problem occurs only on Android which uses GLES, so we should probably refer to the GLES documentation instead:

internalformat:
Specifies the color components in the texture. Must be same as format. The following symbolic values are accepted (…)

Bingo! GLES (Android) imposes certain constraints that GL (Desktop Linux) does not share, hence the disparity. I’m quite proud to have figured that out all by myself, though I should probably be ashamed for making the mistake in the first place. All told the moral to this story is: use glGetError, rtfm and, most importantly, msitrfm!

See y’all next week, I’m exhausted ;)

 

  • Iosif Hamlatzis

    I get an exception and my native application never executes. It doesn’t even get at my android_main function :-(

    • http://wilbefast.com/ Wilbefast

       What’s your exception? Do you have the logs?

  • Batiste Bieler

    Your tutorials are super helpful! Thx