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:
@@ -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
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user