How dFdx Computes Screen-Space Derivatives in GLSL
The dFdx function in GLSL calculates the horizontal rate
of change of any arbitrary value across adjacent screen-space pixels.
Rather than performing mathematical differentiation through analytical
formulas, GPUs approximate this derivative using discrete finite
differences. By organizing fragment shader execution into small 2x2
pixel clusters known as quads, the hardware can evaluate the difference
between a variable's value in neighboring fragments along the horizontal
axis in real time.
The 2x2 Pixel Quad Architecture
Modern GPUs do not process fragment shaders as completely isolated threads. Instead, they execute fragments in lockstep groups called quads (2x2 pixel grids). A single quad consists of four fragments:
- Top-left \((x, y)\)
- Top-right \((x+1, y)\)
- Bottom-left \((x, y+1)\)
- Bottom-right \((x+1, y+1)\)
Because all four fragments inside a quad execute instructions simultaneously on Single Instruction, Multiple Data (SIMD) hardware, their registers can be read by neighboring lanes within the execution unit without costly memory transactions.
The Finite Difference Calculation
To evaluate dFdx(p) for an arbitrary expression or
variable p, the GPU reads the value of p
computed by horizontally adjacent fragments in the quad and computes the
difference:
\[\Delta p = p_{\text{right}} - p_{\text{left}}\]
In GLSL, the exact calculation depends on whether the GPU applies a "coarse" or "fine" derivative:
- Coarse Derivatives (
dFdxCoarse): The GPU calculates a single horizontal delta across the quad (typicallyp(top-right) - p(top-left)) and shares that exact result with all fragments in the quad. - Fine Derivatives (
dFdxFine): The GPU evaluates separate horizontal deltas for both rows:- Top row:
p(top-right) - p(top-left) - Bottom row:
p(bottom-right) - p(bottom-left)
- Top row:
The standard dFdx function is typically mapped by the
GPU driver to either the fine or coarse implementation depending on the
target hardware and driver optimizations.
Helper Invocations at Primitive Edges
If a rendered triangle only covers a portion of a 2x2 quad, the
unshaded pixels still execute the fragment shader as helper
invocations (or dummy fragments). These helper fragments
compute values strictly to provide valid data for derivative operations
like dFdx and dFdy. Once shading finishes, the
output of helper invocations is discarded and never written to the
framebuffer.
Practical Applications
Because dFdx calculates rates of change directly in
window coordinates, it is essential for several core graphics
techniques:
- Texture Filtering (Mipmapping): Determines how fast texture coordinates \((u, v)\) change per screen pixel to select the appropriate mipmap Level of Detail (LOD).
- Faceted Normal Reconstruction: Generates
flat-shading surface normals dynamically using the cross product of
horizontal and vertical position derivatives:
normalize(cross(dFdx(vPosition), dFdy(vPosition))). - Procedural Anti-Aliasing: Sets filter widths for procedural shapes (such as stripes, grids, or circles) to avoid aliasing artifacts.
Divergent Control Flow Limitation
Because dFdx depends on inter-lane communication within
a 2x2 quad, calling it inside non-uniform control flow (such as an
if statement where only some fragments in a quad take the
branch) results in undefined behavior. The GPU cannot reliably compare
neighbor values if those neighbors executed a different set of
instructions.