feat(windows-drivers): STEP 5 — SwapChainProcessor + Direct3DDevice (swap-chain drain)
apple / swift (push) Failing after 1s
apple / screenshots (push) Has been skipped
windows-drivers / probe-and-proto (push) Successful in 18s
ci / rust (push) Successful in 1m14s
windows-drivers / driver-build (push) Successful in 1m11s
ci / web (push) Successful in 41s
ci / docs-site (push) Successful in 1m1s
android / android (push) Successful in 3m22s
deb / build-publish (push) Successful in 2m37s
decky / build-publish (push) Successful in 11s
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 6s
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 5m52s
ci / bench (push) Successful in 4m47s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m28s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m18s
docker / deploy-docs (push) Successful in 17s

The pf-vdisplay driver now consumes the OS swap-chain so a virtual monitor is a usable
display rather than a stalled one. Compiles + loads on-glass (no regression: adapter still
inits, Status=OK); adversarially reviewed — no blockers, the leak/deadlock invariants preserved.

- new swap_chain_processor.rs: a worker thread (MMCSS "Distribution") that binds the render D3D
  device (IddCxSwapChainSetDevice, single-borrow 60x@50ms retry) then drains the swap-chain
  (ReleaseAndAcquireBuffer2 -> FinishedProcessingFrame; E_PENDING waits 16ms on the surface
  event). NO frame publisher yet (STEP 6). RAII terminate+join Drop; the load-bearing
  top-of-loop terminate check (the oracle's reconnect-leak fix). Fixed a Rust-2021 disjoint-
  capture bug: `.0` field access bypassed the Sendable Send wrapper -> rebind the whole wrappers.
- new direct_3d_device.rs: CreateDXGIFactory2 -> EnumAdapterByLuid(render LUID) -> D3D11CreateDevice;
  a DEVICE_POOL of one Arc<Direct3DDevice> per render LUID (the NVIDIA-UMD-worker-thread leak fix).
- monitor.rs: MonitorObject gains swap_chain_processor; set/take helpers return it for the caller
  to drop OUTSIDE the MONITOR_MODES lock (dropping joins the worker — must never happen under the
  lock); remove_monitor/clear_all drop it before IddCxMonitorDeparture.
- callbacks.rs: assign_swap_chain spawns the processor (pooled device per RenderAdapterLuid;
  WdfObjectDelete on D3D-init failure so the OS retries); unassign_swap_chain drops it. Fixed the
  stale `panic = "abort"` doc (workspace is unwind; the extern "C" boundary aborts on unwind).
- Cargo.toml: windows 0.58 + thiserror (both already resolved in the driver lock). The 3 needed
  swap-chain DDIs were already wrapped in wdk-iddcx; their HRESULT-shaped NTSTATUS is classified
  by hand (hr>=0 success, 0x8000000A E_PENDING).
- Also rustfmt'd the whole driver workspace (it had never been driver-fmt'd).

Built via the ultracode flow: STEP-5 map workflow -> agent-implement -> box build (caught the
Send-capture bug) -> adversarial-verify-agent -> deploy (loads). Session-1 on-glass validation
(the drain loop servicing an ACTIVE monitor) is the next gate — assign_swap_chain only fires
under an interactive session. Note for STEP 6: target_id_for_object uses the MONITOR_MODES handle
lookup the oracle moved to a WDF context; revisit before target_id keys the shared frame ring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 09:29:20 +00:00
parent 024e709191
commit d8a453f6ca
10 changed files with 705 additions and 48 deletions
@@ -1,13 +1,14 @@
//! The IddCx client-config callbacks + the PnP `EvtDeviceD0Entry`.
//!
//! STEP 2: stubs with the correct PFN signatures (so the config wires up + the driver loads); the real
//! mode/EDID logic (STEP 4), adapter init (STEP 3), and swap-chain handoff (STEP 5) fill them in. Every
//! callback is `unsafe extern "C"` to match the wdk-sys `PFN_IDD_CX_*` types; with `panic = "abort"`
//! (workspace profile) a panic across the FFI boundary aborts rather than being UB. `query_target_info`
//! is implemented now because it gates HDR (`HIGH_COLOR_SPACE`) and the adapter (STEP 3) sets FP16.
//! 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, WDFREQUEST};
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,
@@ -178,16 +179,67 @@ pub unsafe extern "C" fn set_gamma_ramp(
STATUS_SUCCESS
}
/// A swap-chain was assigned to the monitor. STEP 5: spawn the `SwapChainProcessor`.
/// 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,
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();
processor.run(swap_chain, device, new_frame_event, target_id);
// 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).
pub unsafe extern "C" fn unassign_swap_chain(_monitor: iddcx::IDDCX_MONITOR) -> NTSTATUS {
/// 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
}