SceneKit shader compile and cache issues? :(

I'm making an apple watch game and it's running so smooth and nice while developing but I ran into a showstopping problem that I wouldn't even have noticed until i deleted the app and reinstalled...

Every first-run on a freshly installed copy of the game has major frame hitches as it loads assets. Every run after that has ZERO hitches and runs like butter. Even if i install a new version on top of the old one (which is why i never noticed this problem in my almost finished game) it runs smooth. Framerate issues come back if I delete and reinstall. I think it's compiling shaders or caching geometry but I haven't been able to trick it into doing the hard work during non-critical moments in the game.

I've tried preloading assets in the main menu, loading at the start of the level, loading and rendering in a tiny part of the screen, rendering behind dialog boxes... it seems no matter what i can only get rid of the frame hitches after the game has been played through once... and I'm going to lose all customers on that first terrible impression even though the game is actually really good and fun when it's running properly...

Any suggestions how i can prewarm/precompile the assets to avoid framerate hitches and freezes during gameplay? I thought I was so close to release but I feel this is un-shippable!

I can send a link to my test flight build to anyone that wants to see the problem first hand. Also please note that I am artist-forward with a technical lean, but certainly not a real programmer, unfortunately.

Jiovanie

Answered by DTS Engineer in 884950022

Your diagnosis is correct — this is Metal shader compilation and GPU resource setup. SceneKit compiles pipeline states and uploads textures and geometry buffers lazily the first time a particular combination of geometry, materials, and lighting reaches the GPU. SceneKit caches the compiled results to disk, which is why every subsequent launch is smooth and why installing over an existing copy doesn't trigger the problem.

If you want to confirm this, you can profile a first-launch run in Instruments using the Metal System Trace template — you'll see pipeline state compilation happening on the GPU timeline right at the moments the frame hitches occur. But based on what you're describing (first install hitches, smooth after that, returns after delete/reinstall) this is almost certainly what's happening.

In watchOS, the tools available for prewarming are limited. SCNRenderer (which offers offscreen rendering and snapshots) is not available in watchOS. What you do have are the prepare methods on the SCNSceneRenderer protocol, which your scene view conforms to:

// Async preparation — call this during your loading screen
// before transitioning to gameplay
sceneView.prepare([gameplayScene.rootNode]) { success in
    // Transition to gameplay here
}

or the synchronous version, suitable for a background thread:

// Synchronous — can run on a background queue
sceneView.prepare(gameplayScene.rootNode, shouldAbortBlock: { false })

For this to have the best chance of working, the scene you pass to prepare needs to be fully configured at call time — all geometry, all materials, and all lights present, matching the actual gameplay configuration. If you have shader variants that only appear under certain game conditions (a power-up that changes a material, a different lighting setup in a later level), those materials and lights need to be present during preparation too.

That said, the prepare methods are documented as preparing objects "for drawing," and in practice they may not trigger compilation of every Metal pipeline state variant that actual rendering will require. If you've already tried this approach and still see hitches on first play-through, that's worth including in a feedback report.

A few other things worth trying:

  • Show the real scene early. If your game has a menu or intro screen, consider having the fully configured gameplay scene rendering live behind it (even if obscured by UI). This forces the GPU through real draw calls and should compile the remaining pipeline states before gameplay begins. The key requirement is that the scene must have its final lighting, materials, and geometry — not a subset. If you've already tried this with a fully configured scene and still see hitches, that's another strong data point for a feedback report.
  • Stagger asset introduction. If certain materials or effects only appear later in gameplay, try adding them to the scene (offscreen or transparent) during earlier moments when frame drops would be less noticeable.

If none of this resolves the problem, I'd encourage you to file a feedback report at https://feedbackassistant.apple.com with a sample project that reproduces the issue. The lack of SCNRenderer in watchOS means there's no way to force an offscreen render pass, which is the most reliable prewarming technique on other platforms. A shader prewarming API for SceneKit on watchOS would be a reasonable enhancement request — please include a sysdiagnose captured while reproducing the first-launch hitches, as that will help the SceneKit team see exactly which pipeline states are being compiled during gameplay.

Your diagnosis is correct — this is Metal shader compilation and GPU resource setup. SceneKit compiles pipeline states and uploads textures and geometry buffers lazily the first time a particular combination of geometry, materials, and lighting reaches the GPU. SceneKit caches the compiled results to disk, which is why every subsequent launch is smooth and why installing over an existing copy doesn't trigger the problem.

If you want to confirm this, you can profile a first-launch run in Instruments using the Metal System Trace template — you'll see pipeline state compilation happening on the GPU timeline right at the moments the frame hitches occur. But based on what you're describing (first install hitches, smooth after that, returns after delete/reinstall) this is almost certainly what's happening.

In watchOS, the tools available for prewarming are limited. SCNRenderer (which offers offscreen rendering and snapshots) is not available in watchOS. What you do have are the prepare methods on the SCNSceneRenderer protocol, which your scene view conforms to:

// Async preparation — call this during your loading screen
// before transitioning to gameplay
sceneView.prepare([gameplayScene.rootNode]) { success in
    // Transition to gameplay here
}

or the synchronous version, suitable for a background thread:

// Synchronous — can run on a background queue
sceneView.prepare(gameplayScene.rootNode, shouldAbortBlock: { false })

For this to have the best chance of working, the scene you pass to prepare needs to be fully configured at call time — all geometry, all materials, and all lights present, matching the actual gameplay configuration. If you have shader variants that only appear under certain game conditions (a power-up that changes a material, a different lighting setup in a later level), those materials and lights need to be present during preparation too.

That said, the prepare methods are documented as preparing objects "for drawing," and in practice they may not trigger compilation of every Metal pipeline state variant that actual rendering will require. If you've already tried this approach and still see hitches on first play-through, that's worth including in a feedback report.

A few other things worth trying:

  • Show the real scene early. If your game has a menu or intro screen, consider having the fully configured gameplay scene rendering live behind it (even if obscured by UI). This forces the GPU through real draw calls and should compile the remaining pipeline states before gameplay begins. The key requirement is that the scene must have its final lighting, materials, and geometry — not a subset. If you've already tried this with a fully configured scene and still see hitches, that's another strong data point for a feedback report.
  • Stagger asset introduction. If certain materials or effects only appear later in gameplay, try adding them to the scene (offscreen or transparent) during earlier moments when frame drops would be less noticeable.

If none of this resolves the problem, I'd encourage you to file a feedback report at https://feedbackassistant.apple.com with a sample project that reproduces the issue. The lack of SCNRenderer in watchOS means there's no way to force an offscreen render pass, which is the most reliable prewarming technique on other platforms. A shader prewarming API for SceneKit on watchOS would be a reasonable enhancement request — please include a sysdiagnose captured while reproducing the first-launch hitches, as that will help the SceneKit team see exactly which pipeline states are being compiled during gameplay.

SceneKit shader compile and cache issues? :(
 
 
Q