SceneKit - Adding a new SCNNode to the scene causes severe lag - ios

I found out that adding SCNNodes (with SCNGeometry) to the scene causes a severe lag spike.
According to the Time Profiler it has to generate the geometry (at least the functions/methods are called like that). It does that at the time when the node is added to the scene, not when the node is created. Hence, creating a pool with SCNNodes will not work.
Is there a way to get rid of this lag? I'd like to be able to add nodes to the scene without any FPS drop.
The only idea I have so far is adding everything to the scene already and then hiding / un-hiding it, though this is not really a clean solution.
Here's a shot from Time Profiler:

looks like your are adding a node with an SCNShape or SCNText attached to it and these kinds of geometries are expensive to create (you have to discretize and triangulate the Bézier curve, and eventually have to compute and offset curve for the chamfer).
You can try to preload the following methods from SCNSceneRenderer : -prepareObject:shouldAbortBlock:, -prepareObjects:withCompletionHandler:

Related

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.

How to draw a dynamic line or curve per frame in SceneKit

I have a Metal-backed SceneKit project where I'd like to draw a curve that is modified on the CPU every frame. This seems like it should be a simple task; one that I've done in OpenGL many times.
Something like:
array_of_points[0].x = 25
array_of_points[0].y = 35
upload_array_to_gpu()
render_frame()
It seems that modifying the SCNGeometrySource of an SCNNode is not possible. Recreating and setting the node.geometry works but the position lags / jitters compared to other nodes in the scene. Recreating the entire node each frame also lags.
One possible avenue might be a geometry shader modifier, but I'd still have to somehow pass in my cpu-computed curve positions.
Another option is to use metal APIs directly. Though this approach seems like it will require lots more code, and I couldn't find too much info about mixing SceneKit with Metal.
I also tried setting the preferredRenderingAPI to OpenGL, but it doesn't change the renderingAPI.

Removing SKNodes When Not Visible On Screen

In my game, the size of the level can be larger than the screen of the phone and the camera will follow the player around the level, so there can be a decent amount of content(such as SKEmitterNodes) in the scene that is not visible at any given time. I've been reading through some of the SpriteKit documentation and found this quote in the SMEmitterNode section:
"Consider removing a particle emitter from the scene when it is not
visible onscreen. Add it just before it becomes visible."
Is this something that can be done in my type of game design? I don't want the nodes to be completely removed since they will eventually be put on the screen, but is there a good way for me to add/remove the EmitterNodes (or other SpriteNodes) that are a certain distance from the screen/is this a good idea to do? I'm looking to improve my frame-rate and don't want costly nodes like SMEmitterNodes working while they're not even being displayed, but will adding/removing them as the player moves around reduce the performance?
Here is the idea I currently have: create a rectangle that extends a certain distance around the screen and detect when a node comes into that rectangle, and if it's not already added to the scene, go ahead and add it. Thank you for any suggestions.
SKNodes really aren't a problem because when they are off screen they are not being rendered anyway, just evaluated. So the main thing to worry about with SKNodes are any physics bodies attached to them,
SKEmitterNodes however require some processing power, and that is why apple is recommending not having them emit if they are not on screen. I would just subclass my SKScene class, and do a checks only on SKEmitterNodes whether or not they are in frame, and emit based on that.
So, I would throw all your SKEmitterNodes into a container like an array, and have a loop function to have the node do a CGRectIntersectsRect check based on your camera location and viewable screen size. and if they intersect, add it to the scene, if not remove it from the scene. The array will keep a strong reference so you do not have to worry about it deiniting on you

How to Render Many SpriteKit Nodes at Once?

I am using SpriteKit to render a large (20 x 20) dot grid that looks like this:
I'd like to highlight rows or columns based on user input. For example, I'd like to change rows 1-10 to a red color, or columns 5-15 to a blue color.
What is the most performant way to do this?
I've tried:
Naming each GridNode based on the column it's in (e.g. #"column-4). Then use enumerateChildNodesWithName: with the string as #"column-n", changing the color of each node (by changing SKShapeNode setFillColor:) in the enumerate block.
Giving all the columns a parent node associated with that column. Then telling the parent node to change its alpha (thus changing the alpha of all its children).
Making arrays for the different columns, then looping through each node and changing its color or alpha.
I've tried making the GridDot class an SKEffectNode with shouldRasterize: set to YES. I've tried both an SKShapeNode and a SKSpriteNode as its child. I've also tried taking away the SKEffectNode parent and just render an SKSpriteNode.
Each of these options makes my whole app lag and makes my framerate drop to ~10 FPS. What is the correct way to change the color/alpha of many nodes (without dropping frames)?
At its heart, the issue is rendering this many nodes, yes?
When I faced similar performance problems while using SKShapeNode I came up with this solution:
Create SKShapeNode with required path and color.
Use SKView's method textureFromNode:crop: to convert SKShapeNode to an SKTexture
Repeat steps 1,2 to create all required textures for a node.
Create SKSpriteNode from a texture
Use created SKSpriteNode in your scene instead of SKShapeNode
Change node's texture when needed using SKSpriteNode's texture property
If you have a limited set of collors for your dots, I think this aproach will fit fine for your task.
In contrast to #amobi's statement, 400 nodes is not a lot. For instance, I have a scene with ~400 nodes and a render time of 9.8ms and 9 draw calls.
If you have 400 draw calls though, you should try to reduce that number. To determine the amount of draw calls needed for each frame rendered, implement (some of) the following code. It is actually taken from my own SpriteKit app's ViewController class which contains the SpriteKit scene.
skView.showsFPS = YES;
skView.showsNodeCount = YES;
skView.showsDrawCount = YES;
Proposed solution
I recommend using SKView's ignoresSiblingOrder. This way, SKSpriteNodes with equal zPosition are drawn in one draw call, which (for as many nodes/draw you appear to have) is horribly efficient. Set this in the -viewDidLoad method of the SKView's ViewController.
skView.ignoresSiblingOrder = YES;
I see no reason to burden the GPU with SKEffectNodes in this scenario. They are usually a great way to tank your frame rate.
Final thoughts
Basic performance issues mean you have a CPU or a GPU bottleneck. It is difficult to guess which you're suffering from with the current information. You could launch the Profiler, but Xcode itself also provides valuable information when you are running your app in an attached device. FPS in the Simulator is not representative for device performance.

how to fix occasional stutter in my game?

The context:
In my SKScene I have:
1) a few sprites with no physics controlled by touch using touchesMoved: which instantly changes sprite position property based on touch location,
2) one sprite that while colliding with the others changes its physicsBody.velocity instantly.
That's all - no other fancy processing.
The issue:
I found occasional and random stuttering all of the sprites independently without any FPS dropping.
The questions:
In the context of my performance issue:
1) Is there any reason to update sprite position or velocity in update: method rather than update them instantly?
2) Is there any sense to use SKAction rather than changing directly position or velocity property?
3) If 2 then can make any difference running SKActions instantly or run them in update: method?
Apple says about
update:
method:
This is the primary place to implement your own in-game simulation, including input handling, artificial intelligence, game scripting, and other similar game logic. Often, you use this method to make changes to nodes or to run actions on nodes.
but nothing more like "must,..." or "should, because...".
4) Isn't SpriteKit done like all the changes made to the nodes instantly are processed in respective loop update phase or do I have always go to the appropriate loop method to make/fire changes there to avoid any rendering performance loss or interference?

Resources