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.