1 Stores
dxtr edited this page 2026-10-11 17:48:41 +02:00

Stores

A store holds the shapes a renderer draws, packed into the data of a curve texture and a band texture. Most programs make one store and keep it for as long as they draw.

Making a store

(cl-slug:make-store &key (epsilon 1/1024) (max-rows 4096)) => store
  • epsilon: how far a shape's bands overlap, in ems. A pixel on the boundary of two bands then finds every curve that reaches it, whatever the shader's rounding. The default suits shapes about an em in size.
  • max-rows: the most rows either texture may have. 4096 is the tallest 2D texture every Vulkan device takes. A device that takes taller ones allows up to 65536, the most that the 16-bit places in the band texture reach. Adding a shape that would need more signals a slug-error.

Each row is 4096 texels. The curve texture holds about one texel per curve, and the band texture a few per curve, so a store of the default size holds millions of curves: every glyph of several large fonts.

Adding shapes

(cl-slug:store-add store curves &key (fill-rule :nonzero)) => shape

Adds the shape made of curves and returns it. See Shapes and Curves for the curves, and Shape Readers for what the shape tells. Adding the same curves twice adds them twice: use store-find-or-add to share.

(cl-slug:store-find-or-add store key function) => shape

Returns the shape added for key, compared with equal. The first time key is asked for, it calls function with no arguments for the curves, adds them, and remembers the shape under key. function may return the fill rule as a second value; it is :nonzero if not.

(cl-slug:store-find-or-add store (list font glyph)
                           (lambda () (glyph-curves font glyph)))

function runs outside the store's lock, so threads adding different shapes do not wait for one another. Two threads asking for the same new key at once may both call function; both get the shape the first one added, and the other's curves are dropped.

(cl-slug:store-find store key) => shape or nil

Only looks: the shape store-find-or-add added for key, or nil. Shapes from store-add have no key.

(cl-slug:store-shape-count store) => integer

How many shapes have been added, by either function, empty ones included.

Reading what to upload

(cl-slug:store-curve-rows store) => integer
(cl-slug:store-band-rows store) => integer

How many rows of each texture hold data: how tall the textures must be. A partly filled last row counts.

(cl-slug:take-curve-changes store) => first-row, row-count   or nil
(cl-slug:take-band-changes store) => first-row, row-count    or nil

The rows written since the function was last called, from the first changed row to the last row with data, or nil if none were. Each call forgets the changes it returns, so call each once a frame, before drawing. A row already uploaded may be written again as more shapes fill it: it is then among the changes again.

(cl-slug:copy-curve-rows store row count) => (simple-array (unsigned-byte 16) (*))
(cl-slug:copy-band-rows store row count) => (simple-array (unsigned-byte 16) (*))

A fresh copy of count rows from row, as a GPU API takes texture data: rows top to bottom, texels left to right, channels in order. Curve rows have four 16-bit integers a texel, the bits of half floats; band rows two. Rows below the data are zeros.

See Textures and Uploading for a frame's upload.

Threads

Every function above may be called from any thread, at once with any other. The store holds one lock, taken briefly to look up a key, place a shape, or read data. Shapes are immutable and may be read anywhere.

Lifetime

A store is plain Lisp data: it holds no foreign memory and needs no release. Drop it and the garbage collector takes it. There is no way to remove a shape, since shapes never move; make a new store to start afresh.