> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Meshlet culling example

> Meshlet culling example

We recommend that you first read the [Meshlets](/build/core-features/graphics/newfeatures/ms/ms-meshlet-render) topic.

The Meshlet culling example topic presents the pseudocode of an Amplification Shader implementation of a technique called *meshlet culling*. Since a meshlet's primitives ideally conform to spatial and orientational coherence, we can store efficient culling data per-meshlet. This can be used to quickly cull a meshlet list against a view in an Amplification Shader and only dispatch Mesh Shader instances for those surviving meshlets.

The technique starts with an Amplification Shader where each thread processes a single meshlet. This means that we must dispatch ceil(MeshletCount / GROUP\_SIZE) threadgroups on the command list.

Each Amplification Shader threadgroup is then responsible for processing GROUP\_SIZE meshlets. Each thread culls its meshlet against the view frustum, determines its export slot, and then writes its index to the payload data. It then uses [DispatchMesh](https://microsoft.github.io/DirectX-Specs/d3d/MeshShader.html#dispatchmesh-intrinsic) to dispatch one Mesh Shader threadgroup for each surviving meshlet to render. Each Mesh Shader threadgroup then loads its meshlet from the payload data and exports its geometry.

The full sample can be found on the [XBOX Developer Downloads page](https://aka.ms/xgddl).

## Amplification Shader

The Amplification Shader structure is identical to compute shaders but concludes with at most one 'DispatchMesh(…)' call. First, each thread loads its meshlet and culls it against the view. Next, a compaction routine is performed to determine each thread's final write location. This also provides the final count of unculled meshlets for the threadgroup. Finally, the meshlet index is then written to the groupshared payload data, and DispatchMesh is called with the surviving meshlet count and the payload data.

The following example shows an Amplification Shader implementing meshlet culling in pseudocode.

```
#define GROUP_SIZE 128

struct Meshlet
{
    // Meshlet data.
    uint VertexOffset;
    uint VertexCount;
    uint PrimitiveOffset;
    uint PrimitiveCount;

    // Local Culling data.
    float4 BoundingSphere;
    float4 NormalCone;
};

StructuredBuffer<Meshlet> Meshlets : register(t0);

cbuffer ViewProps : register(b0)
{
    float4x4 ViewMat;
    float4x4 ViewProjMat;
    float3   ViewPosition;
};

struct MyPayload 
{ 
    uint MeshletIndices[GROUP_SIZE]; 
};

groupshared MyPayload payload;

bool CullTests(Meshlet m) { ... }
void CalcPayloadIndexAndCount(bool isCulled, out index, out count) { ... }

[NumThreads(GROUP_SIZE, 1, 1)] 
void main(
    uint dispatchThreadId : SV_DispatchThreadID,
    uint groupThreadId : SV_GroupThreadID
)
{
    Meshlet meshlet = Meshlets[dispatchThreadId];
    bool isCulled = CullTests(meshlet);
    
    uint index, count;
    CalcPayloadIndexAndCount(groupThreadId, isCulled, index, count);
    
    if (!isCulled)
    {
        payload.MeshletIndices[index] = dispatchThreadId;
    }

    DispatchMesh(count, 1, 1, payload);
}
```

## Mesh Shader

The pipeline's Mesh Shader is very similar to meshlet rendering by using a Mesh Shader-only pipeline except that we read the threadgroup's meshlet index from the payload data. The payload data is the same data that's built and exported from the preceding Amplification Shader. GroupIDs and DispatchThreadIDs are localized to the Mesh Shader cluster that's dispatched from the invoking Amplification Shader threadgroup, and they all share the same payload data.

This code example shows the Mesh Shader following the previous Amplification Shader.

```
#define MAX_MESHLET_SIZE 128

StructureBuffer<MyVertex>   Vertices;
StructureBuffer<Meshlet>    Meshlets;
StructuredBuffer<uint>      UniqueVertexIndices;
StructureBuffer<uint3>      PrimitiveIndices;

VertexOut TransformVertex(MyVertex v) { ... }

[NumThreads(MAX_MESHLET_SIZE, 1, 1)]
[OutputTopology("triangle")]
void main(
    uint groupThreadId : SV_GroupIndex,
    uint groupId : SV_GroupID,
    in payload MyPayload payload,
    out vertices VertexOut verts[MAX_MESHLET_SIZE],
    out indices uint3 tris[MAX_MESHLET_SIZE])
{
    uint meshletIndex = payload.MeshletIndices[groupId];
    Meshlet m = Meshlets[meshletIndex];

    SetMeshOutputCounts(m.VertCount, m.PrimCount);

    if (groupThreadId < m.PrimCount) 
    {
        tris[groupThreadId] = PrimitiveIndices[m.PrimOffset + groupThreadId];
    }

    if (groupThreadId < m.VertCount) 
    {
        uint index = UniqueVertexIndices[m.VertOffset + groupThreadId];
        MyVertex vin = Vertices[index];

        verts[groupThreadId] = TransformVertex(vin);
    }
}

```

## See also

[Mesh Shader pipeline overview](/build/core-features/graphics/newfeatures/ms/ms-overview)

[Why Mesh Shaders?](/build/core-features/graphics/newfeatures/ms/ms-why)

[Mesh Shader API overview](/build/core-features/graphics/newfeatures/ms/ms-api-design)

[Meshlets](/build/core-features/graphics/newfeatures/ms/ms-meshlet-render)


## Related topics

- [Mesh shaders and meshlets on XBOX Series X|S](/build/core-features/graphics/newfeatures/ms/index.md)
- [Meshlets](/build/core-features/graphics/newfeatures/ms/ms-meshlet-render.md)
- [Why Mesh Shaders?](/build/core-features/graphics/newfeatures/ms/ms-why.md)
- [XBOX Game Energy Efficiency Essentials](/build/game-principles/sustainability/energy-efficiency-essentials.md)
- [NDA topics index for XBOX GDK console development](/nda/nda-overview.md)
