Manual Buffer Indexing with SV_VertexID in DirectX
In DirectX pipelines (Direct3D 11 and Direct3D 12), developers can
decouple geometry processing from the fixed-function Input Assembler by
utilizing programmable vertex pulling. Instead of declaring rigid input
layouts, vertex attributes can be stored in Shader Resource Views (SRVs)
and fetched manually within the vertex shader using the system-generated
value SV_VertexID. This approach provides flexible data
formats, minimizes driver overhead from layout transitions, and
streamlines GPU data sharing between compute and graphics stages.
Understanding SV_VertexID
The SV_VertexID semantic is an input-assembler-generated
32-bit unsigned integer (uint) that assigns an index to
each vertex invocation. For non-indexed draw calls (such as
Draw or DrawInstanced), the hardware assigns
an identifier based on the vertex position in the draw sequence, taking
the base vertex offset into account. For indexed draw calls (such as
DrawIndexedInstanced), the Input Assembler reads the
traditional index buffer and passes the resulting index value into the
vertex shader through SV_VertexID.
Configuring the Pipeline for Manual Pulling
To pull vertex data manually, the application bypasses standard Input Assembler attribute extraction:
- Omit the Input Layout: Set the input layout to
nullptr(or omit input layout elements in Direct3D 12 pipeline state objects). - Bind Data as SRVs: Instead of binding vertex
buffers via
IASetVertexBuffersor traditional vertex buffer views, bind the buffers as Shader Resource Views (SRVs) accessible to the vertex shader stage. - Execute Empty Draw Calls: Issue standard draw calls
(e.g.,
DrawInstanced(vertexCount, instanceCount, 0, 0)). Because the vertex count is non-zero, the rasterizer and scheduler dispatch the requested number of shader threads, routing the sequential counter toSV_VertexID.
HLSL Implementation Using StructuredBuffer
The simplest way to manually fetch vertex attributes is with a
StructuredBuffer<T>. When declared in HLSL, the
shader compiler exposes array-style indexing, allowing
SV_VertexID to directly subscript the buffer.
struct Vertex
{
float3 position;
float2 uv;
};
StructuredBuffer<Vertex> g_VertexBuffer : register(t0);
cbuffer SceneConstants : register(b0)
{
float4x4 g_WorldViewProj;
};
struct VSOutput
{
float4 position : SV_Position;
float2 uv : TEXCOORD0;
};
VSOutput VSMain(uint vertexID : SV_VertexID)
{
VSOutput output;
// Manually index into the buffer using SV_VertexID
Vertex v = g_VertexBuffer[vertexID];
output.position = mul(float4(v.position, 1.0f), g_WorldViewProj);
output.uv = v.uv;
return output;
}
HLSL Implementation Using ByteAddressBuffer
For custom data packing, non-uniform strides, or zero-copy interop
with compute pipelines, ByteAddressBuffer (raw buffers)
provides low-level memory addressing. The vertex shader multiplies
SV_VertexID by the stride of the vertex structure in bytes
to locate the exact memory offset.
ByteAddressBuffer g_RawVertexBuffer : register(t0);
VSOutput VSMain(uint vertexID : SV_VertexID)
{
VSOutput output;
// Define byte stride: 12 bytes (float3) + 8 bytes (float2) = 20 bytes
uint stride = 20;
uint byteOffset = vertexID * stride;
// Load data at calculated byte offsets
float3 pos = asfloat(g_RawVertexBuffer.Load3(byteOffset));
float2 uv = asfloat(g_RawVertexBuffer.Load2(byteOffset + 12));
output.position = mul(float4(pos, 1.0f), g_WorldViewProj);
output.uv = uv;
return output;
}
Architectural Benefits
Using SV_VertexID for manual buffer indexing offers
distinct performance and architectural advantages:
- State Cache Efficiency: Eliminates pipeline state object (PSO) variations caused by disparate vertex input layouts, keeping pipeline switches lightweight.
- Dynamic Attribute Fetching: Allows shaders to conditionally branch and avoid fetching unused vertex attributes (e.g., normal or tangent vectors during shadow map passes).
- Direct Compute Integration: Meshes generated or animated via Compute Shaders can be consumed directly by the vertex shader without needing conversions or multiple buffer views.