GL_ARB_bindless_texture 64-Bit Sampler Handles

The GL_ARB_bindless_texture extension fundamentally changes how OpenGL shaders access textures by replacing traditional texture unit bindings with direct 64-bit GPU memory handles. This overview explains how the extension exposes these 64-bit handles inside the OpenGL Shading Language (GLSL), detailing the syntax, type representations, conversions, and buffer storage mechanisms that enable limitless, direct texture referencing in shader programs.

Enabling the Extension in GLSL

To use bindless textures in GLSL, the shader must explicitly enable the extension at the top of the source file:

#version 450 core
#extension GL_ARB_bindless_texture : require

Enabling this extension alters the behavior of standard sampler types (such as sampler2D, samplerCube, and sampler2DArray) so that they are treated as first-class, 64-bit data values rather than opaque references tied to limited texture units.

Representation as 64-Bit Types

Under the hood, a bindless texture handle is a 64-bit unsigned integer generated on the CPU via glGetTextureHandleARB or glGetTextureSamplerHandleARB. Inside GLSL, this value is exposed in two primary ways:

  1. Native Sampler Types: Standard sampler variables (e.g., sampler2D) directly occupy 64 bits of storage.
  2. Integer Representations:
    • uvec2: If 64-bit integers are not natively available or enabled, GLSL represents the handle as a two-component 32-bit unsigned integer vector (uvec2).
    • uint64_t: If the GL_ARB_gpu_shader_int64 extension is supported and enabled, handles can be represented directly as native 64-bit unsigned integers.

Explicit Type Casting and Constructors

The extension defines constructors and explicit conversions between sampler types and their underlying integer representations. This allows handles to be passed or unpacked from generic integer formats:

  • From integers to samplers:

    // Constructing a sampler from a uvec2 handle
    uvec2 handleVec = ...;
    sampler2D mySampler = sampler2D(handleVec);
    
    // Constructing a sampler from uint64_t (with GL_ARB_gpu_shader_int64)
    uint64_t handleInt = ...;
    sampler2D mySampler64 = sampler2D(handleInt);
  • From samplers to integers:

    uvec2 handleVec = uvec2(mySampler);
    uint64_t handleInt = uint64_t(mySampler);

Storage in Uniform Buffers and Shader Storage Buffers

Traditionally, samplers could only be declared as loose uniform variables tied to texture image units. GL_ARB_bindless_texture lifts this restriction entirely. Because samplers are 64-bit values, they can be stored in:

  • Uniform Buffer Objects (UBOs)
  • Shader Storage Buffer Objects (SSBOs)
  • User-defined structs
  • Dynamic arrays and function parameters

For example, a Shader Storage Buffer Object can pass an unbounded array of texture handles directly to a draw call:

layout(std430, binding = 0) readonly buffer MaterialBuffer {
    sampler2D textures[];
} materials;

void main() {
    // Directly sample using a handle fetched from an SSBO
    vec4 color = texture(materials.textures[gl_DrawID], texCoords);
}

Layout Qualifiers: Bindless vs. Bound

The extension introduces interface layout qualifiers to control whether samplers behave as bindless handles or fall back to standard OpenGL bindings:

  • layout(bindless_sampler): Forces uniform samplers to be treated as 64-bit bindless handles. By default, enabling GL_ARB_bindless_texture makes all samplers bindless.
  • layout(bound_sampler): Reverts specific sampler uniforms to the legacy binding model, allowing traditional texture unit bindings (glBindTextureUnit) to be used alongside bindless handles in the same shader.