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,119 @@
|
||||
//! Driver entry + WDF device-add. Adapted from virtual-display-rs (its event-log/boot-retry logger
|
||||
//! dance is replaced by the `OutputDebugString` logger in `logger.rs`).
|
||||
|
||||
use log::{error, info};
|
||||
use wdf_umdf::{
|
||||
IddCxDeviceInitConfig, IddCxDeviceInitialize, WdfDeviceCreate, WdfDeviceCreateDeviceInterface,
|
||||
WdfDeviceInitSetPnpPowerEventCallbacks, WdfDriverCreate,
|
||||
};
|
||||
use wdf_umdf_sys::{
|
||||
GUID, IDD_CX_CLIENT_CONFIG, NTSTATUS, WDFDEVICE_INIT, WDFDRIVER__, WDFOBJECT, WDF_DRIVER_CONFIG,
|
||||
WDF_OBJECT_ATTRIBUTES, WDF_PNPPOWER_EVENT_CALLBACKS, _DRIVER_OBJECT, _UNICODE_STRING,
|
||||
};
|
||||
|
||||
use crate::callbacks::{
|
||||
adapter_commit_modes, adapter_init_finished, assign_swap_chain, device_d0_entry,
|
||||
monitor_get_default_modes, monitor_query_modes, parse_monitor_description, unassign_swap_chain,
|
||||
};
|
||||
use crate::context::DeviceContext;
|
||||
use crate::control::device_io_control;
|
||||
|
||||
// SudoVDA control-interface GUID — the host opens this to drive the ADD/REMOVE/PING IOCTLs.
|
||||
// {e5bcc234-1e0c-418a-a0d4-ef8b7501414d}
|
||||
const SUVDA_INTERFACE_GUID: GUID = GUID {
|
||||
Data1: 0xe5bc_c234,
|
||||
Data2: 0x1e0c,
|
||||
Data3: 0x418a,
|
||||
Data4: [0xa0, 0xd4, 0xef, 0x8b, 0x75, 0x01, 0x41, 0x4d],
|
||||
};
|
||||
|
||||
/// Driver entry point (called by the framework via `FxDriverEntryUm`).
|
||||
#[no_mangle]
|
||||
extern "C-unwind" fn DriverEntry(
|
||||
driver_object: *mut _DRIVER_OBJECT,
|
||||
registry_path: *mut _UNICODE_STRING,
|
||||
) -> NTSTATUS {
|
||||
crate::logger::init();
|
||||
crate::panic::set_hook();
|
||||
info!("pf-vdisplay v{} starting", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
let mut attributes = WDF_OBJECT_ATTRIBUTES::init();
|
||||
let mut config = WDF_DRIVER_CONFIG::init(Some(driver_add));
|
||||
|
||||
unsafe {
|
||||
WdfDriverCreate(
|
||||
driver_object,
|
||||
registry_path,
|
||||
Some(&mut attributes),
|
||||
&mut config,
|
||||
None,
|
||||
)
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
extern "C-unwind" fn driver_add(
|
||||
_driver: *mut WDFDRIVER__,
|
||||
mut init: *mut WDFDEVICE_INIT,
|
||||
) -> NTSTATUS {
|
||||
let mut callbacks = WDF_PNPPOWER_EVENT_CALLBACKS::init();
|
||||
callbacks.EvtDeviceD0Entry = Some(device_d0_entry);
|
||||
|
||||
unsafe {
|
||||
_ = WdfDeviceInitSetPnpPowerEventCallbacks(init, &mut callbacks);
|
||||
}
|
||||
|
||||
let Some(mut config) = IDD_CX_CLIENT_CONFIG::init() else {
|
||||
error!("Failed to create IDD_CX_CLIENT_CONFIG");
|
||||
return NTSTATUS::STATUS_NOT_FOUND;
|
||||
};
|
||||
|
||||
config.EvtIddCxAdapterInitFinished = Some(adapter_init_finished);
|
||||
config.EvtIddCxParseMonitorDescription = Some(parse_monitor_description);
|
||||
config.EvtIddCxMonitorGetDefaultDescriptionModes = Some(monitor_get_default_modes);
|
||||
config.EvtIddCxMonitorQueryTargetModes = Some(monitor_query_modes);
|
||||
config.EvtIddCxAdapterCommitModes = Some(adapter_commit_modes);
|
||||
config.EvtIddCxMonitorAssignSwapChain = Some(assign_swap_chain);
|
||||
config.EvtIddCxMonitorUnassignSwapChain = Some(unassign_swap_chain);
|
||||
// IddCx redirects device IOCTLs to this callback — our SudoVDA-compatible control plane.
|
||||
config.EvtIddCxDeviceIoControl = Some(device_io_control);
|
||||
|
||||
let init_data = unsafe { &mut *init };
|
||||
let status = unsafe { IddCxDeviceInitConfig(init_data, &config) };
|
||||
if let Err(e) = status {
|
||||
error!("Failed to init iddcx config: {e:?}");
|
||||
return e.into();
|
||||
}
|
||||
|
||||
let mut attributes =
|
||||
WDF_OBJECT_ATTRIBUTES::init_context_type(unsafe { DeviceContext::get_type_info() });
|
||||
attributes.EvtCleanupCallback = Some(event_cleanup);
|
||||
|
||||
let mut device = std::ptr::null_mut();
|
||||
let status = unsafe { WdfDeviceCreate(&mut init, Some(&mut attributes), &mut device) };
|
||||
if let Err(e) = status {
|
||||
error!("Failed to create device: {e:?}");
|
||||
return e.into();
|
||||
}
|
||||
|
||||
// Register the SudoVDA control interface so the host can open it + send the control IOCTLs.
|
||||
let status =
|
||||
unsafe { WdfDeviceCreateDeviceInterface(device, &SUVDA_INTERFACE_GUID, std::ptr::null()) };
|
||||
if let Err(e) = status {
|
||||
error!("Failed to create control device interface: {e:?}");
|
||||
return e.into();
|
||||
}
|
||||
|
||||
let status = unsafe { IddCxDeviceInitialize(device) };
|
||||
if let Err(e) = status {
|
||||
error!("Failed to init iddcx device: {e:?}");
|
||||
return e.into();
|
||||
}
|
||||
|
||||
let context = DeviceContext::new(device);
|
||||
unsafe { context.init(device as WDFOBJECT).into() }
|
||||
}
|
||||
|
||||
unsafe extern "C-unwind" fn event_cleanup(wdf_object: WDFOBJECT) {
|
||||
_ = unsafe { DeviceContext::drop(wdf_object) };
|
||||
}
|
||||
Reference in New Issue
Block a user