2 Textures and Images
dxtr edited this page 2026-10-11 14:52:57 +02:00

Textures and Images

Textures are read through their samplers, filtered or not. Storage images are read and written texel by texel. Both are declared as uniforms; see Uniforms and Resources. Their types are listed in Types.

A texture or image can be passed to a shader function as a parameter, but cannot be stored in a variable, computed with, or returned:

(define-function tinted ((image :sampler2d) (uv :vec2) (tint :vec4))
  (* (sample image uv) tint))

What a texel is

Texture type Texel
:sampler... :vec4
:isampler... :ivec4
:usampler... :uvec4
:sampler...-shadow with :compare :float, the filtered comparison result from 0 to 1

The same holds for storage images: :image... gives :vec4, :iimage... :ivec4, and :uimage... :uvec4.

cl-gsl knows which texture kinds GLSL can read in each way, and says so when one does not fit, rather than leaving glslang to reject the GLSL:

GLSL cannot read a :SAMPLER-CUBE this way.  It reads 1d, 2d, 3d, 1d-array,
2d-array, buffer, 2d-ms, 2d-ms-array textures this way.

Coordinates

Texture shape sample and gather coordinates fetch coordinates
1D :float :int
1D array :vec2: x, layer :ivec2
2D :vec2 :ivec2
2D array :vec3: x, y, layer :ivec3
3D :vec3 :ivec3
Cube :vec3, a direction not fetched
Cube array :vec4: direction, layer not fetched
Buffer not sampled :int
Multisampled not sampled as 2D or 2D array, with :sample

Filtered coordinates run from 0 to 1 across each dimension. Integer coordinates count texels from 0.

sample: filtered reads

(sample texture coordinates &key bias level ddx ddy offset compare)
(sample image uv)                              ; level from derivatives, in a fragment shader
(sample image uv :level 2)                     ; from mipmap level 2
(sample image uv :bias 1 :offset (ivec2 1 0))  ; one level blurrier, one texel to the right
(sample image uv :ddx (dfdx uv) :ddy (dfdy uv)); level from explicit derivatives
(sample shadow-map uv :compare depth)          ; a :sampler2d-shadow, giving a :float
Key Meaning
(none) In a fragment shader, the mipmap level follows from how fast the coordinates change. In any other stage it is level 0.
:bias A float added to that level. Fragment shaders only.
:level A float that chooses the level outright.
:ddx, :ddy The changes of the coordinates to the next pixel across and down, which choose the level as derivatives do.
:offset An :int or :ivecN of constants, moving the texel read by that many texels.
:compare For a shadow texture: the depth to compare with.

An :isampler... or :usampler... is read with sample too; its sampler must not filter linearly.

Multisampled and buffer textures are read with fetch.

fetch: unfiltered reads

(fetch texture texel &key level sample offset)
(fetch atlas (ivec2 x y) :level 0)     ; one texel, integer coordinates
(fetch samples (ivec2 x y) :sample 3)  ; sample 3 of a multisampled texture
(fetch lookup i)                       ; a buffer texture
Key Meaning
:level The mipmap level, an :int, 0 by default.
:sample For a multisampled texture, which sample, an :int. It takes no level.
:offset An :int or :ivecN of constants.

A buffer texture takes neither a level nor a sample. Reading outside the texture is undefined.

gather: the four texels filtering would blend

(gather texture coordinates &key component offset compare)
(gather image uv)                     ; component 0 (red) of each of the four texels
(gather image uv :component 3)        ; their alpha
(gather shadow-map uv :compare depth) ; four comparison results, a :vec4

component is a constant from 0 to 3. The result is a vector of four.

Queries

Form Gives
(texture-size texture [level]) The size in texels at level (0 by default): width, height and depth, as many as it has, then an array's layer count, as an :int or :ivecN. Multisampled and buffer textures take no level.
(texture-levels texture) The number of mipmap levels, an :int.
(texture-samples texture) The samples per texel of a multisampled texture, an :int.
(texture-query-lod texture coordinates) The mipmap level sample would read at coordinates, without its array layer, as a :vec2: the level the sampler chooses from, then the level relative to the base. Fragment shaders only.

Storage images

Storage images are declared with a :format and an :access; see Uniforms and Resources.

Form Does
(image-load image texel &key sample) Reads the texel at integer coordinates, as for fetch.
(image-store image texel value &key sample) Writes value, a :vec4, :ivec4 or :uvec4 by the image's type.
(image-size image) The size in texels, and an array's layer count, as an :int or :ivecN.
(image-samples image) The samples per texel of a multisampled image.

A multisampled image takes :sample, an :int, in image-load and image-store.

(define-shader invert (:compute :workgroup-size (8 8))
    (&uniform (picture :image2d :format :rgba8-unorm :access :read-write))
  (let ((texel (ivec2 (swizzle (global-invocation-id) :xy))))
    (when (all (< texel (image-size picture)))
      (let ((c (image-load picture texel)))
        (image-store picture texel (vec4 (- 1 (swizzle c :xyz)) (w c)))))))

Components are named x, y, z and w for colours too.

Writes to an image become visible to other invocations after memory-barrier-image; see Compute Shaders.

Derivatives

Implicit-level sample depends on derivatives, and so do dfdx, dfdy and fwidth. All are fragment-shader only, and undefined where neighbouring fragments take different branches. To sample in a branch, compute the derivatives outside it and pass :ddx and :ddy, or choose a :level.

(let ((dx (dfdx uv)) (dy (dfdy uv)))
  (if (> mask 0.5)
      (sample image uv :ddx dx :ddy dy)
      (vec4 0)))