feat(windows): pf-vdisplay — all-Rust IddCx virtual display (replaces SudoVDA)
P1 done: a pure-Rust UMDF2 IddCx driver, drop-in compatible with the host's
existing vdisplay/sudovda.rs control plane (the {e5bcc234} interface + the
SudoVDA IOCTL ABI), so the host drives it unchanged. Validated streaming on
glass at 5120x1440@240 — steady 240 fps, ~2.4 ms encode, clean teardown, full
parity with SudoVDA.
- Vendored wdf-umdf-sys / wdf-umdf bindgen crates (MIT, from virtual-display-rs)
+ the SDK-version build.rs fix that resolves the IddCxStub lib path by the WDK
version actually containing um\x64\iddcx, not the max base SDK.
- pf-vdisplay crate: entry/callbacks/context/control/monitor/edid/
swap_chain_processor. Our OWN 128-byte EDID (manufacturer PNK, product
punktfunk — no SudoVDA bytes), a real swap-chain drain (faithful vdd port,
required so DWM keeps compositing), the SudoVDA-compatible IOCTL control plane
(ADD/REMOVE/PING/GET_WATCHDOG/GET_VERSION/SET_RENDER_ADAPTER) + a watchdog that
tears down orphaned monitors when the host stops pinging.
- deploy-dev.ps1: stage + sign + stampinf (date.time DriverVer) + Inf2Cat +
install, codifying the "bump DriverVer or pnputil keeps the old binary" gotcha.
- docs/windows-virtual-display-rust-port.md: investigation, the on-glass
validation, and the two traps that cost time (Session-0 measurement +
accumulated device-state needing a reboot).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use wdf_umdf_sys::{
|
||||
IDARG_IN_ADAPTERSETRENDERADAPTER, IDARG_IN_ADAPTER_INIT, IDARG_IN_MONITORCREATE,
|
||||
IDARG_IN_QUERY_HWCURSOR, IDARG_IN_SETUP_HWCURSOR, IDARG_IN_SWAPCHAINSETDEVICE,
|
||||
IDARG_OUT_ADAPTER_INIT, IDARG_OUT_MONITORARRIVAL, IDARG_OUT_MONITORCREATE,
|
||||
IDARG_OUT_QUERY_HWCURSOR, IDARG_OUT_RELEASEANDACQUIREBUFFER, IDDCX_ADAPTER, IDDCX_MONITOR,
|
||||
IDDCX_SWAPCHAIN, IDD_CX_CLIENT_CONFIG, NTSTATUS, WDFDEVICE, WDFDEVICE_INIT,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, thiserror::Error)]
|
||||
pub enum IddCxError {
|
||||
#[error("{0}")]
|
||||
IddCxFunctionNotAvailable(&'static str),
|
||||
#[error("{0}")]
|
||||
CallFailed(NTSTATUS),
|
||||
#[error("{0}")]
|
||||
NtStatus(NTSTATUS),
|
||||
}
|
||||
|
||||
impl From<IddCxError> for NTSTATUS {
|
||||
fn from(value: IddCxError) -> Self {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use IddCxError::*;
|
||||
match value {
|
||||
IddCxFunctionNotAvailable(_) => Self::STATUS_NOT_FOUND,
|
||||
CallFailed(status) => status,
|
||||
NtStatus(n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NTSTATUS> for IddCxError {
|
||||
fn from(value: NTSTATUS) -> Self {
|
||||
IddCxError::CallFailed(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for IddCxError {
|
||||
fn from(val: i32) -> Self {
|
||||
IddCxError::NtStatus(NTSTATUS(val))
|
||||
}
|
||||
}
|
||||
|
||||
// void IddCx functions return () on success; required by the call macro's error arm but never an error.
|
||||
impl From<()> for IddCxError {
|
||||
fn from(_: ()) -> Self {
|
||||
IddCxError::NtStatus(NTSTATUS(0))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! IddCxCall {
|
||||
($name:ident ( $($args:expr),* )) => {
|
||||
IddCxCall!(false, $name($($args),*))
|
||||
};
|
||||
|
||||
($other_is_error:expr, $name:ident ( $($args:expr),* )) => {{
|
||||
static CACHED_FN: OnceLock<
|
||||
Result<
|
||||
::paste::paste!(::wdf_umdf_sys::[<PFN_ $name:upper>]),
|
||||
IddCxError
|
||||
>
|
||||
> = OnceLock::new();
|
||||
|
||||
let f = CACHED_FN.get_or_init(|| {
|
||||
::paste::paste! {
|
||||
const FN_INDEX: usize = ::wdf_umdf_sys::IDDFUNCENUM::[<$name TableIndex>].0 as usize;
|
||||
|
||||
// validate that wdf function can be used
|
||||
let is_available = ::wdf_umdf_sys::IddCxIsFunctionAvailable!($name);
|
||||
|
||||
if is_available {
|
||||
// SAFETY: Only immutable accesses are done to this
|
||||
// The underlying array is Copy, so we call as_ptr() directly on it inside block
|
||||
let fn_table = unsafe { ::wdf_umdf_sys::IddFunctions.as_ptr() };
|
||||
|
||||
// SAFETY: Ensured that this is present by if condition from `WdfIsFunctionAvailable!`
|
||||
let f = unsafe {
|
||||
fn_table.add(FN_INDEX)
|
||||
.cast::<::wdf_umdf_sys::[<PFN_ $name:upper>]>()
|
||||
};
|
||||
|
||||
// SAFETY: Ensured that this is present by if condition from `IddIsFunctionAvailable!`
|
||||
let f = unsafe { f.read() };
|
||||
|
||||
Ok(f)
|
||||
} else {
|
||||
Err($crate::IddCxError::IddCxFunctionNotAvailable(concat!(stringify!($name), " is not available")))
|
||||
}
|
||||
}
|
||||
}).clone()?;
|
||||
|
||||
// SAFETY: Above: If it's Ok, then it's guaranteed to be Some(fn)
|
||||
let f = unsafe { f.unwrap_unchecked() };
|
||||
|
||||
// SAFETY: Pointer to globals is always immutable
|
||||
let globals = unsafe { ::wdf_umdf_sys::IddDriverGlobals };
|
||||
|
||||
// SAFETY: None. User is responsible for safety and must use their own unsafe block
|
||||
let result = unsafe { f(globals, $($args),*) };
|
||||
|
||||
if $crate::is_nt_error(&result, $other_is_error) {
|
||||
Err(result.into())
|
||||
} else {
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
}};
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
pub unsafe fn IddCxDeviceInitConfig(
|
||||
// in, out
|
||||
DeviceInit: &mut WDFDEVICE_INIT,
|
||||
// in
|
||||
Config: &IDD_CX_CLIENT_CONFIG,
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall! {
|
||||
IddCxDeviceInitConfig(
|
||||
DeviceInit,
|
||||
Config
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
pub unsafe fn IddCxDeviceInitialize(
|
||||
// in
|
||||
Device: WDFDEVICE,
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall! {
|
||||
IddCxDeviceInitialize(
|
||||
Device
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
pub unsafe fn IddCxAdapterInitAsync(
|
||||
// in
|
||||
pInArgs: &IDARG_IN_ADAPTER_INIT,
|
||||
// out
|
||||
pOutArgs: &mut IDARG_OUT_ADAPTER_INIT,
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall! {
|
||||
IddCxAdapterInitAsync(
|
||||
pInArgs,
|
||||
pOutArgs
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
#[rustfmt::skip]
|
||||
pub unsafe fn IddCxMonitorCreate(
|
||||
// in
|
||||
AdapterObject: IDDCX_ADAPTER,
|
||||
// in
|
||||
pInArgs: &IDARG_IN_MONITORCREATE,
|
||||
// out
|
||||
pOutArgs: &mut IDARG_OUT_MONITORCREATE,
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall!(
|
||||
IddCxMonitorCreate(
|
||||
AdapterObject,
|
||||
pInArgs,
|
||||
pOutArgs
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
#[rustfmt::skip]
|
||||
pub unsafe fn IddCxMonitorArrival(
|
||||
// in
|
||||
MonitorObject: IDDCX_MONITOR,
|
||||
// out
|
||||
pOutArgs: &mut IDARG_OUT_MONITORARRIVAL,
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall!(
|
||||
IddCxMonitorArrival(
|
||||
MonitorObject,
|
||||
pOutArgs
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
#[rustfmt::skip]
|
||||
pub unsafe fn IddCxSwapChainSetDevice(
|
||||
// in
|
||||
SwapChainObject: IDDCX_SWAPCHAIN,
|
||||
// in
|
||||
pInArgs: &IDARG_IN_SWAPCHAINSETDEVICE
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall!(
|
||||
true,
|
||||
IddCxSwapChainSetDevice(
|
||||
SwapChainObject,
|
||||
pInArgs
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
#[rustfmt::skip]
|
||||
pub unsafe fn IddCxSwapChainReleaseAndAcquireBuffer(
|
||||
// in
|
||||
SwapChainObject: IDDCX_SWAPCHAIN,
|
||||
// out
|
||||
pOutArgs: &mut IDARG_OUT_RELEASEANDACQUIREBUFFER
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall!(
|
||||
true,
|
||||
IddCxSwapChainReleaseAndAcquireBuffer(
|
||||
SwapChainObject,
|
||||
pOutArgs
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
#[rustfmt::skip]
|
||||
pub unsafe fn IddCxSwapChainFinishedProcessingFrame(
|
||||
// in
|
||||
SwapChainObject: IDDCX_SWAPCHAIN
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall!(
|
||||
true,
|
||||
IddCxSwapChainFinishedProcessingFrame(
|
||||
SwapChainObject
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
#[rustfmt::skip]
|
||||
pub unsafe fn IddCxMonitorDeparture(
|
||||
// in
|
||||
MonitorObject: IDDCX_MONITOR
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall!(
|
||||
IddCxMonitorDeparture(
|
||||
MonitorObject
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
pub unsafe fn IddCxAdapterSetRenderAdapter(
|
||||
// in
|
||||
AdapterObject: IDDCX_ADAPTER,
|
||||
// in
|
||||
pInArgs: *const IDARG_IN_ADAPTERSETRENDERADAPTER,
|
||||
) -> Result<(), IddCxError> {
|
||||
IddCxCall!(IddCxAdapterSetRenderAdapter(AdapterObject, pInArgs))
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
#[rustfmt::skip]
|
||||
pub unsafe fn IddCxMonitorSetupHardwareCursor(
|
||||
// in
|
||||
MonitorObject: IDDCX_MONITOR,
|
||||
// in
|
||||
pInArgs: &IDARG_IN_SETUP_HWCURSOR
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall!(
|
||||
IddCxMonitorSetupHardwareCursor(
|
||||
MonitorObject,
|
||||
pInArgs
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// None. User is responsible for safety.
|
||||
#[rustfmt::skip]
|
||||
pub unsafe fn IddCxMonitorQueryHardwareCursor(
|
||||
// in
|
||||
MonitorObject: IDDCX_MONITOR,
|
||||
// in
|
||||
pInArgs: &IDARG_IN_QUERY_HWCURSOR,
|
||||
// out
|
||||
pOutArgs: &mut IDARG_OUT_QUERY_HWCURSOR
|
||||
) -> Result<NTSTATUS, IddCxError> {
|
||||
IddCxCall!(
|
||||
IddCxMonitorQueryHardwareCursor(
|
||||
MonitorObject,
|
||||
pInArgs,
|
||||
pOutArgs
|
||||
)
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user