pqtrthe pipeline

HOW A NEGATIVE BECOMES A FRAME

One fused kernel,
not a chain of passes.

aces declares its stages as typed parts, each naming the colour state it operates in. The aggregator plans the state transitions, inserts the sealed transforms, and compiles the lot into a single WGSL dispatch.

Colour states

Every part names the state it runs in. The aggregator routes between them; a part never converts.

state what it is
aces.ap0 ACES2065-1 — the negative's own primaries. The pipe's start_state.
aces.ap1 ACEScg — the working space where grading happens.
display display-referred RGB, after the Output Transform. The display_state.

The sealed pipe

Not parts, not editable, inserted by the aggregator. This is what makes the result ACES-correct rather than merely ACES-shaped.

id in → out call
xf.ap0_to_ap1 aces.ap0aces.ap1 ap0_to_ap1($c)
xf.ap1_to_ap0 aces.ap1aces.ap0 ap1_to_ap0($c)
output.rrt+odt aces.ap0display output_transform($c)

The parts, in pipe order

Every parameter is a prefill, not a lock — a look supplies values, the editor still exposes them.

# part state what it does gated vs
1 exposure aces.ap1 master brightness, in stops standard
2 colour-balance aces.ap1 ASC-CDL — lift / gain / power, each per-channel, plus saturation. An exact port of OCIO's GradingPrimary LIN forward OCIO
3 grading-tone aces.ap1 tonal-region trims — highlights, shadows, whites, blacks provisional
4 vibrance aces.ap1 saturation, weighted toward the less-saturated provisional
5 hsl aces.ap1 per-band hue / sat / luma trim across eight bands provisional
Output Transform aces.ap0display sealed — RRT + ODT
6 apply-look display the look's colour cube, display-sRGB → display-sRGB vibe
7 tonecurve display the look's 1-D luma curve vibe

Every part is point-only and bakeable — which is what lets the whole colour path fuse into one dispatch. Parts 1–5 are scene-referred, before the tone map; parts 6–7 are the look slot, after it. Tone and colour stay separate entries by invariant: tone is never folded into the cube. lmt.gamut-compress is declared in the model ahead of them all, gated against OCIO, but is not in the shipped view's part list.

What the kernel binds

Two assets, both authored offline by vibe, both bound by slot.

slot 0 · look look_n³ × 3

The display colour cube, baked by vibe's bakeCube.

slot 1 · curve 256 samples

The 1-D luma curve, from vibe's toneCurve.

Past the display seam

post has its own param namespace and a declared passthrough pipe — no colour states, no Output Transform.

Its first part is clarity: bilateral local contrast on luma with chroma preserved, gated against darktable-cli's iop/bilat.c. It is the one part that is not point-only, and strength = 0 is an exact identity. Sharpen, texture, grain, vignette, dehaze and a geometry stage are planned. Noise reduction is deliberately not here — sensor noise belongs to dneg.

Direction versus frame

The recipe carries two kinds of value, and confusing them is what breaks a portable look.

belongs to examples
direction the look — travels in a pack key, the CDL, grading-tone, hsl, cube + curve
frame this negative only — never travels shotEv, the white-balance trim

exposure.ev is the sum of the two at render: the frame's own exposure plus the look's key — where key is relative to the target's own auto-exposure. That is precisely why a look re-renders on any negative instead of copying one frame's brightness onto another.

Invariants

The rules that keep the kernel composable.

  1. A part names a state and a call. It never converts between states — the aggregator does.
  2. A missing effect is added to aces, never to vibe. vibe contributes no runtime mechanism; it only prefills parameters and bakes assets.
  3. The sealed pipe and the Output Transform are not parts and are not editable.
  4. Order is the pipe order. Scene-referred grade → Output Transform → display look slot.

The authority for all of the above is aces/etc/model/parts.json. If a page disagrees with it, the JSON is right and the page is stale — read the source documents under Docs.