fix(windows): IDD-push audit highs — keyed-mutex timeout, two per-frame leaks, IDD_PUSH knob, pooled-device threading

Five verified findings from the IDD-push/pf-vdisplay deep audit:

- Keyed-mutex acquire (BOTH endpoints): AcquireSync returns WAIT_TIMEOUT
  (0x102) / WAIT_ABANDONED (0x80) as SUCCESS-severity HRESULTs, which the
  windows-rs Result wrapper erases — a busy slot read as "acquired", so
  driver and host could race the same ring texture (torn frames) and the
  designed busy-skip backpressure was dead code. Both sides now classify
  the raw vtable HRESULT; WAIT_ABANDONED counts as acquired (ownership
  transfers — refusing it would wedge the slot forever).
- Host SDR hot path leaked one ID3D11VideoProcessorInputView per converted
  frame: the D3D11_VIDEO_PROCESSOR_STREAM ManuallyDrop field suppressed the
  release after VideoProcessorBlt. Released by hand now, success or not.
- Driver leaked IddCx's per-acquire surface reference (from_raw_borrowed on
  a TRANSFERRED reference — the MS sample Attach/Reset's it): the swap-chain
  surface set survived swap-chain destruction, the likely true root cause of
  the ~50 MB-per-reconnect VRAM loss that device pooling only mitigated.
  Now adopted via from_raw (publisher or not) and dropped pre-Finished.
- PUNKTFUNK_IDD_PUSH removed: capture is unconditionally IDD-push, but the
  vdisplay manager still gated the lingering-monitor preempt (and render
  pin) on the knob, whose default was OFF — dev/CLI runs reused a lingering
  monitor whose IddCx swap-chain is dead (black reconnect). The preempt and
  the render-GPU pin are now unconditional; host.env comments no longer
  promise the removed DDA/WGC fallback.
- Driver D3D device: dropped D3D11_CREATE_DEVICE_SINGLETHREADED (unsound
  since DEVICE_POOL shares one device across processors) and the pooled
  immediate context is now SetMultithreadProtected — two concurrent
  monitors' workers otherwise race an unlocked context (UB in the UMD).

No wire-contract change (pf-driver-proto untouched); the driver fixes take
effect on the next pf-vdisplay redeploy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 16:27:13 +00:00
parent fbf3fea0c8
commit 0da9d8ec10
10 changed files with 119 additions and 79 deletions
@@ -342,19 +342,28 @@ impl SwapChainProcessor {
logged_frame = true;
}
// STEP 6: copy the acquired surface into the shared ring BEFORE FinishedProcessingFrame
// (the surface is valid until the next ReleaseAndAcquire). The pointer is BORROWED —
// `from_raw_borrowed` does NOT take IddCx's refcount — and the GPU-side copy is ordered
// before the consumer via the slot keyed mutex. (Attach happens at the loop top.)
if let Some(p) = publisher.as_mut() {
// (the surface is valid until the next ReleaseAndAcquire). Every successful acquire
// TRANSFERS one surface reference to the driver — the MS sample `Attach`es it into a
// ComPtr and `Reset`s BEFORE FinishedProcessingFrame, warning that a driver which
// "forgets to release the reference" leaves the surfaces alive after the swap-chain is
// destroyed. Holding it (the old `from_raw_borrowed`) leaked the swap-chain's whole
// surface set per assign/unassign cycle (reconnect, mode change, HDR flip) — so adopt
// the reference UNCONDITIONALLY (publisher or not); it is released when `res` drops at
// the end of this block. (Publisher attach happens at the loop top.)
{
let raw = buffer.MetaData.pSurface as *mut core::ffi::c_void;
if !raw.is_null() {
// SAFETY: `raw` is IddCx's live surface pointer (valid until the next
// ReleaseAndAcquire); `from_raw_borrowed` does not consume the refcount.
if let Some(res) = unsafe { IDXGIResource::from_raw_borrowed(&raw) }
// SAFETY: `raw` is the live surface IddCx just handed us, carrying the acquire's
// transferred reference; `from_raw` adopts exactly that reference (released on
// drop, below — the queued GPU copy is unaffected: D3D defers destruction, and
// the copy is ordered before the consumer via the slot keyed mutex).
let res = unsafe { IDXGIResource::from_raw(raw) };
if let Some(p) = publisher.as_mut()
&& let Ok(tex) = res.cast::<ID3D11Texture2D>()
{
p.publish(&tex);
}
// `res` drops here → the acquire's surface reference is released, pre-Finished.
}
}