feat(windows/capture): host-side cursor compositing for the capture model — the deterministic fix

Root cause, proven on-glass across five driver builds: a declared IddCx
hardware cursor is IRREVOCABLE. There is no un-declare DDI, the
empty-caps re-setup is rejected INVALID_PARAMETER, and after a
successful same-mode re-commit with the driver's re-declare provably
suppressed (sticky per-target flag, zero re-setups logged) DWM still
never composites the software cursor back into the monitor's frames.
Every DWM-based composite flip is therefore unfixable.

The composite (capture) model is now implemented in OUR pipeline:
- the driver keeps its hardware cursor declared for the session's whole
  life (the state that works) — frames stay pointer-free always;
- in composite mode try_consume routes the conversion through a blend
  scratch: slot copy + one alpha-blended quad of the GDI poller's shape
  at its polled position (CursorBlendPass — fullscreen-triangle VS with
  viewport placement, straight-alpha PS, sRGB→linear for FP16/HDR
  rings), covering all four convert paths (NV12, 4:4:4 copy, P010,
  PyroWave) GPU-side under the slot's keyed mutex;
- pointer-only motion produces no driver publish (declared hw cursor),
  so try_consume REGENERATES from the last slot whenever the polled
  cursor state changes — the cursor moves on a static desktop at tick
  rate, without faking freshness for the stall/death watchdogs;
- set_cursor_forward becomes purely host-internal state; the
  SET_CURSOR_FORWARD IOCTL machinery is no longer used by the host
  (driver keeps it, dormant) and its sender plumbing is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-22 23:43:23 +02:00
co-authored by Claude Opus 4.8
parent 89cfef429e
commit 0af280a793
5 changed files with 432 additions and 151 deletions
-21
View File
@@ -174,26 +174,6 @@ pub fn capture_virtual_output(
},
) as pf_capture::CursorChannelSender
});
// The mid-stream cursor-render flip (proto v6) — retained by the capturer so the client's
// mouse-model chords can (un)declare the driver's hardware cursor live. Same facade contract
// as `cursor_sender`.
let cursor_forward_sender: Option<pf_capture::CursorForwardSender> =
want.hw_cursor.then(|| {
std::sync::Arc::new(
move |req: &pf_driver_proto::control::SetCursorForwardRequest| {
// SAFETY: `control_raw` is the pf-vdisplay control handle resolved above; it is
// never closed for the process lifetime (`send_cursor_forward`'s precondition).
unsafe {
crate::vdisplay::driver::send_cursor_forward(
windows::Win32::Foundation::HANDLE(
control_raw as *mut core::ffi::c_void,
),
req,
)
}
},
) as pf_capture::CursorForwardSender
});
pf_capture::open_idd_push(
target,
pref,
@@ -203,7 +183,6 @@ pub fn capture_virtual_output(
keep,
sender,
cursor_sender,
cursor_forward_sender,
)
.map_err(|(e, _keep)| e.context("IDD-push capture open (no fallback)"))
}