Buffer Reference and 64-Bit Integer Casting in GLSL
The GL_EXT_buffer_reference extension introduces C-style
raw pointer semantics to GLSL, allowing shaders to access memory through
GPU virtual addresses. A fundamental mechanism of this extension is the
bidirectional casting between buffer reference types and 64-bit unsigned
integers (uint64_t). This article explains the syntax for
casting between addresses and buffer references, how to implement
pointer arithmetic using integer conversions, and the alignment
constraints necessary to prevent undefined behavior.
Prerequisites and Type Declarations
To use buffer references and 64-bit integer types, shaders must
enable both GL_EXT_buffer_reference and an extension that
provides 64-bit integer support, such as
GL_EXT_gpu_shader_int64 or
GL_ARB_gpu_shader_int64:
#extension GL_EXT_buffer_reference : require
#extension GL_EXT_gpu_shader_int64 : requireA buffer reference is declared similarly to a standard uniform or
shader storage buffer object (SSBO), but with the
buffer_reference layout qualifier:
layout(buffer_reference, std430) buffer Node {
int data;
uint64_t nextNodeAddress;
};Under the hood, any variable of type Node occupies 64
bits of storage, representing a memory address pointing to an instance
of that block layout.
Casting Mechanisms
Casting between buffer references and 64-bit integers relies on GLSL constructor-style explicit type conversions.
Reference to 64-Bit Integer
To retrieve the raw 64-bit memory address from a buffer reference
variable, cast the reference directly to uint64_t:
Node myNode = ...;
uint64_t address = uint64_t(myNode);This operation yields the physical GPU virtual address where the referenced structure resides. It allows shader code to serialize references into standard data fields or transmit addresses between shaders.
64-Bit Integer to Reference
Conversely, a raw 64-bit unsigned integer can be cast back into a typed buffer reference using the reference type's name as a constructor:
uint64_t rawAddress = ...;
Node reconstitutedNode = Node(rawAddress);
// Accessing fields through the reconstituted reference
int value = reconstitutedNode.data;Once cast, the variable behaves as a pointer to memory formatted according to the layout specified in the reference block definition.
Pointer Arithmetic Emulation
GLSL does not support native pointer arithmetic operators (such as
+, -, ++, or --)
directly on buffer reference types. To traverse contiguous arrays or
offset memory manually, operations must be performed using integer
arithmetic:
- Cast the buffer reference to
uint64_t. - Add or subtract the offset measured strictly in bytes.
- Cast the calculated address back to the target buffer reference type.
layout(buffer_reference, std430) buffer Element {
float value;
};
Element getElementAtIndex(Element basePtr, uint index) {
uint64_t baseAddr = uint64_t(basePtr);
uint64_t byteOffset = uint64_t(index) * sizeof(float); // 4 bytes per float
return Element(baseAddr + byteOffset);
}Alignment and Dereferencing Constraints
While casting between arbitrary 64-bit integers and buffer references is syntactically valid, dereferencing the resulting pointer imposes strict requirements:
- Natural Alignment: By default, buffer references
assume an alignment of 16 bytes. If an address converted from a
uint64_tdoes not meet this alignment, dereferencing it causes undefined behavior. - Explicit Alignment
(
buffer_reference_align): Shaders can specify custom alignments using thebuffer_reference_alignlayout qualifier:
layout(buffer_reference, std430, buffer_reference_align = 4) buffer CompactData {
int value;
};When converting a uint64_t to CompactData,
the address must be a multiple of 4 bytes before any read or write
operation takes place.
- Null Pointers: An address value of
0is treated as a null pointer. Casting0to a buffer reference is legal, but attempting to read from or write to a null reference results in undefined behavior or GPU faults.