This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
manual:performance_optimization [2013/05/29 17:23] – daniel | manual:performance_optimization [2013/08/03 16:46] (current) – [Find out if you need Mipmaps] daniel | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== Performance Optimization ====== | ||
+ | While Sparrow does what it can to produce games that run super-smooth, | ||
+ | |||
+ | |||
+ | ===== 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), | ||
+ | |||
+ | To test your game in release mode, I recommend you create a duplicate of your debug scheme, call it " | ||
+ | |||
+ | <note warning> | ||
+ | Don't forget to switch back to Debug mode when you want to debug your code. Due to its code optimizations, | ||
+ | </ | ||
+ | |||
+ | ==== 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" | ||
+ | |||
+ | As an example, you can iterate over the children of a " | ||
+ | |||
+ | <code objc> | ||
+ | SPSprite *sprite = ...; | ||
+ | |||
+ | for (SPDisplayObject *child in sprite) | ||
+ | child.rotation += 0.05f; | ||
+ | </ | ||
+ | |||
+ | That's considerably faster than this code: | ||
+ | |||
+ | <code objc> | ||
+ | // Don't do that! | ||
+ | for (int i=0; i< | ||
+ | { | ||
+ | SPDisplayObject *child = [sprite childAtIndex: | ||
+ | child.rotation += 0.05f; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <note info> | ||
+ | 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 " | ||
+ | |||
+ | <code objc> | ||
+ | // bad: | ||
+ | for (int i=0; i<10; ++i) | ||
+ | { | ||
+ | SPPoint *point = [[SPPoint alloc] initWithX:i y:2*i]; | ||
+ | [self doSomethingWith: | ||
+ | } | ||
+ | |||
+ | // better: | ||
+ | SPPoint *point = [[SPPoint alloc] init]; | ||
+ | |||
+ | for (int i=0; i<10; ++i) | ||
+ | { | ||
+ | [point setX:i y:2*i]; | ||
+ | [self doSomethingWith: | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== 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, | ||
+ | |||
+ | 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 " | ||
+ | |||
+ | <note info> | ||
+ | I use " | ||
+ | </ | ||
+ | |||
+ | These are those crucial properties that make up a state: | ||
+ | |||
+ | * The //texture// (different textures from the same atlas are fine, though) | ||
+ | * The // | ||
+ | * The // | ||
+ | * 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 " | ||
+ | |||
+ | * Drawing them translucently ('' | ||
+ | * Drawing them with a different color (setting '' | ||
+ | |||
+ | 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' | ||
+ | |||
+ | <note tip> | ||
+ | If you are creating a game for hardware that doesn' | ||
+ | |||
+ | There' | ||
+ | </ | ||
+ | |||
+ | == The Statistics Display == | ||
+ | |||
+ | Sparrow' | ||
+ | |||
+ | Sparrow.currentController.showStats = YES; | ||
+ | | ||
+ | shows you the number of draw calls that are executed per frame (second line: '' | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | === The Painter' | ||
+ | |||
+ | To know how to minimize state changes, you need to know the order in which Sparrow processes your objects. | ||
+ | |||
+ | Like Flash, Sparrow uses the [[wp> | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | 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 " | ||
+ | |||
+ | <note tip> | ||
+ | Another tool at your disposal is the [[http:// | ||
+ | </ | ||
+ | |||
+ | === 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' | ||
+ | |||
+ | 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 '' | ||
+ | * 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: '' | ||
+ | |||
+ | <code objc> | ||
+ | SPQuadBatch *quadBatch = [[SPQuadBatch alloc] init]; | ||
+ | SPImage *image = [[SPImage alloc] initWithTexture: | ||
+ | |||
+ | for (int i=0; i<100; ++i) | ||
+ | { | ||
+ | image.x += 10; | ||
+ | [quadBatch addQuad: | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Did you notice? You can add the same image as often as you want! Furthermore, | ||
+ | |||
+ | * 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 '' | ||
+ | * 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. | ||
+ | |||
+ | <note important> | ||
+ | </ | ||
+ | |||
+ | 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 ==== | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | [[wp> | ||
+ | |||
+ | 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 " | ||
+ | |||
+ | ==== 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! | ||
+ | |||
+ | <code objc> | ||
+ | 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! | ||
+ | |||
+ | <code objc> | ||
+ | Sparrow.stage.color = SP_COLOR_GREEN; | ||
+ | </ | ||
+ | |||
+ | ==== Avoid repeated calls to '' | ||
+ | |||
+ | The '' | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <code objc> | ||
+ | // 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/ | ||
+ | |||
+ | Thus, it helps to make objects " | ||
+ | |||
+ | <code objc> | ||
+ | // good: | ||
+ | for (SPDisplayObject *child in container) | ||
+ | child.touchable = NO; | ||
+ | |||
+ | // even better: | ||
+ | container.touchable = NO; | ||
+ | </ | ||
+ | |||
+ | ----- | ||
+ | |||
+ | //Next section: [[Custom Display Objects]]// |