How GLSL textureOffset Samples Adjacent Texels

In GLSL, the textureOffset function enables shaders to sample neighboring texels by applying an exact, integer-based texel shift to a base coordinate without requiring manual normalization calculations. This article explains how textureOffset operates, why it bypasses the need to convert pixel distances into fractional normalized values, and the hardware-level constraints developers must observe when using it.

The Problem with Manual Coordinate Normalization

Standard texture sampling functions, such as texture(), require coordinates to be normalized within the [0.0, 1.0] range. When a shader needs to sample adjacent texels—such as during edge detection, blur passes, or convolution filters—a developer using texture() must explicitly calculate the size of a single texel:

vec2 texelSize = 1.0 / vec2(textureSize(u_sampler, 0));
vec4 adjacentSample = texture(u_sampler, uv + vec2(1.0, 0.0) * texelSize);

This approach requires knowledge of the texture dimensions (often queried via textureSize or passed as a uniform) and incurs extra floating-point arithmetic in the shader pipeline.

How textureOffset Solves This

The textureOffset function modifies the sampling location by adding an exact integer texel offset directly to the coordinate before filtering:

vec4 textureOffset(gsampler2D sampler, vec2 P, ivec2 offset);
  • P: The base texture coordinate (still normalized between 0.0 and 1.0).
  • offset: An integer vector (ivec2, ivec3) representing the number of texels to shift.

When executed, the GPU hardware applies the offset in unnormalized texel units inside the texture sampling unit. For example:

// Samples exactly one texel to the right of the base coordinate
vec4 rightTexel = textureOffset(u_sampler, uv, ivec2(1, 0));

The shader does not need to know the width or height of the texture, nor does it need to normalize the step size. The underlying hardware automatically accounts for the texture dimensions to locate the adjacent texel.

Hardware Execution and Filtering

Unlike texelFetch, which operates strictly on unnormalized integer coordinates and disables texture filtering, textureOffset preserves the sampler’s filtering state:

  1. Sub-Texel Precision: If the base coordinate P falls between texels, bilinear or trilinear interpolation remains active. The offset simply shifts the interpolation window by the specified number of discrete texels.
  2. Dedicated Hardware Units: Modern graphics hardware contains dedicated address-generation units that can add small integer offsets to sample addresses without generating additional shader ALU instructions.

Constraints on textureOffset

While textureOffset simplifies neighbor sampling, it imposes strict operational limitations:

  • Compile-Time Constant Offsets: The offset argument must be a compile-time constant expression. It cannot be derived from dynamic shader variables or uniform values.
  • Limited Offset Range: Because hardware texture address units allocate a limited number of bits for immediate offsets, the range of the integer offset is constrained. OpenGL guarantees a minimum range defined by implementation-dependent constants, typically between GL_MIN_PROGRAM_TEXEL_OFFSET (-8) and GL_MAX_PROGRAM_TEXEL_OFFSET (+7).

For dynamic offsets or distances exceeding this range, developers must compute coordinates manually using normalized arithmetic or switch to unnormalized lookups with texelFetch.