I’ve had a rather productive weekend! Implemented a handful of new art assets from Adam, finished some more gameplay code, optimized a bit, and even released a new build to testers tonight. It wasn’t a smooth process, but when is it ever? I was having an issue with loading the assets of the game at app launch and it caused the game to crash if it ever took more than 20 seconds to load the assets.
Admittedly, the asset loading code could use some optimization. Namely, I need to start using texture atlases for the art assets to minimize the state changes in OpenGLES. The reason I haven’t done this yet is art assets are still in flux and changing almost daily. I haven’t found any decent tools to pack textures into single images yet (haven’t looked too hard yet) and I haven’t taken the time to re-write the resource management in the game to be aware of multiple resources in the same texture.
So I’m putting it off for now and in the meantime wanted to redo the overall asset loading the ‘right way' with a proper load screen and what not. I was faced with:
- The Easy Way: Just draw up a loading screen and then load the assets right after drawing. The game essentially is blocked and there’s no user feedback that the game is running until the assets are all loaded
- The Difficult Way: Whip out some multi-threading API’s and have the assets load on another thread and have some animation play on the main thread.
Naturally, I chose the ‘difficult way'. After doing some reading up on multi-threading assets on the iPhone I figured it wouldn’t be too difficult to get running and I could implement it for tonight’s build. Wow was I wrong. I took the 20 minutes to implement the correct EAGLShareGroups and EAGLContexts and the supporting code to play the load screen and have the background thread load the assets.
It all worked beautifully and having the circles spin while the “loading” words doing some idle animation worked surprisingly well with little stuttering. I tested it on the Simulator and it worked fine, but to get accurate time results I began testing on the device itself. With everything in order I kept testing the loading screen and nothing was amiss. I was minutes away from calling it done and about to start making final test builds when I tried it one more time unhooked from my mac mini… I was looking at the circles spin when suddenly they locked up. The screen turned black, and the next second I was staring at my home screen.
After debugging the build I was still at a loss as to what was going on. Each time the application crashed it was an EXCBAD_ACCESS error in the CGContextDrawImage function. I’m using this function to load a texture into OpenGL and it’s worked _just fine in a single-threaded environment. Unfortunately, I haven’t found any definitive info on whether it’s possible to call this function from a 2nd thread or not. This code listing has a very similar process inside the ‘textureWithName' method (single threaded) in case you wish to see how it works.
Unless I come across something that shows how to use CGContextDrawImage correctly in a multi-threaded application I’m probably going to go the route of libpng. The portions of code I added have worked in regards to openGL so I believe I have that part solved, it’s just the data conversion from a file to raw bytes before openGL gets it’s hands on it is the problem.
Some Data You May Find Useful
My decision to use multiple threads for asset loading wasn’t based on any conceptions of a speed boost. If it loaded faster due to multi-threading that’d be great but it was secondary. I was doing it from a polish point of view and to make the whole experience on the user more seamless. So here’s a list of some data I’m working with:
- 68 png images totaling 609kb. There is huge performance gain potential here when I implement texture atlases.
- 22 audio (caf) files totaling 10.9Mb. Audio is certainly the majority of the game’s data, and I hope to reduce this by tweaking the compression on these files later.
Some load time data, don’t have the log files off hand at the moment so these are approximates:
data lost to time
So the single threaded time is pretty straightforward. In the 2nd column it was my attempt to do both audio+graphics loading in a single background thread. After realizing I wouldn’t be able to fix this in time for the build I opted for a different multi-thread approach, which is shown in the 3rd column.
I load all the graphics assets on the main thread but just before I do that I spin off a background thread to load the audio assets. The result? Pretty much no performance gain! The graphics load in pretty much the same speed but audio loading takes twice as long while doing it at the same time as graphics loading. The net time is still around 7-8 seconds, which is no different than a single-threaded approach.
I’m sure I’m probably doing something dumb in the loading of both assets since both have to hit the file system at the same time and there may be some thread contention there, causing it to lock the audio thread. I’ll need to look into it further because if I could do both simultaneously with minimal blocking then this approach would be the way to go.
The visual result? The load screen animation freezes for 3-4 seconds at the beginning while loading the graphics, but once only the audio is left it starts to animate again. Not ideal, but it doesn’t crash. I’ll be working on getting it all on the background thread in the coming weeks.