kinetograph.
Internals

Engine

How a frame moves from the project through the DOM engine and the GPU compositor to the encoder, and where each part lives.

Kinetograph has two halves. Node runs your project and turns each frame into a scene. A Rust addon, loaded into Node, draws that scene on the GPU. Frame data passes between them as JSON, and the pixels stay in Rust.

How one frame is made

1. Bundle

The CLI bundles the project with esbuild. Imports of remotion and @remotion/* point at @kinetographdev/core, so a Remotion project runs unchanged.

2. Run the project

Kinetograph first installs a shim that makes Date, Math.random, timers and requestAnimationFrame follow the frame number, not the clock. Then the React reconciler renders the component tree for that frame.

A HyperFrames page takes the same path. Its GSAP timelines are moved to the frame's time before each frame.

3. Split into layers and DOM islands

Kinetograph's own elements, such as Rect and Text, become scene layers. Each run of plain HTML and SVG becomes a DOM island and goes to the addon as JSON. Canvas 2D and WebGL contexts record their calls, and the calls become one layer.

4. Lay out and paint the DOM

The addon builds a document for each island. Stylo, the CSS engine from Firefox, works out the styles. Taffy lays out the boxes, and Parley shapes the text. The result is painted into scene layers. Anything the painter cannot draw is reported for that frame.

5. Composite on the GPU

wgpu draws the layer tree. Skia draws vector shapes, text and Canvas 2D. WGSL passes do effects, and naga translates GLSL shaders. A 3D renderer draws meshes and glTF models, and ThorVG draws Lottie.

6. Encode

Each finished frame goes to FFmpeg on another thread, while the GPU draws the next one. The default is libx264 on every platform, with x264's own keyframe interval; the VideoToolbox encoder on macOS is opt-in with --encoder. Audio is rendered offline with the Web Audio API and mixed into the file at the end.

Rules the engine keeps

  • A frame depends only on the project and the frame number. The same frame gives the same pixels every time.
  • No browser is in the render path, including for Canvas 2D and WebGL.
  • Unsupported content is drawn without, or approximated, and reported. --strict makes it an error. See Remotion compatibility.

Speed

Three mechanisms skip work that has not changed: a cache for unchanged layers, reuse of a frame that equals the last one, and edits to a DOM island instead of rebuilding it. The output is the same with them on or off.

render --concurrency runs worker threads. Every worker is a full renderer, so memory grows with the number of workers. On the launch film each one uses 0.5 to 1 GB.

Measure speed with kinetograph bench on your own machine. A number from one project does not apply to another.

Where the code is

PathWhat it is
crates/coreThe scene format and the compositor.
crates/domThe DOM engine: CSS, layout, paint and SVG.
crates/node@kinetographdev/native, the addon Node loads.
packages/reactThe reconciler and the native elements.
packages/coreRemotion's API, the frame loop and the determinism shim.
packages/compatBrowser shims for the DOM and web APIs, and doctor.
packages/audioWeb Audio rendering, mixing and loudness analysis.
packages/cliThe kinetograph CLI and the MCP server.
packages/studiokinetograph studio, the local editor.

The full design is in ENGINE.md. Known gaps are in What works.

On this page