Search code examples
shaderscenekittvosuniform

SceneKit vertex shader uniforms not updating properly when AppleTV remote is used


This is, possibly, the most insane technical problem in existence, and I don't have much hope to get a solution, but I'll try anyway. Maybe somebody might have a suggestion I could try that, while perhaps not explaining the reason behind the problem, might solve it anyway.

I have a SpriteKit game I'm developing for tvOS (Xcode 8.0, Mac OS 10.12), and I have an SK3DNode, which has a triangle mesh, which is using a vertex shader to move the vertices around.

The vertex shader has a couple of mat4 uniforms (although this doesn't make much of a difference; the problem happens even if I use eg. vec4 uniforms), which I'm using to pass data from my code to the shader, from the update method of the current SKScene. I'm using the [SCNMaterial setValue:forKeyPath:] of the material of the triangle mesh for this.

This works well and smooth, except when one thing happens: When I use the touchpad of the AppleTV remote, updating those uniforms lags really badly (like there can be up to a half-second delay between updates).

Note that I did not say that the game lags. Or that even rendering the triangle mesh, or the vertex shader, or the SKScene update method lags. It's literally and explicitly the updating of new values to those particular uniforms that lags, nothing else. And only and explicitly when I'm using the touchpad of the AppleTV remote.

Yes, the problem is absolutely insane. To understand why it's absolutely insane, note that:

  • I have made extra sure that what is lagging is only updating the values of my uniforms, and nothing else. For example, if in addition to using them I add some movement to the vertices in the vertex shader using the system-provided u_time uniform, that works just fine. (In other words, the u_time uniform is updating just fine, 60 times per second, but my mat4 uniforms are not, causing the movement calculated from u_time to be completely smooth, while the extra movement from my uniforms on top of that is really laggy.) Likewise nothing else on screen is lagging.

  • Yes, the update method where I'm setting the values of those uniforms is being called 60 times per second. I have checked this using the system's clock. There are no delays here. It's not like the system is skipping calling my update function and that's causing the problem. It's being called 60 times per second, and I'm updating the uniforms (using [SCNMaterial setValue:forKeyPath:]) every time. I have checked this thoroughly.

  • And it's not just a few skipped frames here and there either. As said earlier, we are talking about delays of up to a half second and even more, at worst; and often repeatedly at that. (The updated values jump ahead by the equivalent amount when they finally get to the shader itself.)

  • And yes, it happens only when I'm using the trackpad of the AppleTV remote. (I'm not sure if it happens when using its buttons. It might be, but it's physically impossible to press them fast enough for it to have an effect. Sliding over the trackpad causes a continuous stream of input to the system, and that's when it happens.) If I don't use the trackpad, the uniforms are updated nicely 60 times per second, and the vertex movements are smooth.

  • The problem does not happen with a gamepad. Even though a gamepad's analog stick produces a similar stream of input, it doesn't seem to cause the same effect.

I suspect that, somehow, SCNMaterial is not updating the uniforms with the values it's getting from its setValue:forKeyPath: method, when something happens with the remote's trackpad. I don't have the faintest idea what or why, and it's a completely insane problem.

Of course the problem here is that since the game is controlled with the remote, it makes it basically unusable. The vertex shader just doesn't work properly. Change directions with the trackpad, and the mesh distortion starts lagging like mad. It's unusable. And I have no idea why this is happening, or how to fix it.


Solution

  • I suspect that the runloop of SpriteKit's rendering thread is running "slowly" when your problem occur. To make sure your SceneKit changes are committed 'asap' to the render tree, you can wrap your changes inside an explicit transaction:

    [SCNTransaction begin];
    [SCNTransaction setAnimationDuration:0];
    
    // your changes
    
    [SCNTransaction commit];
    

    Note that doing this is not necessary when you are writing a 'SceneKit' app and dealing with SceneKit's callbacks directly. Because SceneKit will make sure to commit your changes at the end of the SceneKit callbacks. But here you are modifying SceneKit's model from SpriteKit's game loop.

    It's worth filing a bug though.