Raw Memory Indexing with Byte Address Buffers in DirectX
Byte address buffers in DirectX provide a mechanism for untyped, raw memory access, bypassing standard graphics pipeline format conversions. This article explores how these buffers operate under the hood, how High-Level Shader Language (HLSL) addresses memory using direct byte offsets, the mandatory 4-byte alignment constraints, and how developers leverage intrinsic load and store operations to pack and unpack arbitrary data structures directly on the GPU.
In standard DirectX shader programming, resources like structured
buffers or typed textures enforce strict data formatting. When a shader
accesses a structured buffer, the hardware calculates memory locations
automatically based on the size of the defined structure and the array
index. Byte address buffers—declared in HLSL as
ByteAddressBuffer for read-only access or
RWByteAddressBuffer for read-write access—strip away this
abstraction. Instead of viewing memory as an array of structs or pixels,
the shader sees the resource as a contiguous block of raw bytes.
Raw indexing operates by providing an explicit byte offset rather than an element index. When executing a read operation, the shader specifies the precise byte position from the start of the buffer. For example, instead of requesting index 2 of a custom struct, the shader manually computes the offset in bytes (such as byte 64) and reads the raw binary data present at that exact memory location.
Despite the name "byte address buffer," modern GPU hardware processes memory in 32-bit words rather than individual 8-bit bytes. Consequently, DirectX enforces a strict alignment rule: all memory addresses supplied to byte address buffer operations must be multiples of 4 bytes (DWORD alignment). Attempting to load or store from an address that is not divisible by 4 yields undefined behavior on most GPU architectures.
To read and write data, HLSL provides specialized intrinsic functions that operate on 32-bit scalar or vector quantities:
Load(offset): Reads a single 32-bit unsigned integer (uint) from the specified byte offset.Load2(offset),Load3(offset),Load4(offset): Read two, three, or four sequential 32-bit unsigned integers starting at the offset, mapping up to 128 bits in a single call.Store(offset, value)and its vector variants (Store2,Store3,Store4): Write raw 32-bit unsigned integers into the buffer at the given byte location.
Because these intrinsics natively yield or accept unsigned 32-bit
integers (uint), raw indexing relies on HLSL's
bit-reinterpretation functions. Developers use asfloat(),
asint(), or bit-shifting operations to interpret the
returned raw bits as floating-point numbers, signed integers, or packed
formats (such as 16-bit half-floats or 8-bit color channels).
Byte address buffers also support atomic operations via HLSL
intrinsics like InterlockedAdd,
InterlockedMin, and
InterlockedCompareExchange. These functions accept raw byte
offsets, allowing concurrent shader threads to synchronize and
manipulate specific words within the unstructured memory block
safely.
By bypassing type systems and runtime stride definitions, raw memory indexing through byte address buffers offers maximum flexibility. This capability is widely utilized in advanced rendering techniques, such as GPU-driven rendering pipelines, indirect draw argument generation, variable-rate geometry processing, and custom acceleration structure traversals where data formats vary dynamically.