iOS Development Optimizations Part 3 – Resource Management

Closed

Even having done everything we could to minimize the number of textures we needed to load, there was still room for improvement. Sprite sheets were tightly packed, the entire level was rendered into its own texture set, and we’d even run all of the textures through Apple’s PVR compression to cut the file size down to 25%. †Still, we wanted to be able to run through 20 different kinds of enemies over the course of a level. †We obviously can’t load all of the frames of animation for 20 enemies, for all of their possible actions – running, attacking, idling, and dying are the minimums set, with more for characters with emotes. †And so we enter the realm of Dynamic Resource Management.

My first crack at this was a basic lazy implementation – lazy in the technical sense, not the social one. †How do we get a graphics engine to be lazy? Simply don’t load any texture until it is about to be rendered! † The moment that a texture is requested, make sure that there is enough memory for it. †If not, kick out any texture that isn’t currently in use to make room. †This means that we could have 20 enemies in one level – just, not all on the screen at the same time. †Bring one enemy in, and if there isn’t room, evict the resources of an enemy not in use.

These are MY resources now!

This was a fantastic improvement–with one issue. †By waiting until the moment the frame needed to be rendered in order to load it, I introduced a pause the moment any frame was shown for the first time, as the texture was read from disk. †Being completely lazy about it didn’t quite solve the issue, so I implemented a two-phase mostly-lazy pattern using a custom smart pointer. †Warning – the following is fairly technical stuff, but I’m not going to go in depth, so be strong!

A smart pointer is basically an object that acts like a reference to another object , but does something smart about the reference at the same time. †We typically use them for reference counting things (create a smart pointer to an object, and the reference count goes up; destroy the smart pointer and the reference count goes down. †Now we know how many references to an object there are!). †In this case though, the smart pointer did some extra work. †Instead of immediately creating a reference to the intended object, it sent a request to a background thread warning it that a resource had been requested. †Then, when the application goes to actually use the referenced object, the smart pointer will block until the resource has actually been loaded.

An example:

// Highly fake class to demonstrate the principle
  1. class ImageSmartPointer
  2. {
  3.     public:
  4.         ImageSmartPointer(string resourceName)
  5.         {
  6.              // Let the image loader know we'll want this image soon
  7.              BackgroundImageLoader>RequestLoad(resourceName);
  8.              m_ResourceName = resourceName;
  9.              m_pImage = NULL;
  10.         }
  11.  
  12.         Image* operator>()
  13.         {
  14.              // if we haven't already gotten the image, get it from the loader
  15.              if(m_pImage == NULL)
  16.              {
  17.                  // This may block for a load.  It will increase the reference count on the image as well.
  18.                  m_pImage = BackgroundImageLoader>GetLoadedImage(m_ResourceName);
  19.              }
  20.              return m_pImage;
  21.         }
  22.  
  23.         ~ImageSmartPointer()
  24.         {
  25.              // if we've retrieved the image already, let the loader know we're done with it
  26.              if(m_pImage != NULL)
  27.                   BackgroundImageLoader>ReleaseImage(m_ResourceName);
  28.         }
  29.     private:
  30.         string m_ResourceName;
  31.         Image* m_pImage;
  32. };

.
Although threading doesn’t provide any performance enhancements on the current iOS devices (unlike PCs with multiple cores, for example), it still provides one great benefit – anything done on a background thread doesn’t block the foreground (renderer) thread. †This means that when a character is loaded, it can grab a reference to each of its frames, which are then loaded by the background thread without stalling the frame rate. †By the time the renderer needs to actually render the character’s frame, 95% of the time it’s already been loaded. †They other 5% of the time there’s a slight delay, but now that it’s only one frame (not every single one) it’s basically unnoticeable. So, with a little application of a lot of laziness, we now have a late-binding pre-loading resource tracking system.

This entry is filed under NicholasMTElliott.com, Programming, Prophetic Sky. And tagged with , , , , , , , , . You can follow any responses to this entry through RSS 2.0. Both comments and pings are currently closed.

Comments are currently closed.