GLSL early_fragment_tests and Depth Testing
The layout(early_fragment_tests) in; layout qualifier in
GLSL explicitly forces the GPU to perform depth and stencil testing
prior to executing the fragment shader. Under normal pipeline
conditions, drivers may defer depth tests until after fragment shading,
or use implementation-dependent optimizations. Declaring this layout
qualifier establishes a strict execution order, ensuring that fragments
failing the depth or stencil tests are discarded before any fragment
shader instructions run, thereby optimizing performance and controlling
side effects.
Default Pipeline Execution vs. Early Testing
By default, OpenGL can execute the depth test either before or after fragment shading:
- Late Fragment Tests: Standard pipeline behavior
runs the fragment shader first, followed by depth testing, stencil
testing, and blending. This is mandatory if the fragment shader modifies
the fragment's depth value via
gl_FragDepth. - Driver-Assisted Early-Z: Modern GPUs often perform an implicit early depth test (Early-Z) as an optimization. However, the driver can disable this optimization automatically if the fragment shader uses operations with side effects, such as writing to Shader Storage Buffer Objects (SSBOs), modifying image variables via image load/store, or using atomic counters.
What
layout(early_fragment_tests) in Enforces
Declaring layout(early_fragment_tests) in; removes
driver ambiguity and enforces the following pipeline sequence:
- Mandatory Pre-Shader Depth and Stencil Testing: The depth test and stencil test must execute before any fragment shader invocations.
- Conditional Execution of Fragment Shaders: If a fragment fails the depth test (for example, if it is occluded by geometry closer to the camera), the fragment shader is completely bypassed.
- Suppression of Shader Side Effects: Because occluded fragments are discarded prior to fragment execution, any side effects within the shader—such as atomic operations, SSBO writes, or image stores—are guaranteed not to occur for those fragments. This is crucial for algorithms like order-independent transparency, custom profiling, or software occlusion culling that rely on predictable writes.
- Depth Value Immutability: When this qualifier is
used, the fragment shader is legally prohibited from modifying
gl_FragDepth. The depth value tested and written to the depth buffer is strictly the interpolated depth generated by the primitive's rasterization.
Usage Example
#version 430 core
layout(early_fragment_tests) in;
layout(std430, binding = 0) buffer PixelCounter {
uint visiblePixels;
};
out vec4 fragColor;
void main() {
// Only executed for fragments that pass the depth and stencil tests
atomicAdd(visiblePixels, 1);
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}By enforcing early tests, the counter in the example above only increments for genuinely visible fragments, eliminating inaccurate counts caused by overdraw.