Rether / docs
engine/Buffer
gardenRaw bytes for the GPU, and the memory they have to live in

Buffer

updated 2026.09.01created 2026.07.23~5 min

The concept

A buffer is a block of raw memory the GPU can read: a flat, contiguous run of bytes with no structure of its own. Nothing inside it says where one element ends and the next begins — the shader that reads it supplies that interpretation.

That is what separates a buffer from an image. An image is typed: it has a format, a width and a height, and a memory arrangement the hardware chooses so that sampling neighbouring pixels is fast. A buffer is just bytes in a row. Size is not the difference — a buffer can be a few dozen bytes or hundreds of megabytes.

What a buffer holds

The same block of bytes takes on a role from the way the GPU is told to read it:

  • Vertex — the attributes of each vertex laid end to end: position, normal, texture coordinates, whatever the mesh carries.
  • Index — the order in which those vertices are assembled into triangles, so a vertex shared by several triangles is stored once and referenced many times.
  • Storage — a larger block a shader may read and write, for work that produces data rather than pixels.
  • Uniform — a small block that stays the same for every vertex and every pixel of a draw: the camera matrices, the colour of a light, the factors of a material.

Declaring what it is for

A buffer is not a neutral allocation. It has to be told at creation which of those uses it will be put to, and the declaration is binding: the driver reads it to decide which memory the buffer may live in and how it must be aligned. There is no repurposing afterwards — a buffer meant for something else is a new buffer.

Where the memory lives

This is the part that shapes everything else. GPU memory comes in kinds, and two of them matter here:

  • Host-visible — memory the CPU can map and write into directly. Convenient, but on a discrete card it sits on the far side of the bus, so the GPU reads it slowly.
  • Device-local — the memory physically attached to the GPU. Fast for the GPU, and on a discrete card the CPU cannot address it at all.

On a machine with unified memory — an integrated GPU, a console, an Apple Silicon laptop — the two largely coincide and the tension disappears. On a discrete card it does not, and everything below is a consequence.

Staging

Staging is not a kind of data, it is the answer to the problem above: how do you fill memory the CPU cannot write to?

You write the bytes into a host-visible buffer first, then ask the GPU to copy that buffer into a device-local one. The copy runs on the GPU, where both ends are cheap to reach. The intermediate buffer is scaffolding — once the copy is done it serves no further purpose and is released.

The detour is worth it for data uploaded once and then read for many frames, a mesh being the obvious case. For data rewritten every frame it usually is not: a uniform block of a few dozen bytes is better left in host-visible memory, where the CPU can overwrite it in place and no copy is needed at all.

Rewriting a buffer the GPU is still reading

The CPU runs ahead of the GPU. By the time it writes the camera matrices for frame 4, the GPU may still be reading frame 3's — out of the same buffer, if there is only one. The new values land halfway through the draw and the frame comes out wrong: intermittently, and differently depending on load.

The fix is not to make the CPU wait, which would give up the head start that makes the pipeline fast. Instead the buffer is duplicated, one copy per frame the engine allows in flight, and each frame writes only to its own. The CPU never touches memory the GPU has not finished with.

So the update frequency of a buffer matters more than its contents. Data uploaded once needs none of this; data rewritten every frame needs all of it.

Layout and padding

The shader and the code that fills the buffer must agree, byte for byte, on where each field starts. They do not agree by default.

Hardware imposes alignment rules on the fields inside a uniform block, and the common one is that a three-component vector still takes the room of four. A field declared as three floats is followed by four bytes of padding that nobody wrote and the shader will not read. Pack the fields tightly on the CPU side and everything after that first vector shifts, so the shader reads its neighbour's bytes.

Nothing catches this. There is no type checking across the boundary and no error at any point; the values simply come out wrong. The usual defence is to write the padding out in the open — declare the field with four components even when only three carry meaning — so the two layouts cannot drift apart unnoticed.

Relation with other engine concepts

  • Graphics context: buffers are carved out of the device memory the context owns, and the queues it exposes are what carry a staging copy.
  • glTF: the format has buffers of its own — raw bytes, sliced into views and interpreted by accessors. The same idea one level up, on disk rather than on the GPU.
  • Vulkan descriptor pool: a shader never reaches a uniform buffer directly. It is pointed at by a descriptor, and descriptors are handed out by a pool.

Keyboard

search this vault⌘K / /
step the sidebarjk
open selected↵
switch browse mode123
switch vaultgv
toggle themet
close / clearesc
this help?
esc to close