Skip to content

Buffers and uniforms

Every SDF shape is one fixed-stride record in a single storage buffer. The fragment shader reads it by instance index.

FieldHolds
pos_roundingposition in xyz, rounding in w
size_typesize in xyz, type ID in w
rotation + hColorrotation, and highlight colour as one word
materialfour bit-field words: main colour, outline colour, and two property words
glow_color, glow_intensity, glow_radiusglow
packed_reservedeffect bits, below
modifier … modifier8glass options, read only by glass
modA modB modCthe modifier mask and parameters, on every shape — see Modifiers

A colour is four bytes in one unsigned word, not four floats. Several small options share one word as bit-fields. Nothing is compressed — the record is a fixed 64 floats, 256 bytes, whatever the shape uses; a shape that is not glass never reads its 32 glass slots.

Do not hardcode the stride anywhere. It has grown repeatedly as slots were added, and it is declared in more than one place — see below.

Small per-shape options live in bit ranges rather than their own floats:

[fastAA:6][noMerge:1][grainType:1][grainScale:8][grain:8][reserved:8]

noMerge is set when an outline passes merge: false. fastAA carries the <Fast> antialias width, quantised to six bits — 64 levels, visually smooth.

When adding an option, take bits from the reserved ranges rather than growing the record. Growing it means touching every declaration of it.

The shape record is declared in the main SDF shader and again in the grid compute shader, which reads positions and sizes to insert shapes into cells.

The grid’s copy must match the main one byte for byte, including padding it never reads. If the two disagree, the grid reads fields at the wrong offsets and files shapes into the wrong cells.

That failure is silent. Nothing crashes and nothing errors — scene-level effects simply behave as though shapes were somewhere other than where they are drawn. Any change to the record must be mirrored in both declarations.

One render uniform buffer is created at boot and never recreated. It carries camera matrices, viewport and pointer state, and the scene parameters.

It is read by the SDF, mesh, text, particle, axes and gizmo draws alike. Two consequences:

  • It is stable. Bind groups referencing it never go stale, even when other buffers are destroyed and recreated. This is what lets a particle layer keep its bind group across an SDF buffer rebuild — see Particles.
  • It is shared. A field added for one subsystem is visible to all of them, and the buffer is written once per frame with a single global snapshot. A particle layer that needs its own style values gets its own uniform buffer rather than a second global write.