Amplification Shaders for DirectX 12 Mesh Culling

Amplification shaders work alongside mesh shaders in DirectX 12 Ultimate to establish a highly efficient, two-stage geometry processing pipeline. By serving as an optional, coarse-grain pre-pass before mesh shader execution, the amplification shader evaluates batches of geometry—typically organized as meshlets—and culls entire non-visible clusters before any vertex or primitive processing occurs. This overview explains how amplification shaders determine visibility, leverage spatial hierarchies, dynamically spawn mesh shader threadgroups, and optimize overall GPU rendering efficiency.

The Two-Stage Mesh Pipeline

DirectX 12 Ultimate introduces a compute-like pipeline replacing the fixed-function input assembler, vertex shader, hull/domain shaders, and geometry shader. This pipeline consists of two stages:

  1. Amplification Shader (AS): An execution stage analogous to a compute shader that evaluates scenes at a coarse, per-object or per-meshlet level.
  2. Mesh Shader (MS): A fine-grained execution stage that processes individual vertices and primitives for visible geometry.

A standard meshlet contains a localized cluster of geometry (typically up to 64 vertices and 126 triangles). Without an amplification shader, the GPU must invoke a mesh shader threadgroup for every meshlet in an object. When an amplification shader is placed upstream, it acts as an intelligent scheduler that discards invisible meshlets before mesh shaders ever run.

Hierarchical Culling Mechanisms

Amplification shaders evaluate groups of meshlets using lightweight mathematical tests. Instead of evaluating individual triangles, the amplification shader performs tests on bounding volumes representing whole clusters of geometry:

  • Frustum Culling: The amplification shader tests the bounding sphere or oriented bounding box (OBB) of a meshlet against the active camera view frustum. If a bounding volume falls completely outside the frustum, the shader rejects the entire meshlet.
  • Cone (Backface) Culling: Many meshlets have relatively uniform surface normals. A normal cone (defined by an axis and an angle) can encompass all face normals within a meshlet. If the apex of the cone points entirely away from the camera, every triangle in the cluster is facing away, allowing the amplification shader to discard the cluster at once.
  • Two-Pass Occlusion Culling: Using a hierarchical Z-buffer (HZB) from a previous depth pre-pass, the amplification shader can sample depth values at the screen-space footprint of a meshlet’s bounding box. If the cluster is fully occluded behind existing geometry, it is culled.
  • Level of Detail (LOD) Selection: Beyond binary culling, the amplification shader can compute camera distance to select an appropriate LOD variant of a meshlet, ensuring only the required geometry detail is dispatched.

Dynamic Work Generation via DispatchMesh

The primary mechanism by which an amplification shader controls the mesh shader stage is the intrinsic DispatchMesh function in HLSL.

During execution, each thread in an amplification shader threadgroup tests one or more meshlet bounding volumes. Visible meshlet indices are compacted into a shared payload structure. The amplification shader then calls:

DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ, payload);

If an amplification shader processes 32 meshlets and determines that only 4 are visible, it sets the threadgroup count to 4 and passes the indices of those 4 visible meshlets in the payload. The GPU will only spawn 4 mesh shader threadgroups instead of 32. If none of the meshlets are visible, the amplification shader invokes DispatchMesh(0, 0, 0, payload), terminating the pipeline immediately for that entire workload.

Payload Data Transfer

The amplification shader passes custom user-defined data structures to downstream mesh shaders via an out-parameter payload. This structure contains compacted arrays of visible meshlet identifiers, transform matrices, or LOD parameters.

Because invisible meshlets are filtered out early, downstream mesh shaders do not waste threadgroup registers, scratch memory, or ALU cycles performing redundant bounding box or visibility checks. The mesh shader simply reads its assigned visible meshlet ID directly from the incoming payload and proceeds directly to vertex transformation and primitive assembly.

Performance Advantages Over Traditional Pipelines

Integrating amplification shaders for geometry culling provides several key architectural benefits:

  • Reduced Memory Bandwidth: Culling at the bounding-volume stage prevents the GPU from reading detailed vertex indices and attributes for geometry that will never appear on screen.
  • Zero Vertex Shader Overhead for Culled Data: In traditional pipelines, vertex shaders often transform vertices before primitive assembly determines triangles are off-screen. Amplification shaders cull geometry before per-vertex transformations take place.
  • Offloaded Host CPU Work: Traditional occlusion and frustum culling often require CPU-side scene-graph traversal or compute shader pre-passes with indirect draw calls. Amplification shaders allow these decisions to occur natively on the GPU within the standard rendering pipeline, reducing CPU overhead and eliminating CPU-to-GPU synchronization stalls.