d39da4bc06
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>
320 lines
7.9 KiB
Rust
320 lines
7.9 KiB
Rust
#![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
|
|
)
|
|
)
|
|
}
|