Understanding layout(depth_negative) in GLSL
This article provides an overview of how conservative depth
qualifiers function in GLSL, focusing on the purpose of negative depth
declarations—such as layout(depth_negative) or standard
GLSL layout(depth_less). It covers how these qualifiers
inform the GPU rasterizer about manual modifications to
gl_FragDepth, how they preserve critical Early-Z depth
testing optimizations, and their impact on rendering performance.
The Role of Depth Layout Qualifiers
Modern GPUs utilize Early Depth Testing (Early-Z) and Hierarchical Z
(Hi-Z) to discard occluded fragments before running expensive fragment
shader calculations. Normally, writing manually to
gl_FragDepth forces the rasterizer to disable Early-Z
because the fragment's final depth value is unknown until the fragment
shader finishes executing. Disabling Early-Z can significantly degrade
rendering performance, as every overlapping fragment must be shaded
regardless of visibility.
Depth layout qualifiers solve this problem by providing a contract between the fragment shader and the GPU rasterizer. By explicitly declaring bounds on how the fragment shader modifies depth, the rasterizer can determine fragment visibility early.
The Purpose of
depth_negative / depth_less
In GLSL (standardized under GL_ARB_conservative_depth as
layout(depth_less) and often referred to conceptually as
depth_negative), the layout qualifier informs the
rasterizer that any value assigned to gl_FragDepth will be
strictly less than or equal to the hardware-interpolated fragment depth
(gl_FragCoord.z).
Declaring a negative or decreasing depth modification serves several key purposes:
- Enabling Early-Z Culling: When rendering with depth
comparison functions like
GL_GREATERorGL_GEQUAL(common in reverse-Z projection setups), a fragment can only achieve a smaller depth value than its incoming position. If the incoming interpolated depth fails the depth test against the depth buffer, the rasterizer guarantees the modified fragment would also fail. As a result, the fragment can be safely culled before shader execution. - Preserving Hi-Z Buffer Integrity: Hierarchical depth buffers maintain minimum and maximum depth bounds per tile. Knowing that depth only moves in a negative direction allows the GPU to perform tile-level occlusion checks without CPU or pipeline stalls.
- Preventing Race Conditions on Depth Writes: Although the final depth written to the depth buffer cannot occur until the fragment shader completes, knowing the bound ensures the rasterizer does not update the depth buffer prematurely with values that violate the current depth testing rules.
Syntax in Standard GLSL
While conceptually describing a negative depth displacement, the
standard GLSL core qualifier defined in the specification is
depth_less:
#version 420 core
// Informs the GPU that gl_FragDepth <= gl_FragCoord.z
layout (depth_less) out float gl_FragDepth;
void main()
{
// Depth is only modified to a smaller or equal value
gl_FragDepth = gl_FragCoord.z - 0.05;
}Common Use Cases
Guaranteed negative depth modifications are commonly used in:
- Decal and Detail Rendering: Pushing decals closer to the camera to prevent z-fighting against base geometry.
- Conservative Parallax Mapping: Adjusting surface depth inward or outward where displacement values only subtract from the projected surface.
- Reverse-Z Pipelines: Applications utilizing floating-point reverse depth buffers, where smaller values correspond to points farther from the camera.