Why GPUs Disable Early Fragment Tests in GLSL

Early fragment tests allow a GPU to perform depth and stencil operations before executing the fragment shader, significantly saving processing power by discarding occluded fragments before costly shading occurs. However, because early testing assumes the fragment shader will not alter depth values, coverage, or rely on specific execution counts, certain operations inside the shader or pipeline state will force the driver to disable this optimization automatically and fall back to late fragment tests.

Writing to gl_FragDepth

The most common reason a GPU disables early depth testing is when the fragment shader writes to the built-in variable gl_FragDepth. Early fragment tests compare the interpolated primitive depth against the depth buffer. If the fragment shader calculates and assigns a custom depth value, the hardware cannot determine the final depth until the shader finishes execution, making early depth evaluation impossible.

Note: Developers can mitigate this by declaring depth qualifiers such as layout (depth_greater) out float gl_FragDepth; (conservative depth), which allows the hardware to perform partial early depth rejections.

Using the discard Keyword

Executing a discard statement dynamically kills a fragment during shader execution. If early depth tests were allowed to write directly to the depth buffer before the fragment shader ran, a fragment that updates the depth buffer and is subsequently discarded would leave an incorrect depth value in the buffer. Consequently, the presence of discard forces the driver to either disable early fragment testing entirely or split the test into an early depth read and a late depth write.

Modifying Sample Masks (gl_SampleMask)

In multisample anti-aliasing (MSAA) pipelines, coverage is determined per sub-sample. If a fragment shader writes to the gl_SampleMask[] output array, it modifies which sample locations are covered. Because final coverage is altered dynamically within the shader, the hardware cannot finalize depth and stencil updates for individual samples during an early pass.

Unsynchronized Shader Storage and Images (Side Effects)

When a fragment shader performs writes to Shader Storage Buffer Objects (SSBOs) or writes to textures via imageStore(), early fragment testing would alter the side effects of the shader. If early depth testing is active, occluded fragments never run, suppressing these memory writes. While some drivers still allow early testing with side effects by default, others revert to late fragment tests to avoid dropping unintended memory operations unless the shader explicitly mandates early testing via layout(early_fragment_tests) in;.

Alpha-to-Coverage and Custom Blending Logic

Enabling GL_SAMPLE_ALPHA_TO_COVERAGE computes sample coverage masks based on the alpha value output by the fragment shader. Because the alpha channel cannot be known until the shader computes it, the GPU cannot evaluate sample-accurate depth operations upfront, requiring fragment tests to execute late.

Forcing Early Tests

If a shader uses operations like discard or memory side effects but does not alter gl_FragDepth, developers can explicitly force the GPU to retain early depth and stencil operations by specifying the layout qualifier:

layout(early_fragment_tests) in;

When this layout qualifier is present, depth and stencil tests occur strictly prior to shader execution, ensuring maximum performance even if the shader discards fragments or performs memory writes.