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 togl_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 togl_FragCoord.z(\(depth_{final} \le gl\_FragCoord.z\)).layout (depth_unchanged) out float gl_FragDepth;
Guarantees that the written depth is identical togl_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):
- The GPU checks the incoming, unmodified
gl_FragCoord.zagainst the current depth buffer value. - If
gl_FragCoord.zis already greater than or equal to the depth buffer value, the fragment is already failing the test. - Because the shader uses
depth_greater, the final depth value can only increase or remain the same; it can never become smaller. - Therefore, the fragment is mathematically guaranteed to fail the depth test regardless of the shader's internal calculations.
- 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:
- The GPU evaluates
gl_FragCoord.zagainst the buffer. - If
gl_FragCoord.zis already less than or equal to the buffer value, it fails the current test. - Because
depth_lessguarantees that the final depth can only decrease or stay the same, the fragment can never exceed the buffer value. - 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.