Files
punktfunk/docs/windows-host-goal1-plan.md
T
enricobuehler e5057f6cc1 feat(windows-host): finish HostConfig migration — resolve operator/dispatch knobs once (Goal-1 stage 2)
Migrate 31 genuinely-constant operator/dispatch env::var sites onto HostConfig, so the
capture/topology/encoder decision reads ONE owner instead of being recomputed at each call
site (the latent bug where capture and encode could disagree on the resolved backend, plan §2.4):
idd_push x7, no_wgc, capture_backend, render_adapter, encoder_pref (Linux open_video +
linux_zero_copy_is_vaapi), the Windows vdisplay-backend select, plus the plan-named
secure_dda/idd_depth/zerocopy/ten_bit and the multi-site perf x4 / compositor x5 /
video_source x3 / gamepad. Each HostConfig field's parser is byte-identical to the read it
replaced, so old==new by construction (the plan's "a flipped bool is a silent regression" guard).

Scope correction — the plan's "~64 sites / Linux XDG+compositor included / grep env::var -> 0"
was unsafe as written. Two classes are deliberately KEPT as live reads and documented in config.rs:

  * Runtime-mutated session vars. 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,
    GAMESCOPE_SESSION/NODE, KWIN/MUTTER_VIRTUAL_PRIMARY, FORCE_SHM. Parsing these once would
    freeze them at startup and silently break session-following — they are NOT constant.
  * Single-use local tuning with no resolve-once benefit (and FEC_PCT even has two different
    semantics): FEC_PCT, VIDEO_DROP, VBV_FRAMES, SPLIT_ENCODE, PACE_BURST_KB, the dxgi 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 (1|true|yes|on) parser.

Verified: Linux cargo check + clippy (-D warnings) + fmt clean on the touched files. The
Windows-only edits are 1:1 substitutions; they get a real Windows compile on the box with Stage 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:24:00 +00:00

78 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Goal-1 (clean, layered host architecture) — staged execution plan
The design is in [`windows-host-rewrite.md`](windows-host-rewrite.md) §2.22.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).**
Resolve `display/capture/topology/encoder/format/hdr/bit_depth` ONCE from `HostConfig` into a typed
`SessionPlan`; replace the 3-place dispatch (`capture_virtual_output`, `should_use_helper`/`virtual_stream`,
`windows_resolved_backend`) with reads off the plan. Fixes the capture/encode backend-disagreement bug
class. Risk: medium-high (rewires the deployed decision). Verify: box build + on-glass re-test (NVENC +
IDD-push + a mode switch).
**Stage 4 — `SessionContext` + `SessionFactory`/`Session`.**
Bundle the 1213-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 35 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.