How gl_FragDepth Qualifiers Preserve Early-Z in GLSL

Modifying fragment depth inside a shader typically forces modern GPUs to disable Early Depth Testing (Early-Z), leading to a significant drop in rendering performance. However, GLSL provides conservative depth layout qualifiers—specifically depth_greater and depth_less—that declare the directional intent of depth modifications relative to the hardware-calculated depth. By providing these guarantees, shaders allow the GPU's rasterizer to retain conservative depth culling and preserve critical early depth-rejection optimizations before executing expensive fragment logic.

The Problem with Unconstrained gl_FragDepth

In standard rendering pipelines, Early-Z testing executes before the fragment shader runs. If an incoming fragment fails the depth comparison against the existing depth buffer value, it is discarded immediately, preventing unnecessary execution of shading, texturing, and lighting code.

When a fragment shader writes to gl_FragDepth without qualifiers, the GPU cannot predict the fragment's final depth value until the shader finishes execution. Consequently, the hardware must disable Early-Z and revert to late depth testing. Every fragment generated by rasterization runs the full fragment shader, drastically increasing overdraw costs.

How Conservative Depth Qualifiers Work

The ARB_conservative_depth extension (standardized in core GLSL via layout qualifiers) allows developers to define constraints on the value written to gl_FragDepth relative to the hardware's interpolated fragment depth, gl_FragCoord.z:

  • layout (depth_greater) out float gl_FragDepth;
    Guarantees that the shader will only assign a depth value greater than or equal to gl_FragCoord.z (\(depth_{final} \ge gl\_FragCoord.z\)).
  • layout (depth_less) out float gl_FragDepth;
    Guarantees that the shader will only assign a depth value less than or equal to gl_FragCoord.z (\(depth_{final} \le gl\_FragCoord.z\)).
  • layout (depth_unchanged) out float gl_FragDepth;
    Guarantees that the written depth is identical to gl_FragCoord.z.
  • layout (depth_any) out float gl_FragDepth;
    The default behavior; depth can be anything, completely disabling early depth tests.

Preserving Early Depth Rejection

The GPU uses the mathematical relationship between the layout constraint and the active depth comparison function (glDepthFunc) to determine if a fragment is guaranteed to fail before running the shader.

Case 1: depth_greater with GL_LESS Comparison

When using the standard GL_LESS depth test (where fragments pass only if their depth is smaller than the value in the depth buffer):

  1. The GPU checks the incoming, unmodified gl_FragCoord.z against the current depth buffer value.
  2. If gl_FragCoord.z is already greater than or equal to the depth buffer value, the fragment is already failing the test.
  3. Because the shader uses depth_greater, the final depth value can only increase or remain the same; it can never become smaller.
  4. Therefore, the fragment is mathematically guaranteed to fail the depth test regardless of the shader's internal calculations.
  5. The hardware discards the fragment prior to shading, preserving the performance benefits of early depth rejection.

Case 2: depth_less with GL_GREATER Comparison

When using an inverse or reverse-Z setup with GL_GREATER:

  1. The GPU evaluates gl_FragCoord.z against the buffer.
  2. If gl_FragCoord.z is already less than or equal to the buffer value, it fails the current test.
  3. Because depth_less guarantees that the final depth can only decrease or stay the same, the fragment can never exceed the buffer value.
  4. The GPU safely aborts fragment execution immediately.

Limitations on Depth Buffer Writing

While conservative depth qualifiers preserve early rejection (culling fragments that are certain to fail), they do not allow early depth writes. Because the exact depth value remains unknown until shader execution completes, the GPU must still defer updating the depth buffer until after the fragment shader finishes. However, by discarding invisible fragments before computing complex materials, conservative depth qualifiers maintain the vast majority of Early-Z performance gains.