How do SKTexture caching and reuse work in SpriteKit?

StackOverflow often mentions that SpriteKit does its own internal caching and reuse.

If I'm not making any effort to reuse textures or atlases, what caching and reuse behavior can I expect from SpriteKit?

+6
source share
1 answer

Texture Caching in SpriteKit

β€œIn short: rely on the Sprite Kit to do the right thing for you.” - @ LearnCocos2D

Here is a long story starting with iOS 9.

Volumetric texture data is not saved directly to the SKTexture object. (See SKTexture class link .)

  • SKTexture loading data until it is needed. Creating a texture, even from a large image file, consumes memory quickly and little.

  • Texture data is loaded from disk usually when the corresponding sprite node. (Or indeed, whenever data is needed, such as when the size method is called).

  • The texture data is prepared for rendering during the (first) render pass.

SpriteKit has built-in caching for texture bulky image data. Two functions:

  • Textures of bulky image data are cached by SpriteKit until SpriteKit feels how to get rid of it.

    • According to the SKTexture class SKTexture : "After the SKTexture, the object is ready for rendering, it remains ready until all strong references to the texture object are deleted."

    • In the current iOS, it tends to stick for longer, perhaps even after the whole scene has disappeared. The StackOverflow comment quotes Apple tech support: "iOS frees memory cached + textureWithImageNamed: or + imageNamed: when it sees fit, for when it detects a low memory condition."

    • In the simulator running the test project, I was able to see the texture memory recovered right after removeFromParent . However, working on a physical device, the memory seemed to linger; repeated rendering and removal of the texture did not lead to additional access to the disk.

    • Interesting: can a release memory be released early in some critical situations (when the texture is preserved, but not currently displayed)?

  • SpriteKit reliably uses cached 3D image data.

    • In my experiments it was hard not to reuse it.

    • Say you have a texture mapping in a node sprite, but rather than reusing the SKTexture object, you call [SKTexture textureWithImageNamed:] for the same image name. The texture will not be a pointer identical to the original texture, but it will share bulky image data.

    • The above is true whether the image file is part of the atlas or not.

    • The above is true whether the original texture was loaded using [SKTexture textureWithImageNamed:] or using [SKTextureAtlas textureNamed:] .

    • Another example: Let's say you created a texture atlas object using [SKTextureAtlas atlasNamed:] . You take one of your textures using textureNamed: and you do not save the atlas. You show the texture in the sprite node (and thus the texture is saved in your application), but you do not track the tracking of this specific SKTexture in the cache. Then you do it all again: a new texture atlas, a new texture, a new node. All of these objects will be highlighted again, but they are relatively light. Meanwhile, and what's important: bulky image data originally uploaded will be transparently distributed between instances.

    • Try the following: you load the atlas of monsters by name, and then one of its orc textures and render it in the orc sprite node. Then the player returns to the main screen. You encode the orc node while saving the state of the application, and then decode it while restoring the state of the application. (When he encodes, he does not encode his binary data; instead, he encodes his name.) In the restored application, you create another orc (with a new atlas, texture, and node). Will this new orc share its bulky Orc data with a decoded orc? Yes. Yes, it will be an orc.

    • In fact, the only way to get a texture not for reuse is that the texture image data should initialize it with [SKTexture textureWithImage:] . Of course, maybe UIImage will do its internal caching of the image file, but in any case, SKTexture takes over the data and will not reuse render data elsewhere.

    • In short: if you have two identical sprites displayed in your game at the same time, its fair bet that they use memory efficiently.

Put these two points together: SpriteKit has a built-in cache that saves important bulky image data and makes extensive use of it.

In other words, it just works.

No promises. In the simulator running the test application, I can easily prove that SpriteKit removes my texture data from the cache before Im really done with it.

During prototyping, however, you may be surprised to find reasonably good behavior from your application, even if you never use a single atlas or texture.

SpriteKit has atlas caching too

SpriteKit has a caching mechanism specifically for texture atlases. It works as follows:

  • You call [SKTextureAtlas atlasNamed:] to load the texture atlas. (As mentioned earlier, this does not yet load bulky image data.)

  • You always save the atlas somewhere in your application.

  • Later, if you call [SKTextureAtlas atlasNamed:] with the same atlas name, the returned object will be displayed as a pointer identical to the saved atlas. Textures extracted from satin using textureNamed: then, will also be indicative. (Update: Textures will not necessarily be identical to pointers in iOS10.)

Texture objects, it should be mentioned, do not retain their atlases.

You might want to create your own cache.

So, I see that you create your own caching and reuse mechanisms anyway. Why are you doing this?

  • Ultimately, you have more detailed information about when to save or clean certain textures.

  • You may need full control over download times. For example, if you want your textures to appear instantly when you first use the preload methods from SKTexture and SKTextureAtlas . In this case, you must save links to preloaded textures or atlases, right? Or, SpriteKit cache them for you independently? Unclear. A custom atlas or cache texture is a good way to maintain full control.

  • At a certain point in the optimization (prematurely forbid!), It makes sense to stop creating new SKTexture and / or SKTextureAtlas objects again and again, no matter how light. You will probably first create a reuse mechanism for the atlas, since atlases are less lightweight (they have a texture dictionary, after all). You can later create a separate texture caching mechanism for reusing objects without the SKTexture atlas. Or maybe you will never get around this second. After all, you are busy and the kitchen is not cleaning yourself, damn it.

All told, your caching and reuse behavior is likely to end up being eerily similar to SpriteKits.

How does SpriteKits texture caching affect its design texture cache? Here are the things (above) to keep in mind:

  • You cannot directly control the release time of a bulky image data when using named textures. You publish your recommendations and SpriteKit frees up memory whenever you want.

  • You can control the loading time of bulky image data using preload methods.

  • If you rely on SpriteKits internal caching, then your atlas cache only needs to save references to SKTextureAtlas objects SKTextureAtlas not return them. The atlas object will be automatically reused throughout the application.

  • Similarly, your texture cache only needs to save references to SKTexture objects, and not return them. Bulky image data will be automatically reused throughout the application. (This weird little I am, though; its a pain to check for good behavior.)

  • Given the last two points, consider alternative design options for a singleton cache object. Instead, you can save atlases to use on your sprite objects or their controllers. For the controller lifetime, then any calls in your application on atlasNamed: will reuse an atlas identical to the pointer.

  • Two identical SKTexture object SKTexture use the same memory, yes, but because of SpriteKit caching, the opposite is not necessarily true. If you are debugging memory problems and find two SKTexture objects that you would expect to be identical pointers but arent, they can still share their bulky image data.

Testing

I'm starting to start, so I just measured the memory usage of the application on the release of the assembly using the Allocations tool.

I found that "The whole heap and anonymous virtual machine" will alternate between two stable values ​​for consecutive runs. I did each test several times and as a result I got the smallest memory value.

For my testing, Ive had two different atlases with two images each; call atlases A and B and images 1 and 2. Original images (one 760 KiB, one 950 KiB).

Atlases are loaded using [SKTextureAtlas atlasNamed:] . Textures loaded using [SKTexture textureWithImageNamed:] . In the table below, loading really means "enabling node sprite and rendering."

  All Heap & Anon VM (MiB) Test --------- ------------------------------------------------------ 106.67 baseline 106.67 preload atlases but no nodes 110.81 load A1 110.81 load A1 and reuse in different two sprite nodes 110.81 load A1 with retained atlas 110.81 load A1,A1 110.81 load A1,A1 with retained atlas 110.81 load A1,A2 110.81 load A1,A2 with retained atlas 110.81 load A1 two different ways* 110.81 load A1 two different ways* with retained atlas 110.81 load A1 or A2 randomly on each tap 110.81 load A1 or A2 randomly on each tap with retained atlas 114.87 load A1,B1 114.87 load A1,A2,B1,B2 114.87 load A1,A2,B1,B2 with preload atlases * Load A1 two different ways: Once using [SKTexture textureWithImageNamed:] and once using [SKTextureAtlas textureNamed:]. 

Internal structure

During the investigation, I discovered some true facts about the internal structure of texture and satin objects in SpriteKit.

Interesting? It depends on what things interest you!

Texture Structure From SKTextureAtlas

When the atlas loads [SKTextureAtlas atlasNamed:] , inspecting its textures at runtime reveals some reuse.

  • At build time, the Xcode script compiles the atlas from individual image files into several large sprite images (limited in size and grouped using @ 1x @ 2x @ 3x resolution). Each texture in the atlas refers to its image of the sprite sheet by the bundle stored in _imgName (set to true with _isPath ).

  • Each texture in the atlas is individually identified by _subTextureName , and has a textureRect insert in its sprite sheet.

  • All textures in the atlas that have the same image on the sprite sheet will have the same non-Nile Ivars _originalTexture and _textureCache .

  • General _originalTexture , the SKTexture object SKTexture supposedly an image of the entire sprite sheet. It does not have _subTextureName , and its textureRect is (0, 0, 1, 1) .

If the atlas is freed from memory and then reloaded, the new copy will have different SKTexture objects, different _originalTexture objects and different _textureCache objects. From what I could see, only _imgName (i.e. the actual image file) connects the new atlas to the old atlas.

Non- SKTextureAtlas texture structure

When a texture is loaded using [SKTexture textureWithImageNamed:] , it may come from the atlas, but it does not seem to be SKTextureAtlas .

The texture loaded in this way differs from the above:

  • It has a short _imgName , for example "giraffe.png", and _isPath set to false.

  • It has unset _originalTexture .

  • It has (apparently) its own _textureCache .

Two SKTexture objects loaded by textureWithImageNamed: (using the same image name) have nothing in common except _imgName.

However, as described in detail above, such a texture configuration is divided by cumbersome image data with a different kind of configuration texture. This means that caching is performed close to the actual image file.

+14
source

All Articles