How Does gl_FragDepth Affect Early Depth Testing?

Writing directly to gl_FragDepth in a fragment shader generally forces modern GPUs to disable early depth testing (Early-Z), degrading rendering performance by executing shader workloads on pixels that will ultimately be occluded. Because the GPU cannot predict what value the shader will output, it must postpone depth testing until after the fragment shader finishes executing. However, features like conservative depth qualifiers and explicit early test layouts can mitigate these performance penalties under specific conditions.

The Role of Early Fragment Tests

In standard graphics pipelines, the hardware attempts to perform depth and stencil tests before running the fragment shader. This mechanism, known as Early-Z, allows the GPU to immediately discard fragments that are occluded by previously rendered, closer geometry.

Skipping fragment shading for hidden pixels saves significant GPU cycles, reduces bandwidth consumption, and prevents unnecessary texture lookups or complex arithmetic operations. To run Early-Z safely, the rasterizer derives the fragment depth directly from the interpolated vertex positions (\(z / w\)) generated during primitive rasterization.

Why Writing to gl_FragDepth Disables Early-Z

When a fragment shader contains an assignment to gl_FragDepth, it explicitly overrides the interpolated hardware depth value. The rasterizer no longer knows the final depth value of the fragment at the start of the pipeline.

Because the depth test depends on the final depth value, the pipeline must fall back to late depth testing:

  1. Primitive Rasterization: Fragments are generated with interpolated depths.
  2. Fragment Shader Execution: The shader runs unconditionally for every rasterized fragment to compute both color and the new gl_FragDepth.
  3. Late Depth Test: The GPU compares the newly written gl_FragDepth against the depth buffer.
  4. Framebuffer Write / Discard: If the test passes, the depth and color buffers update; if it fails, the fragment is discarded only after all shader computations have already finished.

This late execution causes high overdraw penalties, especially in scenes with complex geometry, overlapping transparent or opaque layers, and heavy fragment shaders.

Performance Repercussions

Disabling Early-Z impacts rendering pipelines in several ways:

Mitigations and Modern GLSL Features

OpenGL and GLSL provide specific layout qualifiers to preserve or adapt early testing behavior when depth modification is required.

Conservative Depth (layout(depth_*))

Introduced in OpenGL 4.2 (via GL_ARB_conservative_depth), depth layout qualifiers allow developers to provide guarantees to the GPU driver about how gl_FragDepth will be modified relative to the default interpolated depth:

Explicit Early Fragment Tests

GLSL allows developers to force early depth tests explicitly using the early_fragment_tests layout qualifier:

layout(early_fragment_tests) in;

When this qualifier is active, the depth test occurs before shader execution using the standard interpolated depth. If a shader attempts to write to gl_FragDepth while early_fragment_tests is declared, the written value is ignored for the depth test, or the shader will trigger a compilation error depending on driver constraints.

Best Practices

To maintain high frame rates and avoid disabling depth culling: