Rendering text on iPhone with OpenGL

So far on my project I’ve been using SDL, a library I’ve used over the years for small projects. I use it as a quick way to get a cross-platform OpenGL context created, so I figured I’d use it in the same manner for my current game. SDL on iOS has a few quirks to work through, but if you’re using OpenGL with shaders it’s not much work to have the same code working on iOS with OpenGL ES 2.0.

One of the strengths of SDL is its lightweight and easy to use ‘standard’ libraries. I’ve used these before to get some basic sound, image loading and text rendering working. Image loading worked quite nicely (because this guy added a nicer version that uses the built in support, thanks man!), but SDL_ttf, the font renderer, relies on being able to link against FreeType, a nifty open source font rendering library. It’s certainly doable to link against it and some people have built it for iOS, but it’s a bit of a procedure and it’s yet another library to have to keep up to date.

It also seemed a bit… grating to have to use a separate library. Apple has historically placed an above average emphasis on having nice fonts and the iPhone is no exception. Its built-in font rendering lets you choose from the decent selection of built in fonts (Helvetica!) and also allows you to bring your own font to the dance. A roadblock arises when you realize you need to render the fonts to OpenGL textures and not to some mysterious UIGraphicsContext. Luckily I’m not the first one on the internet to have these concerns and I found a blog post about mixing UIKit and OpenGL.

Unfortunately his example code didn’t work for me. The code looks fine and seems straightforward and runs without error. It just wasn’t actually rendering anything into the buffer. After adding every CGContext state setting function I could find I stumbled upon a combo that sets up enough state to render with only a basic SDL OpenGL window set up. Hopefully Google will bring people here to save themselves the hour of randomly setting states to get it to render… :)

Caveats: it doesn’t word wrap, or do any other fancy tricks. For now, I’m using it to aid in debugging and provide frame times and the ilk.

//from bit twiddling hacks
inline uint32_t nextPowerOfTwo(uint32_t v)
{
    v--;
    v |= v >> 1;
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;
    v++;
    return v;
}

GLuint CreateTextureFromText(const char* text, const sRGBA &rgba, int &out_width, int &out_height)
{    
    NSString *txt = [NSString stringWithUTF8String: text];
    UIFont *font = [UIFont fontWithName:@"Helvetica-Bold" size:16.0f];

    CGSize renderedSize = [txt sizeWithFont:font];

    const uint32_t height = nextPowerOfTwo((int)renderedSize.height); out_height = height;
    const uint32_t width = nextPowerOfTwo((int) renderedSize.width); out_width = width;
    const int bitsPerElement = 8;
    int sizeInBytes = height*width*4;
    int texturePitch = width*4;
    uint8_t *data = new uint8_t[sizeInBytes];
    memset(data, 0x00, sizeInBytes);

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    CGContextRef context = CGBitmapContextCreate(data, width, height, bitsPerElement, texturePitch, colorSpace, kCGImageAlphaPremultipliedLast);

    CGContextSetTextDrawingMode(context, kCGTextFillStroke);

    float components[4] = { rgba.r, rgba.g, rgba.b, rgba.a };
    CGColorRef color = CGColorCreate(colorSpace, components);
    CGContextSetStrokeColorWithColor(context, color);    
    CGContextSetFillColorWithColor(context, color);
    CGColorRelease(color);    
    CGContextTranslateCTM(context, 0.0f, height);
    CGContextScaleCTM(context, 1.0f, -1.0f);

    UIGraphicsPushContext(context);

    [txt drawInRect:CGRectMake(0, 0, width, height) withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentLeft];

    UIGraphicsPopContext();

    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);    

    GLuint textureID;
    glGenTextures(1, &textureID);    

    glBindTexture(GL_TEXTURE_2D, textureID);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);     

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

    delete [] data;

    return textureID;
}

You generally don’t want to be creating textures very often, so you’ll want to have some sort of caching for the texture ID that’s being returned.

A few people wrote me to ask for more details about using the texture that’s returned. It’s just a normal quad with the dimensions given by the out_width and out_height parameters. I made a simple C++ class header that should get your most of the way. If you include this header in a .mm file, create the class after your OpenGL initialization and bind a shader which has texturing, it should™ render your text! It requires you to be using OpenGL ES 2.0. I’ve also as of 2013 updated it to use the iOS 6.0 text alignment enums as XCode was complaining.

simplerender.h