Decompose 64-Bit Integer into Two 32-Bit in GLSL

In modern OpenGL Shading Language (GLSL), processing 64-bit unsigned integers (uint64_t) often requires decomposing them into pairs of standard 32-bit unsigned integers (uint) for storage in standard vector types (uvec2), inter-stage communication, or compatibility with hardware lacking native 64-bit interpolation. This article explains how to manually extract the high and low 32-bit components from a 64-bit integer using bitwise operations.

Prerequisites

To use 64-bit integer types in GLSL, you must enable the GL_ARB_gpu_shader_int64 extension or use a core GLSL version that natively supports 64-bit integers (version 4.5+ with the appropriate feature set). Add the following directive to the top of your shader:

#version 450 core
#extension GL_ARB_gpu_shader_int64 : enable

The Manual Decomposition Method

A 64-bit unsigned integer consists of 64 contiguous bits, where bits 0–31 represent the lower 32 bits and bits 32–63 represent the higher 32 bits. You can extract these segments manually using a bitwise shift and masking.

uvec2 decomposeUint64(uint64_t value) {
    // Extract the lower 32 bits by masking or direct casting
    uint low = uint(value & 0xFFFFFFFFul);

    // Extract the upper 32 bits by shifting right by 32 bits
    uint high = uint(value >> 32ul);

    return uvec2(low, high);
}

Step-by-Step Breakdown

  1. Extracting the Low 32 Bits:
    Applying the bitwise AND operation (&) with the 64-bit literal 0xFFFFFFFFul isolates the first 32 bits of the integer while zeroing out bits 32 through 63. Converting the result to a standard uint truncates the 64-bit container down to a 32-bit representation. In GLSL, uint(value) also implicitly discards the upper 32 bits, making uint low = uint(value); a valid shorthand.

  2. Extracting the High 32 Bits:
    The right-shift operator (>> 32ul) moves bits 32–63 into the positions of bits 0–31. The vacated upper bits are filled with zeros because the operation is performed on an unsigned type. Casting the shifted result to uint drops any unused remaining bits, yielding the upper 32-bit integer.

  3. Packaging the Result:
    The resulting components are commonly stored in a uvec2, where the .x component holds the lower 32 bits and the .y component holds the upper 32 bits (little-endian order convention).

Built-in Alternative

While the manual bit-shifting approach works across any custom bit manipulation pipeline, the GL_ARB_gpu_shader_int64 extension also exposes the built-in function unpackUint2x32(uint64_t value). This intrinsic achieves the same result directly in hardware, returning a uvec2 where .x contains the lower 32 bits and .y contains the upper 32 bits. Manual decomposition is typically preferred when implementing custom packing layouts or targeting intermediate compilation targets.