4 Buffers
dxtr edited this page 2026-10-14 19:25:01 +02:00

Buffers

A buffer is a block of GPU memory: vertices, indices, uniforms, storage for compute shaders, or texels read through a texel buffer binding.

Creating

(gfx:create-buffer device :size 72 :usage '(:vertex))
(gfx:create-buffer device :size 1024 :usage '(:uniform) :memory :upload)
(gfx:create-buffer device :size (* 4 count) :usage '(:storage :copy-src))
Key Meaning
:size The size in bytes, at least 1.
:usage A non-empty list of what the buffer is used for (below).
:memory :device (the default) or :upload (below).

Usages

Usage Lets the buffer be
:vertex a vertex buffer, with set-vertex-buffer
:index an index buffer, with set-index-buffer
:uniform a :uniform-buffer binding of a bind group
:storage a :storage-buffer or :read-only-storage-buffer binding
:uniform-texel a :uniform-texel-buffer binding
:storage-texel a :storage-texel-buffer binding
:copy-src read back with read-buffer
:copy-dst a copy destination; write-buffer does not need it

An unknown usage signals an error that lists the valid ones.

Memory

:memory Lives in Written Use for
:device fast GPU memory through a staging copy meshes, static data, storage
:upload memory the CPU writes directly at once, without allocating uniforms and anything else that changes every frame

An :upload buffer is not double-buffered for you. A frame in flight may still read what you overwrite. Give each frame in flight a region of its own:

(defconstant +uniform-size+ 256)   ; a multiple of gfx:+uniform-offset-alignment+

(let ((uniforms (gfx:create-buffer device :size (* +uniform-size+ (gfx:device-frames-in-flight device))
                                          :usage '(:uniform) :memory :upload)))
  ;; Each frame:
  (let ((offset (* +uniform-size+ (gfx:frame-slot frame))))
    (gfx:write-buffer uniforms matrix :offset offset)
    ;; and bind that range: one bind group per slot, made with
    ;; (list 0 uniforms :offset offset :size +uniform-size+)
    ))

Writing

(gfx:write-buffer buffer data &key (offset 0) size)

data is one of:

  • a (simple-array single-float (*))
  • a (simple-array double-float (*))
  • a (simple-array (unsigned-byte 8) (*)), (unsigned-byte 16) or (unsigned-byte 32)
  • a foreign pointer, which needs :size

:offset is in bytes, by default 0. :size writes only that many bytes from the start of data; by default the whole vector is written. Writing past the end of the buffer signals an error.

(gfx:write-buffer vertices (make-array 6 :element-type 'single-float
                                         :initial-contents '(0.0 -0.5 -0.5 0.5 0.5 0.5)))
(gfx:write-buffer indices (make-array 3 :element-type '(unsigned-byte 16)
                                        :initial-contents '(0 1 2)))
(gfx:write-buffer instances more-instances :offset (* 16 instance-count))

When the write takes effect:

  • :upload buffer: at once, by the CPU. Nothing allocates.
  • :device buffer, during a frame: the data is copied into the frame's staging ring, and the GPU copies it into place before the frame's passes. Nothing waits, and in steady state nothing allocates.
  • :device buffer, between frames: the write waits for the GPU. Good for loading.

Either way you may reuse data as soon as write-buffer returns.

Reading back

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

read-buffer returns size bytes from offset, by default to the end. It waits for the GPU to finish writing the buffer, so call it between frames; during a frame it signals an error. The buffer needs :copy-src. It is for tests and tools, not for every frame.

:element-type is any element type write-buffer takes: single-float, double-float, or (unsigned-byte 8), 16 or 32. The range must hold a whole number of elements.

(gfx:read-buffer values :element-type 'single-float)   ; => #(0.0 1.0 4.0 9.0 ...)

Destroying

destroy-buffer marks the buffer destroyed at once and releases it once no frame in flight uses it. Using it afterwards signals destroyed-error.

Accessors

buffer-size, buffer-usage, buffer-memory, and from device-object, object-device and object-alive-p.