Texture atlases for Common Lisp: packs small images such as glyphs or icons into pages, caches them by key, and tracks which regions changed for upload.
  • Common Lisp 100%
Find a file
dxtr d7176bae36
All checks were successful
CI / test (push) Successful in 1m5s
Move the pins on the workspace's libraries
To the latest commits of cl-threads, the API review's fixes, which this
library's own commits of the review follow.
2026-10-19 01:27:59 +02:00
.forgejo/workflows Run on SBCL only 2026-10-17 04:37:04 +02:00
src Declare the arguments' full domain types 2026-10-18 16:18:58 +02:00
tests Declare the arguments' full domain types 2026-10-18 16:18:58 +02:00
.gitattributes Keep LF line endings in every checkout 2026-10-16 15:49:08 +02:00
.gitignore Pin the dependencies with stift in place of qlot 2026-10-16 10:53:49 +02:00
cl-atlas.asd Add cl-atlas: images packed into pages, cached by key 2026-10-11 02:49:55 +02:00
CLAUDE.md Follow the CLAUDE.md template's types rules 2026-10-18 16:19:55 +02:00
LICENSE Add cl-atlas: images packed into pages, cached by key 2026-10-11 02:49:55 +02:00
README.org Run on SBCL only 2026-10-17 04:37:04 +02:00
stiftfile Move the pins on the workspace's libraries 2026-10-19 01:27:59 +02:00

cl-atlas

Texture atlases for Common Lisp: many small images, such as glyphs or icons, packed into a few large pages, which a renderer uploads as textures and draws from. cl-atlas packs the images, caches them by any key, and tells which part of each page changed since it was last uploaded. It knows nothing of GPUs or text.

(asdf:load-system :cl-atlas)

(defvar *atlas* (cl-atlas:make-atlas :page-size 512 :channels 1))

;; An image is a 2D array of octets, rows by bytes.
(defun square (size)
  (make-array (list size size) :element-type '(unsigned-byte 8) :initial-element 255))

;; Packed the first time a key is asked for, the same region after.
(let ((region (cl-atlas:atlas-find-or-add *atlas* '(:square 12) (lambda () (square 12)))))
  (list (cl-atlas:page-index (cl-atlas:region-page region))
        (cl-atlas:region-x region) (cl-atlas:region-y region)
        (multiple-value-list (cl-atlas:region-uv region))))
;; => (0 0 0 (0.0 0.0 0.0234375 0.0234375))

;; Before drawing: upload what changed in each page.
(dolist (page (cl-atlas:atlas-pages *atlas*))
  (multiple-value-bind (x y width height) (cl-atlas:take-page-changes page)
    (when x
      (list x y width height (cl-atlas:copy-page-region page x y width height)))))

Atlases

make-atlas takes :page-size, the width and height of every page in texels (1024 by default); :channels, the bytes of a texel, 1 for coverage such as glyphs' or 4 for colour; and :padding, the empty texels kept right of and below each image (1 by default), so that filtering one image never reads its neighbour. :data rides along on the atlas, as atlas-data, such as the font whose glyphs it holds.

  • atlas-add packs an image and returns its region. An image is a 2D array of octets indexed by row, then byte: each row holds its texels from left to right, :channels bytes each. :data rides along on the region, such as a glyph's offsets from its origin.
  • atlas-find-or-add packs the image a function makes the first time a key is asked for, comparing keys with equal, and returns the same region every time after. atlas-find only looks. It allocates nothing and keeps no reference to the key, so a key allocated on the stack (dynamic-extent) looks a region up without allocating.
  • atlas-clear forgets every page and region.

Images are packed by a skyline packer into the first page with room. When every page is full, the atlas adds a page. Pages never grow and regions never move, so what a renderer recorded about a region, such as its texture coordinates, stays right.

Regions and pages

A region is an image's place: region-page, region-x, region-y, region-width and region-height in texels from the page's top left, and region-uv as texture coordinates from 0 to 1. An empty image gets a region with no page.

A page is page-size texels square, page-channels bytes each, and page-index is its place among atlas-pages. page-atlas is the atlas it belongs to, which a page keeps alive, so a renderer that holds a page reaches the atlas's data. Before drawing, a renderer asks each page what changed: take-page-changes returns the rectangle covering every image packed since it was last asked, or NIL, and copy-page-region reads a rectangle's texels row by row without padding, as a texture upload takes them. Give it a vector to reuse and it allocates nothing.

Threads

An atlas may be used from any number of threads at once: one lock guards its pages and cache. The function atlas-find-or-add calls to make a missing image runs without the lock, so threads can make images at the same time; if two make one for the same key, the first packed is kept.

Errors

Errors are atlas-error conditions, such as an image larger than a page or an image whose rows are no whole number of texels.

Tests

stift test

Licence

MIT. See LICENSE.