1

Similar to the SpriteKit Featured Game "Adventure" from WWDC, I am try to load my background image via tiles. I have created a Texture Atlas that contains 6,300 "tiles" that are each 100x100 pixels in size. The complete background image is a total of 30,000x2048 (for retina displays). The idea is that the background will move from right to left (side-scroller). The first column and the last column match so that they seem continuous.

When the application runs, it loads my initial loading screen and title images and spikes to 54MB in my memory tab with a CPU usage of 16%. This stays the same as I navigate through the menus until I choose my level, which tells a background thread to load the level assets (of which contains the aforementioned background image). The entire .atlas folder shows to be only 35.4MB. I don't believe that this is a problem since the Adventure .atlas folder (from WWDC) shows to be only 32.7MB.

Once I select the level, it loads approximately 20 of the textures in the .atlas folder before I start receiving memory warnings and it crashes the application. I've checked in Instruments for leaks and it doesn't show any memory leaks. I don't receive any compiler errors (not even the EXC_BAD_ACCESS one). I've looked at my device console and have found a few lines of where the application crashes, but it doesn't look to make much sense to me. I've also checked for Zombies, but haven't seemed to find any.

CoreLevel.m

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    // Used to determine time spent to load
    NSDate *startDate = [NSDate date];

    // Atlas to load
    SKTextureAtlas *tileAtlas = [SKTextureAtlas atlasNamed:@"Day"];

    // Make sure the array is empty, before storing the tiles
    sBackgroundTiles = nil;
    sBackgroundTiles = [[NSMutableArray alloc] initWithCapacity:6300];

    // For each row (21 Totals Rows)
    for (int y = 0; y < 21; y++) {

        // For each Column (100 Total Columns)
        for (int x = 1; x <= 100; x++) {

            // Get the tile number (0 * 32) + 0;
            int tileNumber = (y * 300) + x;

            // Create a SpriteNode of that tile
            SKSpriteNode *tileNode = [SKSpriteNode spriteNodeWithTexture:[tileAtlas               textureNamed:[NSString stringWithFormat:@"tile_%d.png", tileNumber]]];

            // Position the SpriteNode
            CGPoint position = CGPointMake((x * 100), (y * 100));
            tileNode.position = position;

            // At layer
            tileNode.zPosition = -1.0f;
            tileNode.blendMode = SKBlendModeReplace;

            // Add to array
            [(NSMutableArray *)sBackgroundTiles addObject:tileNode];
        }
    }
    NSLog(@"Loaded all world tiles in %f seconds", [[NSDate date] timeIntervalSinceDate:startDate]);
});

This is what seems to pertain to the crash from the Debug console:

com.apple.debugserver-300.2[9438] <Warning>: 1 +0.000000 sec [24de/1807]: error: ::read ( -1, 0x4069ec, 18446744069414585344 ) => -1 err = Bad file descriptor (0x00000009)
com.apple.debugserver-300.2[9438] <Warning>: Exiting.
com.apple.launchd[1] (UIKitApplication:tv.thebasement.Coin-Voyage[0x641d][9441]) <Notice>:     (UIKitApplication:tv.thebasement.Coin-Voyage[0x641d]) Exited: Killed: 9

I don't have enough reputation to post images so here is a link to a screenshot screenshot of my allocations in Instruments:

http://postimg.org/image/j17xl39et/

Any help and advice is much appreciated! If I've left out some pertinent information, I'm glad to update.

  • I know that loading the textures is a time-consuming process, but is it really necessary to be using GCD here? `SKTextureAtlas` has a built in method to preload all the textures asynchronously. You could call this and then have your `for` loop be contained in the callback block. – 67cherries Feb 13 '14 at 20:55
  • If you're speaking about `+preloadWithCompletionHandler:`, I'm using that for my initial title screen and level selection screen atlases, but it didn't seem to help with this loading process. Still ended up with the memory spikes and the crashes. – Matt Wagner Feb 13 '14 at 20:57
  • Essentially I used GCD in hopes that it would alleviate some of the memory pressure. – Matt Wagner Feb 13 '14 at 20:59
  • SpriteKit now includes a Tile interface in the new version of Xcode, have a look at that instead of rolling your own. Also, a more general way of lowering texture memory usage can be found here: http://stackoverflow.com/a/38679128/763355 – MoDJ Aug 04 '16 at 20:20

1 Answers1

3

The file size of an image file (PNG, JPG, atlas folder, etc) tells you nothing about the memory usage.

Instead you have to calculate the texture memory usage using the formula:

width * height * (color bit depth / 8) = texture size in bytes

For example an image with dimensions 4096x4096 pixels and 32 bits color depth (4 bytes) uses this much memory when loaded as a texture (uncompressed):

4096 * 4096 * 4 = 67108864 bytes (64 Megabytes)

According to your specs (6,300 tiles, each 100x100 pixels, assuming they're all unique) you're way, wayyyyyy above any reasonable limit for texture memory usage (about 1.5 Gigabytes!). Considering the atlas size of 35 Megabytes (which is huge for an atlas btw) and assuming a mere 10:1 compression ratio you may still be looking at 350+ Megabytes of texture memory usage.

CodeSmile
  • 64,284
  • 20
  • 132
  • 217
  • So basically the Adventure games count of only being 1000 tiles at 128x128 is still **drastically** lower that of my own, even though the atlas folder is only 3MB smaller? – Matt Wagner Feb 13 '14 at 21:16
  • Have you checked the contents of the atlas folder? Perhaps they include sets of assets for different devices, ie @2x, ~ipad etc. Also compression ratio of PNG heavily depends on the image contents. You can have blank images with huge sizes taking only a few KB because uniform images are highly compressible. – CodeSmile Feb 13 '14 at 21:22
  • Yes, the contents of the atlas folder are of my own making and both in Finder and Xcode, the folder only shows sequential tiles (1..6300). – Matt Wagner Feb 13 '14 at 21:30
  • I decreased the size down to half, (15000x1024) and it became 1600 tiles at 100x100. This allowed me to load them in without any crashing. Thanks for the help! – Matt Wagner Feb 14 '14 at 16:31