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>
This commit is contained in:
2026-06-25 17:24:00 +00:00
parent a3eefc2374
commit e5057f6cc1
14 changed files with 138 additions and 65 deletions
+3 -5
View File
@@ -327,7 +327,7 @@ pub fn capture_virtual_output(
/// compiled and comes back the moment the flag is unset.
#[cfg(target_os = "windows")]
pub(crate) fn wgc_disabled() -> bool {
std::env::var_os("PUNKTFUNK_NO_WGC").is_some()
crate::config::config().no_wgc
}
#[cfg(target_os = "windows")]
@@ -345,7 +345,7 @@ pub fn capture_virtual_output(
// P2 direct frame push (kill DDA): consume frames straight from the pf-vdisplay driver's shared
// ring — no Desktop Duplication, no win32u reparenting hook. Opt-in while it's A/B'd against DDA;
// `idd_push` takes the keepalive (owns the virtual display) so there's no fall-through.
if std::env::var_os("PUNKTFUNK_IDD_PUSH").is_some() {
if crate::config::config().idd_push {
// Recreate the monitor + ring per session (fix-teardown): a FRESH monitor reliably gets a
// working IddCx swap-chain, whereas a REUSED monitor's swap-chain dies after ~2 sessions and
// the host can't revive it. The driver's recreate crash (target id resolved to 0) is fixed by
@@ -371,9 +371,7 @@ pub fn capture_virtual_output(
// and has no ACCESS_LOST-on-overlay churn. DDA stays available via PUNKTFUNK_CAPTURE=dda and is
// the secure-desktop (lock/UAC) fallback (WGC can't capture those). `keep` is moved into the
// chosen backend (it owns the SudoVDA keepalive), so there's no open-time auto-fallback.
let backend = std::env::var("PUNKTFUNK_CAPTURE")
.unwrap_or_default()
.to_ascii_lowercase();
let backend = crate::config::config().capture_backend.as_str();
if backend == "dda" || backend == "dxgi" || wgc_disabled() {
return dxgi::DuplCapturer::open(target, pref, keep, false)
.map(|c| Box::new(c) as Box<dyn Capturer>);