How to Retrieve Texels Using GLSL imageLoad

The imageLoad function in GLSL allows shaders to read raw, unformatted texel data directly from specific coordinates within an image object without hardware filtering. This article explains the mechanics of how imageLoad operates, how it translates integer coordinates into memory addresses, handles data format conversions, and how its operation fundamentally differs from conventional texture sampling.

The Mechanics of imageLoad

Unlike the standard texture() family of functions, which rely on texture samplers, normalized floating-point coordinates [0.0, 1.0], and fixed-function filtering hardware (such as bilinear or trilinear interpolation), imageLoad interacts directly with image units bound to OpenGL textures or texture buffers.

The function operates through several distinct steps:

  1. Discrete Coordinate Addressing:
    imageLoad takes an image variable (e.g., image2D, iimage3D, uimage2DArray) and an integer coordinate vector (ivec2, ivec3, etc.). Because the coordinates are discrete integers, they correspond directly to texel indices rather than continuous spatial positions. The coordinate ivec2(0, 0) points explicitly to the first texel at the base of the specified mipmap level of the bound texture.

  2. Memory Address Calculation:
    The GPU calculates the exact memory offset for the target texel based on the bound texture's internal layout, stride, dimensions, and the supplied coordinates: \[\text{Offset} = (y \times \text{Stride}) + (x \times \text{BytesPerTexel})\] Because this bypasses traditional sampler state pipelines, no derivative calculations, mipmap level selections, or neighbor blending occurs.

  3. Format Unpacking and Conversion:
    Images in GLSL require an explicit format qualifier in the shader layout (for example, layout(rgba32f) or layout(r32ui)). When imageLoad reads the raw bytes from memory, the hardware unpacks the data into a four-component vector matching the image prefix:

    • Floating-point images (image*) return vec4.
    • Signed integer images (iimage*) return ivec4.
    • Unsigned integer images (uimage*) return uvec4.

    If the internal texture format has fewer than four channels (such as r32f), the missing channels are filled with default values: \((0, 0, 1)\) for RGB and alpha respectively, resulting in vec4(R, 0.0, 0.0, 1.0).

  4. Out-of-Bounds Handling:
    If the integer coordinates provided to imageLoad fall outside the dimensions of the image, the operation is safe from application crashes, but the returned value depends on driver implementation and context flags. If robustness extensions are active, out-of-bounds reads safely return a vector of zeros (vec4(0.0)). Otherwise, the return value is undefined.

Example Syntax

#version 450 core

// Binding the image with an explicit format layout
layout(binding = 0, rgba32f) readonly uniform image2D uInputImage;

void main() {
    // Define the exact texel coordinate to read
    ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy);

    // Retrieve the unfilitered texel directly from memory
    vec4 texel = imageLoad(uInputImage, texelCoord);

    // texel now contains the exact data stored at (texelCoord.x, texelCoord.y)
}

Key Differences from Standard Sampling

  • No Filtering: imageLoad performs a direct memory fetch; it cannot perform linear or anisotropic filtering.
  • No Normalized Coordinates: Coordinates must strictly match the texel grid dimensions.
  • Coherency and Synchronization: Unlike read-only samplers, imageLoad can read data that has been modified within the same pass by imageStore, provided memory barriers (such as memoryBarrierImage()) are properly deployed.