1 Threads
dxtr edited this page 2026-10-14 04:36:31 +02:00

Threads

An atlas, its pages and its regions may be used from any number of threads at once.

  • One lock guards an atlas's pages and its cache. Adding an image, looking a key up, taking a page's changes and copying a page's texels each hold it for as long as they take.
  • Regions never change, so they may be kept and shared freely.
  • Images are made outside the lock. 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, such as the workers of a job pool rasterizing glyphs. Only packing the finished image takes the lock.
  • A key may be made twice. If two threads ask for the same new key at once, both run their function; the first image packed is kept, and the second thread gets the same region, its own image dropped. Make the function free of other side effects.
  • Uploading while others pack. copy-page-region reads under the lock, so it never sees half an image. Take a page's changes before copying, as Uploading Pages does, and an image packed in between is in the copy or in the next changes.

Making images on a job pool

With cl-threads, the workers of a pool pack images into one atlas:

(asdf:load-system :cl-threads)

(defpackage #:example
  (:use #:cl)
  (:local-nicknames (#:atlas #:cl-atlas)
                    (#:th #:cl-threads)))
(in-package #:example)

(defun square (size &optional (value 255))
  "Return an image of SIZE by SIZE texels of one byte, each VALUE."
  (make-array (list size size) :element-type '(unsigned-byte 8) :initial-element value))

(th:with-job-pool (pool)
  (let ((atlas (atlas:make-atlas :page-size 256)))
    ;; A thousand requests for squares of 1 to 32 texels: each size is
    ;; packed once, however many workers ask for it.
    (th:parallel-for pool 0 1000
                     (lambda (start end)
                       (loop for request from start below end
                             do (let ((size (1+ (mod request 32))))
                                  (atlas:atlas-find-or-add atlas size
                                                           (lambda () (square size)))))))
    (list (atlas:atlas-page-count atlas)
          (loop for size from 1 to 32 count (atlas:atlas-find atlas size)))))
;; => (1 32)

Where the squares go on the page depends on which worker packs first, so it differs from run to run; which squares are there does not.