Mesh & Amplification Shaders
Mesh shaders (SM 6.5, D3D12 Ultimate) replace IA + VS + HS/DS + GS with a compute-like model: a thread group outputs a small mesh (meshlet) of vertices and primitives directly to the rasterizer.
Mesh shaders (SM 6.5, D3D12 Ultimate) replace IA + VS + HS/DS + GS with a compute-like model: a thread group outputs a small mesh (meshlet) of vertices and primitives directly to the rasterizer. An optional amplification shader runs first, does culling/LOD, and launches mesh groups. Benefits: GPU-driven culling at meshlet granularity, no index-buffer bottleneck, procedural geometry.
31.1 Limits
Threads per group Payload (AS → MS) Mesh shader output data ≤ 32 KB (vertex + primitive attributes) Mesh shader groupshared
Typical meshlets: 64 vertices / 124–126 triangles (meshoptimizer meshopt_buildMeshlets).
31.2 Mesh shader
struct Meshlet { uint vertexCount; uint vertexOffset; uint primCount; uint primOffset; };
struct Vertex { float3 pos; float3 normal; };
StructuredBuffer<Vertex> gVertices : register(t0);
StructuredBuffer<Meshlet> gMeshlets : register(t1);
StructuredBuffer<uint> gUniqueIndices : register(t2); // meshlet-local → global vertex index
StructuredBuffer<uint> gPrimIndices : register(t3); // 3 × 10-bit local indices packed per uint
cbuffer MeshCB : register(b0) { float4x4 gMVP; float4x4 gWorldM; };
struct MSVert { float4 pos : SV_Position; float3 normal : NORMAL; float3 color : COLOR; };
struct MSPrim { uint meshletId : MESHLET_ID; }; // per-primitive attribute
struct Payload { uint meshletIds[32]; };
[outputtopology("triangle")]
[numthreads(128, 1, 1)]
void MSMain(uint gtid : SV_GroupThreadID, uint gid : SV_GroupID,
in payload Payload payload,
out vertices MSVert verts[64],
out indices uint3 tris[126],
out primitives MSPrim prims[126])
{
uint meshletIndex = payload.meshletIds[gid];
Meshlet m = gMeshlets[meshletIndex];
SetMeshOutputCounts(m.vertexCount, m.primCount); // must be c
…31.3 Amplification shader with culling
Host: create the PSO with D3D12_PIPELINE_STATE_STREAM_DESC (AS/MS/PS subobjects), check D3D12_FEATURE_DATA_D3D12_OPTIONS7.MeshShaderTier, and call ID3D12GraphicsCommandList6::DispatchMesh(groupsX, groupsY, groupsZ). Compile with -T as_6_5 / -T ms_6_5. The same model exists in Vulkan (VK_EXT_mesh_shader) — DXC can emit it with -spirv.
struct CullData { float4 boundingSphere; uint normalCone; float apexOffset; }; // meshoptimizer-style
StructuredBuffer<CullData> gCull : register(t4);
cbuffer ASCB : register(b1) { float4 gFrustumPlanes[6]; float3 gCamPosAS; uint gMeshletCount; };
groupshared Payload sPayload;
bool IsVisible(CullData c)
{
float3 center = mul(gWorldM, float4(c.boundingSphere.xyz, 1)).xyz;
float radius = c.boundingSphere.w; // assumes uniform scale 1
[unroll] for (int i = 0; i < 6; ++i)
if (dot(gFrustumPlanes[i].xyz, center) + gFrustumPlanes[i].w < -radius) return false;
return true;
}
[numthreads(32, 1, 1)]
void ASMain(uint dtid : SV_DispatchThreadID)
{
bool visible = dtid < gMeshletCount && IsVisible(gCull[dtid]);
if (visible)
{
uint slot = WavePrefixCountBits(visible);
sPayload.meshletIds[slot] = dtid;
}
uint visibleCount = WaveActiveCountBits(visible);
DispatchMesh(visibleCount, 1, 1, sPayload); // called exactly once per group, uniformly
}