Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
manual:performance_optimization [2013/06/23 09:38]
daniel [Syntax Tips]
manual:performance_optimization [2013/08/03 16:46] (current)
daniel [Find out if you need Mipmaps]
Line 1: Line 1:
 +====== 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.
 +
 +<note warning>
 +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.
 +</​note>​
 +
 +==== 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:
 +
 +<code objc>
 +SPSprite *sprite = ...;
 +
 +for (SPDisplayObject *child in sprite)
 +    child.rotation += 0.05f; ​  
 +</​code>​
 +
 +That's considerably faster than this code:
 +
 +<code objc>
 +// Don't do that!
 +for (int i=0; i<​sprite.numChildren;​ ++i)
 +{
 +    SPDisplayObject *child = [sprite childAtIndex:​i];​
 +    child.rotation += 0.05f;
 +}
 +</​code>​
 +
 +<note info>
 +ActionScript developers might find this odd, because it's exactly the other way round in AS3! Mad World ...
 +</​note>​
 +
 +== 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.
 +
 +<code objc>
 +// 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];​
 +}
 +</​code>​
 +
 +===== 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. ​
 +
 +<note info>
 +I use "​Quad"​ and "​Image"​ synonymously in this article. Remember, SPImage is just a subclass of SPQuad that adds a texture.
 +</​note>​
 +
 +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.
 +
 +<note tip>
 +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.
 +</​note>​
 +
 +== 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.
 +
 +{{ :​manual:​stats_display.png?​nolink |}}
 +
 +=== 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 [[wp>​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.
 +
 +{{ :​manual:​painters_algorithm.png?​nolink&​600 |}}
 +
 +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.
 +
 +{{ :​manual:​landscape.png?​nolink |}}
 +
 +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.
 +
 +<note tip>
 +Another tool at your disposal is the [[http://​doc.sparrow-framework.org/​v2/​Classes/​SPDisplayObjectContainer.html#//​api/​name/​sortChildren:​|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
 +</​note>​
 +
 +=== 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.)
 +
 +{{ :​manual:​landscape-2.png?​nolink |}}
 +
 +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.
 +
 +{{ :​manual:​landscape-3.png?​nolink |}}
 +
 +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:
 +
 +<code objc>
 +SPQuadBatch *quadBatch = [[SPQuadBatch alloc] init];
 +SPImage *image = [[SPImage alloc] initWithTexture:​texture];​
 + 
 +for (int i=0; i<100; ++i)
 +{
 +    image.x += 10;
 +    [quadBatch addQuad:​image];​
 +}
 +</​code>​
 +
 +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.
 +
 +<note important>​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.
 +</​note>​
 +
 +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 ====
 +
 +{{ :​manual:​mipmap.jpg?​192}}
 +
 +[[wp>​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!
 +
 +<code objc>
 +backgroundImage.blendMode = SP_BLEND_MODE_NONE;​
 +</​code>​
 +
 +==== 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;​
 +</​code>​
 +
 +==== 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.
 +
 +<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];​
 +}
 +</​code>​
 +
 +==== 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.
 +
 +<code objc>
 +// good:
 +for (SPDisplayObject *child in container)
 +    child.touchable = NO;
 +
 +// even better:
 +container.touchable = NO;
 +</​code>​
 +
 +-----
 +
 +//Next section: [[Custom Display Objects]]//
  manual/performance_optimization.txt · Last modified: 2013/08/03 16:46 by daniel
 
Powered by DokuWiki