Skip to Content
Core conceptsHow a frame is rendered

How a frame is rendered

You do not need this page to use Premation. You need it to understand why certain classes of bug do not happen here, and to predict what a change will do.

The pipeline

Scene graph + animation engine │ buildSnapshot(graph, animation, time, …) RenderSnapshot a flat, immutable description of one frame │ snapshotToFrameScene() FrameScene renderer-native draw list │ Renderer.render(viewport, scene) Render graph passes clear → background → composition → selection → overlay WebGPU or WebGL2 backend

The snapshot is the seam

buildSnapshot samples the animation engine at one time and produces a plain data structure describing that frame. Everything downstream draws from it:

  • the viewport,
  • the secondary view panes in 2-up and 4-up layouts,
  • library and template card previews,
  • the export preview in the export dialog,
  • the offline export loop itself,
  • the pixel-comparison test harness.

This is why “the export doesn’t match the preview” is not a class of bug that can exist here. They are not two renderers that have been kept in sync — they are one renderer called from six places.

Backend tiers

The engine chooses its tier at startup, walking the whole adapter → device → configure path with a delayed retry at each rung:

  1. WebGPU if the full path succeeds.
  2. WebGL2 otherwise.
  3. If both fail, a visible error — never a blank canvas.

The tier it landed on is shown in the viewport header. There is also a Canvas2D backend, used for library and template card previews, which is why those cards show real frames rather than static images.

Canvas2D previews skip GPU-only effects. A library card is an accurate preview of geometry and timing, not necessarily of every filter.

Determinism

Frame time is exactly index / fps — never wall-clock. Two renders of the same project produce identical frames, and a re-render after a crash resumes identical work. Particles are deterministic for the same reason: the same frame always produces the same field.

See Determinism.

Design rules you can rely on

These are enforced in the codebase and they show up in how the app behaves:

  • The scene graph is the source of truth. A node’s transform lives in its transform component; nothing else holds a shadow copy.
  • Every mutation is a command. Direct writes would bypass undo, and undo is the feature people notice being broken first.
  • Pure where it can be. Math, interpolation, geometry, muxing and encoding are pure functions with unit tests. Only the parts that genuinely need a canvas or a GPU touch one.
  • No silent degradation. If a code path cannot do what was asked, it raises an error with a reason you can act on. Producing a plausible-looking wrong result is treated as worse than failing.

That last rule is why video export refuses to write a zero-frame file, why the renderer fails loudly when the GPU backend did not initialise, and why the info readout shows a placeholder instead of an invented colour.

Last updated on