Historical evidence, superseded guidance. This is the earlier article, preserved with its original workload, measurements and revision links. Its defaults, branch/deployment labels and recommendations are not current. Read the current recipe.
AVPlumber · Mixer optimization cookbook
DSK fades in one pass
Implementation links are pinned to develop at c20464a5 (2026-10-09). The mixer has changed since; this page was checked against develop at 8fa15c92 on 2026-10-13. Measurements retain their original workload and scope. Watch the current 192-input L4 demo.
Current scope. The single-pass alpha fade remains in the keyer. Its scope is DSK fades; it does not qualify wipe-clip preload or transition timing.
A downstream key fades by weighting its own alpha in the existing single keyer pass, so the mixer needs no second compositor and frames without a fade run exactly the old kernel code.
Status: ImplementedThe idea
Downstream keys are up to 4 alpha browser pages drawn over the finished program. The clean feed stays unkeyed. Until now a key could only cut. An M/E fade needs two compositors (A/B) because it mixes two full frames. A key fade mixes nothing new: it only scales one layer's coverage. That fits in the one pass the keyer already runs.
mul) of each 128-byte layer table entry the kernel already reads. The table keeps its size.clip((t−start)/dur), then shaped by the curve. The fade starts on the next program frame after the command. A reversal keeps the same pace, so it covers only the distance left. A fade settles on its target on the first frame within 1 ms of its end, so millisecond-rounded timestamps (at 60 fps) do not add a frame.At a glance
- WhatEach key fades in or out:
mixer.dsk {key, on, fade_seconds, curve}. Config defaults aredsk.fade_seconds(0.4; 0 cuts) anddsk.fade_curve(linear). Curves: linear, ease-in, ease-out, ease-in-out. The Web UI has a seconds field with a curve menu in the Keys card header, and a curve menu next to Fade in the take bar. - WhyKeys could only cut. A second compositor would copy the M/E's A/B design for no gain: a key fade needs one weight per layer, not a second full frame.
- LatencyProgram latency: +0 frames. The keyer stays one pass clocked by the program and adds no playout buffer. Command to screen: unchanged. The fade starts on the next program frame (≤ 1 frame, 40 ms at 25 fps), the same frame a cut would switch on. A fade then takes
fade_secondsto reach full level, by design. - Side effectsNone while
fade_secondsis 0: the command and the kernel are the old ones. A fade does not force a preview keyframe; a cut still does, because a cut changes the picture at once. A command fades only the keys it flips; another key's running fade continues. - Cost0 nodes, 0 queues, 0 threads, no new buffers. Two small headers (curve 84 lines, envelope 84 lines). One extra kernel entry: 80 registers, launched only on fade frames. On the T4 test host, a 1080×1920 16-tile composite plus a key takes 0.139 ms per frame, of a 40 ms frame budget. That is the whole pass, not the fade's extra cost.
- ProofOn the T4 test host, the three existing entries compile to byte-identical machine code:
composite_planes,_yuvand_packed4keep 16,578 / 2,530 / 2,770 SASS lines and 79 / 58 / 64 registers. All composite tests pass, including 5 fade cases: opacity 0.5, 0.02, 0.97, premultiplied, and HLG 10-bit. - RiskA fading-out key is still drawn until its level reaches 0. A faded layer that is not a blended RGBA source draws opaque, with a one-time warning. If the program PTS goes backwards, the fade restarts from the current level instead of jumping. Fixed on the way: toggling one key cleared every key's held frame from the control thread, which raced the render thread and could blank keys that stayed on air.
- Switch it offSet
dsk.fade_secondsto 0 (the default is 0.4). A 0-secondmixer.dsksends exactly the old cut command. With no key below full opacity, the host picks the old kernel entries.
Where in the code
- src/mixer/primitives/fade_curve.hpp: the four curves; source the same curves as an FFmpeg expression for the M/E fade, which transition_control.cpp wraps around
clip((t−start)/dur). - src/mixer/primitives/key_fade.hpp:
KeyFade::retarget(a fade or a cut, reversal from the current level); source the level per program frame; source a fade settles within 1 ms of its end. - src/nodes/hwaccel/cuda_rect_scale.cu: the
kOpacitytemplate flag; source thecomposite_planes_opacityentry. - src/nodes/hwaccel/cuda_rect_table.h: the table's
mulfloat that carries the opacity. - src/nodes/hwaccel/cuda_rect_draw.cpp: detects a faded RGBA layer; source picks the kernel entry per frame.
- src/nodes/mixer_keyer.cpp:
stepKeyFades, one envelope step per key per program frame; source a key is drawn only above level 0; source thefade_inputscommand; source the race fix. - pyplumber/mixer/dsk.py:
mixer.dsk; a 0-second fade sends the oldactive_inputscut. - pyplumber/mixer/gui/assets/index.html: the key-fade seconds field in the Keys card header; source sends it with the curve.
- Docs: config.md · dsk (fields
fade_secondsandfade_curve, and themixer.dskcommand).
Source links identify the reviewed code, independently of deployment state. The kernel was verified on the T4 test host.