June 4th, 2012
Death of the White Box
A little change of plans; Whilst I originally planned to spend the next blog on the 'music that inspired TAGAP' feature, something special happened since the last update. Long story short; I've managed, after four false-starts over five years, to finally beat the horrendous plague called The White Box.
Anatomy of the White Box
White Box bug is the direct result of the video card driver not supporting OpenGL extension GL_ARB_texture_rectangle. This extension is one of the oldest and definitely the most basic of them all; All it does is to give support for textures (images) that are not power-of-two in dimensions. In layman's terms, without GL_ARB_texture_rectangle you must have image width and height set to power-of-two range, otherwise the image cannot be bound as an OpenGL texture.
Not supporting it is akin to having a computer capable of running Windows Vista but not the Notepad; I, in all honesty, believed every single graphics card since days of 3DFx Voodoos were able to handle it. But no, they weren't, which was something I learned the hard way.
First fixing attempt: Resized images
If the problem is with the image dimensions being unsupported, the easiest way to fix it should be resizing the images, right? Simply open every image Photoshop, redo canvas size to power-of-two, wrap it in a patch and call it a day. Only it wasn't like that at all.
First off, resizing the images resulted in Godzilla-sized assets. For example; If an image is 68x260 pixels, the nearest power-of-two equivalent would be 128x512 pixels. That results in an image 47 856 pixels (with transparent image, that's 191 424 bytes) larger the original image (of total 17 680 pixels). While majority of even Intel Graphics cards could handle the extra work-load in terms of GPU cycles and memory use, other issues torpedoed the plan.
For example; If the source image has almost four times the pixels to process on load-time, it automatically means it will take four times longer to load it. The image would take four times more disc space, too, meaning the patch would've resulted in something almost as large as the main game itself! Simply put; Not practical. Like, at all.
Attempts two and three: Load-time program-side resize
I studied this quite a bit and one of the most simple solutions suggested – one from image processing message boards, not gaming ones – that re-sizing the image canvas engine-side on load-time should do the trick. So, on I went with this. I was working on TAGAP 2 engine at the time.
I banged my head into a brick wall quite early on. Most of the material I read focused using the abilities of OpenGL to achieve the rescaling. More specifically, the most recommended way to do this was to load the image, render it into an off-screen buffer and then overwrite the texture with the one from the said buffer. Perfect and fast image resize, one that could be done even in runtime!
Except it doesn't work in this case; If the problem is that I can't make OpenGL load the image in the first place, I hardly can render it to the FBO, now can I? I obviously had to do the resize to the raw data instead before the image was handed over to OpenGL. So the idea was to load the image, get the dimensions, allocate a byte buffer of nearest power-of-two dimensions and copy the image data over to the new buffer. I'm no math wizard, but I managed to get it done.
There were two major problems with the results. First off, my limited math skills resulted in loop quite resource-wasteful, meaning copying the image over took quite a while – almost as long as with the first attempt. Not only that, but allocated memory had to be naturally 'zeroed' to prevent garbage data showing up on rendering, lengthening the load times even more. Once more, I sighed and moved on.
TAGAP 3 and success
During TAGAP 3 benchmarking test session, I noticed that with the HD graphics involved the loading times were getting quite long. Moreover, for some reason Petja's Crysis-capable monster rig was loading the images slower than my barely-Quake-3-capable laptop, so something in my image loading – or TARGA decompressing – routines was too slow to be viable this time around.
Instead of re-coding something I've already streamlined to the best of my know-how, I started looking for non-copyleft image libraries that could do what I needed. I ultimately discovered FreeImage Library, image loading and processing library for multiple formats and platforms. The test results were very encouraging; Petja's initial load time improved for several seconds and mine shortened for half-a-second. Not only that, but since the multi-format nature of library, I was able to throw the old IPicture-based JPEG routines out the window, meaning JPEGs, GIFs and alike became viable format to use outside the extras galleries. Granted, some of the speediness can be attributed to switch from converted RGB to native BGR texture format, but who am I kidding; People who made FreeImage simply are better in what they do than I ever could dream to be.
And then I realized that indeed, FreeImage wasn't just image loading library, but image PROCESSING library as well. Not only that, but it had it's own image blitting functions, usable without any dependancies. I soon crafted a meta code for image load time rescaling functions using FreeImage, but I didn't raise it too high on my to-do list. After so many failed attempts, I was keener to produce TAGAP 3 content than doodling with something I had failed to solve for almost five years.
And then I gave it test spin one day, thinking what the heck, at least I can scratch it off the to-do-list. But to my utter surprise it worked. Like, perfectly. I instantly took a workspace snap, took it to Petja's (who was hosting may-day party) and occupied his computer for further benchmarking. Naturally the load times with anti-whitebox measures were longer, but thanks to the improvements brought by FreeImage, it didn't really matter.
Other changes needing to be made
While image loading is the reason white box exists, it isn't the only thing that needs to be changed in order to get a completely white box free TAGAP experience on Intel Graphics. Since the game was so reliant on GL_ARB_texture_rectangle anyway, I had programmed everything with it in mind. Thanks to that I now have to go through every single rendering call involving textures and/or sprite frames and ensure they all support power-of-two textures as well.
You see, power-of-two and rectangle textures are handled differently on runtime by OpenGL, most notably the texture coordinates. Where top right corner of rectangle images is expressed in pixels (width,height i.e. 15,45), power-of-two coordinates are time coordinates (ranging from 0 to 1, top-right corner being 1,1). It's easier-than-easy matter of dividing, sure, but it needs to be done – and done without breaking the previous functionality.
Another thing needing to be recoded are the post-processing effects, like drug trip distortions and water waves. In the past I buffered them into a fixed-sized buffer of 800x600, but obviously I have to convert that buffer into p-o-t as well (1024x1024) and ensure the effect calls can still use the texture.
So, it's loads of work.
What am I working on right now?
Well, the stuff explained above for starters. And the website upgrade. And designs. And something I cannot disclose at the moment. Everything!
Also in the works right now; Cloth physics. No, really. If anyone would've said to me five years ago that TAGAP 3 would be in HD, feature skeletal animations, bezier batches and cloth physics, I would've laughed him/her/it out of this planet, but here we are.
Anyhow, all I really wanted was to set up large, hanging propaganda flags for the totalitarian penguins of Pluto. I started with static layer images, but with all the stuff going on, them being so static really stood out. I first intended to do some highly trickery-based guide-trigger, but I soon realized that if I ever wanted to do more than one type of banner flag, that would take ages! So, a couple of lunch breaks later I had written meta-code for a flag that reacts to shockwaves etc., can be set to hang, wave to sides or even float up. The best bit is that they're based on image layers, so if you want to add new kinds of flags, it's just a matter of linking the image to TAGAP Editor.
The result looks mighty fine – in fact I'm not sure if it looks too good and realistic for the rest of it, but toning it down should be easy if need arises. There's also an optional support for a sound loop to be played when the flag moves and the engine handles the pitch and volume of the loop according to the movement. By the way, you can do much more than just flags with this, theoretically I could use this to do stuff line vines or seaweed, for example.
But that's enough techno-babble for you to stomach!
Until next time,
Jouni Lahtinen, the head penguin