refactor(windows-host): §2.5 step 1 — delete the dead/write-only monitor-lifecycle code
Removes the cruft the §2.5 ownership-model rewrite would otherwise carry forward, and corrects a
false invariant the docs described:
* CURRENT_MON_GEN (sudovda) — the "current monitor generation" global was WRITE-ONLY. It was
stored on every mgr_acquire (both backends) but its only reader, idd_push's `my_gen`, was set
and NEVER read. The "session capturer re-checks the monitor gen each frame and bails on a
reconnect" behaviour the doc describes was never wired — per-frame staleness is the SEPARATE
ring FrameToken.generation / IDD_GENERATION mechanism (which works and is untouched). So the
monitor-gen-via-WinCaptureTarget carry the design proposed is unnecessary. Deleted the static,
its stores in both backends, the pf_vdisplay import, and idd_push's dead `my_gen` field/read.
(MON_GEN — the lease-generation counter behind the stale-lease no-op — is REAL and kept.)
* IDD_PERSIST + open_or_reuse + IddReuseHandle (idd_push) — a persistent-capturer reuse path
from an early prototype, defined but with ZERO callers across the crate. Deleted, plus the now
-orphaned `use std::sync::Mutex` and the now-dead `set_client_10bit` setter.
Windows-only; grep confirms no remaining references to any deleted symbol. Box build to follow.
First of the incremental §2.5 steps (user-approved OnceLock VirtualDisplayManager design).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,6 @@ use super::{CapturedFrame, Capturer, FramePayload, PixelFormat};
|
|||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use pf_vdisplay_proto::frame;
|
use pf_vdisplay_proto::frame;
|
||||||
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
use windows::core::{w, Interface, HSTRING};
|
use windows::core::{w, Interface, HSTRING};
|
||||||
use windows::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE, LUID};
|
use windows::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE, LUID};
|
||||||
@@ -152,111 +151,11 @@ pub struct IddPushCapturer {
|
|||||||
last_seq: u64,
|
last_seq: u64,
|
||||||
last_present: Option<(ID3D11Texture2D, PixelFormat)>,
|
last_present: Option<(ID3D11Texture2D, PixelFormat)>,
|
||||||
status_logged: bool,
|
status_logged: bool,
|
||||||
/// The monitor generation this capturer was opened for. When the active monitor gen changes (a
|
|
||||||
/// reconnect preempted + recreated the monitor), `next_frame` bails immediately so this session
|
|
||||||
/// releases its NVENC encoder instead of lingering on the dead ring's 20s deadline.
|
|
||||||
my_gen: u64,
|
|
||||||
_keepalive: Box<dyn Send>,
|
_keepalive: Box<dyn Send>,
|
||||||
}
|
}
|
||||||
// COM objects used only from the owning (encode) thread.
|
// COM objects used only from the owning (encode) thread.
|
||||||
unsafe impl Send for IddPushCapturer {}
|
unsafe impl Send for IddPushCapturer {}
|
||||||
|
|
||||||
/// The persistent IDD-push capturer, kept alive for the host lifetime and SHARED across client
|
|
||||||
/// sessions. The driver's per-session monitor TEARDOWN→RECREATE path is unstable (on session 2 the
|
|
||||||
/// target-id resolves to 0, `IddCxSwapChainSetDevice` fails `0x80070057`, then an access violation),
|
|
||||||
/// while the FIRST-session path is solid. So we create the monitor + ring + swap-chain ONCE and hand
|
|
||||||
/// every later session a thin handle delegating to this one. The persistent capturer holds a monitor
|
|
||||||
/// lease for the host lifetime, so `VirtualDisplay::create` always JOINs the same live monitor (same
|
|
||||||
/// target id) and the reuse match always hits — no recreate, no driver crash. Prototype scope:
|
|
||||||
/// single-client, single-mode (a different mode would need a recreate, the unstable path).
|
|
||||||
static IDD_PERSIST: Mutex<Option<IddPushCapturer>> = Mutex::new(None);
|
|
||||||
|
|
||||||
/// Open the IDD-push capturer, reusing the persistent one across sessions (see [`IDD_PERSIST`]).
|
|
||||||
pub fn open_or_reuse(
|
|
||||||
target: WinCaptureTarget,
|
|
||||||
preferred: Option<(u32, u32, u32)>,
|
|
||||||
client_10bit: bool,
|
|
||||||
keepalive: Box<dyn Send>,
|
|
||||||
) -> Result<Box<dyn Capturer>> {
|
|
||||||
let (w, h, _) =
|
|
||||||
preferred.context("IDD push needs the negotiated mode (WxH) to size the ring")?;
|
|
||||||
let mut slot = IDD_PERSIST.lock().unwrap();
|
|
||||||
let reuse = matches!(slot.as_ref(), Some(c) if c.target_id == target.target_id && c.width == w && c.height == h);
|
|
||||||
match slot.as_mut() {
|
|
||||||
Some(c) if reuse => {
|
|
||||||
// Reuse: the persistent capturer already owns the monitor + ring + driver attach. Drop the
|
|
||||||
// new per-session monitor lease (the persistent capturer's lease keeps the monitor live).
|
|
||||||
// The ring tracks the display, not the client; only the client's 10-bit cap can differ.
|
|
||||||
drop(keepalive);
|
|
||||||
c.set_client_10bit(client_10bit);
|
|
||||||
tracing::info!(
|
|
||||||
target_id = target.target_id,
|
|
||||||
client_10bit,
|
|
||||||
"IDD push: reusing the persistent capturer (no monitor/ring recreate)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Some(c) => bail!(
|
|
||||||
"IDD-push persistent capturer is {}x{} target {}, this session wants {}x{} target {} — a \
|
|
||||||
mode/target change needs a recreate (the driver's recreate path is unstable); not \
|
|
||||||
supported in the persistent prototype",
|
|
||||||
c.width,
|
|
||||||
c.height,
|
|
||||||
c.target_id,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
target.target_id
|
|
||||||
),
|
|
||||||
None => {
|
|
||||||
tracing::info!(
|
|
||||||
target_id = target.target_id,
|
|
||||||
client_10bit,
|
|
||||||
"IDD push: creating the persistent capturer (first session)"
|
|
||||||
);
|
|
||||||
// (dead persistent path) open() now returns the keepalive on failure; this path has no
|
|
||||||
// fallback, so discard it on error.
|
|
||||||
*slot = Some(
|
|
||||||
IddPushCapturer::open(target, preferred, client_10bit, keepalive)
|
|
||||||
.map_err(|(e, _keepalive)| e)?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Box::new(IddReuseHandle))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Thin per-session handle: every method delegates to the single persistent [`IddPushCapturer`].
|
|
||||||
/// Dropping it (session end) does NOT tear down the ring/monitor — that's the whole point.
|
|
||||||
struct IddReuseHandle;
|
|
||||||
impl Capturer for IddReuseHandle {
|
|
||||||
fn next_frame(&mut self) -> Result<CapturedFrame> {
|
|
||||||
IDD_PERSIST
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.as_mut()
|
|
||||||
.context("IDD-push persistent capturer missing")?
|
|
||||||
.next_frame()
|
|
||||||
}
|
|
||||||
fn try_latest(&mut self) -> Result<Option<CapturedFrame>> {
|
|
||||||
IDD_PERSIST
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.as_mut()
|
|
||||||
.context("IDD-push persistent capturer missing")?
|
|
||||||
.try_latest()
|
|
||||||
}
|
|
||||||
fn set_active(&self, active: bool) {
|
|
||||||
if let Some(c) = IDD_PERSIST.lock().unwrap().as_ref() {
|
|
||||||
c.set_active(active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn hdr_meta(&self) -> Option<punktfunk_core::quic::HdrMeta> {
|
|
||||||
IDD_PERSIST
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|c| c.hdr_meta())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a permissive (Everyone:GenericAll) `SECURITY_ATTRIBUTES` so the restricted WUDFHost driver
|
/// Build a permissive (Everyone:GenericAll) `SECURITY_ATTRIBUTES` so the restricted WUDFHost driver
|
||||||
/// can OPEN the host-created objects — the same `D:(A;;GA;;;WD)` SDDL the gamepad shared section uses.
|
/// can OPEN the host-created objects — the same `D:(A;;GA;;;WD)` SDDL the gamepad shared section uses.
|
||||||
/// The returned `psd` backing must outlive `sa`; both are dropped when the process exits.
|
/// The returned `psd` backing must outlive `sa`; both are dropped when the process exits.
|
||||||
@@ -521,7 +420,6 @@ impl IddPushCapturer {
|
|||||||
last_seq: 0,
|
last_seq: 0,
|
||||||
last_present: None,
|
last_present: None,
|
||||||
status_logged: false,
|
status_logged: false,
|
||||||
my_gen: crate::vdisplay::sudovda::CURRENT_MON_GEN.load(Ordering::Relaxed),
|
|
||||||
// Placeholder; `open()` attaches the real keepalive on success, so a FAILED open can hand
|
// Placeholder; `open()` attaches the real keepalive on success, so a FAILED open can hand
|
||||||
// it back to the caller for the DDA fallback (audit §5.1).
|
// it back to the caller for the DDA fallback (audit §5.1).
|
||||||
_keepalive: Box::new(()),
|
_keepalive: Box::new(()),
|
||||||
@@ -662,12 +560,6 @@ impl IddPushCapturer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the client's 10-bit capability (the reuse path). Only affects whether a fresh `open`
|
|
||||||
/// proactively enables advanced color; the per-frame conversion follows the display, not the client.
|
|
||||||
fn set_client_10bit(&mut self, client_10bit: bool) {
|
|
||||||
self.client_10bit = client_10bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recreate the ring at the format for `new_display_hdr` (the user flipped "Use HDR"). Bumps the
|
/// Recreate the ring at the format for `new_display_hdr` (the user flipped "Use HDR"). Bumps the
|
||||||
/// generation so the driver re-attaches ([`is_stale`]) to the new-format textures; clears the
|
/// generation so the driver re-attaches ([`is_stale`]) to the new-format textures; clears the
|
||||||
/// header's `latest` so we don't consume a stale slot from the old ring; drops the conversion
|
/// header's `latest` so we don't consume a stale slot from the old ring; drops the conversion
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ use pf_vdisplay_proto::control;
|
|||||||
|
|
||||||
use super::{Mode, VirtualDisplay, VirtualOutput};
|
use super::{Mode, VirtualDisplay, VirtualOutput};
|
||||||
// Backend-NEUTRAL CCD/DXGI helpers reused from the SudoVDA backend (a pf-vdisplay monitor's target_id
|
// Backend-NEUTRAL CCD/DXGI helpers reused from the SudoVDA backend (a pf-vdisplay monitor's target_id
|
||||||
// is a real OS target id, so these operate identically). The shared MON_GEN/CURRENT_MON_GEN generation
|
// is a real OS target id, so these operate identically). The shared MON_GEN lease-generation counter is
|
||||||
// counter is reused too, so the IDD-push stale-ring bail works regardless of which backend is active.
|
// reused too, so a stale preempted lease can't tear down the live monitor regardless of which backend is active.
|
||||||
use super::sudovda::{CURRENT_MON_GEN, MON_GEN};
|
use super::sudovda::MON_GEN;
|
||||||
use crate::win_adapter::resolve_render_adapter_luid;
|
use crate::win_adapter::resolve_render_adapter_luid;
|
||||||
use crate::win_display::{
|
use crate::win_display::{
|
||||||
isolate_displays_ccd, resolve_gdi_name, restore_displays_ccd, set_active_mode, SavedConfig,
|
isolate_displays_ccd, resolve_gdi_name, restore_displays_ccd, set_active_mode, SavedConfig,
|
||||||
@@ -554,7 +554,6 @@ fn mgr_acquire(mode: Mode) -> Result<VirtualOutput> {
|
|||||||
let pm = Some((mon.mode.width, mon.mode.height, mon.mode.refresh_hz));
|
let pm = Some((mon.mode.width, mon.mode.height, mon.mode.refresh_hz));
|
||||||
let target = mon.target();
|
let target = mon.target();
|
||||||
let gen = mon.gen;
|
let gen = mon.gen;
|
||||||
CURRENT_MON_GEN.store(gen, Ordering::Relaxed);
|
|
||||||
return Ok(VirtualOutput {
|
return Ok(VirtualOutput {
|
||||||
node_id: 0,
|
node_id: 0,
|
||||||
preferred_mode: pm,
|
preferred_mode: pm,
|
||||||
@@ -581,7 +580,6 @@ fn mgr_acquire(mode: Mode) -> Result<VirtualOutput> {
|
|||||||
let pm = Some((mon.mode.width, mon.mode.height, mon.mode.refresh_hz));
|
let pm = Some((mon.mode.width, mon.mode.height, mon.mode.refresh_hz));
|
||||||
let target = mon.target();
|
let target = mon.target();
|
||||||
let gen = mon.gen;
|
let gen = mon.gen;
|
||||||
CURRENT_MON_GEN.store(gen, Ordering::Relaxed);
|
|
||||||
g.state = MgrState::Active { mon, refs: 1 };
|
g.state = MgrState::Active { mon, refs: 1 };
|
||||||
Ok(VirtualOutput {
|
Ok(VirtualOutput {
|
||||||
node_id: 0,
|
node_id: 0,
|
||||||
|
|||||||
@@ -19,12 +19,6 @@ use std::sync::{Arc, Mutex, Once};
|
|||||||
// backends keeps the idd_push stale-ring bail working regardless of which backend is active).
|
// backends keeps the idd_push stale-ring bail working regardless of which backend is active).
|
||||||
pub(crate) static MON_GEN: AtomicU64 = AtomicU64::new(1);
|
pub(crate) static MON_GEN: AtomicU64 = AtomicU64::new(1);
|
||||||
|
|
||||||
/// The gen of the CURRENTLY-active monitor. A session capturer captures this at open and re-checks it
|
|
||||||
/// each frame; when it changes (a reconnect preempted + recreated the monitor), the old session bails
|
|
||||||
/// IMMEDIATELY instead of lingering on the dead ring's 20s frame deadline — which would otherwise hold
|
|
||||||
/// its NVENC encoder open and exhaust the GPU's encode-session limit under rapid reconnects.
|
|
||||||
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 {
|
||||||
@@ -590,7 +584,6 @@ fn mgr_acquire(mode: Mode) -> Result<VirtualOutput> {
|
|||||||
let pm = Some((mon.mode.width, mon.mode.height, mon.mode.refresh_hz));
|
let pm = Some((mon.mode.width, mon.mode.height, mon.mode.refresh_hz));
|
||||||
let target = mon.target();
|
let target = mon.target();
|
||||||
let gen = mon.gen;
|
let gen = mon.gen;
|
||||||
CURRENT_MON_GEN.store(gen, Ordering::Relaxed);
|
|
||||||
return Ok(VirtualOutput {
|
return Ok(VirtualOutput {
|
||||||
node_id: 0,
|
node_id: 0,
|
||||||
preferred_mode: pm,
|
preferred_mode: pm,
|
||||||
@@ -617,7 +610,6 @@ fn mgr_acquire(mode: Mode) -> Result<VirtualOutput> {
|
|||||||
let pm = Some((mon.mode.width, mon.mode.height, mon.mode.refresh_hz));
|
let pm = Some((mon.mode.width, mon.mode.height, mon.mode.refresh_hz));
|
||||||
let target = mon.target();
|
let target = mon.target();
|
||||||
let gen = mon.gen;
|
let gen = mon.gen;
|
||||||
CURRENT_MON_GEN.store(gen, Ordering::Relaxed);
|
|
||||||
g.state = MgrState::Active { mon, refs: 1 };
|
g.state = MgrState::Active { mon, refs: 1 };
|
||||||
Ok(VirtualOutput {
|
Ok(VirtualOutput {
|
||||||
node_id: 0,
|
node_id: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user