4 Textures and Samplers
dxtr edited this page 2026-10-18 16:56:34 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Textures and Samplers

Creating a texture

(gfx:create-texture device :width 512 :height 512 :format :rgba8-srgb
                           :mip-levels :all :usage '(:sampled :copy-dst))

(gfx:create-texture device :width 1280 :height 720 :format :depth32-float
                           :usage '(:render-target))

(gfx:create-texture device :width 256 :dimension :cube :format :rgba16-float
                           :usage '(:render-target :sampled))
Key Default Meaning
:width required Width in texels.
:height 1 Height in texels. A :1d texture has none, and a cube takes its :width.
:depth 1 Depth in texels, for a :3d texture only.
:array-layers 1 Number of layers, for array textures only.
:dimension :2d The shape (below).
:mip-levels 1 A count, or :all for every level down to one texel.
:samples 1 1, 2, 4 or 8. More than 1 makes a multisampled texture.
:format required A texture format (below).
:usage required What the texture is used for (below).

Sizes and layer counts are 32-bit unsigned integers from 1, and a mip level is from 0 to 31, as write-texture and read-texture take it.

Dimensions

:dimension Shape Sized by
:1d a row of texels :width
:2d a rectangle :width, :height
:3d a box :width, :height, :depth
:cube six square faces, sampled by direction :width
:1d-array, :2d-array layers of the above plus :array-layers
:cube-array layers of cubes plus :array-layers

:1d, :1d-array and :3d textures cannot be render targets.

Usages

Usage Lets the texture be
:render-target a color or depth attachment of a render pass
:sampled a :texture binding, read by shaders through a sampler
:storage a :storage-texture binding, read and written texel by texel
:copy-src read back with read-texture
:copy-dst written with write-texture

Formats

Kind Formats
8-bit colour :rgba8-unorm, :rgba8-srgb, :bgra8-unorm, :bgra8-srgb, :r8-unorm, :rg8-unorm
Float :r32-float, :rg16-float, :rg32-float, :rgba16-float, :rgba32-float
Unsigned integer :r32-uint, :rg16-uint, :rgba8-uint, :rgba16-uint, :rgba32-uint
Signed integer :r32-sint
Depth :depth32-float, :depth24-stencil8
  • sRGB formats store sRGB-encoded texels. Shaders read them decoded to linear values and write linear values that get encoded. srgb-format-p tells you whether a format is one.
  • Integer formats are read by shaders as they are, unfiltered, through integer sampler types such as GLSL's usampler2D. integer-format-p tells you whether a format is one.
  • Depth formats are used as depth attachments, and can be sampled, for example for shadows through a comparing sampler. depth-format-p tells you whether a format is one.
  • texel-size returns the size of one texel in bytes.

See Formats and Types for the full table.

Writing texels

(gfx:write-texture texture data &key (x 0) (y 0) (z 0) width height depth (level 0))

data is a vector, of bytes or of anything else write-buffer takes, such as single-floats for :rgba32-float, or a foreign pointer. It holds the texels of the region, layer by layer (or slice by slice for a :3d texture), rows from top to bottom without padding, each texel as its format lays it out, for example red, green, blue, alpha for :rgba8-unorm. The texture needs :copy-dst.

The region defaults to the whole mip level. :z and :depth count:

  • slices of a :3d texture;
  • layers of an array texture;
  • faces of a cube, six per cube, in the order +X, −X, +Y, −Y, +Z, −Z.

texture-layer-count returns how many layers :z counts for a texture.

;; A 2 by 2 checkerboard
(let ((image (gfx:create-texture device :width 2 :height 2 :format :rgba8-srgb
                                        :usage '(:sampled :copy-dst))))
  (gfx:write-texture image (make-array 16 :element-type '(unsigned-byte 8)
                                          :initial-contents '(255 255 255 255    0   0   0 255
                                                                0   0   0 255  255 255 255 255)))
  image)

;; A cube map, face by face
(let ((sky (gfx:create-texture device :width 512 :dimension :cube :format :rgba8-srgb
                                      :mip-levels :all :usage '(:sampled :copy-dst))))
  (dotimes (face 6)
    (gfx:write-texture sky (face-texels face) :z face :depth 1))
  (gfx:generate-mipmaps sky))

When the write takes effect works as it does for :device buffers: during a frame it is staged and copied before the frame's passes, without waiting; between frames it waits for the GPU. You may reuse data as soon as the call returns. A wrong data length, or a region outside the level, signals an error that gives the expected sizes.

Mipmaps

Make a texture with :mip-levels :all (or a count), fill level 0 by writing or drawing into it, then call generate-mipmaps. It fills each level after the first by filtering down from the one before. During a frame it runs on the GPU without waiting; between frames it waits.

Depth textures and multisampled textures cannot have their mipmaps generated.

To write or read a specific level, pass :level to write-texture or read-texture. To draw into a level, pass :level to the attachment in with-render-pass.

Multisampling

(let ((samples (gfx:create-texture device :width 640 :height 480 :format :rgba8-unorm
                                          :samples 4 :usage '(:render-target)))
      (image (gfx:create-texture device :width 640 :height 480 :format :rgba8-unorm
                                        :usage '(:render-target :sampled))))
  (gfx:with-render-pass (pass frame :color ((samples :clear '(0 0 0 1) :resolve image)))
    (gfx:set-pipeline pass pipeline)   ; made with :samples 4
    ...))

A multisampled texture is :2d or :2d-array, with one mip level; any other dimension is refused, with a message that says which. It is drawn into, a layer at a time for an array, and resolved into a texture of one sample, with the same format and size and :render-target usage, when the pass ends; the pipeline must be made with the same :samples. With :sampled usage, shaders read it a sample at a time, as a :sampler2d-ms or :sampler2d-ms-array does; it cannot be sampled with filtering or mipmapped. With :storage usage, shaders write it a sample at a time, where the device has the :multisample-storage-textures feature.

Reading texels back

(gfx:read-texture texture &key (level 0) (element-type '(unsigned-byte 8)))
;; => a vector of ELEMENT-TYPE

Returns every texel of a mip level, laid out as write-texture takes them, as bytes or as the :element-type you ask for, as read-buffer takes it:

(gfx:read-texture hdr-image :element-type 'single-float)  ; :rgba32-float texels

It waits for the GPU, so call it between frames; during a frame it signals an error. The texture needs :copy-src. It is meant for tests and screenshots.

Accessors

texture-width, texture-height, texture-depth, texture-array-layers, texture-dimension, texture-mip-levels, texture-samples, texture-format, texture-usage, and texture-layer-count (layers as :z and a pass's :layer count them: 6 per cube, so 6 × texture-array-layers for a :cube-array).

Destroying

destroy-texture marks the texture destroyed at once and releases it once no frame in flight uses it. Don't destroy the textures acquire-surface-texture returns: the surface owns them.

Samplers

A sampler says how a shader reads a texture. It is paired with a texture in a :texture binding of a bind group.

(gfx:create-sampler device)                                   ; linear, clamped
(gfx:create-sampler device :filter :nearest)                  ; pixel art
(gfx:create-sampler device :address-mode :repeat :mipmap-filter :linear
                           :max-anisotropy 16)                ; tiled ground
(gfx:create-sampler device :compare :less-equal)              ; shadow maps
Key Default Values
:filter :linear :linear blends the nearest texels; :nearest takes the nearest one.
:address-mode :clamp-to-edge Outside 0 to 1: :clamp-to-edge, :repeat or :mirror-repeat.
:mipmap-filter :nearest :nearest reads the nearest mip level; :linear blends the two nearest.
:compare nil A compare function, such as :less-equal, makes a comparing sampler for shadow maps: reading a depth texture through it compares a depth with the texels.
:max-anisotropy 1 1 to 16. Sharpens textures seen at a slant. Every device has anisotropic filtering.

Accessors: sampler-filter, sampler-address-mode, sampler-mipmap-filter, sampler-compare, sampler-max-anisotropy. Destroy with destroy-sampler.