Skip to content

fix(compositor): honour force_live on the CPU mixer path - #745

Open
wagenet wants to merge 1 commit into
Eyevinn:mainfrom
wagenet:wagenet/compositor-force-live
Open

fix(compositor): honour force_live on the CPU mixer path#745
wagenet wants to merge 1 commit into
Eyevinn:mainfrom
wagenet:wagenet/compositor-force-live

Conversation

@wagenet

@wagenet wagenet commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

The bug

build_software_compositor in backend/src/blocks/builtin/compositor.rs discarded its
force_live parameter, justified by this comment:

// Note: compositor element has force-live as read-only (unlike glvideomixerelement)
// so we don't set it here - it defaults based on whether live sources are connected
let _ = force_live; // Acknowledge parameter even though we can't use it for CPU backend

That is factually wrong. gst-inspect-1.0 compositor reports:

force-live          : Always operate in live mode and aggregate on timeout regardless of
                      whether any live sources are linked upstream
                      flags: readable, writable, can be set only at object construction time
                      Boolean. Default: false

Construct-only, not read-only — the same flags as glvideomixerelement. The code built the
element first and only then tried to set properties, which is why it concluded the property was
unusable.

Net effect before this change: the CPU mixer always ran with force-live=false, while the GL
path received the caller's value. The parameter was silently dropped for CPU.

The fix

Set it at construction, the pattern already used in vision_mixer/elements.rs, which even
documents it: "force-live is construct-only and must be set via
ElementFactory::make().property()"
.

let mixer = gst::ElementFactory::make("compositor")
    .name(&mixer_id)
    .property("force-live", force_live)
    .build()

The incorrect comment and the let _ = force_live; line are removed, and the info! now reports
the value the way the GL path already does. The GL path is otherwise unchanged.

What this does NOT do

This is a correctness fix, not a behaviour fix. It does not fix the vision mixer cold-start
stall.
That was tested specifically. Isolated gst-launch-1.0 runs show a compositor with
linked-but-never-negotiating sink pads keeps producing frames in every configuration tried:

  • CPU and GL backends
  • force-live true and false
  • ignore-inactive-pads true and false
  • silent upstream both live (udpsrc) and non-live (appsrc)
  • 2 pads with 1 live, and 5 pads with 1 live
  • with and without queues

All produced ~294–297 frames per 10s at 30fps, against a ~295 frame all-live control.

Also worth recording, and deliberately not built on here: glvideomixerelement has no
ignore-inactive-pads property at all — that escape hatch is CPU-only.

Tests

Two tests in a new mod tests in compositor.rs. They drive the public
CompositorBuilder::build() with compositor_preference = "cpu" — pinning the backend so the
test exercises the CPU path regardless of whether the host has GL — and then read force-live
back off the built mixer element.

  • cpu_mixer_honours_force_live_truethe guard. Reverting the one-line fix makes it fail,
    because a compositor built without the construct-time property stays at its false default,
    and being construct-only nothing downstream can change it afterwards. Verified by actually
    reverting the line locally and re-running: it failed with "CPU mixer must report
    force-live=true when the block is built with force_live true"
    , then passed again on restore.
  • cpu_mixer_honours_force_live_false — the counterpart. It does not fail on revert, so it is
    not the guard; it earns its place because parse_force_live defaults to true, so a wrong
    property key would make this one fail. It pins the key name and the plumbing.

compositor ships in gst-plugins-base, already in the CI package list
(.github/workflows/ci.yml), so these tests execute in CI rather than skipping.

Commands actually run

Command Result
gst-inspect-1.0 compositor Confirmed the construct-only flags quoted above
cargo fmt --all -- --check Passed
cargo clippy --workspace --all-targets -- -D warnings Passed
cargo test --lib blocks::builtin::compositor 2 passed, 0 failed
Same test with the fix line reverted 1 passed, 1 failed — the guard fired
Pre-commit hook (fmt + clippy + secret scan) Passed; --no-verify not used

All runs used the dev profile with CARGO_PROFILE_DEV_DEBUG=0 and CARGO_INCREMENTAL=0.

Not run

  • cargo test --workspacenot run. The build volume was nearly full for this work, and a
    full workspace test build did not fit. Only the two tests above were executed.
  • No runtime verification in the running app. This change alters only which value a
    construct-time property receives, and the assertion reads that property back directly.
  • Nothing was run against the GL path; it is untouched.

🤖 Generated with Claude Code

build_software_compositor discarded its force_live parameter, justified by a
comment claiming compositor exposes force-live as read-only. That is wrong:
gst-inspect-1.0 reports it as "readable, writable, can be set only at object
construction time" — construct-only, the same flags as glvideomixerelement.

The code built the element first and only then tried to set properties, which
is why it concluded the property was unusable. Set it at construction instead,
the pattern already used in vision_mixer/elements.rs.

Before this, the CPU mixer always ran with force-live=false while the GL path
received the caller's value.

Adds two tests driving CompositorBuilder::build() with compositor_preference
"cpu" and reading force-live back off the built mixer. Reverting the fix makes
cpu_mixer_honours_force_live_true fail (verified locally).

This is a correctness fix, not a behaviour fix — it does not address the vision
mixer cold-start stall.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wagenet
wagenet marked this pull request as ready for review September 2, 2026 17:47

@srperens srperens left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: Comment — correct fix, brings the CPU path to parity with the pre-existing GL
path, but the current head's CI is not green.

Claims

Claim Verdict Evidence
force-live set at construction on the CPU mixer, matching the GL path CONFIRMED backend/src/blocks/builtin/compositor.rs:465`.property("force-live", force_live)`; the pre-existing GL path does the identical thing at backend/src/blocks/builtin/compositor.rs:247`.property("force-live", force_live)`, both fed from one force_live computed at :139 and passed to both builders at :157 and :168
build_software_compositor has one caller CONFIRMED only call site is backend/src/blocks/builtin/compositor.rs:160
CI is green at the current head CONTRADICTED gh pr checks 745 at 88a6a11: Build (Linux x86_64) is cancelled, run 33662772217, Build step alone ran 30m+ against the job's own timeout-minutes: 30 (.github/workflows/ci.yml:229) — a job timeout, not a compile failure; Build (Linux ARM64) on the identical SHA passed in 9m32s and Check (Linux) (which runs the new tests) passed
The new tests execute in CI, not skipped CONFIRMED Check (Linux) runs cargo test --package strom --features efp,nvidia (.github/workflows/ci.yml step under that job); compositor.rs is not feature-gated, so cpu_mixer_honours_force_live_{true,false} ran there and that job is green

Diagnosis — Matches the stated cause: force-live is construct-only on compositor
(same as glvideomixerelement), so the old post-build() no-op silently kept the CPU path at
the element's false default regardless of the caller's value. The fix is ABSOLUTE for this
bug: one property, one call site, and the two tests read the property back off the live element
rather than re-asserting the code's own logic. The PR is explicit that this does not touch the
separate cold-start-stall issue — correctly out of scope, not this diagnosis's problem to solve.

RadiusLOCAL. Single function, single call site, no new dependency, GL path untouched.

Tests & CI — The guard (cpu_mixer_honours_force_live_true) is real: the PR body states it
was verified to fail on a manual revert of the fix line, which is consistent with the element's
documented false default and the property being construct-only — reading it back after
build() cannot pick up a value that arrived too late. Check (Linux), Check & Build (WASM),
API Contract Check, Build (Linux ARM64), sccache preflight are all green at 88a6a11.
Build (Linux x86_64) is not — it needs a clean rerun before merge:
gh run rerun 33662772217 --failed. Build (macOS)/Build (Windows) skip on
push/pull_request as usual; nothing in this diff is platform-specific.

Repo rules — No BUFFER probe, no pipeline-element closure, no shared-type or endpoint
change. Nothing to flag.

Confidence: HIGH

@srperens srperens mentioned this pull request Sep 3, 2026
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.

2 participants