Table of Contents
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.
Uploading Pages
cl-atlas keeps every page in memory as bytes. A renderer gives each page a texture, and before drawing uploads what changed in it. Each page remembers the rectangle that changed since it was last asked, so only new images are uploaded, and a frame in which nothing was packed uploads nothing.
Each frame
Before drawing from an atlas, for each page:
- Ask
take-page-changes. It returns the column, row, width and height of the rectangle that changed, and forgets it; ornilfour times when nothing changed. It allocates nothing when nothing changed. - If the page has no texture yet, make one of
page-sizebypage-sizetexels, of a format ofpage-channelsbytes, and upload the whole page into it. A new texture's texels are undefined until written, and filtering reads the padding around each image, which no change covers: the whole page brings its padding, empty, along. - Otherwise, if something changed, read the rectangle's texels with
copy-page-regionand upload them to the same place in the texture.
Ask for the changes before copying: an image packed meanwhile, on another thread, is then in the copy or in the next frame's changes, never lost.
(atlas:take-page-changes page) => x, y, width, height ; or NIL, NIL, NIL, NIL
(atlas:copy-page-region page x y width height &optional into) => octets
copy-page-region returns the texels row by row from the top, without
padding between rows, page-channels bytes each: what a texture upload
takes. With into, a vector of octets long enough, it copies into that and
returns it, allocating nothing; the vector may then be longer than the
texels, so pass their length, width × height × page-channels, on to
the upload. Without into, it returns a new vector of exactly the texels.
Then draw each region with its page's texture and region-uv.
With cl-gfx
cl-gfx makes the textures and uploads
the changes with gfx:write-texture:
(asdf:load-system :cl-atlas)
(asdf:load-system :cl-gfx/vulkan)
(defpackage #:example
(:use #:cl)
(:local-nicknames (#:atlas #:cl-atlas)
(#:gfx #:cl-gfx)))
(in-package #:example)
(defun upload-atlas (device atlas textures format)
"Bring TEXTURES, an adjustable vector of the textures of ATLAS's pages by
page index, up to date: make a texture of FORMAT on DEVICE for each new
page, and upload what changed in each page since the last call. Call on
the render thread, before drawing from ATLAS."
(dotimes (index (atlas:atlas-page-count atlas))
(let ((page (atlas:atlas-page atlas index)))
(multiple-value-bind (x y width height) (atlas:take-page-changes page)
(cond ((= index (length textures))
;; A new texture's texels are undefined: upload the whole
;; page, with the empty padding that filtering reads.
(let ((size (atlas:page-size page)))
(vector-push-extend (gfx:create-texture device :width size :height size
:format format
:usage '(:sampled :copy-dst))
textures)
(gfx:write-texture (aref textures index)
(atlas:copy-page-region page 0 0 size size))))
(x
(gfx:write-texture (aref textures index)
(atlas:copy-page-region page x y width height)
:x x :y y :width width :height height)))))))
(gfx:with-device (device :backend :vulkan)
(let ((atlas (atlas:make-atlas :page-size 256))
(textures (make-array 0 :adjustable t :fill-pointer 0)))
(atlas:atlas-add atlas (make-array '(8 8) :element-type '(unsigned-byte 8)
:initial-element 255))
(upload-atlas device atlas textures :r8-unorm) ; once a frame
(prog1 (length textures)
(map nil #'gfx:destroy-texture textures))))
;; => 1
- The format follows
atlas-channels::r8-unormfor one byte,:rg8-unormfor two,:rgba8-unormor:rgba8-srgbfor four. cl-gfx has no format of three bytes a texel, as WebGPU has none, so give colour atlases four channels. - Allocation.
gfx:write-texturetakes a vector of exactly the rectangle's texels, whichcopy-page-regionwithoutintomakes. That allocates only in a frame where images were packed; in steady state,upload-atlasallocates nothing. - Threads. Images may be packed on other threads while the render
thread uploads:
copy-page-regionreads a page under the atlas's lock, so it never sees half an image. See Threads.
Drawing a region
region-uv gives a region's corners as texture coordinates of its page.
Draw a quad of region-width by region-height texels, scaled as you
need, with those coordinates, from the texture of (atlas:page-index (atlas:region-page region)). An empty image's region has no page: draw
nothing for it.
After clearing
atlas-clear forgets the pages, and the pages after it count from 0
again. Destroy the textures of the old pages and empty the vector, so that
each new page gets a texture of its own, uploaded whole, and forget the
regions kept from before the clear.