feat(vdisplay/driver): the frame pump survives MMCSS refusal and outranks GPU contention

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 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 15:24:10 +02:00
co-authored by Claude Fable 5
parent 94f0207cbd
commit 19d6f79d2d
3 changed files with 70 additions and 7 deletions
@@ -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;
@@ -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);
@@ -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
);