How Early Depth Testing Optimizes GLSL Fill Rate
Early depth testing, often called Early-Z, is a hardware optimization technique in OpenGL and GLSL that evaluates the depth buffer before the fragment shader executes rather than after it. By rejecting occluded fragments prior to shading, the graphics pipeline avoids performing computationally expensive calculations on pixels that will ultimately be hidden by other geometry. This process significantly reduces overdraw, conserves memory bandwidth, and optimizes overall fill rate in graphics-intensive applications.
The Traditional Graphics Pipeline vs. Early Depth Testing
In the standard OpenGL rendering pipeline, fragment processing follows this sequence:
- Rasterization: Primitives are converted into candidate fragments.
- Fragment Shader Execution: The fragment shader computes colors, textures, lighting, and procedural effects.
- Per-Sample Operations: The GPU performs the depth test, stencil test, and blending before writing to the framebuffer.
Under this default behavior, if five overlapping polygons cover the same pixel, the fragment shader runs five distinct times—even though only the frontmost polygon is visible in the final image. This redundant processing is known as overdraw, and it severely bottlenecks GPU fill rate.
Early depth testing moves the depth and stencil tests ahead of the fragment shader. If a fragment fails the depth comparison against the current value in the depth buffer, it is discarded immediately. Consequently, the fragment shader executes only for fragments that are actually visible, drastically reducing the total number of shader invocations.
How Fill Rate is Optimized
Fill rate refers to the speed at which a GPU can render pixels to the screen, constrained by processing cores and memory bandwidth. Early depth testing optimizes fill rate through several mechanisms:
- Eliminating Redundant Shader Work: Complex operations like dynamic lighting calculations, shadow map sampling, and normal mapping are skipped entirely for occluded geometry.
- Conserving Memory Bandwidth: Skipping fragment shading prevents unnecessary texture lookups and decreases the volume of data read from and written to the framebuffer cache.
- Maximizing Front-to-Back Rendering: When opaque objects are sorted and rendered from front to back, the closest objects populate the depth buffer first. Subsequent objects behind them fail the early depth test instantly, allowing scenes with heavy geometric density to render at near-constant fill rate costs.
Enforcing Early Depth Testing in GLSL
Modern GPUs often enable early depth testing automatically when it is safe to do so. However, certain shader operations can force the GPU to fall back to late depth testing. To guarantee that early testing occurs, GLSL 4.2 and later provides an explicit layout qualifier:
#version 430 core
layout(early_fragment_tests) in;
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}Using layout(early_fragment_tests) in; instructs the
driver to perform depth tests and depth buffer writes before running the
shader body.
Conditions That Break Early Depth Testing
The hardware cannot safely discard fragments early if the fragment shader's execution affects the depth buffer or depends on side effects. Early depth testing is typically disabled automatically under the following conditions:
- Writing to
gl_FragDepth: If the shader dynamically calculates or alters the fragment's depth value, the pipeline cannot determine whether the fragment passes the depth test until the shader finishes. - Fragment Discarding: Using the
discardkeyword can complicate early depth writes. While some architectures still execute an early depth test, they may delay depth writes to avoid incorrectly occluding background objects with fragments that are later discarded. - Side Effects: Shaders performing writes to image
variables (image load/store) or Shader Storage Buffer Objects (SSBOs)
may require strict execution guarantees that conflict with speculative
early rejection unless
early_fragment_testsis explicitly declared.
By structuring rendering passes to draw opaque geometry from front to
back, avoiding runtime modifications to gl_FragDepth, and
leveraging early_fragment_tests, developers can maximize
GPU throughput and prevent shader-bound fill rate limitations.