Why might zero Z-coordinates make a custom shader take twice as long?

I have a custom shader (using ShaderMaterial) which generates gl_Position based on some arbitrary logic I’ve written. The shader takes “position” and “uv” attributes but they aren’t really positions or uvs, they’re just arguments to the custom function. In particular, the “z” coordinate of the position attribute has nothing to do with where vertices are actually going to end up - it’s just an argument.

I’ve spotted an odd behaviour. If the z-coordinates in the position attribute of the mesh input to the shader are all zeroes (or are large random floats) the gpuFrameTimeCounter (part of Babylon’s EngineInstrumentation) is about double - i.e. the GPU rendering time is twice as long. That suggests the something other than my vertex shader is reading those positions and doing something with them which gets expensive if the z-coordinates are all zeroes. Can anyone tell me what that might be?

I’ve verified that it’s not my shader by changing the shader so it doesn’t actually read the z-coordinate - the effect persists. I’ve already tried the following settings:

On the ShaderMaterial:

  • needDepthPrePass = false
  • disableDepthWrite = true
  • separateCullingPass = false
  • backFaceCulling = false
  • depthFunction = Engine.ALWAYS
  • useClipPlane = false

On the mesh:

  • alwaysSelectAsActiveMesh = true
  • mustDepthSortFacets = false

The ShaderMaterial has alpha blending turned on, and alpha testing turned off, in case that’s relevant.

This will be quite tricky to reproduce in a playground. I can do that if needed though - but I thought I would ask in case somebody more familiar with how Babylon works will just know the answer!

I could of course work around this by moving my parameters to other attributes, e.g. uv2, uv3 etc., however whatever the process is that’s reading position data, it’s definitely not doing anything correct, so it seems better to find out what it is and switch it off if that’s possible.

I’m using Babylon.js v8.18.0 with WebGL2, in Google Chrome on MacOS.

Be careful with gpuFrameTimeCounter, as it is not very accurate due to security issues (fingerprinting). If your GPU measurements are low (1 ms or less), it is possible that the difference is solely due to the browser not providing accurate values.

Other than that, I don’t see any reason why the measurements would be different, unless gl_Position depends on the position values, which means that the triangles will not be the same size and their rasterization will not take the same amount of time (but you said that the z-coordinates are not used in your shader).

You can try using Spector and see if the snapshots are different when the z coordinates are equal to 0 and when they take other values.

If you can set up a reproduction in the Playground, we will be able to investigate the problem better.

Thanks for the suggestions. The numbers are reasonably high - about 4-5ms when it’s fast, 10ms when it’s slow, so I don’t think it can be just inaccuracy.

I had a look in Spector too and couldn’t see anything obvious. I’ll see if I can reproduce it in a playground.

1 Like

You and @Evgeni_Popov are much more knowledgable than I, but my first thought is “what’s happening in the fragment shader?” Nominally, 0 and large z values are clipped. Do your macro definitions prevent fragment shader from running? If clipping is turned off, does that mean all triangles/elements are sent to fragment shader?

Thanks for the suggestion. As I understand it, what happens in the rasteriser and the fragment shader should only be affected by the gl_Position output by my vertex shader - but those gl_Positions aren’t changed by the z-coordinates input to the shader.

I got it though! It was the glow layer (apologies, should have mentioned I was using that, only just realised it’s relevant). The problem was visible in Spector, I just needed to know where the look. There was a call to draw with a number of vertices suspiciously equal to my problem mesh with a shader specific to the glow layer, not my shader. A call to glowLayer.addExcludedMesh has solved the problem.

1 Like