Prequel
← All posts
Rendering5 min read

Geometry is computed once

A preview and an export that disagree are only ever noticed after the file is written. The fix is boring: one module owns every position, and neither rasteriser is allowed to re-derive one.


Prequel draws the same frame twice. Once in the editor, in WebGL, sixty times a second while you drag a slider. Once in the exporter, in Metal, as fast as the machine will go. They have to agree.

The failure mode when they do not is unusually cruel: everything looks right while you work, and the disagreement only surfaces in a file you have already told someone about.

One module owns every position

buildRenderPlan takes the project settings and emits a flat list of primitives in absolute output pixels — this rectangle here, that rounded quad there, this shadow with this blur. The preview draws that list. The exporter deserialises the same list and draws it.

Neither side computes a position. Not "the camera sits 6% in from the left" — that arithmetic happened once, upstream, and both rasterisers received the answer.

The rule sounds obvious written down. It is not what you get by default. The default is a preview written in TypeScript because that is where the canvas is, an exporter written in Rust because that is where Metal is, and two functions called cameraRect that were the same on the day they were written.

What is still allowed to differ

Antialiasing and gradient interpolation. Two rasterisers will not produce byte-identical edges, and chasing that would be a poor use of a week.

What they cannot differ on is where things are. That distinction is the whole point: a half-pixel of edge softness is invisible, and a camera in the wrong corner is the only thing anyone will see.

Fractions, not pixels

A second rule falls out of the first. Every geometry setting is stored as a fraction of the frame's shorter edge — never in pixels.

Store the camera's inset as 64 px and it is a sensible margin in 1920×1080 and a quarter of the width in 1080×1920. Store it as 0.06 of the shorter edge and the look you set in landscape survives the switch to vertical. Given that people export the same take to YouTube and to Shorts, the pixel version breaks on the second export, every time.

The shorter edge specifically, rather than the width or the diagonal, because it is the dimension that constrains the composition in both orientations. Padding measured against the width vanishes when the frame goes tall.

How it is tested

Not by rendering a frame and checking its dimensions. Shape assertions — width, height, duration, frame count — pass happily on output that is completely wrong.

The tests assert properties of the plan: that no primitive's geometry leaves the frame, at any aspect ratio, for any settings. And where pixels matter, they assert pixels — render a frame, read it back, check that the colour at a specific coordinate is the one that belongs there.

A test that would pass on a black frame is not a test.