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
}