Skip to content

fix(vision mixer): keep per-pixel alpha on keyed pads with an alpha-less output_format - #748

Draft
wagenet wants to merge 2 commits into
Eyevinn:mainfrom
wagenet:wagenet/fix-keyed-alpha-output-format
Draft

fix(vision mixer): keep per-pixel alpha on keyed pads with an alpha-less output_format#748
wagenet wants to merge 2 commits into
Eyevinn:mainfrom
wagenet:wagenet/fix-keyed-alpha-output-format

Conversation

@wagenet

@wagenet wagenet commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

The bug

The vision mixer's output_format property offers alpha-less formats (I420, NV12, RGB, ...). Setting one on the CPU path destroyed the per-pixel alpha of every keyed pad:

  • a DSK graphic's transparent areas composited as the graphic's own black instead of the program picture — and DSK is a downstream keyer;
  • the multiview overlay covers the whole canvas, so the multiview went entirely black except for the overlay's own graphics.

There is a standing incentive to set the property: unpinned RGBA into vtenc_h264_hw negotiates AYUV64 rather than NV12.

Cause

Two places, one mechanism.

The builder inserted output_format capsfilters on the DSK chains and on the overlay appsrc chain, flattening alpha before the compositor saw it. Removing those alone does not fix it — measured, not assumed. compositor blends in its own output format and converts every sink pad to that format first, so pinning an alpha-less format on the compositor's src caps makes its sink pads alpha-less too. Directly:

compositor ! video/x-raw,format=NV12   # RGBA input: not-negotiated
compositor ! videoconvert ! video/x-raw,format=NV12   # RGBA input: key survives

The hazard was already known in this file — the border underlays pin RGBA with a comment saying an alpha-less output_format "would silently drop the alpha of #RRGGBBAA border colors" — but the conclusion was never applied to the compositor's own output.

Fix

When a compositor has keyed pads and output_format cannot carry alpha, blend in the alpha-carrying counterpart of the requested format (A420, A422, A422_10LE, or RGBA — matching the requested chroma subsampling) and convert to output_format after the mixer. output_format still pins what leaves the block; it no longer dictates the blend space.

The dist compositor gets this when DSK inputs or PiP border underlays exist; the multiview always does, since its overlay is a full-canvas RGBA pad.

The DSK and overlay capsfilters are dropped. The video-input capsfilters stay — their tee feeds two independently negotiating compositors, which is what they are for.

builder/pipeline_gpu.rs never referenced output_format and is unchanged.

Tests

New backend/tests/vision_mixer_keyed_alpha_test.rs builds a real flow through PipelineManager (following vision_mixer_fx_test.rs): a white program input, a DSK graphic that is opaque red on the left half and fully transparent on the right, output_format=NV12, CPU backend. It asserts the transparent half still reads the white background, that the multiview shows picture once the overlay renderer has actually pushed frames, and that PGM still negotiates NV12. A second test runs the same flow with output_format=Auto as the never-broken baseline.

Two things the measurement has to get right, both of which produced a green test that guarded nothing until fixed. The multiview reading is gated on a buffer probe counting overlay appsrc pushes, so it cannot pass by measuring a multiview the overlay has not reached yet. And readiness is detected by colour, not brightness: an all-black startup frame is dark in exactly the way an opaque-keyed failure is dark, so the run waits for the graphic's opaque half to actually be red before measuring.

Verified by reverting the fix and re-running — the guard goes red on both halves:

fix reverted with fix
DSK transparent half, fraction white 0.000 1.000
multiview picture, fraction bright 0.000 0.144
PGM negotiated format NV12 NV12

Run locally on macOS (Apple silicon, GStreamer 1.28.6): the full cargo test suite, all green, including the new test and vision_mixer_fx_test. Nothing was skipped in the new test — it needs compositor (gst-plugins-base) and videobox (gst-plugins-good), both already in the CI package list on every platform, and it has no skip path. Not run: anything requiring hardware absent from this machine (NVIDIA/CUDA paths).

🤖 Generated with Claude Code

wagenet and others added 2 commits September 2, 2026 19:26
…ess output_format

The CPU path forced `output_format` on the compositor and on every keyed
input feeding it. `compositor` blends in its own output format and converts
each sink pad to that format first, so an alpha-less choice (NV12, I420,
RGB, ...) flattened the alpha of DSK graphics, the multiview overlay and the
border underlays: a transparent DSK region composited as the graphic's own
black, and the multiview showed nothing but the overlay.

Blend in the alpha-carrying counterpart of the requested format (A420, A422,
A422_10LE or RGBA) whenever a compositor has keyed pads, and convert to
`output_format` after the mixer. The property still pins what leaves the
block. The DSK and overlay capsfilters are gone — they only stripped alpha
before the compositor could use it. The video-input capsfilters stay: their
tee feeds two independently negotiating compositors and needs the pin.

The GPU path never referenced `output_format` and is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The readiness loop waited for the opaque half of the DSK graphic to go dark,
which an all-black startup frame also satisfies — so the run could measure a
frame composited before anything had arrived. That is what failed on Linux CI
while passing locally; an opaque-keyed failure is dark in exactly the same way.

Wait for that half to actually be red, and assert on the fraction of red and
white pixels rather than mean luma. Readings go to 0.000/1.000 instead of
luma thresholds with a 30-point margin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant