Amplification Shader Mesh Dispatch in DirectX
In DirectX 12, the Amplification Shader acts as an optional, programmable pre-pass to the Mesh Shader that determines how many downstream mesh threadgroups to execute. By evaluating scene data directly on the GPU—such as frustum visibility, occlusion tests, or distance-based Level of Detail (LOD)—the amplification shader dynamically dictates the execution grid of the mesh shader. This article explains the intrinsic mechanisms, execution logic, and data flow used to launch mesh shader threadgroups.
The DispatchMesh
Intrinsic
The amplification shader controls mesh shader execution through a
built-in HLSL function called DispatchMesh. This function
is the sole mechanism by which an amplification shader creates mesh
shader work.
The signature of the function is:
void DispatchMesh(
uint ThreadGroupCountX,
uint ThreadGroupCountY,
uint ThreadGroupCountZ,
in payload_t Payload
);
The arguments ThreadGroupCountX,
ThreadGroupCountY, and ThreadGroupCountZ
define the three-dimensional dimensions of the mesh shader threadgroup
grid. The total number of mesh shader threadgroups launched is the
product of these three dimensions (\(X \times
Y \times Z\)).
Determining Grid Dimensions
Because the amplification shader runs per-threadgroup computation, it can calculate the dispatch dimensions dynamically at runtime rather than relying on static values from the CPU.
Common methods for calculating these counts include:
- Frustum and Occlusion Culling: The amplification
shader tests coarse bounding volumes (such as bounding spheres or
oriented bounding boxes) against the camera frustum or a hierarchical
Z-buffer. If a meshlet or object is invisible, the shader sets the
dispatch counts to
(0, 0, 0). This completely culls the geometry and prevents any mesh shader threadgroups from being allocated. - Dynamic LOD Selection: By computing the distance
between a bounding sphere center and the camera, the shader selects an
appropriate level of detail. Coarser LODs require fewer meshlets,
resulting in lower dispatch dimensions (e.g.,
DispatchMesh(2, 1, 1, payload)), whereas finer LODs request more threadgroups (e.g.,DispatchMesh(16, 1, 1, payload)). - Subdivision and Dynamic Expansion: When tessellating or procedurally generating geometry, the amplification shader computes how many meshlets are required to represent the expanded surface and outputs the corresponding threadgroup count.
The Payload Structure
Along with the dispatch dimensions, DispatchMesh accepts
a user-defined Payload parameter. This payload is stored in
shared memory and passed directly to all mesh shader threadgroups
instantiated by that specific call.
The payload typically contains:
- An array of meshlet indices that survived culling.
- Calculated LOD offsets or transformation matrices.
- Dynamic material or instance IDs.
Downstream mesh shaders access this data using the
[[vk::payload]] or HLSL in payload attribute,
enabling each generated mesh threadgroup to identify which specific
piece of geometry it is responsible for processing.
Execution Constraints
- Single Invocation per Threadgroup: While multiple
threads within an amplification shader threadgroup can participate in
culling tests and calculating the final counts using group shared memory
or wave intrinsics,
DispatchMeshmust be called uniformly or by a single designated thread (often the first thread, using anif (GroupThreadID == 0)check) after all calculations and data synchronization are complete. - Dispatch Limits: The maximum number of total mesh shader threadgroups that can be dispatched by a single amplification shader group is subject to hardware and API limits, typically up to \(2^{16} - 1\) threadgroups across all dimensions.