How to release memory created by SKTexture - ios

I have an application that creates scene kit textures using the following:
let texture = SKTexture(image: image)
The problem is that after several calls, the application crashes due to low memory.
I really only need the most recently created texture so when creating a new one, the preview ones' memory can be released, however, Scene Kit seems to be holding on to these textures even when they aren't being used anymore (the nodes are no longer part of the scene).
How can I manually tell Scene Kit to release the memory and cache of the texture so I don't get into the low memory crashing situation?
Also, I know that the SceneKit documentation recommends removing all references to the texture by removing nodes and such, but that should be happening and yet the Texture is not being released. Is there a good way of debugging and seeing what's holding on to the reference?
In re-reviewing the code, I'm creating a scene to hold the image to be rendered in the primary scene (whose node doesn't change). Basically, there is an object in the scene that can display an image or a video and that gets changed out by returning a Scene which is used as the material diffuse contents.
let texture = SKTexture(image: image)
let sprite = SKSpriteNode(texture: texture)
let scene = SKScene(size: sprite.size)
sprite.position = CGPointMake(CGRectGetMidX(scene.frame), CGRectGetMidY(scene.frame));
scene.addChild(sprite)
And then being used here:
primaryObjectMaterial.diffuse.contents = scene
I would expect once the scene's node material contents are changed, this scene (and the texture and image) would be released as there would no longer be any references to it but perhaps the caching system is hanging onto something or I'm doing something wrong?

It's hard to give you an exact answer with out seeing your code or knowing how the app works but you need to take the node that the texture is on and remove it
nameOfNode.removeFromParentNode
This will remove it from memory and you need to add a new node with the new texture.

Related

iOS: Can I use Texture Atlas already created with Adobe Animate?

I know that I can add sequences of individual images in Xcode, and let it create the Texture Atlas. This process is well-described in easily-searched venues. (Like this one.)
I am getting atlases from designers, created with Adobe Animate (v18.0) already combined into the full sheet, and moreover with the accompanying XML file describing the animation. (In which sub-images and display frames do not match 1:1, so it's hard to see how Xcode would figure that out.)
It's not clear to me from the SpriteKit docs whether/how to use these pre-defined Texture Atlases. Is this possible?
If you're getting pre-baked texture atlases, with externally-generated descriptions of where the sprites should get their textures, you'll probably have to create your sprites using SKTextures that you create using the init(rect:in:) initializer of a SKTexture.
You'll need to read the sprite's extents out of the XML file, and then create a texture out of the atlas. Then you can create a new SKTexture object that represents a part of the larger texture to act as your sprite's texture.
This is untested pseudocode, but it shows the process:
let spriteRect = (get the rect from the XML)
let atlas = SKTexture( imageNamed: "myTextureAtlas" )
let spriteTexture = SKTexture( rect:spriteRect, in:atlas )
let sprite = SKSpriteNode( texture:spriteTexture )
Once you have this process in place, you can animate the sprites using the usual methods, like setting up SKActions with a list of textures out of the texture atlas.

Changing SCNNode scale has no effect

I'm new to ARKit and SceneKit. I have a rather complex scene coming from an 3D artist. He replaced some items with SceneKit objects. I now try to change the properties of one of the SCNNodes, in this case the scale property, in another case the position in space, and in yet another I have a SCNText where I change the string property:
feedScaler?.scale = SCNVector3Make(1.0, scale, 1.0)
feedText?.string = String(feedValue)
feedIndicator?.position.y = someNewValue
So, pretty straight forward. When I run the scene though, it seems that the changes here are only commited once, before the scene appears. Then nothing happens. Here's the thing:
the method which updates the properties runs once per frame
I print out the property values of the nodes to the console and they update each frame
the text updates, too, and is the only update which is actually rendered and visible.
Note: There's also animations in that scene that do not play unless I uncheck "Use scene time base". Maybe that is a hint regarding how the Scene's animations are handled...
I found the issue. I used a clone of the scene which caused the references to get lost. The documentation says:
Cloning or copying a node creates a duplicate of the node object, but not the geometries, lights, cameras, and other SceneKit objects attached to it—instead, each copied node shares references to these objects.
When the original gets lost all the references get useless.

Preloading textures in SpriteKit

I've done some research and I can't seem to find anything that clearly explains how to go about preloading both single textures and textures within animations. I'm currently using Atlas's in Assets.xcassets to group related animation images. Does having my images in the Atlas mean that they are preloaded? As far as single images, does it make sense to declare the texture before GameScene like this: let laserImage = SKTexture(imageNamed: "Sprites/laser.jpg") and then (for example) within one of my SKSpriteNode subclass I can just pass laserImage through?
I ultimately wanted to know if there was a well defined way of going about this or if I should just store each texture as a constant before GameScene. Any advice on the proper (and most efficient) way of going about this would be great.
I tried to implement appzYourLife answer but it increased the time to load every texture. So I ended up putting all the most used Sprites in one single atlas and puting it in a singleton.
class Assets {
static let sharedInstance = Assets()
let sprites = SKTextureAtlas(named: "Sprites")
func preloadAssets() {
sprites.preloadWithCompletionHandler {
print("Sprites preloaded")
}
}
}
I call Assets.sharedInstance.preloadAssets() in menuScene. And:
let bg1Texture = Assets.sharedInstance.sprites.textureNamed("background1")
bg1 = SKSpriteNode(texture: bg1Texture)
to reference a texture already loaded in memory.
One Single Texture Atlas
Put all your assets into a single Sprite Atlas. If they don't fit, try at least to put all the assets of a single scene into a single Sprite Atlas
Preloading
If you want you can preload the texture atlas in memory with this code
SKTextureAtlas(named: "YourTextureAtlasName").preloadWithCompletionHandler {
// Now everything you put into the texture atlas has been loaded in memory
}
Automatic caching
You don't need to save a reference to the texture atlas, SpriteKit has an internal caching system. Let it do it's job.
Which name should I use to reference my image?
Forget the name of the file image, the name you assign to the image into Asset Catalog is the only name you will need.
How can I create a sprite from an image into a texture atlas?
let texture = SKTextureAtlas(named:"croc").textureNamed("croc_walk01")
let croc = SKSpriteNode(texture: texture)

SpriteKit SKTexture is not passed as reference to SKSpriteNode

Apple recommends to use one SKTexture object if it will be used multiple times (https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKTexture_Ref/).
So I wanted to test if it is really passed as reference:
var fireSpriteTexture:SKTexture = SKTexture(imageNamed: "fire")
let fireSprite = SKSpriteNode(texture: fireSpriteTexture)
fireSprite.position = CGPointMake(50,50)
fireSprite.zPosition = 100
// Change texture -> We should see no fire
fireSpriteTexture = SKTexture(imageNamed: "water")
self.addChild(fireSprite)
In my understanding this swift code should make the water texture visible in my app BUT in fact the fire texture is placed on the screen.
Why? Is it a bug?
To make it more clear I would also like to know what happens if I init my node with SKSpriteNode(imageNamed: "fire") and then copy it with its copy() method, do both objects share the same "fire" resource?
I do not think you are understanding pass by reference correctly, you pass the reference of fire into your sprite, the sprite will then retain a reference of this texture.
You are changing the fire texture to a new texture with a new reference. This does not mean the sprite with the old reference is going to automatically change to the new reference.
If you want to check for reference, you want to compare the fire texture to the sprite texture, not create a whole new texture.
If you want to change the texture, then you need to edit the data of the texture without creating a new reference.

Updating geometry in SpriteKit (or SceneKit)

We are porting a game to SpriteKit and I've run in to a bit of a problem. Some objects in our game have triangle-strip trails attached to them. The vertex buffers of the trails are continuously updated as the objects move in the world to create a seamless and flowing effect (there are constraints on how many vertices are in the buffer and how often we emit new vertices).
In the previous implementation we simply updated the affected vertices in the corresponding buffer whenever we emitted new vertices. In SceneKit it seems I am unable to update geometry sources, unless I use geometrySourceWithBuffer:vertexFormat:semantic:vertexCount:dataOffset:dataStride:. To do this however, it seems I need a Metal device, command queue and encoder, to be able to submit commands to update my buffer before it renders.
Is there any way I can do this with a normal SCNView, or do I have to do everything manually with a CAMetalLayer, creating my own metal device etc?
In essence, everything we need, bar this trail geometry is available in SpriteKit, so I was hoping there was some way I could get a hold of the metal device and command queue used by the SKView and simply use that to upload my geometry. This would make things a lot simpler.
I've used a particle system to get a similar effect. The particle emitter is attached to the moving object, with particles set to fade out and eventually die. You might need to set the emitter's targetNode to your scene, depending on the effect you want.
emitter = SKEmitterNode()
emitter.name = marbleNodeNames.trail.rawValue
emitter.particleTexture = SKTexture(imageNamed: "spark")
emitter.particleAlphaSpeed = -1.0
emitter.particleLifetime = 2
emitter.particleScale = 0.2
marbleSprite.addChild(emitter)

Resources