loss of depth buffer accuracy on iOS

I'm doing depth peeling with a very simple fragment function:


struct VertexOut {
        float4 position  [[ position ]];
    };
  
fragment void depthPeelFragment(VertexOut in [[ stage_in ]],
                                depth2d<float, access::read=""> previousDepth)
{
    float4 p = in.position;
    if(!is_null_texture(previousDepth) && p.z <= previousDepth.read(uint2(p.xy)))
    {
        discard_fragment();
    }
}


(My depth buffer pixel format is `MTLPixelFormatDepth32Float`)


This works well on my Mac. In each pass I submit the same geometry, and eventually no more fragments are written and the process terminates. For example, with a test sphere, there are two passes with the same number of fragments written each pass (front and back hemispheres).


However on iPad, the process does not terminate. There are some (not all) fragments which, despite being rendered in the previous pass, are not discarded in subsequent passes.


What platform differences could cause this?


Is the z-coordinate of the position attribute always the value written to the depth buffer?


Note that I cannot simply limit the number of passes (I'm not using this for OIT).

Replies

I'm not sure what's going on as I'm not super familiar with depth peeling. But I can make some guesses.


If your using different vertex shaders, even if the geometry passed in and transform code is the same, the z-values produced may not be the same. The compiler may make different optimization between two different shaders which can produce slightly different results. You can use the fmul instruction in your vertex shaders to implement the tranforms. This will likely varaince between the vertex sahders (but there is no absolute guarantee that you'll completely avoid it even with fmul)

Well, non-strict inequalities are always tricky with depth. This might not only be caused by optimization, but also by the GPU using its own representation of depth, although I highly doubt this is the case. I'd try using [[invariant]] for position, which was added in 2.1 (or going even further and turning off fast math on library compilation) to shut down the compiler shenanigans. If this doesn't help, probably using sample_compare will (again, I really doubt that).

If this too doesn't help, you can try drawing z value into a separate R32Float color attachment. Then you could visualize the difference by drawing a quad and sampling them both.

Regarding your second question, depth should be clipped/clamped before going to fragment shader. It also gets mapped into [zNear; zFar] range of the viewport

Thanks Dan for the thoughts. It's the same shader and geometry each time.

Thanks eviom. I found other issues with depth peeling for my application and switched to a different approach. I appreciate the help though!