I have an issue with a SceneKit app (using Metal) stuttering when new nodes appear on screen even though the app is running smoothly at 60 fps before and after.
Imagine a game where things are destroyed and sometimes power-ups appear in the place where something was destroyed. I'm pretty sure the stuttering relates to the power-ups appearing because it doesn't happen when things are just destroyed (and thus removed from the scene).
What I've done so far to try to fix the stuttering: I preload the nodes via the SceneKit view's preload method and add them to the scene only in its completion handler. I add them above the camera long before they need to be shown and when it's time I just move them to the right position. I've implemented a queuing mechanism to ensure only one change (removing the destroyed item's node, moving the power-up in its place) is done per frame.
But the stuttering still occurs sometimes (not always) when power-ups appear. I'm wondering if SceneKit is doing something only when nodes appear for the first time (even if they have been preloaded). Whatever happens seems to be enough to cause the stutter, but too short for the XCode performance meters to show it. There's plenty of idle time in every frame, CPU and GPU are never even close to being maxed out.
I don't think the issue is related to complicated geometries or huge textures because it still happens when I use simple cubes with uniform colors instead.
Any idea what's going on here or how I can track this down?
I found the reason by myself and I want to share it with you guys in case you run into the same problem.
The mysterious task that SceneKit is doing behind the scenes is recompiling the shaders of the various nodes. Although Apple didn't confirm this, I'm pretty sure that SceneKit has a policy to always use the most efficient shaders that are just complex enough to render the respective node as intended. That means that it will compile a more complex shader whenever you add effects, material properties or light sources. And it will replace it again with a simpler shader when you remove the reason for the increased complexity of the lighting calculations.
While this is great in terms of always getting the highest possible performance, it also has a downside which is what I've experienced. Recompiling the shaders takes some time, causes load on the CPU and forces the GPU to wait for the new version of the shaders. In the end, it causes apps to stutter even if they run perfectly smooth most of the time.
The easiest way to get around this is to replace the SceneKit shaders by your own code (using SCNProgram), but this also makes you loose most of the comfort that SceneKit provides. Since this is not what I wanted, I ended up with the following approach:
I force SceneKit to initially compile all the shaders of all nodes that might become visible later by covering the scene with a black overlay at the beginning, adding all nodes in front of the camera and then moving them to their correct positions before fading out the cover layer. I also avoid recompilations by never fully turning off effects that I don't need all the time (e.g. a light is turned off by changing its intensity to 0.1 instead of 0). This makes SceneKit keep the same shaders the whole time and thus avoids the stuttering.
Again: This has not been confirmed by Apple, but it worked so far, so I think my assumptions are correct ;-).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With