83d3d6384a
apple / swift (push) Successful in 1m7s
ci / rust (push) Successful in 1m14s
windows-drivers / driver-build (push) Successful in 1m8s
apple / screenshots (push) Successful in 3m14s
windows-drivers / probe-and-proto (push) Successful in 19s
ci / web (push) Successful in 40s
ci / docs-site (push) Successful in 1m1s
android / android (push) Successful in 3m13s
deb / build-publish (push) Successful in 2m38s
decky / build-publish (push) Successful in 12s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
windows-host / package (push) Successful in 5m18s
ci / bench (push) Successful in 4m35s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m26s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m16s
docker / deploy-docs (push) Successful in 31s
Audit pass over the new pf-vdisplay driver's unsafe surface: 92 per-site // SAFETY comments added across adapter.rs / monitor.rs / entry.rs / callbacks.rs / swap_chain_processor.rs / frame_transport.rs / direct_3d_device.rs (control.rs already had full coverage). COMMENTS ONLY — zero logic, signature, or control-flow change (verified via git diff: every added line is a // SAFETY comment or blank). The dominant gap was the pervasive `core::mem::zeroed()` FFI-struct builds (IDDCX_*/WDF_*/ DISPLAYCONFIG_* C PODs whose all-zero bit pattern is a valid uninitialized/Invalid state, with the required .Size/fields set immediately after) — each now carries a one-line // SAFETY. Plus explicit notes on the two stack/local-pointer-into-FFI hazards (adapter.rs `version` ptr into IddCxAdapterInitAsync; monitor.rs `edid` Vec ptr into IddCxMonitorCreate — both read synchronously before the local drops) and the frame_transport.rs raw-HANDLE / mapped-header derefs + cleanup paths. The already-justified Send/Sync wrappers (SendAdapter, CtxTypeInfo/DevCtxInfo, MonitorObject, Sendable, FramePublisher) were audited — each already carried a // SAFETY. No site needed a code change. First slice of STEP 8 (the SudoVDA drop). Comments-only ⇒ build-neutral; windows-drivers.yml verifies on the next runner build. Remaining STEP 8: re-vendor the installer's driver binary from the new drivers/ tree (the shipping packaging/windows/pf-vdisplay/ binary is still built from the OLD oracle tree with the SudoVDA-compat GUID — ABI-mismatched with the host's proto GUID), add an .inx to the new tree, re-point scripts/README from vdisplay-driver/ to drivers/, flip the selector default to pf-vdisplay, then delete the old oracle tree. Keep sudovda.rs (the runtime fallback + the backend-neutral CCD helpers pf_vdisplay.rs reuses) and the WGC-relay/DDA secure path (the secure-desktop gate is not yet passed on glass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
332 lines
15 KiB
Rust
332 lines
15 KiB
Rust
//! The IddCx client-config callbacks + the PnP `EvtDeviceD0Entry`.
|
|
//!
|
|
//! The mode/EDID logic (STEP 4), adapter init (STEP 3), and swap-chain handoff (STEP 5) are wired in; the
|
|
//! `*2`/HDR-metadata/gamma callbacks remain stubs (STEP 7). Every callback is `unsafe extern "C"` to match
|
|
//! the wdk-sys `PFN_IDD_CX_*` types; a panic unwinding across that `extern "C"` boundary aborts the process
|
|
//! (Rust >= 1.81 default) rather than being UB. (The swap-chain WORKER is a plain `thread::spawn`, so a
|
|
//! panic there only unwinds + ends that thread — it must not panic.) `query_target_info` is implemented
|
|
//! because it gates HDR (`HIGH_COLOR_SPACE`) and the adapter (STEP 3) sets FP16.
|
|
|
|
use wdk_sys::iddcx;
|
|
use wdk_sys::{NTSTATUS, WDFDEVICE, WDFOBJECT, WDFREQUEST, call_unsafe_wdf_function_binding};
|
|
|
|
use crate::{
|
|
STATUS_BUFFER_TOO_SMALL, STATUS_INVALID_PARAMETER, STATUS_NOT_FOUND, STATUS_NOT_IMPLEMENTED,
|
|
STATUS_SUCCESS,
|
|
};
|
|
|
|
/// PnP `EvtDeviceD0Entry` (not an IddCx config callback). Adapter creation is deferred to the first D0
|
|
/// (the adapter object is only valid after D0), not driver_add.
|
|
pub unsafe extern "C" fn device_d0_entry(
|
|
device: WDFDEVICE,
|
|
_previous_state: wdk_sys::WDF_POWER_DEVICE_STATE,
|
|
) -> NTSTATUS {
|
|
dbglog!("[pf-vd] device_d0_entry");
|
|
crate::adapter::init_adapter(device)
|
|
}
|
|
|
|
/// Async completion of `IddCxAdapterInitAsync`: stash the adapter for later DDIs. STEP 4 also starts the
|
|
/// watchdog here.
|
|
pub unsafe extern "C" fn adapter_init_finished(
|
|
adapter: iddcx::IDDCX_ADAPTER,
|
|
_p_in: *const iddcx::IDARG_IN_ADAPTER_INIT_FINISHED,
|
|
) -> NTSTATUS {
|
|
dbglog!("[pf-vd] adapter_init_finished");
|
|
crate::adapter::set_adapter(adapter);
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// SDR mode list for an EDID monitor: EDID-serial lookup → count-then-fill `IDDCX_MONITOR_MODE`.
|
|
pub unsafe extern "C" fn parse_monitor_description(
|
|
p_in: *const iddcx::IDARG_IN_PARSEMONITORDESCRIPTION,
|
|
p_out: *mut iddcx::IDARG_OUT_PARSEMONITORDESCRIPTION,
|
|
) -> NTSTATUS {
|
|
// SAFETY: framework-provided in/out args, valid for the call.
|
|
let in_args = unsafe { &*p_in };
|
|
let out_args = unsafe { &mut *p_out };
|
|
// SAFETY: the framework supplies a valid EDID buffer of `DataSize` bytes.
|
|
let edid = unsafe {
|
|
core::slice::from_raw_parts(
|
|
in_args.MonitorDescription.pData.cast::<u8>(),
|
|
in_args.MonitorDescription.DataSize as usize,
|
|
)
|
|
};
|
|
let Ok(id) = crate::edid::Edid::get_serial(edid) else {
|
|
return STATUS_INVALID_PARAMETER;
|
|
};
|
|
let Some(modes) = crate::monitor::modes_for_id(id) else {
|
|
return STATUS_NOT_FOUND;
|
|
};
|
|
let count = crate::monitor::flatten(&modes).count() as u32;
|
|
out_args.MonitorModeBufferOutputCount = count;
|
|
if in_args.MonitorModeBufferInputCount < count {
|
|
// A zero input count is a count-only probe (success); a non-zero too-small buffer is an error.
|
|
return if in_args.MonitorModeBufferInputCount > 0 {
|
|
STATUS_BUFFER_TOO_SMALL
|
|
} else {
|
|
STATUS_SUCCESS
|
|
};
|
|
}
|
|
// SAFETY: `pMonitorModes` points to >= `count` IDDCX_MONITOR_MODE entries (validated above).
|
|
let out = unsafe { core::slice::from_raw_parts_mut(in_args.pMonitorModes, count as usize) };
|
|
for (item, slot) in crate::monitor::flatten(&modes).zip(out.iter_mut()) {
|
|
// SAFETY: building a C POD — the all-zero bit pattern is a valid uninitialized IDDCX_MONITOR_MODE;
|
|
// the required `.Size` (+ origin / signal info) are set immediately below.
|
|
let mut mode: iddcx::IDDCX_MONITOR_MODE = unsafe { core::mem::zeroed() };
|
|
mode.Size = core::mem::size_of::<iddcx::IDDCX_MONITOR_MODE>() as u32;
|
|
mode.Origin = iddcx::IDDCX_MONITOR_MODE_ORIGIN::IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR;
|
|
mode.MonitorVideoSignalInfo =
|
|
crate::monitor::display_info(item.width, item.height, item.refresh_rate);
|
|
*slot = mode;
|
|
}
|
|
out_args.PreferredMonitorModeIdx = 0;
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// HDR (`*2`) mode list — writes `IDDCX_MONITOR_MODE2` (+BitsPerComponent). Mandatory under FP16. Mirrors
|
|
/// the v1 `parse_monitor_description` exactly (EDID-serial lookup → count-then-fill, same
|
|
/// BUFFER_TOO_SMALL/SUCCESS logic), but emits `IDDCX_MONITOR_MODE2` with the per-mode wire bit-depth so the
|
|
/// OS offers HDR10 modes. `IDARG_OUT_PARSEMONITORDESCRIPTION` is the SAME out struct as v1 (shared by the C
|
|
/// header); only the in-args / mode struct are the `*2` variants.
|
|
pub unsafe extern "C" fn parse_monitor_description2(
|
|
p_in: *const iddcx::IDARG_IN_PARSEMONITORDESCRIPTION2,
|
|
p_out: *mut iddcx::IDARG_OUT_PARSEMONITORDESCRIPTION,
|
|
) -> NTSTATUS {
|
|
// SAFETY: framework-provided in/out args, valid for the call.
|
|
let in_args = unsafe { &*p_in };
|
|
let out_args = unsafe { &mut *p_out };
|
|
// SAFETY: the framework supplies a valid EDID buffer of `DataSize` bytes.
|
|
let edid = unsafe {
|
|
core::slice::from_raw_parts(
|
|
in_args.MonitorDescription.pData.cast::<u8>(),
|
|
in_args.MonitorDescription.DataSize as usize,
|
|
)
|
|
};
|
|
let Ok(id) = crate::edid::Edid::get_serial(edid) else {
|
|
return STATUS_INVALID_PARAMETER;
|
|
};
|
|
let Some(modes) = crate::monitor::modes_for_id(id) else {
|
|
return STATUS_NOT_FOUND;
|
|
};
|
|
let count = crate::monitor::flatten(&modes).count() as u32;
|
|
out_args.MonitorModeBufferOutputCount = count;
|
|
if in_args.MonitorModeBufferInputCount < count {
|
|
// A zero input count is a count-only probe (success); a non-zero too-small buffer is an error.
|
|
return if in_args.MonitorModeBufferInputCount > 0 {
|
|
STATUS_BUFFER_TOO_SMALL
|
|
} else {
|
|
STATUS_SUCCESS
|
|
};
|
|
}
|
|
// SAFETY: `pMonitorModes` points to >= `count` IDDCX_MONITOR_MODE2 entries (validated above).
|
|
let out = unsafe { core::slice::from_raw_parts_mut(in_args.pMonitorModes, count as usize) };
|
|
for (item, slot) in crate::monitor::flatten(&modes).zip(out.iter_mut()) {
|
|
// SAFETY: building a C POD — the all-zero bit pattern is a valid uninitialized IDDCX_MONITOR_MODE2;
|
|
// the required `.Size` (+ origin / signal info / bit depth) are set immediately below.
|
|
let mut mode: iddcx::IDDCX_MONITOR_MODE2 = unsafe { core::mem::zeroed() };
|
|
mode.Size = core::mem::size_of::<iddcx::IDDCX_MONITOR_MODE2>() as u32;
|
|
mode.Origin = iddcx::IDDCX_MONITOR_MODE_ORIGIN::IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR;
|
|
mode.MonitorVideoSignalInfo =
|
|
crate::monitor::display_info(item.width, item.height, item.refresh_rate);
|
|
mode.BitsPerComponent = crate::monitor::wire_bits();
|
|
*slot = mode;
|
|
}
|
|
out_args.PreferredMonitorModeIdx = 0;
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// Only called for EDID-less monitors; ours always carry an EDID, so this stays NOT_IMPLEMENTED.
|
|
pub unsafe extern "C" fn monitor_get_default_modes(
|
|
_monitor: iddcx::IDDCX_MONITOR,
|
|
_p_in: *const iddcx::IDARG_IN_GETDEFAULTDESCRIPTIONMODES,
|
|
_p_out: *mut iddcx::IDARG_OUT_GETDEFAULTDESCRIPTIONMODES,
|
|
) -> NTSTATUS {
|
|
STATUS_NOT_IMPLEMENTED
|
|
}
|
|
|
|
/// SDR target (scan-out) modes: pointer-match the monitor → fill `IDDCX_TARGET_MODE`.
|
|
pub unsafe extern "C" fn monitor_query_modes(
|
|
monitor: iddcx::IDDCX_MONITOR,
|
|
p_in: *const iddcx::IDARG_IN_QUERYTARGETMODES,
|
|
p_out: *mut iddcx::IDARG_OUT_QUERYTARGETMODES,
|
|
) -> NTSTATUS {
|
|
// SAFETY: framework-provided in/out args, valid for the call.
|
|
let in_args = unsafe { &*p_in };
|
|
let out_args = unsafe { &mut *p_out };
|
|
let Some(modes) = crate::monitor::modes_for_object(monitor) else {
|
|
return STATUS_NOT_FOUND;
|
|
};
|
|
let count = crate::monitor::flatten(&modes).count() as u32;
|
|
out_args.TargetModeBufferOutputCount = count;
|
|
if in_args.TargetModeBufferInputCount >= count {
|
|
// SAFETY: `pTargetModes` points to >= `count` IDDCX_TARGET_MODE entries.
|
|
let out = unsafe { core::slice::from_raw_parts_mut(in_args.pTargetModes, count as usize) };
|
|
for (item, slot) in crate::monitor::flatten(&modes).zip(out.iter_mut()) {
|
|
*slot = crate::monitor::target_mode(item.width, item.height, item.refresh_rate);
|
|
}
|
|
}
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// HDR (`*2`) target modes — writes `IDDCX_TARGET_MODE2`. Mandatory under FP16. Mirrors the v1
|
|
/// `monitor_query_modes` exactly (pointer-match the monitor → count → fill), but emits `IDDCX_TARGET_MODE2`
|
|
/// (with per-mode wire bit-depth) via `monitor::target_mode2`. `IDARG_OUT_QUERYTARGETMODES` is the SAME out
|
|
/// struct as v1.
|
|
pub unsafe extern "C" fn monitor_query_modes2(
|
|
monitor: iddcx::IDDCX_MONITOR,
|
|
p_in: *const iddcx::IDARG_IN_QUERYTARGETMODES2,
|
|
p_out: *mut iddcx::IDARG_OUT_QUERYTARGETMODES,
|
|
) -> NTSTATUS {
|
|
// SAFETY: framework-provided in/out args, valid for the call.
|
|
let in_args = unsafe { &*p_in };
|
|
let out_args = unsafe { &mut *p_out };
|
|
let Some(modes) = crate::monitor::modes_for_object(monitor) else {
|
|
return STATUS_NOT_FOUND;
|
|
};
|
|
let count = crate::monitor::flatten(&modes).count() as u32;
|
|
out_args.TargetModeBufferOutputCount = count;
|
|
if in_args.TargetModeBufferInputCount >= count {
|
|
// SAFETY: `pTargetModes` points to >= `count` IDDCX_TARGET_MODE2 entries.
|
|
let out = unsafe { core::slice::from_raw_parts_mut(in_args.pTargetModes, count as usize) };
|
|
for (item, slot) in crate::monitor::flatten(&modes).zip(out.iter_mut()) {
|
|
*slot = crate::monitor::target_mode2(item.width, item.height, item.refresh_rate);
|
|
}
|
|
}
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// Diagnostic only — assign drives everything. STEP 4 logs the committed paths.
|
|
pub unsafe extern "C" fn adapter_commit_modes(
|
|
_adapter: iddcx::IDDCX_ADAPTER,
|
|
_p_in: *const iddcx::IDARG_IN_COMMITMODES,
|
|
) -> NTSTATUS {
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// HDR (`*2`) commit over `IDDCX_PATH2`. Mandatory under FP16.
|
|
pub unsafe extern "C" fn adapter_commit_modes2(
|
|
_adapter: iddcx::IDDCX_ADAPTER,
|
|
_p_in: *const iddcx::IDARG_IN_COMMITMODES2,
|
|
) -> NTSTATUS {
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// Report `HIGH_COLOR_SPACE` so the OS enables the HDR10 wide-gamut/PQ target. Mandatory under FP16.
|
|
pub unsafe extern "C" fn query_target_info(
|
|
_adapter: iddcx::IDDCX_ADAPTER,
|
|
_p_in: *mut iddcx::IDARG_IN_QUERYTARGET_INFO,
|
|
p_out: *mut iddcx::IDARG_OUT_QUERYTARGET_INFO,
|
|
) -> NTSTATUS {
|
|
// SAFETY: p_out is the framework's (uninitialised) out buffer; zero then set the one field we report.
|
|
unsafe {
|
|
core::ptr::write(p_out, core::mem::zeroed());
|
|
(*p_out).TargetCaps = iddcx::IDDCX_TARGET_CAPS::IDDCX_TARGET_CAPS_HIGH_COLOR_SPACE;
|
|
}
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// Accept the OS's default HDR10 static metadata (the host/client own the stream's final metadata).
|
|
/// Mandatory under FP16.
|
|
pub unsafe extern "C" fn set_default_hdr_metadata(
|
|
_monitor: iddcx::IDDCX_MONITOR,
|
|
_p_in: *const iddcx::IDARG_IN_MONITOR_SET_DEFAULT_HDR_METADATA,
|
|
) -> NTSTATUS {
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// Accept (do not apply) the gamma ramp — the client display applies its own transform. MANDATORY once
|
|
/// FP16 is set, or the OS rejects the adapter at init ("Failed to get adapter").
|
|
pub unsafe extern "C" fn set_gamma_ramp(
|
|
_monitor: iddcx::IDDCX_MONITOR,
|
|
_p_in: *const iddcx::IDARG_IN_SET_GAMMARAMP,
|
|
) -> NTSTATUS {
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// A swap-chain was assigned to the monitor. STEP 5: spawn the `SwapChainProcessor` that drains it (so
|
|
/// the monitor is a usable display). Always returns `STATUS_SUCCESS` — on D3D-init failure we delete the
|
|
/// swap-chain so the OS makes a fresh one and re-assigns (the oracle pattern).
|
|
pub unsafe extern "C" fn assign_swap_chain(
|
|
monitor: iddcx::IDDCX_MONITOR,
|
|
p_in: *const iddcx::IDARG_IN_SETSWAPCHAIN,
|
|
) -> NTSTATUS {
|
|
// SAFETY: framework-provided in args, valid for the call.
|
|
let in_args = unsafe { &*p_in };
|
|
let swap_chain = in_args.hSwapChain;
|
|
let render_adapter = in_args.RenderAdapterLuid;
|
|
let new_frame_event = in_args.hNextSurfaceAvailable;
|
|
|
|
// wdk-sys LUID → windows-crate LUID (identical { LowPart: u32, HighPart: i32 } layout). The render
|
|
// adapter is the GPU the OS picked to render this virtual monitor; the pooled D3D device is keyed by
|
|
// it (relevant on a hybrid iGPU+dGPU box).
|
|
let luid = windows::Win32::Foundation::LUID {
|
|
LowPart: render_adapter.LowPart,
|
|
HighPart: render_adapter.HighPart,
|
|
};
|
|
dbglog!(
|
|
"[pf-vd] assign_swap_chain: OS render adapter LUID = {:08x}:{:08x}",
|
|
render_adapter.HighPart,
|
|
render_adapter.LowPart
|
|
);
|
|
|
|
// FIRST drop any existing processor on this monitor (RAII-joins its worker), OUTSIDE the lock.
|
|
drop(crate::monitor::take_swap_chain_processor(monitor));
|
|
|
|
// The OS target id (stamped on the monitor at creation, after IddCxMonitorArrival) keys the
|
|
// per-monitor objects STEP 6's host opens. 0 (default) if the monitor isn't found.
|
|
let target_id = crate::monitor::target_id_for_object(monitor).unwrap_or(0);
|
|
|
|
if let Some(device) = crate::direct_3d_device::pooled_device(luid) {
|
|
let mut processor = crate::swap_chain_processor::SwapChainProcessor::new();
|
|
// STEP 6: the publisher reports this render LUID into the host header so the host detects a
|
|
// render-adapter mismatch (it created the ring textures on its own GPU). `luid` is the OS-picked
|
|
// render adapter built above.
|
|
processor.run(
|
|
swap_chain,
|
|
device,
|
|
new_frame_event,
|
|
target_id,
|
|
luid.LowPart,
|
|
luid.HighPart,
|
|
);
|
|
// Install on the monitor; drop any processor it replaced (a race lost above) OUTSIDE the lock.
|
|
drop(crate::monitor::set_swap_chain_processor(monitor, processor));
|
|
} else {
|
|
// D3D init failed: delete the swap-chain so the OS generates a fresh one + retries.
|
|
dbglog!(
|
|
"[pf-vd] assign_swap_chain: pooled Direct3DDevice unavailable — deleting swap-chain for OS retry"
|
|
);
|
|
// SAFETY: `swap_chain` is the framework-provided IddCx swap-chain handle.
|
|
unsafe {
|
|
call_unsafe_wdf_function_binding!(WdfObjectDelete, swap_chain as WDFOBJECT);
|
|
}
|
|
}
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// The monitor went inactive. STEP 5: drop the processor (RAII joins the worker thread, which deletes the
|
|
/// swap-chain object before returning).
|
|
pub unsafe extern "C" fn unassign_swap_chain(monitor: iddcx::IDDCX_MONITOR) -> NTSTATUS {
|
|
// Take + drop OUTSIDE any lock (the take releases `MONITOR_MODES` before the join).
|
|
let had = crate::monitor::take_swap_chain_processor(monitor);
|
|
dbglog!(
|
|
"[pf-vd] unassign_swap_chain — dropped live processor: {}",
|
|
had.is_some()
|
|
);
|
|
drop(had);
|
|
STATUS_SUCCESS
|
|
}
|
|
|
|
/// The pf-vdisplay-proto control plane. Returns `()` and completes the request itself (matches the C
|
|
/// `EVT_IDD_CX_DEVICE_IO_CONTROL` shape). STEP 4: dispatch the proto IOCTLs; for now just complete.
|
|
pub unsafe extern "C" fn device_io_control(
|
|
_device: WDFDEVICE,
|
|
request: WDFREQUEST,
|
|
_output_len: usize,
|
|
_input_len: usize,
|
|
ioctl_code: u32,
|
|
) {
|
|
// SAFETY: `request` is the framework-provided WDFREQUEST; `control::dispatch` completes it exactly once.
|
|
unsafe { crate::control::dispatch(request, ioctl_code) };
|
|
}
|