21

I am making a game and I noticed that during some scenes, my FPS kept dropping around the 55-60FPS area (using texture atlas). This drove me nuts so I decided to put all my assets to the Images.xcassets folder and voila, steady 60FPS.

I thought this was a fluke or that I was doing something wrong, so I decided to start a new project and perform some benchmarks...


Apple's documentation says that using texture atlas's will improve app performance. Basically, allowing your app to take advantage of batch rendering. However...

The Test (https://github.com/JRam13/JSGlitch):

- (void)runTest
{
    SKAction *spawn = [SKAction runBlock:^{

        for (int i=0; i<10; i++) {


            SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];

            sprite.xScale = 0.5;
            sprite.yScale = 0.5;
            sprite.position = CGPointMake(0, [self randomNumberBetweenMin:0 andMax:768]);

            SKAction *action = [SKAction rotateByAngle:M_PI duration:1];

            [sprite runAction:[SKAction repeatActionForever:action]];

            SKAction *move = [SKAction moveByX:1200 y:0 duration:2];

            [sprite runAction:move];

            //notice I don't remove from parent (see test2 below)
            [self addChild:sprite];

        }

    }];

    SKAction *wait = [SKAction waitForDuration:.1];

    SKAction *sequence = [SKAction sequence:@[spawn,wait]];
    SKAction *repeat = [SKAction repeatActionForever:sequence];

    [self runAction:repeat];
}

Results:

enter image description here

Tests repeatedly show that using the xcassets performed way better than the atlas counterpart in FPS. The atlas does seem to manage memory marginally better than the xcassets though.

Anybody know why these results show that images.xcassets has better performance than the atlas?


Some hypotheses I've come up with:

  • xcassets is just better optimized than atlasas.
  • atlasas are good at drawing lots of images in one draw pass, but have bigger overhead with repeated sprites. If true, this means that if your sprite appears multiple times on screen (which was the case in my original game), it is better to remove it from the atlas.
  • atlas sheets must be filled in order to optimize performance

Update

For this next test I went ahead and removed the sprite from parent after it goes offscreen. I also used 7 different images. We should see a huge performance gain using atlas due to the draw count but...

enter image description here

JRam13
  • 1,132
  • 1
  • 15
  • 25
  • is this on the simulator? , please post device specs, 32 bit? 64 bit?, metal-able device? etc... – Juan Boero Apr 01 '16 at 21:10
  • I think this question is moot. You're creating an issue due to misunderstanding what a texture atlas is designed for. It's like asking, is it better to batch draw calls (texture atlas) or not batch draw calls (xcassets)? One way to think of it is to consider an individual asset as a texture atlas that contains only one image. Your best bet is to study up on texture atlases and best practices for how to use them correctly. – Gino Feb 11 '17 at 15:12

1 Answers1

16

First, revise your test to match this :

    for (int i=0; i<10; i++) {


        SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];

        sprite.xScale = 0.5;
        sprite.yScale = 0.5;
        float spawnY = arc4random() % 768;
        sprite.position = CGPointMake(0, spawnY);

        SKAction *action = [SKAction rotateByAngle:M_PI duration:1];

        [sprite runAction:[SKAction repeatActionForever:action]];


        SKAction *move = [SKAction moveByX:1200 y:0 duration:2];

        // next three lines replace the runAction line for move
        SKAction *remove = [SKAction removeFromParent];
        SKAction *sequence = [SKAction sequence:@[move, remove]];
        [sprite runAction:sequence];

        [self addChild:sprite];

    }

Rerun your tests and you should notice that your framerate NEVER deteriorates as in your tests. Your tests were basically illustrating what happens when you never remove nodes, but keep creating new ones.

Next, add the following line to your ViewController when you set up your skview :

skView.showsDrawCount = YES;

This will allow you to see the draw count and properly understand where you are getting your performance boost with SKTextureAtlas.

Now, instead of having just one image , gather 3 images and modify your test by choosing a random one of those images each time it creates a node, you can do it something like this :

 NSArray *imageNames = @[@"image-0", @"image-1", @"image-2"];
 NSString *imageName = imageNames[arc4random() % imageNames.count];

In your code, create your sprite with that imageName each time through the loop. ie :

SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:imageName];

In your SKTextureAtlas test, use that same imageName obviously to create each sprite.

Now... rerun your tests and take note of the draw count in each test.

This should give you a tangible example of what batch rendering is about with SKTextureAtlas.

It's no about rendering the same sprite image thousands of times.

It's about drawing many different sprites images in the same draw pass.

There is likely some overhead in getting this rendering optimization, but I think the draw count should be self explanatory as to why that overhead is moot when all things are considered.

Now, you can hypothesize some more :)

UPDATE

As mentioned in the comments, my post was to expose why your test was not a good test for the benefits of SKTextureAtlas and was flawed if looking to analyze it in a meaningful way. Your test was like testing for measles with a mumps test.

Below is a github project that I put together to pinpoint where an SKTextureAtlas is appropriate and indeed superior to xcassets.

atlas-comparison

Just run the project on your device and then tap to toggle between tests. You can tell when it's testing with SKTextureAtlas because the draw count will be 1 and the framerate will be 60fps.

I isolated what will be optimized with a SKTextureAtlas. It's a 60 frame animation and 1600 nodes playing that animation. I offset their start frames so that they all aren't on the same frame at the same time. I also kept everything uniform for both tests, so that it's a direct comparison.

It's not accurate for someone to think that using SKTextureAtlas will just optimize all rendering. It's optimization comes by reducing draw count via batch rendering. So, if your framerate slowdown is not something that can be improved via batch rendering, SKTexture atlas is the wrong tool for the job. right ?

Similar to pooling of objects, where you can gain optimization via not creating and killing your game objects constantly, but instead reusing them from a pool of objects. However if you are not constantly creating and killing objects in your game, pooling ain't gonna optimize your game.

Based on what I saw you describe as your game's issue in the discussion log , pooling is probably the right tool for the job in your case.

prototypical
  • 6,731
  • 3
  • 24
  • 34
  • I take it you didn't try this out for yourself? Because I'm still getting poor performances when using atlas (about a 5FPS difference). Atlas 43FPS, xcassets at 48FPS using 3 random images. Also, in my original test, I chose not to remove from parent to see how performance deteriorated over time. Anyway, I can see the fundamental advantages of batch rendering, unfortunately, it seems that there is too much overhead accompanied with it if you're trying to redraw duplicate sprites. I think this goes back to hypothesis #2. Let me know if you find any scenario where atlas surpasses xcassets. – JRam13 Aug 11 '14 at 06:09
  • I also just noticed that performance goes down even further if your atlas sheet(s) are filled with sprites that aren't used. It almost feels like it buffers the entire sheet and only draws what it needs. I might be wrong since I'm not too sure how the atlas works internally, but thats my best guess. – JRam13 Aug 11 '14 at 07:04
  • I did indeed try it. You are doing something incorrect. I get a solid 60fps on the device. – prototypical Aug 11 '14 at 20:28
  • Now you say you "chose" to not remove from the parent ? :/ The deterioration has nothing to do with batch rendering, it has to do with thousands of nodes in memory that are not being used. That is your efficiency problem, not batch rendering. So not sure why you would use that to benchmark batch rendering. – prototypical Aug 11 '14 at 20:31
  • I have shown you a scenario that surpasses xcassets, but it seems you don't understand what a draw count is and what impact that has on performance. :/ – prototypical Aug 11 '14 at 20:36
  • Can you push your code to github? Maybe I am missing something. I understand the impact a draw count has on performance. Technically, drawing something 2 times/frame should be a lot more efficient than drawing something 7 times/frame. I understand that. However, these tests show this is not translating to a better FPS (which defies this logic). Also, removing from parent it a moot point, I'm comparing apples to apples here. Everything is kept constant. I might not be showing a true benchmark on batch rendering, but it shows that there is an inherent performance downgrade with atlasas. – JRam13 Aug 11 '14 at 21:23
  • I think it'll be best if I post a project somewhere for you to compare, so we can ensure we are looking at the same thing. Keep in mind that we are talking 7 draws compared to 2 in your test, which is not where batch rendering is going to have a huge effect. However in many games you will have many animated characters, as well as many other potentially animated elements. That was the point of my example was to expose the draw count difference. For instance What happens when there are 300 draws compared to 5 ? Your test isn't even testing that aspect. – prototypical Aug 11 '14 at 22:52
  • The goal of my post was to expose to you that there is a "draw count" difference. – prototypical Aug 11 '14 at 22:53
  • Let me make two example projects , one with xcassets and one with an atlas. The test will show you what a texture atlas buys you. Your test is simply not testing in a way that will show the benefit of the atlas. So, I will isolate what you need to be the takeaway on the comparison. – prototypical Aug 11 '14 at 22:59
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59142/discussion-between-jonny-ramos-and-prototypical). – JRam13 Aug 11 '14 at 23:08
  • I saw your comment in chat, and pooling is potentially what can help your framerate in that situation. – prototypical Aug 12 '14 at 00:31
  • I have a moderately related question. Let me know if it's not OK to ask it here. I just noticed that the Other Processes memory is a very high number in all tests above. I've had a similar issue that's crashing my app: http://stackoverflow.com/questions/27431561/ios-memory-management-other-processes-ram-increasing. Any thoughts? Is it related? I mostly use atlases. – rizzes Jan 03 '15 at 06:57
  • yep, not the right place for this comment. Your issue , if you want efficient help in solving, would be github or some way where someone could have access to the code. Otherwise everyone is just making guesses and theorizing, which is not efficient at all. – prototypical Jan 03 '15 at 18:30