0a63154293
New src/session_plan.rs: a Copy `SessionPlan { capture, topology, encoder, bit_depth, hdr }`
resolved ONCE from HostConfig (+ the negotiated bit_depth) at the top of `virtual_stream`,
logged, and threaded through build_pipeline_with_retry/build_pipeline. The three scattered
Windows dispatch points now read this one typed artifact instead of re-deriving from config
(plan §2.4, the "capture and encode disagree on the backend" hazard):
* capture: capture::capture_virtual_output takes a CaptureBackend IN (was re-reading
config().idd_push / capture_backend / no_wgc internally). CaptureBackend::resolve() is the
single resolver, shared with the GameStream + spike call sites.
* topology: virtual_stream reads plan.topology; should_use_helper is deleted (its body is
session_plan::resolve_topology, verbatim). The IDD-push reconnect-preempt guard reads
plan.capture too.
* encoder: recorded as EncoderBackend from encode::windows_resolved_backend (config-backed +
GPU-vendor cached since stage 2 -> already a single source). Threading encoder/input_format
into the encoder+capturer opens (which removes the capture->windows_resolved_backend()
back-reference recomputed in dxgi.rs) is stage 5.
Behavior-preserving by construction: each resolved decision is provably equivalent to the
pre-stage-3 reads (same config() + the same cached running_as_system()/GPU-vendor probes), so
old==new. SessionPlan is platform-neutral so it threads the shared virtual_stream/build_pipeline
signatures; on Linux it resolves to the single portal/single-process path.
Also fixes a pre-existing mod-ordering fmt drift in main.rs (mod config; / mod capture;).
Verified: Linux cargo check + clippy (-D warnings) + fmt clean on the touched files. Box build
(Windows compile) + on-glass (NVENC + IDD-push + mode switch) pending on the RTX box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
89 lines
6.5 KiB
Markdown
89 lines
6.5 KiB
Markdown
# Goal-1 (clean, layered host architecture) — staged execution plan
|
||
|
||
The design is in [`windows-host-rewrite.md`](windows-host-rewrite.md) §2.2–2.4. This file is the **ordered,
|
||
independently-shippable execution plan**, because the host is **live-validated** (GameStream + punktfunk/1,
|
||
NVENC + IDD-push on-glass) and Goal-1 rewires its session/config/dispatch flow — so every stage must
|
||
**preserve behavior**, compile + box-verify on its own, and be committed before the next starts. The plan's
|
||
own §14 makes the §1 preservation checklist a mandatory per-module assert contract; honour it.
|
||
|
||
## Why staged (not one big rewrite)
|
||
|
||
`main` is at parity and shipping. A monolithic rewrite would put the validated host in a broken
|
||
intermediate state for a long window and make a regression impossible to bisect. Each stage below is a
|
||
behaviour-preserving transform with its own verification, so a regression is caught at the stage that
|
||
introduced it.
|
||
|
||
## Stages (ordered; each = goal · files · risk · verify)
|
||
|
||
**Stage 1 — `HostConfig` foundation. ✅ DONE (this commit).**
|
||
`config.rs`: typed `HostConfig` parsed ONCE from env (`idd_push`/`encoder_pref`/`no_helper`/`force_helper`).
|
||
Migrated the two highest-churn dispatch reads onto it (`encode::windows_resolved_backend`,
|
||
`punktfunk1::should_use_helper`). Risk: low (env constant at runtime → identical behaviour). Verify: box
|
||
`cargo check --features nvenc`.
|
||
|
||
**Stage 2 — finish `HostConfig` + resolve-once. ✅ DONE (this commit).**
|
||
Migrated **31** genuinely-constant operator/dispatch sites onto `HostConfig`: `idd_push` ×7 (the
|
||
capture/topology disagreement knob), `no_wgc`, `capture_backend`, `render_adapter`, `encoder_pref` (Linux),
|
||
the Windows vdisplay-backend select, plus the plan-named `secure_dda`/`idd_depth`/`zerocopy`/`ten_bit` and the
|
||
multi-site `perf` ×4 / `compositor` ×5 / `video_source` ×3 / `gamepad`. Each `HostConfig` field's parser is
|
||
**byte-identical** to the read it replaced, so `old == new` by construction (the §1 "flipped bool" guard).
|
||
|
||
**Scope correction (the plan's "~64 sites / Linux XDG+compositor / grep→0" was unsafe as written):** two
|
||
classes of `env::var` read are deliberately **kept live** and documented in `config.rs`:
|
||
- **Runtime-mutated session vars.** On Linux, `vdisplay::apply_session_env` *rewrites the process env on
|
||
every connect* (the Bazzite Gaming↔Desktop follow): `WAYLAND_DISPLAY`, `XDG_CURRENT_DESKTOP`,
|
||
`XDG_RUNTIME_DIR`, `DBUS_SESSION_BUS_ADDRESS`, and the derived `PUNKTFUNK_INPUT_BACKEND`,
|
||
`PUNKTFUNK_GAMESCOPE_SESSION/NODE`, `PUNKTFUNK_KWIN/MUTTER_VIRTUAL_PRIMARY`, `PUNKTFUNK_FORCE_SHM`.
|
||
Parse-once would freeze them at startup → silent session-following regression. They are NOT constant.
|
||
- **Single-use local tuning** (no resolve-once benefit, call-site-local default/clamp, and `FEC_PCT` even has
|
||
*two different* semantics): `FEC_PCT`, `VIDEO_DROP`, `VBV_FRAMES`, `SPLIT_ENCODE`, `PACE_BURST_KB`, the
|
||
`capture/dxgi.rs` timing knobs, the `*_LIVE`/test gates, plus path/dynamic reads (config-dir, `PATH`
|
||
search, env-forward-to-child). `PUNKTFUNK_ZEROCOPY` is split on purpose: Windows presence-semantics moved
|
||
to the field; Linux keeps its own truthy parser.
|
||
|
||
Risk: medium (semantics-preservation). Verify: Linux `cargo check`/`clippy`/`fmt` green (the Windows-only
|
||
edits are 1:1 substitutions, compile-verified on the box as part of Stage 3's build).
|
||
|
||
**Stage 3 — `SessionPlan` (the single biggest clarity lever, plan §2.4). ✅ IMPLEMENTED (this commit; on-glass pending).**
|
||
New `src/session_plan.rs`: a `Copy` `SessionPlan { capture, topology, encoder, bit_depth, hdr }` resolved
|
||
**once** from `HostConfig` (+ the negotiated `bit_depth`) in `virtual_stream`, logged, and threaded through
|
||
`build_pipeline_with_retry`/`build_pipeline`. The three dispatch points now read it:
|
||
- **capture** — `capture::capture_virtual_output` takes a `CaptureBackend` IN (was re-deriving from
|
||
`config().idd_push`/`capture_backend`/`no_wgc`); `CaptureBackend::resolve()` is the one resolver (also
|
||
used by the GameStream + spike call sites).
|
||
- **topology** — `virtual_stream` reads `plan.topology` (`should_use_helper` deleted; its logic is
|
||
`session_plan::resolve_topology`, verbatim). The IDD-preempt guard reads `plan.capture` too.
|
||
- **encoder** — recorded as `EncoderBackend` from `encode::windows_resolved_backend` (config-backed +
|
||
GPU-vendor cached since stage 2, already a single source). Threading `encoder`/`input_format` into the
|
||
encoder + capturer opens (which removes the `dxgi.rs` back-reference) is **stage 5**.
|
||
|
||
Every decision is provably equivalent to the pre-stage-3 scattered reads (same `config()` + cached probes),
|
||
so it is behavior-preserving. Risk: medium-high (rewires the deployed decision). Verify: box build (Windows
|
||
compile, which also covers stage 2's Windows-only edits) + on-glass re-test (NVENC + IDD-push + a mode
|
||
switch) — **pending** (RTX box `192.168.1.173`).
|
||
|
||
**Stage 4 — `SessionContext` + `SessionFactory`/`Session`.**
|
||
Bundle the 12–13-arg `#[allow(too_many_arguments)]` signatures into `SessionContext`; `SessionFactory.build()`
|
||
owns the RAII chain `vdm.lease(mode) → open_capturer(vout, fmt) → open_encoder(plan) → spawn pipeline`, with
|
||
`Session::drop` the ONLY teardown path. Risk: high (teardown ordering — the §1 RAII asserts are mandatory).
|
||
Verify: box build + on-glass (connect/disconnect/reconnect, no leaked monitors/threads).
|
||
|
||
**Stage 5 — seam-trait tightenings (plan §2.3).**
|
||
`Capturer::open_capturer(vout, want: OutputFormat)` takes the format IN (kills the
|
||
`capture → encode::windows_resolved_backend()` back-reference recomputed in `dxgi.rs`); HDR/release become
|
||
`VirtualLease` methods (session glue names no concrete backend, contains no `unsafe`); optional encoder
|
||
features move to `EncoderCaps`. Risk: medium. Verify: box build + on-glass.
|
||
|
||
**Stage 6 — `src/windows/` tree (cfg-sprawl confinement, plan §2.2).**
|
||
Move the Windows backends under `src/windows/` + `capture/windows/`, `encode/windows/`, `inject/windows/`,
|
||
`audio/windows/`, `vdisplay/windows.rs` behind one `#[cfg(windows)] mod windows;` seam. Pure file move +
|
||
mod/use-path updates — behaviour-identical. Risk: low-but-huge (dozens of files; compile-verify catches all).
|
||
Do LAST so the earlier semantic stages don't fight path churn. Verify: Linux + box build.
|
||
|
||
## Guardrails (mandatory, plan §14)
|
||
|
||
- Each stage is its own commit; box-verify before moving on.
|
||
- Stages 3–5 touch the deployed path → **on-glass re-test** (NVENC + IDD-push, a mode switch, a
|
||
connect/disconnect cycle) before the next stage.
|
||
- Preserve every `PUNKTFUNK_*` var's exact semantics; when in doubt, assert old==new at the call site.
|