Table of Contents
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:
:uploadbuffer: at once, by the CPU. Nothing allocates.:devicebuffer, 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.:devicebuffer, 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.