GLSL imageStore Behavior for Raw Texels

This article provides an overview of the imageStore function in GLSL, detailing how it writes raw texel data into image variables. It covers function mechanics, type-matching rules, component truncation, format conversions, out-of-bounds behavior, and the memory coherency requirements necessary to ensure written values are read correctly across shader invocations.

Function Signature and Core Mechanism

In OpenGL Shading Language (GLSL), imageStore is a built-in function used to perform random-access writes to image variables without blending or filtering. Its general signature is:

void imageStore(writeonly gimageIMAGE_PARAMS image, ivecCOORD_PARAMS P, gvec4 data);

The function takes an image unit, an integer coordinate vector matching the dimensionality of the target image, and a four-component vector (vec4, ivec4, or uvec4) containing the raw texel value to be written. The data type of the input vector must match the underlying scalar type of the image format: floating-point images require vec4, signed integer images require ivec4, and unsigned integer images require uvec4.

Component Truncation and Unused Channels

Regardless of the target image format, imageStore always expects a four-component vector as the payload. When writing to formats with fewer than four channels, excess components are discarded:

  • Single-channel formats (e.g., r32f, r8): Only data.r (or data.x) is written. Components y, z, and w are ignored.
  • Two-channel formats (e.g., rg16f, rg32ui): Only data.rg (or data.xy) are written. Components z and w are ignored.
  • Three-channel formats (where supported) and four-channel formats: Components are mapped directly to the available channels in order (R, G, B, A).

Data Conversion and Quantization

The raw values passed to imageStore undergo conversion according to the memory format qualifier defined on the image variable in the shader (or the bound texture's internal format):

  • Floating-Point Formats (rgba32f, rgba16f): Values are stored directly or rounded to the appropriate floating-point precision (e.g., 32-bit values are converted to half-precision floats for 16-bit targets).
  • Normalized Formats (rgba8, r16_snorm): Floating-point values are converted to fixed-point integers. Unsigned normalized formats (unorm) clamp input values to the range [0.0, 1.0] before scaling to integer space. Signed normalized formats (snorm) clamp input values to the range [-1.0, 1.0].
  • Pure Integer Formats (rgba32i, rgba32ui, r8ui): Raw integer values are written without normalization. If the target format has fewer bits than the shader integer (e.g., writing to r8ui), values are truncated to the target bit depth.

Out-of-Bounds Behavior

If the coordinate P passed to imageStore falls outside the valid dimensions of the target image, the write operation is discarded. Unlike standard memory writes in native C/C++, writing out of bounds via imageStore does not cause memory corruption or a hardware fault; it acts as a safe no-op.

Memory Coherency and Visibility

Writes performed by imageStore are not automatically coherent with subsequent memory reads. Because the graphics pipeline executes work concurrently across cache domains:

  1. In-Shader Reads: If an invocation writes data with imageStore and later reads it via imageLoad, the variable should be declared with the coherent memory qualifier.
  2. Pipeline Barriers: For subsequent draw calls, compute dispatches, or texture sampling operations to see texels written by imageStore, an explicit memory barrier must be issued on the host side (such as glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) or GL_TEXTURE_FETCH_BARRIER_BIT) or within the shader via memoryBarrierImage().