Role of uvec2 Handles in GLSL Bindless Textures
Bindless texturing in OpenGL eliminates the CPU overhead of binding
state to fixed texture units by representing textures directly through
unique 64-bit integer handles. In GLSL, the uvec2 type
serves as a fundamental, cross-platform container used to store, pass,
and unpack these 64-bit handles, especially when native 64-bit integer
support is absent or restricted. This article breaks down why the
two-component 32-bit unsigned integer vector is utilized, how it
interfaces with buffer layouts, and the methods used to cast it back
into usable texture samplers.
The Need for 64-Bit Handles
Standard OpenGL texturing requires binding a texture to an active
texture image unit before a draw call. The
GL_ARB_bindless_texture extension bypasses this bottleneck.
Instead of binding to a slot, the driver generates a globally unique
64-bit handle using glGetTextureHandleARB(). This handle
contains all internal metadata required by the GPU to sample the
texture, allowing applications to store arrays of thousands of texture
pointers in buffers.
Because these handles are 64 bits wide, the shader needs a representation that accommodates this memory footprint.
Why uvec2
Represents 64-Bit Handles
In standard GLSL, native 64-bit integers (uint64_t) are
not part of the core language and require the
GL_ARB_gpu_shader_int64 extension. Not all hardware or
driver implementations support 64-bit integer arithmetic in shaders,
even if they support bindless texturing.
The uvec2 type (a two-component vector of standard
32-bit unsigned integers) provides a universal solution:
- Bit-Width Matching: A
uvec2consists of two 32-bit integers, totaling exactly 64 bits (8 bytes), which matches the width of aGLuint64handle. - Buffer Alignment: Under standard GLSL layout rules
(such as
std140andstd430), auvec2is 8-byte aligned. This matches the native 8-byte alignment of 64-bit pointers on modern GPU architectures, preventing padding and alignment mismatches in Uniform Buffer Objects (UBOs) and Shader Storage Buffer Objects (SSBOs). - Portability: Shaders can ingest handles through
buffers as
uvec2arrays across virtually all modern hardware without declaring optional 64-bit integer extensions.
Unpacking and Sampler Reconstruction
The GL_ARB_bindless_texture extension overloads GLSL
sampler constructors to accept uvec2 representations
directly, as well as uint64_t values.
When a handle is stored in a buffer as a uvec2, the
lower 32 bits typically reside in the .x component and the
upper 32 bits in the .y component (depending on system
endianness). The GLSL extension provides explicit constructor functions
that interpret this layout:
#version 450
#extension GL_ARB_bindless_texture : require
layout(std430, binding = 0) readonly buffer MaterialData {
uvec2 textureHandles[];
};
layout(location = 0) in vec2 v_TexCoord;
layout(location = 0) out vec4 o_Color;
void main() {
// Retrieve the 64-bit handle as a uvec2
uvec2 handle = textureHandles[gl_DrawID];
// Reconstruct the sampler directly from the uvec2 handle
sampler2D mySampler = sampler2D(handle);
// Sample the texture normally
o_Color = texture(mySampler, v_TexCoord);
}If GL_ARB_gpu_shader_int64 is also enabled, functions
such as packUint2x32(uvec2) and
unpackUint2x32(uint64_t) can convert between a single
uint64_t and uvec2, but direct construction of
sampler types via sampler2D(uvec2) removes the need for
explicit arithmetic packing.
Summary
The uvec2 type acts as a universal 64-bit payload in
GLSL. It guarantees correct 8-byte memory layout in memory buffers,
circumvents the hardware requirement for native 64-bit integer math
pipelines, and directly feeds the sampler conversion constructors
defined by bindless texturing extensions.