Skip to content

fix(vision mixer): keep the FX slot's pool across passthrough so wipes don't stall - #1013

Closed
srperens wants to merge 2 commits into
mainfrom
fix/fx-wipe-passthrough
Closed

srperens wants to merge 2 commits into
mainfrom
fix/fx-wipe-passthrough

Conversation

@srperens

@srperens srperens commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

Since #1007 (idle FX slots in passthrough), a classic wipe on the GitHub macOS runner reads as a hard cut: wipe_between_letterboxed_sources_animates fails 2 of 2 with classic wipe: animated=false end white=0.00 red=0.76. Before the perf merges (main at f8a83b8 and 48cd16e) it passed on the same runner; locally it passes.

Mechanism: entering passthrough called reconfigure_src(), so the slot renegotiated and, in passthrough, do_bufferpool released its GL output pool. Leaving passthrough on the wipe's first buffer renegotiated again and sent a serialized ALLOCATION query from the fx_take branch's streaming thread into the mixer. When that query is slow (virtualized GL on the runner), the branch stalls while the mixer keeps compositing the last unmasked frame on a pad whose alpha the classic take has already raised (+60 ms); the masked frames that follow arrive too late, so the wipe shows as a cut. Before #1007 the pool already existed and a take never queried allocation. The runner's GStreamer is the same 1.28.6 as local; the difference is speed.

Change

backend/src/gst/shaders/passthrough.rs:

  • Entering passthrough no longer renegotiates, so the pool negotiated at start stays allocated.
  • Leaving passthrough renegotiates only if buffer_pool() is gone (caps changed while the slot was in passthrough).
  • A normal take sends no ALLOCATION query and renders into the existing pool immediately. Idle slots still render nothing (6 × 1080p30: 0 FX renders/s).

Evidence

  • Reproduced locally on main with a test-only QUERY_DOWNSTREAM probe that sleeps 2.5 s on every ALLOCATION query from fx_take_0/1 (installed after start-up): exactly the CI output, classic wipe: animated=false end white=0.00 red=0.76. Slowing buffers instead did not reproduce it. Delaying only the fx_take_* allocation reproduces it and changing only passthrough.rs removes it, which rules out the other perf merges.
  • New guard wipe_animates_when_take_slot_allocation_is_slow (same body as the letterbox wipe test, with the delay probe): fails with the CI signature when the old reconfigure behaviour is put back, passes with the fix.
  • To confirm on the macOS runner: this PR carries ci:macos.

Tests

Ran (macOS, GStreamer 1.28.6, GL): vision_mixer_fx_test (3), vision_mixer_fx_passthrough_test (1 + the ignored measurement once), vision_mixer_cpu_test (7), pipeline_lifecycle_test (4), vision_mixer_source_resize_test (8), passthrough lib unit tests (4), clippy -D warnings, fmt.

For the reviewer

🤖 Generated with Claude Code

Since the idle FX slots run in passthrough (#1007), a 2 s classic wipe
became a hard cut on the virtualized-GL macOS CI runner
(wipe_between_letterboxed_sources_animates: animated=false, end
white=0.00 red=0.76).

Entering passthrough renegotiated the slot, which released its GL output
pool. Leaving passthrough on the wipe's first buffer then renegotiated
again, and decide_allocation sent a serialized ALLOCATION query down into
the mixer from the branch's streaming thread. When that query is slow,
the branch stalls: the mixer keeps compositing the last unmasked frame on
a pad whose alpha the take has already turned on, and the masked frames
that follow arrive too late to be shown. The wipe reads as a cut.

Enter passthrough without renegotiating, so the pool negotiated at start
stays allocated, and leave it without renegotiating when the pool is
still there; a take no longer queries allocation. Only a slot whose pool
was dropped (caps changed while in passthrough) renegotiates on the way
out. Idle slots still render nothing (6 x 1080p30: 0 FX renders/s).

Guard: wipe_animates_when_take_slot_allocation_is_slow runs the same
letterbox wipes with every ALLOCATION query from a TAKE slot delayed by
2.5 s after start-up. Without the fix it fails with the CI signature
(animated=false, end white=0.00 red=0.76); with it, it passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@srperens srperens added the ci:macos Run Build (macOS) on this pull request label Oct 6, 2026
srperens added a commit that referenced this pull request Oct 6, 2026
cargo test stops at the first failing test binary, so one flaky test on the
macOS runner hid every binary after it. Verifying #1013 took three runs that
each stopped on a different unrelated test (keyed_alpha, port_pool,
thumbnail_tap_cuda) before the vision mixer FX tests were ever reached.
With --no-fail-fast every binary runs and all failures show in one run; the
step still fails if any test fails.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

@srperens srperens left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Request changes. This is posted as a COMMENT review because GitHub refuses REQUEST_CHANGES from the account this stage runs under, but the verdict is Request changes. The PR's own ci:macos run contradicts its central claim: at 62f75a2 the macOS runner still fails both wipe tests with the exact signature the PR sets out to fix. #994 offers a different cause for that same signature.

Requested changes

  1. Rebase onto #994, or wait for it to land, and re-run with ci:macos. The wipe observer here still pulls from an appsink that drops frames: backend/tests/vision_mixer_fx_test.rs:337 — ("drop", PV::Bool(true)),. #994 makes this observer lossless, for the same animated=false end white=0.00 red=0.76 failure on Linux. Until the observer stops dropping frames, a red wipe test on a slow runner cannot tell a stalled take from a missed frame.
  2. Narrow the body once that run is in. If the wipe passes on macOS with #994 alone, then this change is a latency improvement (no ALLOCATION query on the take path), not the fix for the runner, and the Problem section should say so.

Claims

Claim Verdict Evidence
Fixes the classic wipe on the macOS runner CONTRADICTED Build (macOS), job 112456924613: wipe_between_letterboxed_sources_animates ... FAILED and wipe_animates_when_take_slot_allocation_is_slow ... FAILED, both with classic wipe: animated=false end white=0.00 red=0.76
An injected slow ALLOCATION query turns the wipe into a cut, and the fix removes that CONFIRMED On Linux only: Check (Linux) reported 3 passed in 5.05 s. Leaving passthrough now reconfigures only when no pool exists (citation below). I could not determine whether the guard fails on Linux with the change reverted
Leaving passthrough finds the start-up pool still in place UNVERIFIED Slots request passthrough at build time, backend/src/blocks/builtin/vision_mixer/elements.rs:170 — crate::gst::shaders::request_passthrough(&elem, true);. SPECULATIVE (not verified): if basetransform renegotiates in passthrough on its first buffer anyway, the pool is gone before the first take, and this path still queries

backend/src/gst/shaders/passthrough.rs:109 — if !passthrough && transform.buffer_pool().is_none() {

Diagnosis: the injected-delay reproduction shows that a slow allocation query can produce this symptom. It does not show that this is what the runner hits, and the runner says it is not, or not only. A frame-dropping observer produces the same symptom on a slow host, and that is #994's account. Coverage of the stated bug is therefore unproven.

Radius: LOCAL. One function. Every GPU vision mixer FX slot keeps its GL output pool while idle, which costs memory and no GPU work, as the body says.

Overlaps: #994 changes the same test's observer for the same failure signature, and it may make this PR unnecessary as a CI fix. It should land first. My review there stands at Approve. Also on this test file: #935 and #936 (shared test setup), which are textual only.

Tests & CI: at 62f75a2, Check (Linux) is green and Build (macOS) is red. The other macOS failures in that job (tally_follows_a_cut_on_the_same_frame_with_late_inputs, static_underlays_are_not_repushed_cpu) are in files this diff does not touch. The branch predates #1018, which changed the tally guard. Windows was skipped.

Confidence: MED

@srperens

srperens commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Runner traces from a diagnostic branch show that this PR's premise is not what fails CI. On macos-latest the take slot keeps its pool (pool=true) and sends no slow ALLOCATION query. It renders the mask correctly; a readback shows alpha 0. The mixer then drops every masked frame as late (gst_aggregator_pad_skip_buffers), because GL on the runner renders below real time. Details and the test fix are in #1027 (6 of 6 green on the runner). The change here may still be worth keeping on its own guard, but it does not fix wipe_between_letterboxed_sources_animates; its CI failure is the same lateness.

🤖 Generated with Claude Code

@srperens

srperens commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Closing: the cause this PR fixes (a slow ALLOCATION query on the take path) did not occur on the runner. The take slot kept its pool there and no query stalled. The real cause of the wipe flake is late frames, fixed in #1027. The guard here reproduces only a probe-injected delay, so it does not guard an observed failure. Reopen if we see allocation stalls on real hardware.

🤖 Generated with Claude Code

@srperens srperens closed this Oct 7, 2026
@srperens
srperens deleted the fix/fx-wipe-passthrough branch October 8, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:macos Run Build (macOS) on this pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant