From 19d6f79d2dd6247ca5520d3627934373743a6328 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Mon, 27 Jul 2026 15:24:10 +0200 Subject: [PATCH] feat(vdisplay/driver): the frame pump survives MMCSS refusal and outranks GPU contention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two stall-immunity hardenings for the swap-chain drain thread (branch-2 of the disturbance-immunity program — failures in OUR delivery leg, as opposed to adapter-wide display servicing, which no priority survives): - MMCSS registration fails under the restricted WUDFHost token on some boxes; the drain thread then ran UNPRIORITIZED — the whole display's frame pump at normal priority, starvable by DDC/HPD-servicing DPC pressure into multi-hundred-ms delivery holes. Fall back to TIME_CRITICAL. - IddCxSetRealtimeGPUPriority (IddCx 1.9) raises the processing device above every regular application's GPU priority. Slot availability comes from raising the exported IddMinimumVersionRequired 4 → 10, which was overdue independently: the drain loop already calls ReleaseAndAcquireBuffer2 (1.10) unconditionally, so binding a pre-1.10 framework would dispatch past the populated IddFunctions table — with 10 an old framework fails the bind cleanly instead. The product floor is already Win11 22H2 (installer MinVersion=10.0.22621, whose framework is 1.10), so no supported system changes behavior. Co-Authored-By: Claude Fable 5 --- .../windows/drivers/pf-vdisplay/src/lib.rs | 13 ++++- .../pf-vdisplay/src/swap_chain_processor.rs | 47 +++++++++++++++++-- .../windows/drivers/wdk-iddcx/src/lib.rs | 17 +++++++ 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/packaging/windows/drivers/pf-vdisplay/src/lib.rs b/packaging/windows/drivers/pf-vdisplay/src/lib.rs index 1fef993c..38f2aefe 100644 --- a/packaging/windows/drivers/pf-vdisplay/src/lib.rs +++ b/packaging/windows/drivers/pf-vdisplay/src/lib.rs @@ -41,6 +41,15 @@ pub(crate) const STATUS_BUFFER_TOO_SMALL: NTSTATUS = 0xC000_0023u32 as NTSTATUS; /// IddCx (stub mode) requires the driver to export the minimum IddCx framework version it needs — the /// `#ifndef IDD_STUB` branch of `IddCxFuncEnum.h` that normally emits it is compiled out under -/// `IDD_STUB`. `4` matches the proven `wdf-umdf` oracle. +/// `IDD_STUB`. +/// +/// `10` = IddCx 1.10, the TRUTHFUL floor: the drain loop calls +/// `IddCxSwapChainReleaseAndAcquireBuffer2` (1.10) unconditionally and the swap-chain worker uses +/// `IddCxSetRealtimeGPUPriority` (1.9); the product floor is already Windows 11 22H2 / build 22621, +/// whose framework is 1.10 (the installer gate — `MinVersion=10.0.22621` in punktfunk-host.iss — +/// exists precisely for this driver). The oracle's historical `4` predated that floor and would let +/// the driver load against a SHORT `IddFunctions` table, where dispatching any post-1.4 DDI reads +/// past the populated entries. With `10`, an older framework fails the bind cleanly (driver doesn't +/// load) instead of limping into undefined dispatch. #[unsafe(no_mangle)] -pub static IddMinimumVersionRequired: wdk_sys::ULONG = 4; +pub static IddMinimumVersionRequired: wdk_sys::ULONG = 10; diff --git a/packaging/windows/drivers/pf-vdisplay/src/swap_chain_processor.rs b/packaging/windows/drivers/pf-vdisplay/src/swap_chain_processor.rs index c2e33ba0..c7ac3516 100644 --- a/packaging/windows/drivers/pf-vdisplay/src/swap_chain_processor.rs +++ b/packaging/windows/drivers/pf-vdisplay/src/swap_chain_processor.rs @@ -29,8 +29,8 @@ use std::{ }; use wdk_sys::iddcx::{ - IDARG_IN_RELEASEANDACQUIREBUFFER2, IDARG_IN_SWAPCHAINSETDEVICE, - IDARG_OUT_RELEASEANDACQUIREBUFFER2, IDDCX_SWAPCHAIN, + IDARG_IN_RELEASEANDACQUIREBUFFER2, IDARG_IN_SETREALTIMEGPUPRIORITY, + IDARG_IN_SWAPCHAINSETDEVICE, IDARG_OUT_RELEASEANDACQUIREBUFFER2, IDDCX_SWAPCHAIN, }; // `HANDLE` is the shared wdk-sys typedef (`crate::types`) re-used by the iddcx bindings — take it from // the crate root, which is guaranteed to export it (the iddcx module only re-exports it if bindgen @@ -44,7 +44,8 @@ use windows::{ Dxgi::{IDXGIDevice, IDXGIResource}, }, System::Threading::{ - AvRevertMmThreadCharacteristics, AvSetMmThreadCharacteristicsW, WaitForSingleObject, + AvRevertMmThreadCharacteristics, AvSetMmThreadCharacteristicsW, GetCurrentThread, + SetThreadPriority, THREAD_PRIORITY_TIME_CRITICAL, WaitForSingleObject, }, }, core::{Interface, w}, @@ -125,12 +126,25 @@ impl SwapChainProcessor { // MMCSS can fail under the restricted WUDFHost token ('Distribution' task unregistered / // service unavailable). The MS sample CONTINUES unprioritized — never abort: returning // here would leave the assigned swap-chain undrained (the monitor stalls, DWM blocks on - // it) and leak the WDF swap-chain object until device teardown. + // it) and leak the WDF swap-chain object until device teardown. But "unprioritized" is + // not acceptable either: this thread is the whole display's frame pump, and at normal + // priority a display-stack disturbance (DDC/HPD servicing DPC pressure, poller-software + // storms) can starve it into multi-hundred-ms delivery holes. Fall back to + // TIME_CRITICAL — the highest band available without the realtime priority class, and + // the closest to what MMCSS 'Distribution' would have granted. The thread spends its + // life blocked on the surface-available event / keyed mutex, so it cannot starve others. let av_handle = match res { Ok(h) => Some(h), Err(e) => { + // SAFETY: plain FFI; GetCurrentThread returns a pseudo-handle (never fails, + // nothing to close), SetThreadPriority on it affects only this thread. + let fallback = unsafe { + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) + }; dbglog!( - "[pf-vd] swap-chain: MMCSS prioritization failed ({e:?}) — continuing unprioritized" + "[pf-vd] swap-chain: MMCSS prioritization failed ({e:?}) — fell back to \ + TIME_CRITICAL thread priority (ok={})", + fallback.is_ok() ); None } @@ -229,6 +243,29 @@ impl SwapChainProcessor { } thread::sleep(Duration::from_millis(50)); } + // IddCx 1.9 realtime GPU scheduling priority for the processing device (stall-immunity + // program, branch-2 hardening): swap-chain buffer processing outruns ordinary GPU + // contention — "higher priority than any regular application can set". The slot is + // guaranteed populated (`IddMinimumVersionRequired = 10`, lib.rs); the DDI itself may + // still decline (e.g. E_NOTIMPL on pre-WDDM-3.0 hardware) — best-effort, never fatal. + // Called while our borrowed device reference is still alive; IddCx uses it synchronously. + if set_ok { + let mut rt = pod_init!(IDARG_IN_SETREALTIMEGPUPRIORITY); + rt.pDevice = dxgi_device.as_raw().cast(); + // SAFETY: driver is loaded; `swap_chain` is the live assigned swap-chain whose device + // bind just succeeded; `rt.pDevice` is that same bound DXGI device, alive across the + // synchronous call; `rt` points to valid local storage. + let hr = unsafe { wdk_iddcx::IddCxSetRealtimeGPUPriority(swap_chain, &rt) }; + if hr_success(hr) { + dbglog!( + "[pf-vd] swap-chain: processing device raised to REALTIME GPU priority (target={target_id})" + ); + } else { + dbglog!( + "[pf-vd] swap-chain: realtime GPU priority declined ({hr:#x}) — normal scheduling (target={target_id})" + ); + } + } // Release our borrowed device reference — IddCx holds its own now, or we gave up. (Explicit drop // so NLL can't release it mid-loop while the swap-chain still references the raw ptr.) drop(dxgi_device); diff --git a/packaging/windows/drivers/wdk-iddcx/src/lib.rs b/packaging/windows/drivers/wdk-iddcx/src/lib.rs index b2c3289d..45c8573f 100644 --- a/packaging/windows/drivers/wdk-iddcx/src/lib.rs +++ b/packaging/windows/drivers/wdk-iddcx/src/lib.rs @@ -200,3 +200,20 @@ iddcx_ddi!( IddCxSwapChainFinishedProcessingFrame(swap_chain: iddcx::IDDCX_SWAPCHAIN) @ IddCxSwapChainFinishedProcessingFrameTableIndex as PFN_IDDCXSWAPCHAINFINISHEDPROCESSINGFRAME ); + +iddcx_ddi!( + /// Raise the swap-chain's processing D3D device to realtime GPU scheduling priority — "higher + /// than any regular application can set" (IddCx 1.9) — so buffer processing outruns ordinary + /// GPU contention. It does NOT help against adapter-wide display servicing (modeset-class DDIs + /// idle the hardware outright); it defends the contention case only. Best-effort at the call + /// site: the DDI itself may decline (e.g. E_NOTIMPL on WDDM < 3.0 hardware). + /// + /// Table-slot availability rests on the driver's `IddMinimumVersionRequired = 10` export + /// (pf-vdisplay lib.rs): the loader refuses to bind a framework older than IddCx 1.10, so + /// every slot of our compiled 1.10 surface — this 1.9 one included — is populated wherever the + /// driver runs at all. + IddCxSetRealtimeGPUPriority( + swap_chain: iddcx::IDDCX_SWAPCHAIN, + in_args: *const iddcx::IDARG_IN_SETREALTIMEGPUPRIORITY, + ) @ IddCxSetRealtimeGPUPriorityTableIndex as PFN_IDDCXSETREALTIMEGPUPRIORITY +);