Text in OpenGL on iOS

I’ve been playing around with OpenGL for the first time in a while for the last couple of days and I’ve now written up a group of classes that make my life a lot easier so that I don’t have to worry as much about displaying graphics in 2D games. Although the classes aren’t exactly Cocos2D standard they do show how to set up OpenGL with GLKit and produce complex 2D games without too much overhead.

My current challenge has been displaying text in OpenGL. Displaying text in a game is incredibly useful; you can feedback to the user on performance, score, achievements and simple notifications. I’ve been working on a simple game that needs to display the score at the top of the screen. Working on this I found that there were at least three good approaches.

The first is to use bitmap fonts and create vertices for each character and display the appropriate texture in each position. An advantage of this method is that it is very quick if you have relatively simple text to display. There are three main problems with this:

  • It wastes time and space: You have to create new font assets from a system font or build a custom font. This is going to take a lot of time without some automation and it will add to the size of your end executable.
  • You will likely limit yourself to regular English text: Unless you create fonts for a huge variety of character sets you are limited to ASCII characters which makes English the only viable language, which is a problem if you are displaying foreign names from Game Center for example.
  • Bad scaling: You have to include your bitmap font at the highest resolution it will need to be and if you are using iPhone, iPad, retina and non-retina graphics in your game this is going to be pretty large.

The second method is to use UIKit elements such as UILabel to present text. This works alright in a 2D scenario where you don’t need to regularly update text (if you do, enjoy seeing your frame rates drop to about 10fps) but Quartz transformations aren’t ever going to allow you to perfectly position your text where you need in a 3D environment. I don’t particularly like this method, but it will work for some people. It is worth noting that this method doesn’t fall down on any of the problems that the first method does, however.

Finally, the most effective method in my opinion is to use GLKit to create textures from CGImages which can be drawn to with Quartz 2D. This is incredibly advantageous because it means that you can use any of the system fonts with a huge variety of character sets. You can also effectively scale for different screen resolutions, which you simply wouldn’t be able to do with bitmapped fonts. Here is roughly how I’m doing it at the moment:


//You may wish to use the extras to set an appropriate scale factor
CGSize size = CGSizeMake(100,100);
float scale = [[UIScreen mainScreen] scale];
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
//Drawing code
CGImageRef image = CGBitmapCreateFromContext(context);
UIGraphicsEndImageContext();
GLKTextureInfo *texture;
texture = [GLKTextureLoader textureWithCGImage:image options:nil error:nil];
CGImageRelease(image);
self.scoreTexture = texture;

Initially I found this code to work very well, however as soon as I needed to update the image regularly I found that my app would go from using 10MB of memory total (including other sprites) to around 1GB of virtual + real memory before it crashed out. This seemed very odd as I was replacing scoreTexture each time. I was releasing unneeded Quartz resources. It turns out the real reason is that GL takes over the texture memory, so you actually have to use OpenGL functions to release the texture first:


GLuint name = self.scoreTexture.name;
glDeleteTextures(1, &name);
//Replace GLTextureInfo in memory

Thanks to alokoko on Stack Overflow for point this out.

Once you’ve done this you can easily update text regularly and draw on screen however there are disadvantages. I firstly tried doing the new texture generation in a separate thread using NSOperationQueue however I found it a little unreliable because there was no guarantee of the length of time it takes to generate the new texture, so the queue rapidly filled up which produced further memory errors. Ultimately, it seemed that the safest way of doing this was creating a new texture up to 10 times a second in a separate thread and then queuing an operation to update the main texture in memory on the main thread.

Of course, there are other approaches and I would be interested to hear if you have any better ones.

Advertisements

6 thoughts on “Text in OpenGL on iOS

  1. Awesome article.

    When adding iOS and OS X text rendering backends to an OpenGL 2D scene graph, I went straight to rendering CoreText to a texture.

    CoreText is pretty low-level, annoying, and verbose, but has a lot of pros. The biggest upside, in my case, was that it doesn’t have to know much about its environment or context in which it’s being used. That made it easy to segregate in a multi-platform codebase (just another TextRenderer alongside Pango/Cairo). This also removed the UIKit/AppKit OS X/iOS inconsistencies. Just throw it a bitmap context and it spits out kerned, locale-aware, typeset bitmap with tunable line breaks, justification, and anything you can throw in an CFAttributedString.

    I had heard rumors of CoreText not being particularly performant, but it has never shown up near high enough in the profile to bother with. I get the impression that that it wouldn’t be an issue unless you’re updating many labels/text fields once per frame, which would be too often to be legible anyway.

    • I had considered CoreText as well however the learning curve is a lot greater than Quartz 2D or the NSString drawing functions which give you some of the basic functionality. Because I ideally wanted to get this done pretty quickly, I chose to instead just use Quartz because I knew where the text would be.

      I guess CoreText has its main advantages in text layout but if you really need to lay out text that complex it is probably a little easier to ‘capture’ the contents of a UIWebView with Quartz than using CoreText if you are just using Quartz.

  2. I shocked that the GLKTextureInfo doesn’t automatically glDeleteTextures in its destructor when it goes out of scope. I guess the problem is that it doesn’t have any way to know whether GL is still using it or not.

    I’m starting to wonder if GLKit as an abstraction is causing more problems than it’s saving. There are a bunch of cases where the abstraction doesn’t match what’s going on with GL and you get funny little problems.

    Example: http://stackoverflow.com/questions/8611063/glktextureloader-fails-when-loading-a-certain-texture-the-first-time-but-succee

    Apparently GL errors if un received can cause parts of GLKit to fail. Before some GLKit calls a spurious call to glGetError() may be in order.

    • I’ve gotten to the stage now where I will only use GLKit for matrix math – which does work really nicely. I prefer to set up all of the buffers myself and do the animation with Core Animation because you can access so much more. In my opinion GLKit would work much better if it was more focused at being something like SceneKit on the Mac.

  3. hi Thomas can i get any working sample code that u have explained above?I m new to opengl and hoping a working model will help me to implement this same.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s