Files
punktfunk/packaging/windows/vdisplay-driver/pf-vdisplay/src/monitor.rs
T
enricobuehler e2c9bfd3d9
apple / swift (push) Successful in 1m4s
windows-host / package (push) Successful in 6m28s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m14s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m10s
release / apple (push) Successful in 7m53s
android / android (push) Successful in 10m33s
ci / web (push) Successful in 44s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 3m4s
ci / docs-site (push) Successful in 53s
ci / rust (push) Successful in 12m22s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m11s
apple / screenshots (push) Successful in 5m24s
deb / build-publish (push) Successful in 3m16s
decky / build-publish (push) Successful in 21s
ci / bench (push) Successful in 4m42s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 27s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m34s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m42s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m13s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 47s
flatpak / build-publish (push) Successful in 4m24s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m5s
docker / deploy-docs (push) Successful in 25s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 7m44s
feat(windows): pf-vdisplay IDD-push — HDR + pipelined zero-copy capture
HDR (display-driven, matching the WGC path):
- CTA-861.3 HDR EDID (BT.2020 primaries + HDR Static Metadata block) so Windows
  offers "Use HDR" on the virtual display. The host FOLLOWS the display's live
  advanced-color state, recreating the shared ring at the matching format
  (FP16 in HDR / BGRA in SDR) on a toggle — no freeze.
- Always emit Main10/BT.2020-PQ Rgb10a2 while the display is HDR; the client
  auto-detects PQ from the HEVC VUI (clients under-report VIDEO_CAP_10BIT).
  Generic HDR10 mastering SEI on every IDR.
- Generation-tagged `latest` (gen<<40|seq<<8|slot) + driver `is_stale` re-attach
  kill the toggle-time garbage frame and any stale-ring read.

Perf:
- Pipeline the encode loop (Capturer::pipeline_depth; IDD-push = 2): submit N+1
  before polling N so the convert/copy on the 3D engine overlaps the NVENC encode
  of N on the ASIC. PUNKTFUNK_IDD_DEPTH overrides (1 = synchronous).
- Rotating host output ring (OUT_RING) so the in-flight encode and the next
  convert never touch the same texture.
- HDR converts directly from the keyed-mutex slot's SRV into the output ring
  (drops the redundant slot->fp16 scratch copy); SDR copies the BGRA slot in.
  The slot mutex is held only across the convert/copy, not the encode.
  RING_LEN 3->6 for publish headroom.
- Capture-health diagnostic: new_fps vs repeat_fps under PUNKTFUNK_PERF (a low
  new_fps at a high send rate means the source isn't compositing, not an encode
  stall).

Validated live on the RTX box: 5120x1440@240 HDR streams; driver composes
~180 new fps, encode 240 fps @ ~4.3 ms p50.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 00:39:28 +02:00

112 lines
4.1 KiB
Rust

//! The monitor + mode model and control-plane state. Replaces virtual-display-rs's `ipc.rs`
//! (named-pipe IPC + serde `driver_ipc` types). Monitors are created on demand by the SudoVDA IOCTL
//! control plane (`control.rs`); each carries the GUID the host keys it by plus the OS target id +
//! render-adapter LUID captured at arrival (the ADD reply).
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU32, AtomicU64};
use std::sync::{Mutex, OnceLock};
use std::time::Instant;
use wdf_umdf_sys::{IDDCX_ADAPTER__, IDDCX_MONITOR__};
pub type Dimen = u32;
pub type RefreshRate = u32;
/// One resolution with the refresh rates it supports.
#[derive(Clone, PartialEq, Eq)]
pub struct Mode {
pub width: Dimen,
pub height: Dimen,
pub refresh_rates: Vec<RefreshRate>,
}
/// A monitor's identity (the EDID serial) + advertised modes.
#[derive(Clone)]
pub struct MonitorData {
pub id: u32,
pub modes: Vec<Mode>,
}
/// A live (or pending) monitor.
pub struct MonitorObject {
pub object: Option<NonNull<IDDCX_MONITOR__>>,
pub data: MonitorData,
/// The full GUID the host keys this monitor by (ADD dedup / REMOVE).
pub guid: u128,
/// OS target id + render-adapter LUID, captured from `IDARG_OUT_MONITORARRIVAL` (the ADD reply).
pub target_id: u32,
pub adapter_luid_low: u32,
pub adapter_luid_high: i32,
/// When the entry was pushed (`do_add`). The watchdog skips monitors younger than the host's
/// setup window (CCD commit + GDI-name resolve + settle) so a still-initializing monitor is never
/// torn down mid-birth during reconnect churn.
pub created_at: Instant,
}
// SAFETY: the raw IddCx object ptr is framework-managed; access is serialized by MONITOR_MODES.
unsafe impl Send for MonitorObject {}
unsafe impl Sync for MonitorObject {}
/// The IddCx adapter object, stashed for the control plane (SET_RENDER_ADAPTER).
pub struct AdapterObject(pub NonNull<IDDCX_ADAPTER__>);
// SAFETY: raw ptr managed by the framework.
unsafe impl Send for AdapterObject {}
unsafe impl Sync for AdapterObject {}
pub static ADAPTER: OnceLock<AdapterObject> = OnceLock::new();
pub static MONITOR_MODES: Mutex<Vec<MonitorObject>> = Mutex::new(Vec::new());
/// Monitor id / EDID-serial counter (unique per created monitor).
pub static NEXT_ID: AtomicU32 = AtomicU32::new(1);
/// Watchdog (seconds). The host reads the timeout via GET_WATCHDOG and PINGs to keep alive. 8 s (was
/// 3) gives the host's between-session teardown gap — stop old pinger → CCD display re-attach (a slow
/// `SetDisplayConfig`) → REMOVE — headroom, so the watchdog doesn't spuriously fire during reconnect
/// churn. The host derives its PING interval from this (timeout/3), so it auto-adjusts.
pub static WATCHDOG_TIMEOUT: AtomicU32 = AtomicU32::new(8);
pub static WATCHDOG_COUNTDOWN: AtomicU32 = AtomicU32::new(8);
/// The preferred render adapter LUID set via SET_RENDER_ADAPTER, packed `(high<<32)|low`. 0 = none.
pub static PREFERRED_RENDER_ADAPTER: AtomicU64 = AtomicU64::new(0);
/// Protocol version reported by GET_VERSION: {major, minor, incremental, testbuild} — matches SudoVDA.
pub const PROTOCOL_VERSION: [u8; 4] = [0, 2, 1, 1];
/// A single (width, height, refresh) tuple — modes flattened across their refresh rates.
#[derive(Copy, Clone)]
pub struct ModeItem {
pub width: Dimen,
pub height: Dimen,
pub refresh_rate: RefreshRate,
}
pub trait FlattenModes {
fn flatten(&self) -> impl Iterator<Item = ModeItem>;
}
impl FlattenModes for Vec<Mode> {
fn flatten(&self) -> impl Iterator<Item = ModeItem> {
self.iter().flat_map(|m| {
m.refresh_rates.iter().map(|&rr| ModeItem {
width: m.width,
height: m.height,
refresh_rate: rr,
})
})
}
}
/// Fallback modes appended after the client's requested mode, so a topology change still has options.
pub fn default_modes() -> Vec<Mode> {
vec![
Mode {
width: 1920,
height: 1080,
refresh_rates: vec![60, 120],
},
Mode {
width: 1280,
height: 720,
refresh_rates: vec![60],
},
]
}