Swift:How to Handle a Lot of Textures in Memory

Swift : How to handle a lot of textures in memory

Rule 1

First of all you don't need to manually load in memory your texture atlas.
When you create a sprite with this code

let sprite = SKSpriteNode(imageNamed: "Dog")

SpriteKit looks for an image named Dog and if it cannot find it then it automatically looks for the image inside all your texture atlases.

When the image is found inside a texture atlas the whole texture atlas is automatically loaded in memory.

This is why you can avoid manually loading the texture atlas.

Rule 2

Don't bother about removing the texture atlas from memory

When you stop using all the images inside a given texture atlas it is automatically removed from memory. This could not happen immediately if the system has "enough" memory but it will happen eventually.

This is why you don't need to manually remove a texture atlas from memory

What can you do?

You should group your textures into several texture atlases following the logic of your game.

If you have 30 textures and you know that only the 1...10 OR 11...20 OR 21...30 will be used at the same time then create 3 texture atlases like follow:

  • TextureAtlas1: images from 1 to 10
  • TextureAtlas2: images from 11 to 20
  • TextureAtlas3: images from 21 to 30

This will make the SpriteKit work more effective.

SpriteKit SKTexture.preloadTextures high memory usage Swift

You should keep in mind that image file size (1Kb in your example) have nothing with amount of memory required for same image to be stored in RAM . You can calculate that amount of memory required with this formula:

width x height x bytes per pixel = size in memory

If you using standard RGBA8888 pixel format this means that your image will require about 0.5 megabytes in RAM memory, because RGBA8888 uses 4 bytes per pixel – 1 byte for each red, green, blue, and 1 byte for alpha transparency. You can read more here.

So what you can do, is to optimize you textures and use different texture formats. Here is another example about texture optimization.

Sprite kit, remove animated textures from memory

SpriteKit utilizes its own caching mechanism and there is no way for you to clear out the cache yourself.

From what you said, it appears that you have a memory leak somewhere in your code. Keep in mind that you need to remove all references for a texture to get deallocated by ARC.

Removing a texture from parent is not enough in your case as the textures are stored in an array. You would need to remove all textures from the array and have no other references to them in order to properly release them.

450MB+ is absolutely too much for just a couple of textures. I suggest you run Instruments and check for memory leaks.

Is there a way to compute how much memory a Metal texture is using before allocation?

At least one issue is the row byte alignment (a.k.a. bytesPerRow). For the hardware, accessing rows that are aligned to certain powers of two is more efficient. So, that's how data is stored internally.

From your numbers, it seems the texture is using 256 bytes per row. So a row that's 414 pixels wide with 4 bytes per pixel would require 1656 bytes. But that's not a multiple of 256. So, the rows will be padded out to the next multiple of 256, which is 1792. That times 512 rows is 917504.

But, as Idoogy mentions in a comment, that's not predictable across all hardware.

SceneKit: too much memory persisting

Spheres, capsules, and cylinders all have fairly dense meshes. Do you need all that detail? Try reducing the various segment count properties (segmentCount, radialSegmentCount, etc). As a quick test, substitute SCNPyramid for all of your primitive types (that's the primitive with the lowest vector count). You should see a dramatic reduction in memory use if this is a factor (it will look ugly, but will give you immediate feedback on whether you're on a usable track). Can you use a long SCNBox instead of a cylinder?

Another optimization step would be to use SCNLevelOfDetail to allow substitute, low vertex count geometry when an object is far away. That would be more work than simply reducing the segment counts uniformly, but would pay off if you sometimes need greater detail.

Instead of managing the components yourself in arrays, use the node hierarchy to do that. Create each molecule, or animatable piece of a molecule, as a tree of SCNNodes. Give it a name. Make a flattenedClone. Now archive that. Read the node tree from archive when you need it; don't worry about arrays of nodes.

Consider writing two programs. One is your iOS program that manipulates/displays the molecules. The other is a Mac (or iOS?) program that generates your molecule node trees and archives them. That will give you a bunch of SCNNode tree archives that you can embed, as resources, in your display program, with no on-the-fly generation.

An answer to scene kit memory management using swift notes the need to nil out "textures" (materials or firstMaterial properties?) to release the node. Seems worth a look, although since you're just using UIColor I doubt it's a factor.

Here's an example of creating a compound node and archiving it. In real code you'd separate the archiving from the creation. Note also the use of a long skinny box to simulate a line. Try a chamfer radius of 0!

extension SCNNode {

public class func gizmoNode(axisLength: CGFloat) -> SCNNode {
let offset = CGFloat(axisLength/2.0)
let axisSide = CGFloat(0.1)
let chamferRadius = CGFloat(axisSide)

let xBox = SCNBox(width: axisLength, height: axisSide, length: axisSide, chamferRadius: chamferRadius)
xBox.firstMaterial?.diffuse.contents = NSColor.redColor()
let yBox = SCNBox(width: axisSide, height: axisLength, length: axisSide, chamferRadius: chamferRadius)
yBox.firstMaterial?.diffuse.contents = NSColor.greenColor()
let zBox = SCNBox(width: axisSide, height: axisSide, length: axisLength, chamferRadius: chamferRadius)
zBox.firstMaterial?.diffuse.contents = NSColor.blueColor()
let xNode = SCNNode(geometry: xBox)
xNode.name = "X axis"
let yNode = SCNNode(geometry: yBox)
yNode.name = "Y axis"
let zNode = SCNNode(geometry: zBox)
zNode.name = "Z axis"

let result = SCNNode()
result.name = "Gizmo"
result.addChildNode(xNode)
result.addChildNode(yNode)
result.addChildNode(zNode)
xNode.position.x = offset
yNode.position.y = offset
zNode.position.z = offset

let data = NSKeyedArchiver.archivedDataWithRootObject(result)
let filename = "gizmo"

// Save data to file
let DocumentDirURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)

// made the extension "plist" so you can easily inspect it by opening in Finder. Could just as well be "scn" or "node"
// ".scn" can be opened in the Xcode Scene Editor
let fileURL = DocumentDirURL.URLByAppendingPathComponent(filename).URLByAppendingPathExtension("plist")
print("FilePath:", fileURL.path)

if (!data.writeToURL(fileURL, atomically: true)) {
print("oops")
}
return result
}
}


Related Topics



Leave a reply



Submit