I’m taking a couple of days to upgrade some of my libraries for doing prototyping both in 2D and 3D. One of the many overdue things I wanted to do, was to finally ditch OpenGL ES 1.1 and move to 2.0 exclusively. Yes, even if you’re only doing a 2D game, OpenGL ES 2.0 is way worth it.
There were even a couple of cases during Casey’s Contraptions that we wanted a particular effect, and couldn’t get it quite right, but it would have been trivial to whip up a shader if we had been using OpenGL ES 2.0. In the end, we had to resort to texture combiners (yuck), and it wasn’t exactly what we had in mind.OpenGL ES 2.0 requires that you write shaders for any different kind of “thing” you render, whether it’s a simple 2D quad, or a fancy skinned character. I wanted to provide at least a basic 2D rendering shader in my graphics library, but the workflow was a bit awkward.
First of all, shaders on iOS are compiled at runtime [1], so we need to access the shader source and compile it. Initially I was thinking of having the shader source files as text in the graphics library, but for the game to access those files, it would have to explicitly add them to its resources so they can be copied during the build phase. That’s error prone (I would always forget to do that with every new project) and just plain ugly.
Another possibility would be to include the source text as a string in the library. That would simplify things, but it would mean that:
- You would lose shader syntax highlighting.
- You would need to have the annoying \ at the end of every line.
After asking on Twitter and getting some good suggestions, I decided the easiest way would be to use xxd. It’s a shell tool that comes preinstalled in all Macs (and Linux) that takes a file and converts it into a C array that can be included in a program directly.
I liked xxd even better than the old standby of bin2c because it already comes installed, and it generates the whole file, not just an array that then needs to be included from another file. Yes, it would be easy enough to modify, but why bother when you have a perfect tool for the job already?
Now, I can set up a custom rule in Xcode 4 like this:

Update: The array generated by xxd isn’t null-terminated, so we need to add a zero to the array. The easiest way is to use sed. Thanks to SixEcho Studios for the heads up. Also, I updated it to use the varaibles INPUT_FILE_DIR and INPUT_FILE_NAME thanks to Miguel’s comments.
Now, whenever the shader source changes, it generates a header file like this:
unsigned char PassThrough_fsh[] = {
0x0a, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x6f,
0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x44, 0x65, 0x73, 0x74,
0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6c, 0x6f, 0x72,
0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e,
0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f,
0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20,
0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43,
0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x7d
,0x00};
unsigned int PassThrough_fsh_len = 91;
To compile that shader in the game, all I have to add is two lines:
#include//... const int fs = ShaderUtils::CompileShader(PassThrough_fsh, GL_FRAGMENT_SHADER);
Done! Clean, simple, and minimal cruft.
Update: I haven’t tried it yet because I haven’t had the need for any expensive shaders, but I hear really good things about Aras Pranckevičius GLSL optimizer. In that case, I would add it before the xxd step. You would still use the shader the same way, but it would be optimized and perform faster.
[1] That’s a huge mistake and I hope Apple fixes that ASAP. Loading times for any amount of non-trivial shaders adds up really quickly. That’s the last thing you want on a mobile device! The OpenGL ES 2.0 standard provides an optional way to load binary shaders, and all that Apple has to do is provide a tool that will compile shaders for the iOS hardware. To be completely future-proof, you they can even mandate that if the binary load fails, you still need to compile from source.