Adventures in Retina Display Land

I spent this past weekend getting retina display graphics implemented in Tilt to Live for iPhone 4. The cynic in me hates the marketing term ‘retina graphics’ because on the production side, it’s simply just a bigger image. But I guess you do need a name for it, and since ‘HD’ has already been bastardized on the iPad I guess this will do. I decided to write this post to bring a bit of awareness to some of the process of updating your OpenGL game for the retina displays if you choose to do so. I thought it’d be riddled with lots of coding work-arounds and such since our game is a 2D game with specifically sized sprites based off the resolution, but it turned out to be pretty quick and easy.

Anyway, Adam had re-exported all of Tilt to Live’s graphics at 2 times the size so the hard part was pretty much done. It was made much easier by the fact that he was using Illustrator and vector graphics, so there was zero need to redraw or redo any assets. On the coding side of things, I feared I’d be spending hours re-tweaking game code because a lot of constraints and boundaries were hardwired to image width’s and heights (bad I know). But in the end, I luckily avoided all of that. Most of my sprite rendering code is encapsulated in a SpriteImage class with width/height properties. I made a simple modification to those properties where the returned width or height was multiplied by a ‘renderScale’ variable that represents the scale of the display. In effect, the width/heights would return whatever the game was designed for at the 480×320 resolution, but under the hood would be using a larger texture.

For non-openGL apps, they get a few things for free on the retina display such as font rendering and UI scaling, but how do you set up your graphics window to support retina graphics? A quick look over at the apple docs shows how to setup your OGL context to scale by a factor of 2. When it came down to implementing it, just doing:

[code]
self.contentScaleFactor = DisplayScale();
glGenFramebuffersOES(1, &viewFramebuffer);
glGenRenderbuffersOES(1, &viewRenderbuffer);;

//..continue setting up framebuffer
[/code]

…got me rendering 4 times as many pixels. The game still looked exactly the same since I hadn’t replaced the graphics yet. What is DisplayScale()? It’s simply a wrapper I wrote:

[code]
CGFloat DisplayScale()
{
return [UIScreen mainScreen].scale;
}
[/code]

Now if you’re using UIImage to load your textures then you’re in luck for updating graphics. Adding a file with the ‘@2x’ suffix will automatically be loaded on an iPhone 4 instead of the normal resolution. For example, myImage.png and myImage@2x.png live side-by-side in your bundle, but on the iPhone 4, it will load the @2x one. I had been using libpng directly to load textures a while ago, but opted to revert back to UIImage for performance reasons.

Now this is all well and good if you’re using separate png’s for your images. But to improve rendering speed and memory consumption a lot of times one will use spritesheets. Having a @2x sprite sheet is nice if the spritesheet generator you are using puts them in the same relative positions. As for me, there were a lot of unused sprites from previous versions so I took the time to clean up things. This was a bit time consuming as I had to verify which images were still being used and which ones weren’t. The ‘@2x’ trick only works on UIImage, so if you have any supporting files (font descriptions in particular) that are needed in conjunction with the graphics file you’ll probably have to write your own ‘@2x’ searching and loading for those particular assets, which is no biggie either:

[code]
NSString *path = nil;
NSString *resource2x = [fontName stringByAppendingString:@”@2x”];
if(IsRetinaDisplay())
{
path = [[NSBundle mainBundle] pathForResource:resource2x ofType:@”fnt”];
}

if(path == nil)
{
path = [[NSBundle mainBundle] pathForResource:fontName ofType:@”fnt”];
}
[/code]

As for other caveats? Fonts were a big one depending on how you export and render them. I’ve been using BMFont to do all my font exporting. It’s a great tool but a little ambiguous when it comes to font sizes. There’s a ‘size’ field, but it refers to character height. Does doing a doubling of character height garuantee exactly twice the width/height]? What about the option for ‘line height’ [unchecked character height? I ended up using line height, since that’s the option I used initially when making the fonts. It worked pretty well. I tweaked my bitmap font rendering a bit and it supported retina graphics shortly after without having to change game or UI code. There was a slight issue with some lines of text being rendered 3-4 pixels lower, but I didn’t spend too much time investigating it as the issue isn’t noticeable in normal use.

Another caveat has to do with rendering GLPoints. Be sure to scale GLPoints when setting glPointSize() if you’re using that functionality at all. Tilt to Live uses it extensively to render enemies faster instead of quads.

6 comments

  1. When I did the retina graphics for Trainyard I actually decided to avoid using @2x pngs, and instead suffixed all my pngs with HD (someAtlas.png + someAtlasHD.png). I wanted to use the high res assets on the iPad as well, and this was the only way to do it. Just curious if you considered doing the same thing, or if you’re just going to leave the iPad version low-res?

    1. I had considered it, but Tilt to Live HD’s graphics have diverged quite a bit as well as the code base. So the retina display graphics and original Tilt to Live are the same, but HD is simply using a new set of graphics and animations. So no, it’s not low-res, just different :).

Leave a Reply

Your email address will not be published. Required fields are marked *