Structured Buffer Alignment in DirectX 11 and 12

In DirectX 11 and DirectX 12, structured buffers require strict byte stride alignment to ensure peak memory performance, deterministic address calculation, and hardware compatibility across diverse GPU architectures. A structured buffer contains an array of identically sized user-defined structures, where the byte stride specifies the exact size in bytes of each individual element. This article explores how modern GPU memory controllers, SIMD execution units, hardware cache-line constraints, and the Direct3D runtime mandate these strict alignment rules to prevent execution penalties and hardware-level faults.

GPU Memory Architecture and Memory Coalescing

Modern GPUs execute shaders in parallel groups of threads, known as warps on NVIDIA hardware or wavefronts on AMD hardware. When these threads simultaneously read or write data in a structured buffer, the GPU’s memory controller attempts to coalesce these individual memory requests into as few memory transactions as possible.

GPU memory controllers typically operate on fixed-size cache lines, commonly 32, 64, or 128 bytes in width. If a structured buffer's byte stride is properly aligned—at a minimum to a multiple of 4 bytes (standard 32-bit scalar alignment) and ideally aligned to 16 bytes (128-bit vector alignment)—adjacent threads access contiguous, predictably spaced addresses. When byte strides are unaligned or arbitrarily sized:

  • Memory Divergence: Individual structure members end up straddling cache-line boundaries. A single element read might require two separate cache-line fetches instead of one.
  • Bandwidth Saturation: A warp of 32 threads reading misaligned data can require double or quadruple the number of memory transactions, cutting effective memory bandwidth drastically.

Hardware Address Calculation (ALU Offsets)

Shader instructions that access structured buffers, such as HLSL's StructuredBuffer<T> or RWStructuredBuffer<T>, map directly to dedicated load and store instructions in hardware ISA (Instruction Set Architecture), such as ld_structured and store_structured.

To locate a member within a structured buffer, the GPU hardware performs the following calculation:

\[\text{Address} = \text{BufferBase} + (\text{Index} \times \text{Stride}) + \text{MemberOffset}\]

To keep hardware address generation units (AGUs) simple and fast:

  1. Shift Optimization: GPUs optimize multiplication by powers of two into simple bit-shift operations. When strides conform to power-of-two alignments (or multiples of 4 bytes), address math requires minimal ALU cycles.
  2. Atomic and Vector Operations: Many hardware platforms lack unaligned vector load instructions. If an HLSL structure contains a float4 and the stride is not a multiple of 16 bytes, subsequent elements will place that float4 at addresses that are not 16-byte aligned. On architectures that enforce strict alignment for vector or atomic operations, this causes severe performance throttling or hardware fault exceptions.

API Requirements: DirectX 11 vs. DirectX 12

Both DirectX 11 and 12 enforce strict structural validation at the runtime and driver layers:

  • DirectX 11 Specifications: When defining a buffer using D3D11_BUFFER_DESC, the StructureByteStride parameter must be a multiple of 4 bytes and cannot exceed 2048 bytes. If StructureByteStride is not a multiple of 4, device creation or buffer view creation fails immediately via the runtime's validation layer.
  • DirectX 12 Explicit Alignment: In DirectX 12, manual resource management requires adhering to additional hardware alignment barriers. While D3D12_BUFFER_SRV::StructureByteStride still mandates a 4-byte multiple, the underlying resource allocations via D3D12_RESOURCE_DESC often require alignment to 64 KB (or 4 MB for MSAA), and descriptor offsets must align with hardware-specific limits.

HLSL Struct Packing Rules

HLSL applies strict packing rules to user-defined structures. By default, scalar types like float or uint occupy 4 bytes, while vector types like float3 and float4 occupy 12 and 16 bytes, respectively.

HLSL prevents members from spanning across 16-byte boundaries (registers) in constant buffers, and it applies similar padding logic to structured buffers to preserve natural alignment:

struct BadAlignment
{
    float3 position; // 12 bytes
    float2 uv;       // 8 bytes -> Straddles boundary without padding
};
// Total size: 20 bytes (multiple of 4, but creates misaligned vector access on element 1)

struct GoodAlignment
{
    float3 position; // 12 bytes
    float padding;   // 4 bytes explicit padding
    float2 uv;       // 8 bytes
    float2 padding2; // 8 bytes padding
};
// Total size: 32 bytes (aligned to 16 bytes, optimal for caching and SIMD execution)

Adhering to strict byte stride alignment is not merely an API constraint, but a fundamental requirement to align high-level shader code with the physical realities of modern parallel memory subsystems. Failure to align strides results in API validation failures, disabled hardware-level coalescing, and severe execution bottlenecks.