Performance Optimization

While Sparrow does what it can to produce games that run super-smooth, it can't do all the work for you. To achieve the best possible performance, you have to understand some key concepts of its architecture. Here is a list of best practices you can follow to have your game run as fast as possible.

General Objective-C Tips

Always test on an actual Device

While the iOS simulator is a great way to test and debug your code quickly, it does not allow you to make any conclusions about the performance of your game on an actual device.

Not only does the simulator run on a completely different hardware, it also uses different implementations of the APIs that run on iOS. In most situations, rendering will be much faster on a real device, while most of the game logic will be faster on the Mac, thanks to its powerful processor.

Bottom line: performance tests must be done on a real device.

Always make a Release Build

The Objective-C compiler can make huge optimizations for your code. In Debug mode (which is the default during development), they are all deactivated.

To test your game in release mode, I recommend you create a duplicate of your debug scheme, call it “[Name] Release” and change the “Build Configuration” to “Release”. That way, you can easily switch to release mode for performance tests.

Don't forget to switch back to Debug mode when you want to debug your code. Due to its code optimizations, the debugger can behave quite weird with Release code.

Syntax Tips

Sometimes, you can achieve the same target via different routes in Objective-C. Some are faster than others.

Loops

Whenever possible, use “Fast Enumeration” instead of “for i” loops. It's called “Fast” for a reason!

As an example, you can iterate over the children of a “SPDisplayObjectContainer” like this:

SPSprite *sprite = ...;
 
for (SPDisplayObject *child in sprite)
    child.rotation += 0.05f;   

That's considerably faster than this code:

// Don't do that!
for (int i=0; i<sprite.numChildren; ++i)
{
    SPDisplayObject *child = [sprite childAtIndex:i];
    child.rotation += 0.05f;
}

ActionScript developers might find this odd, because it's exactly the other way round in AS3! Mad World …

Avoid Object Creation

Avoid creating a lot of temporary objects. They take up memory and need to be cleaned up afterwards. ARC may hide those “release” calls from you — but I guarantee you, they are still there, and they take up CPU time whether you see them or not.

// bad:
for (int i=0; i<10; ++i)
{
    SPPoint *point = [[SPPoint alloc] initWithX:i y:2*i];
    [self doSomethingWith:point];
}
 
// better:
SPPoint *point = [[SPPoint alloc] init];
 
for (int i=0; i<10; ++i)
{
    [point setX:i y:2*i];
    [self doSomethingWith:point];
}

Sparrow Specific Tips

Minimize State Changes

As you know, Sparrow uses OpenGL to render all visible objects. This means that all drawing is done by the GPU.

Now, Sparrow could send one quad after the other to the GPU, drawing one by one. In fact, this is how the first Sparrow releases worked. However, for optimal performance, GPUs prefer to get a huge pile of data and draw all of it at once.

That's why newer Sparrow versions batch as many quads together as possible before sending them to the GPU. However, it can only batch quads that have similar properties. Whenever a quad with a different “state” is encountered, a “state change” occurs, and the previously batched quads are drawn.

I use “Quad” and “Image” synonymously in this article. Remember, SPImage is just a subclass of SPQuad that adds a texture.

These are those crucial properties that make up a state:

  • The texture (different textures from the same atlas are fine, though)
  • The blendMode of display objects
  • The smoothing value of textures
  • The repeat mode of textures
  • The tinted property of quads (see below)

If you set up your scene in a way that creates as little state changes as possible, your rendering performance will profit immensely.

Tinted Quads

The first generation of the iPad has a hard time “tinting” textures, that is:

  • Drawing them translucently (alpha is not one)
  • Drawing them with a different color (setting image.color to something else than white)

For this reason, Sparrow optimizes the rendering code of untinted images. The downside: switching between tinted and untinted objects will cause a state change. Keep that in mind when you change an image's color or alpha value.

If you are creating a game for hardware that doesn't care about tinting, those state changes will degrade your performance needlessly.

There's a simple trick to avoid the state changes then: just set the alpha value of your root object to “0.999” or a similar value. Since the alpha value propagates down to the children on rendering, Sparrow will now treat every object as tinted, and no more state changes will be triggered.

The Statistics Display

Sparrow's statistics display, which you can activate by calling

Sparrow.currentController.showStats = YES;

shows you the number of draw calls that are executed per frame (second line: DRW). The more state changes you have, the higher this number will be. Your target should always be to keep it as low as possible. The following tips will show you how.

The Painter's Algorithm

To know how to minimize state changes, you need to know the order in which Sparrow processes your objects.

Like Flash, Sparrow uses the Painter's algorithm to process the display list. This means that it draws your scene like a painter would do it: starting at the object at the bottom layer (e.g. the background image) and moving upwards, drawing new objects on top of previous ones.

If you'd set up such a scene in Sparrow, you could create three sprites: one containing the mountain range in the distance, one with the ground, and one with the vegetation. The mountain range would be at the bottom (index 0), the vegetation at the top (index 2). Each sprite would contain images that contain the actual objects.

On rendering, Sparrow would start at the left with “Mountain 1” and continue towards the right, until it reaches “Tree 2”. Now, if all those objects have a different state, it would have to make 6 draw calls. If you load each object's texture from a separate Bitmap, this is what will happen.

Another tool at your disposal is the SPDisplayObjectContainer::sortChildren: method which can be used to sort layers, within an SPSprite object for example, based on properties such as x, y, alpha etc. The method accepts a compare function which means you can sort objects based on any criteria you wish! :-D

Texture Atlas

That's one of the reasons why texture atlases are so important. If you load all those textures from one single atlas, Sparrow will be able to draw all objects at once! (At least if the other properties listed above do not change.)

Here, each image uses the same atlas (depicted by all nodes having the same color). The consequence of this is that you should always use an atlas for your textures.

Sometimes, though, not all of your textures will fit into a single atlas. Depending on the device you are using, textures will be limited to a certain size, so you'll run out of space sooner or later. But this is no problem — as long as you arrange your textures in a smart way.

Both those examples use two atlases (again, one color per atlas). But while the display list on the left will force a state change for each object, the version on the right will be able to draw all objects in just two batches.

Flattened Sprites

By minimizing state changes, you have already done a lot for the performance of your game. However, Sparrow still needs to iterate over all your objects, check their state, and then upload their data to the GPU — on each frame!

This is where the next optimization step comes into play. If there's a part of your game's geometry that is static and does not (or only rarely) change, call the flatten method on that sprite. Sparrow will preprocess the children and upload their data to the GPU. On each of the following frames, it will now be able to draw them right away, without any additional CPU processing, and without having to upload new data to the GPU.

This is a powerful feature that can potentially reduce the burden on the CPU immensely. Just keep in mind that even flattened sprites suffer from state changes: if the geometry of a flattened sprite contains different render states, it will still be drawn in multiple steps.

The SPQuadBatch class

Flattened sprites are very fast and easy to use. However, they still have some overhead:

  • When you add objects to a sprite, they will dispatch ADDED and ADDED_TO_STAGE events, which can be some overhead if there are lots of children.
  • As any display object container, you can add any child only once.

To get rid of these limitations as well, you can go down to the low-level class that Sparrow uses for all the batching internally: SPQuadBatch. It works like this:

SPQuadBatch *quadBatch = [[SPQuadBatch alloc] init];
SPImage *image = [[SPImage alloc] initWithTexture:texture];
 
for (int i=0; i<100; ++i)
{
    image.x += 10;
    [quadBatch addQuad:image];
}

Did you notice? You can add the same image as often as you want! Furthermore, adding it won't cause any event dispatching. As expected, this has some downsides, though:

  • All the objects you add must have the same state (i.e. use textures from the same atlas). The first image you add to the SPQuadBatch will decide on its state. You can't change the state later, except by resetting it completely.
  • You can only add instances of the SPImage, SPQuad, or SPQuadBatch class.
  • It's a one-way road: you can only add objects. The only way to remove an object is to reset the batch completely.

For these reasons, it's only suitable for very specific use-cases (the bitmap font class, for example, now uses quad batches directly). In those cases, it's definitely the fastest option, though. You won't find a more efficient way to render objects in Sparrow.

Use Bitmap Fonts

TextFields support two different kinds of fonts: True Type fonts and Bitmap Fonts.

TrueType fonts are easiest to use: just use a system font (or include a font in your app) and you're done. For static text fields that contain hundreds of characters, they are a good and fast option. Sparrow will render the text into a bitmap and display the text just like a texture. For short texts that change repeatedly (e.g. a score display), this is too slow, though.

If your game needs to display texts with many non-ASCII characters (e.g. Chinese or Arabic), TrueType fonts may be your only option. Bitmap Fonts are simply limited by their texture size.

TextFields that use a Bitmap Font can be created and updated very fast. Another advantage is that they don't take up any additional texture memory except what's needed for the original texture. That makes them the preferred way of displaying text in Sparrow. My recommendation is to use them whenever possible.

Find out if you need Mipmaps

mipmap.jpg

Mipmaps are downsampled versions of your textures, intended to increase rendering speed and reduce aliasing effects.

Per default, Sparrow does not create them for you when you load a texture (note that the default differs to Starling). That's because without mipmaps,

  • your textures will load faster and
  • they will look sharper when you scale them down.

However, textures that are frequently scaled down a lot will cause some strain on the GPU, because it has to work with more pixels during rendering. And the sharpness might reach a level that actually looks worse, because you start to see aliasing effects.

In any case, if you need to activate mipmaps, pass “YES” to the “generateMipmaps:” parameter of the texture initialization methods.

Use SP_BLEND_MODE_NONE

If you've got totally opaque, rectangular textures, help the GPU by disabling blending for those textures. This is especially useful for large background images. Don't be afraid of the additional state change this will cause; it's worth it!

backgroundImage.blendMode = SP_BLEND_MODE_NONE;

Use stage.color

If the background of your game is a flat color, set the stage color to that value instead of adding a texture or a colored quad. Sparrow has to clear the stage once per frame, anyway — thus, if you change the stage color, that operation won't cost anything. There is such a thing as a free lunch, after all!

Sparrow.stage.color = SP_COLOR_GREEN;

Avoid repeated calls to ''width'' and ''height''

The width and height properties are more expensive than one would guess intuitively, especially on sprites (a matrix has to be calculated, then each vertex of each child will be multiplied with that matrix).

For that reason, avoid accessing them repeatedly, e.g. in a loop. In some cases, it might even make sense to use a constant value instead.

// bad:
for (SPDisplayObject *child in sprite)
{
    if (child.x > wall.width)
        [child removeFromParent];
}
 
// better:
float wallWidth = wall.width;
for (SPDisplayObject *child in sprite)
{
    if (child.x > wallWidth)
        [child removeFromParent];
}

Make containers non-touchable

When you move the mouse/finger over the screen, Sparrow has to find out which object is hit. This can be an expensive operation, because it has to iterate over all of your display objects and call their hitTest: method.

Thus, it helps to make objects “untouchable” if you don't care about them being touched, anyway. It's best to disable touches on complete containers: that way, Sparrow won't even have to iterate over their children.

// good:
for (SPDisplayObject *child in container)
    child.touchable = NO;
 
// even better:
container.touchable = NO;

Next section: Custom Display Objects

  manual/performance_optimization.txt · Last modified: 2013/08/03 16:46 by daniel
 
Powered by DokuWiki