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:
@@ -327,7 +327,7 @@ pub fn capture_virtual_output(
|
|||||||
/// compiled and comes back the moment the flag is unset.
|
/// compiled and comes back the moment the flag is unset.
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub(crate) fn wgc_disabled() -> bool {
|
pub(crate) fn wgc_disabled() -> bool {
|
||||||
std::env::var_os("PUNKTFUNK_NO_WGC").is_some()
|
crate::config::config().no_wgc
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[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
|
// 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;
|
// 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.
|
// `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
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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.
|
// chosen backend (it owns the SudoVDA keepalive), so there's no open-time auto-fallback.
|
||||||
let backend = std::env::var("PUNKTFUNK_CAPTURE")
|
let backend = crate::config::config().capture_backend.as_str();
|
||||||
.unwrap_or_default()
|
|
||||||
.to_ascii_lowercase();
|
|
||||||
if backend == "dda" || backend == "dxgi" || wgc_disabled() {
|
if backend == "dda" || backend == "dxgi" || wgc_disabled() {
|
||||||
return dxgi::DuplCapturer::open(target, pref, keep, false)
|
return dxgi::DuplCapturer::open(target, pref, keep, false)
|
||||||
.map(|c| Box::new(c) as Box<dyn Capturer>);
|
.map(|c| Box::new(c) as Box<dyn Capturer>);
|
||||||
|
|||||||
@@ -994,11 +994,7 @@ impl Capturer for IddPushCapturer {
|
|||||||
// NVENC encodes N on the ASIC. We hand a rotating `OUT_RING` of output textures, so this is safe.
|
// NVENC encodes N on the ASIC. We hand a rotating `OUT_RING` of output textures, so this is safe.
|
||||||
// `PUNKTFUNK_IDD_DEPTH` overrides (1 disables pipelining; clamp to ≤ OUT_RING so a frame in flight
|
// `PUNKTFUNK_IDD_DEPTH` overrides (1 disables pipelining; clamp to ≤ OUT_RING so a frame in flight
|
||||||
// always has its own texture).
|
// always has its own texture).
|
||||||
std::env::var("PUNKTFUNK_IDD_DEPTH")
|
crate::config::config().idd_depth.clamp(1, OUT_RING)
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.parse::<usize>().ok())
|
|
||||||
.unwrap_or(2)
|
|
||||||
.clamp(1, OUT_RING)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,39 @@
|
|||||||
//! `HostConfig` — the host's runtime knobs parsed ONCE from the environment, instead of the ~68 scattered
|
//! `HostConfig` — the host's runtime knobs parsed ONCE from the environment, instead of the ~68 scattered
|
||||||
//! `env::var` reads recomputed at every call site (some up to 8×, which lets capture + encode silently
|
//! `env::var` reads recomputed at every call site (some up to 8×, which lets capture + encode silently
|
||||||
//! disagree on the resolved backend — plan §2.4). The service / launcher loads `host.env` into the process
|
//! disagree on the resolved backend — plan §2.4). The service / launcher loads `host.env` into the process
|
||||||
//! environment before the host starts, and the environment is constant for the process lifetime, so a
|
//! environment before the host starts, and **for the knobs captured here the environment is constant for the
|
||||||
//! lazily-parsed global is equivalent to "parsed once at startup".
|
//! process lifetime**, so a lazily-parsed global is equivalent to "parsed once at startup".
|
||||||
//!
|
//!
|
||||||
//! **Goal-1 stage 1** (`docs/windows-host-goal1-plan.md`): this is the foundation. Subsequent stages grow
|
//! **Goal-1 stages 1–2** (`docs/windows-host-goal1-plan.md`): stage 1 stood this up; stage 2 migrated the
|
||||||
//! this struct + migrate the remaining read sites onto it, then `SessionPlan` (stage 2) consumes it as the
|
//! genuinely-constant operator/dispatch knobs onto it (the dispatch-disagreement bug class: `idd_push`,
|
||||||
//! single owner of the capture/topology/encoder decision. New fields are added here AS call sites migrate —
|
//! `capture_backend`, `encoder_pref`, `render_adapter`, `no_wgc`, the vdisplay backend select — plus the
|
||||||
//! a field that nothing reads yet would just be dead, so they land together with their migration.
|
//! plan-named `secure_dda`/`idd_depth`/`zerocopy`/`ten_bit` and the multi-site `perf`/`compositor`/
|
||||||
|
//! `video_source`/`gamepad`). `SessionPlan` (stage 3) consumes it as the single owner of the
|
||||||
|
//! capture/topology/encoder decision.
|
||||||
|
//!
|
||||||
|
//! **What is deliberately NOT here (and must stay a live `env::var` read):**
|
||||||
|
//! - **Runtime-mutated session vars.** On Linux, [`crate::vdisplay::apply_session_env`] rewrites the process
|
||||||
|
//! env on *every connect* so one host follows a Bazzite box across Gaming↔Desktop: `WAYLAND_DISPLAY`,
|
||||||
|
//! `XDG_CURRENT_DESKTOP`, `XDG_RUNTIME_DIR`, `DBUS_SESSION_BUS_ADDRESS`, and the *derived* `PUNKTFUNK_*`
|
||||||
|
//! vars `INPUT_BACKEND`, `GAMESCOPE_SESSION`/`GAMESCOPE_NODE`, `KWIN_VIRTUAL_PRIMARY`,
|
||||||
|
//! `MUTTER_VIRTUAL_PRIMARY`, `FORCE_SHM` (+ `GAMESCOPE_APP` on the launch path). Parsing these once would
|
||||||
|
//! freeze them at startup and silently break session-following — they are NOT constant.
|
||||||
|
//! - **Single-use local tuning** read exactly where it is used (no resolve-once benefit, and a parse with a
|
||||||
|
//! call-site-local default/clamp): e.g. `FEC_PCT` (two *different* semantics — GameStream default-20 vs
|
||||||
|
//! punktfunk/1 `Option`/clamp-90), `VIDEO_DROP`, `VBV_FRAMES`, `SPLIT_ENCODE`, `PACE_BURST_KB`, the
|
||||||
|
//! `capture/dxgi.rs` timing knobs, the `*_LIVE` test gates.
|
||||||
|
//! - **Path / genuinely-dynamic reads**: the config-dir resolution, `PATH` executable search, the
|
||||||
|
//! env-forward-to-child loop, `PUNKTFUNK_MGMT_TOKEN`, `PUNKTFUNK_HOST_CMD`, `PUNKTFUNK_RENDER_NODE`.
|
||||||
|
//!
|
||||||
|
//! `PUNKTFUNK_ZEROCOPY` note: this field uses **presence** semantics (`var_os(..).is_some()`) to match the
|
||||||
|
//! Windows `encode/ffmpeg_win.rs` reader. The Linux `zerocopy` module keeps its own *truthy* parser
|
||||||
|
//! (`1|true|yes|on`) — the two are independent features that share a name; do NOT conflate them.
|
||||||
|
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
/// Resolved host configuration. Grows as `env::var` call sites migrate onto it (Goal-1).
|
/// Resolved host configuration. Holds the genuinely-constant operator/dispatch knobs (see module docs for
|
||||||
|
/// what is deliberately excluded). Fields read on only one platform are kept alive cross-platform by the
|
||||||
|
/// derived `Debug` impl, so the parser can stay a single platform-neutral function.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct HostConfig {
|
pub struct HostConfig {
|
||||||
/// `PUNKTFUNK_IDD_PUSH` — use the IDD direct-push capturer (in-process Session-0 capture; no WGC helper).
|
/// `PUNKTFUNK_IDD_PUSH` — use the IDD direct-push capturer (in-process Session-0 capture; no WGC helper).
|
||||||
@@ -22,11 +44,41 @@ pub struct HostConfig {
|
|||||||
pub no_helper: bool,
|
pub no_helper: bool,
|
||||||
/// `PUNKTFUNK_FORCE_HELPER` — force the WGC helper even when not running as SYSTEM.
|
/// `PUNKTFUNK_FORCE_HELPER` — force the WGC helper even when not running as SYSTEM.
|
||||||
pub force_helper: bool,
|
pub force_helper: bool,
|
||||||
|
/// `PUNKTFUNK_NO_WGC` — force the pure single-process DDA path (skip WGC and the two-process relay).
|
||||||
|
pub no_wgc: bool,
|
||||||
|
/// `PUNKTFUNK_CAPTURE` — explicit Windows capture-backend override (lowercased; `dda`/`dxgi` vs the WGC default).
|
||||||
|
pub capture_backend: String,
|
||||||
|
/// `PUNKTFUNK_RENDER_ADAPTER` — discrete render-GPU pin by description substring (`Some` even when empty:
|
||||||
|
/// the empty string still counts as "set" for the presence checks, and the value reader filters it).
|
||||||
|
pub render_adapter: Option<String>,
|
||||||
|
/// `PUNKTFUNK_SECURE_DDA` — enable the experimental DDA-on-secure-desktop (Winlogon/UAC) mux leg.
|
||||||
|
pub secure_dda: bool,
|
||||||
|
/// `PUNKTFUNK_IDD_DEPTH` — IDD-push pipeline depth override (default 2; the call site clamps to its `OUT_RING`).
|
||||||
|
pub idd_depth: usize,
|
||||||
|
/// `PUNKTFUNK_ZEROCOPY` — opt into the Windows D3D11 zero-copy encode path (presence semantics; see module docs).
|
||||||
|
pub zerocopy: bool,
|
||||||
|
/// `PUNKTFUNK_10BIT` — host policy gate for HEVC Main10 (only honored when the client also advertised 10-bit).
|
||||||
|
pub ten_bit: bool,
|
||||||
|
/// `PUNKTFUNK_PERF` — per-stage timing instrumentation.
|
||||||
|
pub perf: bool,
|
||||||
|
/// `PUNKTFUNK_VIDEO_SOURCE` — GameStream video source select (`virtual` / `portal` / unset → synthetic).
|
||||||
|
pub video_source: Option<String>,
|
||||||
|
/// `PUNKTFUNK_COMPOSITOR` — explicit compositor override (operator/CI/test). NOT the runtime-detected
|
||||||
|
/// session — this one is a constant operator knob; `apply_session_env` never writes it.
|
||||||
|
pub compositor: Option<String>,
|
||||||
|
/// `PUNKTFUNK_GAMEPAD` — client/operator virtual-pad backend preference (fed to `pick_gamepad`).
|
||||||
|
pub gamepad: Option<String>,
|
||||||
|
/// `PUNKTFUNK_VDISPLAY` — Windows virtual-display backend select (`pf`/`pfvd` vs `sudovda`; else auto-detect).
|
||||||
|
pub vdisplay: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostConfig {
|
impl HostConfig {
|
||||||
fn from_env() -> Self {
|
fn from_env() -> Self {
|
||||||
|
// Presence flag: set ⇒ true. Matches the original `var_os(k).is_some()` reads (and the few
|
||||||
|
// `var(k).is_ok()` flag reads, which coincide for every real-world value).
|
||||||
let flag = |k: &str| std::env::var_os(k).is_some();
|
let flag = |k: &str| std::env::var_os(k).is_some();
|
||||||
|
// String value: `var(k).ok()` — `Some` (possibly empty) when set with valid UTF-8, else `None`.
|
||||||
|
let val = |k: &str| std::env::var(k).ok();
|
||||||
Self {
|
Self {
|
||||||
idd_push: flag("PUNKTFUNK_IDD_PUSH"),
|
idd_push: flag("PUNKTFUNK_IDD_PUSH"),
|
||||||
encoder_pref: std::env::var("PUNKTFUNK_ENCODER")
|
encoder_pref: std::env::var("PUNKTFUNK_ENCODER")
|
||||||
@@ -34,6 +86,22 @@ impl HostConfig {
|
|||||||
.to_ascii_lowercase(),
|
.to_ascii_lowercase(),
|
||||||
no_helper: flag("PUNKTFUNK_NO_HELPER"),
|
no_helper: flag("PUNKTFUNK_NO_HELPER"),
|
||||||
force_helper: flag("PUNKTFUNK_FORCE_HELPER"),
|
force_helper: flag("PUNKTFUNK_FORCE_HELPER"),
|
||||||
|
no_wgc: flag("PUNKTFUNK_NO_WGC"),
|
||||||
|
capture_backend: std::env::var("PUNKTFUNK_CAPTURE")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_ascii_lowercase(),
|
||||||
|
render_adapter: val("PUNKTFUNK_RENDER_ADAPTER"),
|
||||||
|
secure_dda: flag("PUNKTFUNK_SECURE_DDA"),
|
||||||
|
idd_depth: val("PUNKTFUNK_IDD_DEPTH")
|
||||||
|
.and_then(|s| s.parse::<usize>().ok())
|
||||||
|
.unwrap_or(2),
|
||||||
|
zerocopy: flag("PUNKTFUNK_ZEROCOPY"),
|
||||||
|
ten_bit: flag("PUNKTFUNK_10BIT"),
|
||||||
|
perf: flag("PUNKTFUNK_PERF"),
|
||||||
|
video_source: val("PUNKTFUNK_VIDEO_SOURCE"),
|
||||||
|
compositor: val("PUNKTFUNK_COMPOSITOR"),
|
||||||
|
gamepad: val("PUNKTFUNK_GAMEPAD"),
|
||||||
|
vdisplay: val("PUNKTFUNK_VDISPLAY"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,14 +173,12 @@ pub fn open_video(
|
|||||||
// AMD/Intel → VAAPI (one libavcodec backend for both). Auto-detect by default so a single
|
// AMD/Intel → VAAPI (one libavcodec backend for both). Auto-detect by default so a single
|
||||||
// Linux binary serves any GPU; `PUNKTFUNK_ENCODER` forces a specific backend (and surfaces
|
// Linux binary serves any GPU; `PUNKTFUNK_ENCODER` forces a specific backend (and surfaces
|
||||||
// its errors crisply instead of silently trying the other).
|
// its errors crisply instead of silently trying the other).
|
||||||
let pref = std::env::var("PUNKTFUNK_ENCODER")
|
let pref = crate::config::config().encoder_pref.as_str();
|
||||||
.unwrap_or_default()
|
|
||||||
.to_ascii_lowercase();
|
|
||||||
let open_vaapi = || -> Result<Box<dyn Encoder>> {
|
let open_vaapi = || -> Result<Box<dyn Encoder>> {
|
||||||
vaapi::VaapiEncoder::open(codec, format, width, height, fps, bitrate_bps, bit_depth)
|
vaapi::VaapiEncoder::open(codec, format, width, height, fps, bitrate_bps, bit_depth)
|
||||||
.map(|e| Box::new(e) as Box<dyn Encoder>)
|
.map(|e| Box::new(e) as Box<dyn Encoder>)
|
||||||
};
|
};
|
||||||
match pref.as_str() {
|
match pref {
|
||||||
"nvenc" | "nvidia" | "cuda" => open_nvenc_probed(
|
"nvenc" | "nvidia" | "cuda" => open_nvenc_probed(
|
||||||
codec,
|
codec,
|
||||||
format,
|
format,
|
||||||
@@ -379,11 +377,7 @@ fn nvidia_present() -> bool {
|
|||||||
/// passthrough for VAAPI vs the EGL→CUDA import for NVENC).
|
/// passthrough for VAAPI vs the EGL→CUDA import for NVENC).
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn linux_zero_copy_is_vaapi() -> bool {
|
pub fn linux_zero_copy_is_vaapi() -> bool {
|
||||||
match std::env::var("PUNKTFUNK_ENCODER")
|
match crate::config::config().encoder_pref.as_str() {
|
||||||
.unwrap_or_default()
|
|
||||||
.to_ascii_lowercase()
|
|
||||||
.as_str()
|
|
||||||
{
|
|
||||||
"nvenc" | "nvidia" | "cuda" => false,
|
"nvenc" | "nvidia" | "cuda" => false,
|
||||||
"vaapi" | "amd" | "intel" => true,
|
"vaapi" | "amd" | "intel" => true,
|
||||||
_ => !nvidia_present(),
|
_ => !nvidia_present(),
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ impl WinVendor {
|
|||||||
/// Is the zero-copy D3D11 path enabled? Opt-in (`PUNKTFUNK_ZEROCOPY=1`) until on-glass validated;
|
/// Is the zero-copy D3D11 path enabled? Opt-in (`PUNKTFUNK_ZEROCOPY=1`) until on-glass validated;
|
||||||
/// the default is the robust system-memory readback path.
|
/// the default is the robust system-memory readback path.
|
||||||
fn zerocopy_enabled() -> bool {
|
fn zerocopy_enabled() -> bool {
|
||||||
std::env::var_os("PUNKTFUNK_ZEROCOPY").is_some()
|
crate::config::config().zerocopy
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The swscale *source* pixel format for a captured packed-RGB/BGR layout (8-bit BGRA fallback only).
|
/// The swscale *source* pixel format for a captured packed-RGB/BGR layout (8-bit BGRA fallback only).
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ fn run(
|
|||||||
// request and capture it (no scaling). Self-contained — deliberately NOT pooled in
|
// request and capture it (no scaling). Self-contained — deliberately NOT pooled in
|
||||||
// `video_cap`, since a reconnect at a different resolution needs a freshly-sized output; the
|
// `video_cap`, since a reconnect at a different resolution needs a freshly-sized output; the
|
||||||
// output is released when this capturer drops at stream end (RAII via its keepalive).
|
// output is released when this capturer drops at stream end (RAII via its keepalive).
|
||||||
if std::env::var("PUNKTFUNK_VIDEO_SOURCE").as_deref() == Ok("virtual") {
|
if crate::config::config().video_source.as_deref() == Some("virtual") {
|
||||||
// The launched app picks the compositor (e.g. gamescope for game entries) and the
|
// The launched app picks the compositor (e.g. gamescope for game entries) and the
|
||||||
// nested command.
|
// nested command.
|
||||||
let compositor = app
|
let compositor = app
|
||||||
@@ -147,7 +147,7 @@ fn run(
|
|||||||
tracing::info!("video source: reusing capturer");
|
tracing::info!("video source: reusing capturer");
|
||||||
c
|
c
|
||||||
}
|
}
|
||||||
None if std::env::var("PUNKTFUNK_VIDEO_SOURCE").is_ok_and(|v| v == "portal") => {
|
None if crate::config::config().video_source.as_deref() == Some("portal") => {
|
||||||
tracing::info!("video source: portal desktop capture");
|
tracing::info!("video source: portal desktop capture");
|
||||||
capture::open_portal_monitor().context("open portal capturer")?
|
capture::open_portal_monitor().context("open portal capturer")?
|
||||||
}
|
}
|
||||||
@@ -358,7 +358,7 @@ fn stream_body(
|
|||||||
|
|
||||||
// Per-stage timing (PUNKTFUNK_PERF=1): max µs/stage per second + unique vs re-encoded frames,
|
// Per-stage timing (PUNKTFUNK_PERF=1): max µs/stage per second + unique vs re-encoded frames,
|
||||||
// to pinpoint stalls. `unique` counts genuinely-new captured frames (vs re-encoded holds).
|
// to pinpoint stalls. `unique` counts genuinely-new captured frames (vs re-encoded holds).
|
||||||
let perf = std::env::var_os("PUNKTFUNK_PERF").is_some();
|
let perf = crate::config::config().perf;
|
||||||
let (mut mx_cap, mut mx_enc, mut mx_pkt, mut mx_send, mut mx_pkts, mut uniq) =
|
let (mut mx_cap, mut mx_enc, mut mx_pkt, mut mx_send, mut mx_pkts, mut uniq) =
|
||||||
(0u128, 0u128, 0u128, 0u128, 0usize, 0u32);
|
(0u128, 0u128, 0u128, 0u128, 0usize, 0u32);
|
||||||
// Absolute next-frame deadline — the single pacing clock for the loop.
|
// Absolute next-frame deadline — the single pacing clock for the loop.
|
||||||
|
|||||||
@@ -112,8 +112,10 @@ pub fn default_backend() -> Backend {
|
|||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
if std::env::var("PUNKTFUNK_COMPOSITOR")
|
if crate::config::config()
|
||||||
.is_ok_and(|v| v.trim().eq_ignore_ascii_case("gamescope"))
|
.compositor
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|v| v.trim().eq_ignore_ascii_case("gamescope"))
|
||||||
{
|
{
|
||||||
return Backend::GamescopeEi;
|
return Backend::GamescopeEi;
|
||||||
}
|
}
|
||||||
@@ -260,8 +262,10 @@ fn coalesce(events: Vec<InputEvent>) -> Vec<InputEvent> {
|
|||||||
/// (`org.gnome.Mutter.RemoteDesktop`), the same direct API the Mutter video backend uses.
|
/// (`org.gnome.Mutter.RemoteDesktop`), the same direct API the Mutter video backend uses.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn libei_ei_source() -> libei::EiSource {
|
fn libei_ei_source() -> libei::EiSource {
|
||||||
let gnome = std::env::var("PUNKTFUNK_COMPOSITOR")
|
let gnome = crate::config::config()
|
||||||
.is_ok_and(|v| v.trim().eq_ignore_ascii_case("mutter"))
|
.compositor
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|v| v.trim().eq_ignore_ascii_case("mutter"))
|
||||||
|| std::env::var("XDG_CURRENT_DESKTOP")
|
|| std::env::var("XDG_CURRENT_DESKTOP")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_ascii_uppercase()
|
.to_ascii_uppercase()
|
||||||
|
|||||||
@@ -599,7 +599,7 @@ async fn serve_session(
|
|||||||
// opted in (PUNKTFUNK_10BIT). A client that can't decode 10-bit (caps bit clear, or an older
|
// opted in (PUNKTFUNK_10BIT). A client that can't decode 10-bit (caps bit clear, or an older
|
||||||
// client) always gets the 8-bit stream. PUNKTFUNK_10BIT is the host policy gate until a
|
// client) always gets the 8-bit stream. PUNKTFUNK_10BIT is the host policy gate until a
|
||||||
// mgmt/console toggle replaces it.
|
// mgmt/console toggle replaces it.
|
||||||
let host_wants_10bit = std::env::var_os("PUNKTFUNK_10BIT").is_some();
|
let host_wants_10bit = crate::config::config().ten_bit;
|
||||||
let client_supports_10bit = hello.video_caps & punktfunk_core::quic::VIDEO_CAP_10BIT != 0;
|
let client_supports_10bit = hello.video_caps & punktfunk_core::quic::VIDEO_CAP_10BIT != 0;
|
||||||
let bit_depth: u8 = if host_wants_10bit && client_supports_10bit {
|
let bit_depth: u8 = if host_wants_10bit && client_supports_10bit {
|
||||||
10
|
10
|
||||||
@@ -1616,7 +1616,7 @@ fn pick_gamepad(pref: GamepadPref, env: Option<&str>, linux: bool, windows: bool
|
|||||||
/// Resolve the client's gamepad-backend preference (the env/logging shell around
|
/// Resolve the client's gamepad-backend preference (the env/logging shell around
|
||||||
/// [`pick_gamepad`]). Always concrete — the `Welcome` reports what the session will drive.
|
/// [`pick_gamepad`]). Always concrete — the `Welcome` reports what the session will drive.
|
||||||
fn resolve_gamepad(pref: GamepadPref) -> GamepadPref {
|
fn resolve_gamepad(pref: GamepadPref) -> GamepadPref {
|
||||||
let env = std::env::var("PUNKTFUNK_GAMEPAD").ok();
|
let env = crate::config::config().gamepad.clone();
|
||||||
let chosen = pick_gamepad(
|
let chosen = pick_gamepad(
|
||||||
pref,
|
pref,
|
||||||
env.as_deref(),
|
env.as_deref(),
|
||||||
@@ -1683,7 +1683,7 @@ fn resolve_compositor(pref: CompositorPref) -> Result<crate::vdisplay::Composito
|
|||||||
{
|
{
|
||||||
// Explicit operator override (legacy / CI / forcing a backend for a test) wins and is assumed
|
// Explicit operator override (legacy / CI / forcing a backend for a test) wins and is assumed
|
||||||
// to come with a hand-set env — don't retarget the process env in that case.
|
// to come with a hand-set env — don't retarget the process env in that case.
|
||||||
let overridden = std::env::var_os("PUNKTFUNK_COMPOSITOR").is_some();
|
let overridden = crate::config::config().compositor.is_some();
|
||||||
let detected = if overridden {
|
let detected = if overridden {
|
||||||
crate::vdisplay::detect().ok()
|
crate::vdisplay::detect().ok()
|
||||||
} else {
|
} else {
|
||||||
@@ -2220,11 +2220,11 @@ fn virtual_stream(
|
|||||||
// driver-churning teardown of a monitor under a still-live session. Register THIS session's stop so
|
// driver-churning teardown of a monitor under a still-live session. Register THIS session's stop so
|
||||||
// the next reconnect preempts it.
|
// the next reconnect preempts it.
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let idd_setup_guard = std::env::var_os("PUNKTFUNK_IDD_PUSH")
|
let idd_setup_guard = crate::config::config()
|
||||||
.is_some()
|
.idd_push
|
||||||
.then(|| IDD_SETUP_LOCK.lock().unwrap());
|
.then(|| IDD_SETUP_LOCK.lock().unwrap());
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
if std::env::var_os("PUNKTFUNK_IDD_PUSH").is_some() {
|
if crate::config::config().idd_push {
|
||||||
let prev = IDD_SESSION_STOP.lock().unwrap().replace(stop.clone());
|
let prev = IDD_SESSION_STOP.lock().unwrap().replace(stop.clone());
|
||||||
if let Some(prev_stop) = prev {
|
if let Some(prev_stop) = prev {
|
||||||
prev_stop.store(true, Ordering::SeqCst);
|
prev_stop.store(true, Ordering::SeqCst);
|
||||||
@@ -2251,7 +2251,7 @@ fn virtual_stream(
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let _composed_flip = crate::capture::composed_flip::ForceComposedFlip::start();
|
let _composed_flip = crate::capture::composed_flip::ForceComposedFlip::start();
|
||||||
|
|
||||||
let perf = std::env::var("PUNKTFUNK_PERF").is_ok();
|
let perf = crate::config::config().perf;
|
||||||
// Microburst cap (applied in send_loop/paced_submit): a frame ≤ this bursts out immediately;
|
// Microburst cap (applied in send_loop/paced_submit): a frame ≤ this bursts out immediately;
|
||||||
// only a bigger frame's overflow is spread. PUNKTFUNK_PACE_BURST_KB overrides the 128 KB default.
|
// only a bigger frame's overflow is spread. PUNKTFUNK_PACE_BURST_KB overrides the 128 KB default.
|
||||||
let burst_cap = std::env::var("PUNKTFUNK_PACE_BURST_KB")
|
let burst_cap = std::env::var("PUNKTFUNK_PACE_BURST_KB")
|
||||||
@@ -2291,7 +2291,7 @@ fn virtual_stream(
|
|||||||
let mut compositor = compositor;
|
let mut compositor = compositor;
|
||||||
let (session_tx, session_rx) = std::sync::mpsc::channel::<SessionSwitch>();
|
let (session_tx, session_rx) = std::sync::mpsc::channel::<SessionSwitch>();
|
||||||
let watch = std::env::var_os("PUNKTFUNK_SESSION_WATCH").is_some()
|
let watch = std::env::var_os("PUNKTFUNK_SESSION_WATCH").is_some()
|
||||||
&& std::env::var_os("PUNKTFUNK_COMPOSITOR").is_none();
|
&& crate::config::config().compositor.is_none();
|
||||||
let _watcher = if watch {
|
let _watcher = if watch {
|
||||||
let stop = stop.clone();
|
let stop = stop.clone();
|
||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
@@ -2731,7 +2731,7 @@ fn virtual_stream_relay(
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let perf = std::env::var("PUNKTFUNK_PERF").is_ok();
|
let perf = crate::config::config().perf;
|
||||||
let burst_cap = std::env::var("PUNKTFUNK_PACE_BURST_KB")
|
let burst_cap = std::env::var("PUNKTFUNK_PACE_BURST_KB")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|s| s.parse::<usize>().ok())
|
.and_then(|s| s.parse::<usize>().ok())
|
||||||
@@ -2770,7 +2770,7 @@ fn virtual_stream_relay(
|
|||||||
// the secure desktop's HDR independent-flip (it storms ACCESS_LOST → black), whereas the WGC helper
|
// the secure desktop's HDR independent-flip (it storms ACCESS_LOST → black), whereas the WGC helper
|
||||||
// STAYS LIVE through a lock/UAC. So by default the mux keeps WGC the whole time (no DesktopWatcher
|
// STAYS LIVE through a lock/UAC. So by default the mux keeps WGC the whole time (no DesktopWatcher
|
||||||
// switch, no overlay). Enable the experimental DDA-on-secure path with PUNKTFUNK_SECURE_DDA=1.
|
// switch, no overlay). Enable the experimental DDA-on-secure path with PUNKTFUNK_SECURE_DDA=1.
|
||||||
let dda_secure = std::env::var("PUNKTFUNK_SECURE_DDA").is_ok() || secure_test_ms.is_some();
|
let dda_secure = crate::config::config().secure_dda || secure_test_ms.is_some();
|
||||||
// The authoritative Default↔Winlogon signal (requires SYSTEM to read the Winlogon desktop name);
|
// The authoritative Default↔Winlogon signal (requires SYSTEM to read the Winlogon desktop name);
|
||||||
// only needed when the DDA-on-secure path is enabled.
|
// only needed when the DDA-on-secure path is enabled.
|
||||||
let watcher = dda_secure.then(crate::capture::desktop_watch::DesktopWatcher::start);
|
let watcher = dda_secure.then(crate::capture::desktop_watch::DesktopWatcher::start);
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ pub fn apply_input_env(_chosen: Compositor) {}
|
|||||||
/// a backend for a test), else the **live session** ([`detect_active_session`] — so a Bazzite box
|
/// a backend for a test), else the **live session** ([`detect_active_session`] — so a Bazzite box
|
||||||
/// follows Gaming↔Desktop switches), else a last-resort `XDG_CURRENT_DESKTOP` read.
|
/// follows Gaming↔Desktop switches), else a last-resort `XDG_CURRENT_DESKTOP` read.
|
||||||
pub fn detect() -> Result<Compositor> {
|
pub fn detect() -> Result<Compositor> {
|
||||||
if let Ok(v) = std::env::var("PUNKTFUNK_COMPOSITOR") {
|
if let Some(v) = crate::config::config().compositor.as_deref() {
|
||||||
return match v.trim().to_ascii_lowercase().as_str() {
|
return match v.trim().to_ascii_lowercase().as_str() {
|
||||||
"kwin" | "kde" | "plasma" => Ok(Compositor::Kwin),
|
"kwin" | "kde" | "plasma" => Ok(Compositor::Kwin),
|
||||||
"wlroots" | "sway" | "hyprland" | "wlr" => Ok(Compositor::Wlroots),
|
"wlroots" | "sway" | "hyprland" | "wlr" => Ok(Compositor::Wlroots),
|
||||||
@@ -551,11 +551,7 @@ pub fn open(compositor: Compositor) -> Result<Box<dyn VirtualDisplay>> {
|
|||||||
/// default) auto-detects, preferring pf-vdisplay if its device interface is enumerable.
|
/// default) auto-detects, preferring pf-vdisplay if its device interface is enumerable.
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn windows_use_pf_vdisplay() -> bool {
|
fn windows_use_pf_vdisplay() -> bool {
|
||||||
match std::env::var("PUNKTFUNK_VDISPLAY")
|
match crate::config::config().vdisplay.as_deref().map(str::trim) {
|
||||||
.ok()
|
|
||||||
.as_deref()
|
|
||||||
.map(str::trim)
|
|
||||||
{
|
|
||||||
Some("pf") | Some("pf-vdisplay") | Some("pfvd") => true,
|
Some("pf") | Some("pf-vdisplay") | Some("pfvd") => true,
|
||||||
Some("sudovda") | Some("sudo") => false,
|
Some("sudovda") | Some("sudo") => false,
|
||||||
_ => pf_vdisplay::is_available(),
|
_ => pf_vdisplay::is_available(),
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const PF_VDISPLAY_INTERFACE: GUID =
|
|||||||
/// IDD-push mode: a new client connection preempts + recreates the monitor (single-client reconnect),
|
/// IDD-push mode: a new client connection preempts + recreates the monitor (single-client reconnect),
|
||||||
/// because a REUSED IddCx monitor's swap-chain is dead. Off → monitors are shared across sessions.
|
/// because a REUSED IddCx monitor's swap-chain is dead. Off → monitors are shared across sessions.
|
||||||
fn idd_push_mode() -> bool {
|
fn idd_push_mode() -> bool {
|
||||||
std::env::var_os("PUNKTFUNK_IDD_PUSH").is_some()
|
crate::config::config().idd_push
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Monotonic per-session id keying a pf-vdisplay monitor for `IOCTL_ADD`/`IOCTL_REMOVE`. Unlike
|
/// Monotonic per-session id keying a pf-vdisplay monitor for `IOCTL_ADD`/`IOCTL_REMOVE`. Unlike
|
||||||
@@ -249,9 +249,9 @@ unsafe fn create_monitor(device: isize, mode: Mode, watchdog_s: u32) -> Result<M
|
|||||||
// the discrete render GPU it pins here). The pf-vdisplay driver now IMPLEMENTS this IOCTL
|
// the discrete render GPU it pins here). The pf-vdisplay driver now IMPLEMENTS this IOCTL
|
||||||
// (IddCxAdapterSetRenderAdapter); a failure is still tolerated (the driver also reports its real
|
// (IddCxAdapterSetRenderAdapter); a failure is still tolerated (the driver also reports its real
|
||||||
// render LUID in the shared header, so the host binds to the right GPU regardless).
|
// render LUID in the shared header, so the host binds to the right GPU regardless).
|
||||||
let pinned = if std::env::var("PUNKTFUNK_RENDER_ADAPTER").is_ok() {
|
let pinned = if crate::config::config().render_adapter.is_some() {
|
||||||
unsafe { resolve_render_adapter_luid() }
|
unsafe { resolve_render_adapter_luid() }
|
||||||
} else if std::env::var_os("PUNKTFUNK_IDD_PUSH").is_some() {
|
} else if crate::config::config().idd_push {
|
||||||
// P2 direct frame push: the host opens the driver's shared textures AND runs NVENC on the
|
// P2 direct frame push: the host opens the driver's shared textures AND runs NVENC on the
|
||||||
// RENDER adapter, so on a hybrid box (dGPU + iGPU) it MUST be the discrete encoder GPU — an
|
// RENDER adapter, so on a hybrid box (dGPU + iGPU) it MUST be the discrete encoder GPU — an
|
||||||
// iGPU-rendered surface is untouchable by NVENC. pf-vdisplay now IMPLEMENTS
|
// iGPU-rendered surface is untouchable by NVENC. pf-vdisplay now IMPLEMENTS
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ pub(crate) static CURRENT_MON_GEN: AtomicU64 = AtomicU64::new(0);
|
|||||||
/// IDD-push mode: a new client connection preempts + recreates the monitor (single-client reconnect),
|
/// IDD-push mode: a new client connection preempts + recreates the monitor (single-client reconnect),
|
||||||
/// because a REUSED IddCx monitor's swap-chain is dead. Off → monitors are shared across sessions.
|
/// because a REUSED IddCx monitor's swap-chain is dead. Off → monitors are shared across sessions.
|
||||||
fn idd_push_mode() -> bool {
|
fn idd_push_mode() -> bool {
|
||||||
std::env::var_os("PUNKTFUNK_IDD_PUSH").is_some()
|
crate::config::config().idd_push
|
||||||
}
|
}
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -301,9 +301,9 @@ unsafe fn create_monitor(device: isize, mode: Mode, watchdog_s: u32) -> Result<M
|
|||||||
// the real source of the perpetual ACCESS_LOST + MODE_CHANGE_IN_PROGRESS storm. So default to
|
// the real source of the perpetual ACCESS_LOST + MODE_CHANGE_IN_PROGRESS storm. So default to
|
||||||
// NOT pinning — let the IDD use its natural adapter like Apollo. Opt in with
|
// NOT pinning — let the IDD use its natural adapter like Apollo. Opt in with
|
||||||
// PUNKTFUNK_RENDER_ADAPTER=<name substring> only on a box that genuinely needs steering.
|
// PUNKTFUNK_RENDER_ADAPTER=<name substring> only on a box that genuinely needs steering.
|
||||||
let pinned = if std::env::var("PUNKTFUNK_RENDER_ADAPTER").is_ok() {
|
let pinned = if crate::config::config().render_adapter.is_some() {
|
||||||
unsafe { resolve_render_adapter_luid() }
|
unsafe { resolve_render_adapter_luid() }
|
||||||
} else if std::env::var_os("PUNKTFUNK_IDD_PUSH").is_some() {
|
} else if crate::config::config().idd_push {
|
||||||
// P2 direct frame push: the host opens the driver's shared textures AND runs NVENC on the
|
// P2 direct frame push: the host opens the driver's shared textures AND runs NVENC on the
|
||||||
// RENDER adapter, so on a hybrid box (4090 + iGPU) it MUST be the discrete encoder GPU —
|
// RENDER adapter, so on a hybrid box (4090 + iGPU) it MUST be the discrete encoder GPU —
|
||||||
// an iGPU-rendered surface is untouchable by NVENC. pf-vdisplay HONORS SET_RENDER_ADAPTER
|
// an iGPU-rendered surface is untouchable by NVENC. pf-vdisplay HONORS SET_RENDER_ADAPTER
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ pub fn run(opts: HelperOptions) -> Result<()> {
|
|||||||
// the GPU scheduling priority the SYSTEM host stamps on us, not pipeline depth.
|
// the GPU scheduling priority the SYSTEM host stamps on us, not pipeline depth.
|
||||||
let interval = std::time::Duration::from_secs_f64(1.0 / opts.fps.max(1) as f64);
|
let interval = std::time::Duration::from_secs_f64(1.0 / opts.fps.max(1) as f64);
|
||||||
|
|
||||||
let perf = std::env::var_os("PUNKTFUNK_PERF").is_some();
|
let perf = crate::config::config().perf;
|
||||||
let mut frames = 0u64;
|
let mut frames = 0u64;
|
||||||
let mut repeats = 0u64; // frames where no newer capture had arrived (duplicate re-encode)
|
let mut repeats = 0u64; // frames where no newer capture had arrived (duplicate re-encode)
|
||||||
let mut cap_ns = 0u64; // time in try_latest (capture + video-processor convert)
|
let mut cap_ns = 0u64; // time in try_latest (capture + video-processor convert)
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ use windows::Win32::Foundation::LUID;
|
|||||||
/// already satisfy this).
|
/// already satisfy this).
|
||||||
pub(crate) unsafe fn resolve_render_adapter_luid() -> Option<LUID> {
|
pub(crate) unsafe fn resolve_render_adapter_luid() -> Option<LUID> {
|
||||||
use windows::Win32::Graphics::Dxgi::{CreateDXGIFactory1, IDXGIFactory1};
|
use windows::Win32::Graphics::Dxgi::{CreateDXGIFactory1, IDXGIFactory1};
|
||||||
let want = std::env::var("PUNKTFUNK_RENDER_ADAPTER")
|
let want = crate::config::config()
|
||||||
.ok()
|
.render_adapter
|
||||||
|
.clone()
|
||||||
.filter(|s| !s.is_empty());
|
.filter(|s| !s.is_empty());
|
||||||
let factory: IDXGIFactory1 = CreateDXGIFactory1().ok()?;
|
let factory: IDXGIFactory1 = CreateDXGIFactory1().ok()?;
|
||||||
let mut best: Option<(LUID, u64, String)> = None;
|
let mut best: Option<(LUID, u64, String)> = None;
|
||||||
|
|||||||
@@ -21,12 +21,28 @@ Migrated the two highest-churn dispatch reads onto it (`encode::windows_resolved
|
|||||||
`punktfunk1::should_use_helper`). Risk: low (env constant at runtime → identical behaviour). Verify: box
|
`punktfunk1::should_use_helper`). Risk: low (env constant at runtime → identical behaviour). Verify: box
|
||||||
`cargo check --features nvenc`.
|
`cargo check --features nvenc`.
|
||||||
|
|
||||||
**Stage 2 — finish `HostConfig` + resolve-once.**
|
**Stage 2 — finish `HostConfig` + resolve-once. ✅ DONE (this commit).**
|
||||||
Migrate the remaining ~64 `env::var` sites onto `HostConfig` fields (esp. `PUNKTFUNK_IDD_PUSH` ×8,
|
Migrated **31** genuinely-constant operator/dispatch sites onto `HostConfig`: `idd_push` ×7 (the
|
||||||
`PUNKTFUNK_RENDER_ADAPTER`, `PUNKTFUNK_ZEROCOPY`, `PUNKTFUNK_SECURE_DDA`, `PUNKTFUNK_IDD_DEPTH`,
|
capture/topology disagreement knob), `no_wgc`, `capture_backend`, `render_adapter`, `encoder_pref` (Linux),
|
||||||
`PUNKTFUNK_NO_WGC`, the perf/debug flags). Linux vars (XDG/compositor) included for one config. Risk:
|
the Windows vdisplay-backend select, plus the plan-named `secure_dda`/`idd_depth`/`zerocopy`/`ten_bit` and the
|
||||||
medium (must preserve each var's exact semantics — a flipped bool is a silent regression; assert per the §1
|
multi-site `perf` ×4 / `compositor` ×5 / `video_source` ×3 / `gamepad`. Each `HostConfig` field's parser is
|
||||||
checklist). Verify: Linux + box build; `grep env::var` should reach ~0 outside `config.rs`.
|
**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).**
|
**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
|
Resolve `display/capture/topology/encoder/format/hdr/bit_depth` ONCE from `HostConfig` into a typed
|
||||||
|
|||||||
Reference in New Issue
Block a user