Merge perf/first-frame-latency: driver proto v4 + first-frame/resize latency (P0-P2)
Brings the first-frame-latency branch (P0.1 transition tracing, P1.1/P1.2 Welcome-time display prep, P2 in-place resize; pf-driver-proto v3 -> v4 with IOCTL_UPDATE_MODES) onto current main. The branch predates the W6.2/W7 splits, so git's rename detection carried most of it into the moved crates (pf-capture idd_push, pf-vdisplay manager/pf_vdisplay, pf-win-display, pf-driver-proto, the driver workspace) and the punktfunk1.rs remainder was re-homed by hand: - native/handshake.rs: welcome/start trace marks + the Welcome-time display prep spawn (the prep thread BECOMES the stream thread; hand-off via a SyncSender<SessionContext>). negotiate() gains bringup/quit/stop and returns the PrepHandle. - native.rs: bringup/resize_ms creation + the stop/quit flags hoisted BEFORE the handshake (the close watcher splits: flags pre-handshake, lifecycle events post-handshake where `hello` exists); punch_done stamp; the data plane adopts the prep thread's result or builds inline. - native/stream.rs: SessionContext/SendStats carry the trace; send_loop finishes it on the first video packet; the resize path gains the in-place fast path (try_inplace_resize) with the full rebuild as fallback, restructured so both share the post-rebuild bookkeeping; prepare_display/PreparedDisplay/ PrepHandle; build_pipeline(+retry) thread the stage marks. - session_status/mgmt: ttff_ms + last_resize_ms per session (union with the lifecycle-events fields main added to the same spots). - pf-capture: Capturer gains capture_target_id() + resize_output() defaults. - pf-vdisplay manager: perf's faster activation poll (60x50ms) + the settle floor before the PnP sweep, on main's knobs/no-trait shape. Also: packaging/windows/build-gamepad-drivers.ps1 is ASCII again (an em-dash from the pf-mouse work tripped windows-host.yml's locale-safety gate on main). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
--gamepad` consumes (per-driver .inf/.cat/.dll + one shared punktfunk-driver.cer).
|
||||
|
||||
Output (-Out): pf_dualsense.{dll,inf,cat} + pf_xusb.{dll,inf,cat} + pf_mouse.{dll,inf,cat} +
|
||||
punktfunk-driver.cer. (pf_mouse is the resident virtual HID pointer, not a gamepad — it shares
|
||||
punktfunk-driver.cer. (pf_mouse is the resident virtual HID pointer, not a gamepad - it shares
|
||||
this pipeline + the --gamepad install path.)
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
@@ -129,6 +129,16 @@ pub unsafe extern "C" fn parse_monitor_description2(
|
||||
return STATUS_NOT_FOUND;
|
||||
};
|
||||
let count = crate::monitor::flatten(&modes).count() as u32;
|
||||
// Bring-up/diagnostic visibility (P2): does the OS ever RE-parse the description after an
|
||||
// UPDATE_MODES? The head mode names which list generation this call served.
|
||||
if let Some(head) = crate::monitor::flatten(&modes).next() {
|
||||
dbglog!(
|
||||
"[pf-vd] parse_monitor_description2(id={id}): {count} modes, head {}x{}@{}",
|
||||
head.width,
|
||||
head.height,
|
||||
head.refresh_rate
|
||||
);
|
||||
}
|
||||
out_args.MonitorModeBufferOutputCount = count;
|
||||
if in_args.MonitorModeBufferInputCount < count {
|
||||
// A zero input count is a count-only probe (success); a non-zero too-small buffer is an error.
|
||||
@@ -204,6 +214,17 @@ pub unsafe extern "C" fn monitor_query_modes2(
|
||||
return STATUS_NOT_FOUND;
|
||||
};
|
||||
let count = crate::monitor::flatten(&modes).count() as u32;
|
||||
// Diagnostic visibility (P2): shows whether/when the OS re-queries target modes after an
|
||||
// UPDATE_MODES (the head mode names the list generation this call served).
|
||||
if let Some(head) = crate::monitor::flatten(&modes).next() {
|
||||
dbglog!(
|
||||
"[pf-vd] monitor_query_modes2: {count} modes, head {}x{}@{} (fill={})",
|
||||
head.width,
|
||||
head.height,
|
||||
head.refresh_rate,
|
||||
in_args.TargetModeBufferInputCount >= count
|
||||
);
|
||||
}
|
||||
out_args.TargetModeBufferOutputCount = count;
|
||||
if in_args.TargetModeBufferInputCount >= count {
|
||||
// SAFETY: `pTargetModes` points to >= `count` IDDCX_TARGET_MODE2 entries.
|
||||
|
||||
@@ -95,6 +95,8 @@ pub unsafe fn dispatch(request: WDFREQUEST, ioctl_code: u32) {
|
||||
control::IOCTL_SET_RENDER_ADAPTER => unsafe { set_render_adapter(request) },
|
||||
// SAFETY: `request` is the framework WDFREQUEST.
|
||||
control::IOCTL_SET_FRAME_CHANNEL => unsafe { set_frame_channel(request) },
|
||||
// SAFETY: `request` is the framework WDFREQUEST.
|
||||
control::IOCTL_UPDATE_MODES => unsafe { update_modes(request) },
|
||||
_ => complete(request, STATUS_NOT_FOUND),
|
||||
}
|
||||
}
|
||||
@@ -198,6 +200,28 @@ unsafe fn set_frame_channel(request: WDFREQUEST) {
|
||||
}
|
||||
}
|
||||
|
||||
/// `IOCTL_UPDATE_MODES` (v4): refresh a LIVE monitor's target-mode list to a new preferred mode —
|
||||
/// the in-place mid-stream resize (`design/first-frame-and-resize-latency.md` P2). The monitor is
|
||||
/// NOT departed: its OS identity, swap-chain machinery and retained frame stash all survive; the
|
||||
/// host force-sets the freshly-advertised mode afterwards.
|
||||
///
|
||||
/// # Safety
|
||||
/// `request` is the framework `WDFREQUEST`.
|
||||
unsafe fn update_modes(request: WDFREQUEST) {
|
||||
// SAFETY: `request` is the framework WDFREQUEST.
|
||||
let Some(req) = (unsafe { read_input::<control::UpdateModesRequest>(request) }) else {
|
||||
complete(request, STATUS_INVALID_PARAMETER);
|
||||
return;
|
||||
};
|
||||
if !valid_mode(req.width, req.height, req.refresh_hz) {
|
||||
complete(request, STATUS_INVALID_PARAMETER);
|
||||
return;
|
||||
}
|
||||
let st =
|
||||
crate::monitor::update_monitor_modes(req.session_id, req.width, req.height, req.refresh_hz);
|
||||
complete(request, st);
|
||||
}
|
||||
|
||||
/// `IOCTL_REMOVE`: depart + drop the monitor for the given session id.
|
||||
///
|
||||
/// # Safety
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use std::sync::Mutex;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use wdk_sys::{WDFOBJECT, call_unsafe_wdf_function_binding, iddcx};
|
||||
use wdk_sys::{NTSTATUS, WDFOBJECT, call_unsafe_wdf_function_binding, iddcx};
|
||||
|
||||
/// One resolution with the refresh rates it supports.
|
||||
#[derive(Clone)]
|
||||
@@ -146,6 +146,41 @@ pub fn reap_orphaned(grace: Duration) -> usize {
|
||||
n
|
||||
}
|
||||
|
||||
/// Append `from`'s modes to `into`, skipping resolutions already present, capped at
|
||||
/// [`MODE_LIST_CAP`] — the accumulate half of the union semantics (see [`update_monitor_modes`]).
|
||||
fn union_modes(into: &mut Vec<Mode>, from: &[Mode]) {
|
||||
for m in from {
|
||||
if into.len() >= MODE_LIST_CAP {
|
||||
break;
|
||||
}
|
||||
if !into
|
||||
.iter()
|
||||
.any(|e| (e.width, e.height) == (m.width, m.height))
|
||||
{
|
||||
into.push(m.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The last advertised mode list of a DEPARTED monitor, per monitor id — consumed by the next
|
||||
/// same-id [`create_monitor`] so a re-arrived monitor's ARRIVAL list already contains every mode
|
||||
/// its predecessor ever served. The OS pins a monitor's settable set at arrival (see
|
||||
/// [`update_monitor_modes`]), so this is what makes a windowed↔fullscreen cycle (or any return to
|
||||
/// a previously-used size) an IN-PLACE mode set instead of another hotplug. In-process only (a
|
||||
/// WUDFHost restart forgets it — harmless, the next resizes re-teach it); bounded: ≤ 16 ids ×
|
||||
/// [`MODE_LIST_CAP`] modes.
|
||||
static MODE_HISTORY: Mutex<Vec<(u32, Vec<Mode>)>> = Mutex::new(Vec::new());
|
||||
|
||||
/// Record a departing monitor's advertised list for its id ([`MODE_HISTORY`]).
|
||||
fn remember_modes(id: u32, modes: &[Mode]) {
|
||||
let mut hist = MODE_HISTORY.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if let Some(slot) = hist.iter_mut().find(|(i, _)| *i == id) {
|
||||
slot.1 = modes.to_vec();
|
||||
} else {
|
||||
hist.push((id, modes.to_vec()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Fallback modes appended after the requested mode, so a topology change still has options.
|
||||
fn default_modes() -> Vec<Mode> {
|
||||
vec![
|
||||
@@ -365,9 +400,7 @@ pub fn preserve_publisher(
|
||||
/// swap-chain's render adapter matches the publisher's ([`FramePublisher::render_adapter`]) — same
|
||||
/// pooled device, so its context + opened ring textures are still valid; on a mismatch the caller drops
|
||||
/// it and waits for a fresh channel delivery instead. `None` until a worker has stashed one.
|
||||
pub fn take_preserved_publisher(
|
||||
target_id: u32,
|
||||
) -> Option<crate::frame_transport::FramePublisher> {
|
||||
pub fn take_preserved_publisher(target_id: u32) -> Option<crate::frame_transport::FramePublisher> {
|
||||
if target_id == 0 {
|
||||
return None;
|
||||
}
|
||||
@@ -496,6 +529,15 @@ pub fn create_monitor(
|
||||
let id = {
|
||||
let mut lock = lock_monitors();
|
||||
let id = resolve_id(&lock, preferred_id);
|
||||
// Same-id mode history (P2 union semantics): a RE-ARRIVED monitor advertises every mode
|
||||
// its departed predecessor served, so the OS's arrival-pinned settable set already
|
||||
// contains them — a return to any previously-used size is then an IN-PLACE mode set.
|
||||
{
|
||||
let hist = MODE_HISTORY.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if let Some((_, prev)) = hist.iter().find(|(i, _)| *i == id) {
|
||||
union_modes(&mut modes, prev);
|
||||
}
|
||||
}
|
||||
lock.push(MonitorObject {
|
||||
object: None,
|
||||
id,
|
||||
@@ -600,6 +642,77 @@ pub fn create_monitor(
|
||||
Some((id, target_id, luid_low, luid_high))
|
||||
}
|
||||
|
||||
/// How many distinct resolutions a monitor's advertised list may accumulate (the requested head +
|
||||
/// history + the built-in fallbacks). Bounds the union growth across many resizes; the OLDEST
|
||||
/// history entries fall off first.
|
||||
const MODE_LIST_CAP: usize = 12;
|
||||
|
||||
/// `IOCTL_UPDATE_MODES` (v4): refresh the LIVE monitor's advertised mode list to lead with a new
|
||||
/// preferred mode and push the new TARGET mode list to the OS via `IddCxMonitorUpdateModes2` —
|
||||
/// the in-place mid-stream resize (`design/first-frame-and-resize-latency.md` P2). No departure:
|
||||
/// the monitor's OS identity, its swap-chain worker and the retained frame stash all survive.
|
||||
/// The `*2` (HDR) DDI matches the `*2` mode/buffer family this driver already requires
|
||||
/// (IddCx 1.10), so it adds no new OS floor.
|
||||
///
|
||||
/// UNION semantics (on-glass finding, build 26200): the OS re-parses the description AND
|
||||
/// re-queries target modes after `UpdateModes2` — our callbacks served the fresh list — yet the
|
||||
/// SETTABLE set stays pruned to the modes known at monitor ARRIVAL (the monitor source-mode set
|
||||
/// is pinned then). So replacing the list can only ever LOSE settable modes (v1 of this op
|
||||
/// dropped the arrival mode from the target list, breaking even a resize BACK to it); the update
|
||||
/// therefore accumulates — new mode first, every previously-advertised mode kept (deduped by
|
||||
/// resolution, capped at [`MODE_LIST_CAP`]) — and the real payoff is at the NEXT re-arrival,
|
||||
/// where [`create_monitor`]'s same-id history union makes every previously-used mode settable.
|
||||
///
|
||||
/// The stored list is updated FIRST (under the lock) so any OS re-query through the mode DDIs
|
||||
/// ([`modes_for_object`]/[`modes_for_id`]) sees the new list, and REVERTED if the DDI fails — the
|
||||
/// OS then still holds the old list and the two stay coherent. The DDI itself is called OUTSIDE
|
||||
/// the lock (it may re-enter the mode-query callbacks, which lock [`MONITOR_MODES`]).
|
||||
pub fn update_monitor_modes(session_id: u64, width: u32, height: u32, refresh: u32) -> NTSTATUS {
|
||||
// Swap the stored list (union — see above) + grab the live handle under the lock.
|
||||
let (object, old_modes, new_modes) = {
|
||||
let mut lock = lock_monitors();
|
||||
let Some(m) = lock.iter_mut().find(|m| m.session_id == session_id) else {
|
||||
return crate::STATUS_NOT_FOUND;
|
||||
};
|
||||
let Some(object) = m.object else {
|
||||
return crate::STATUS_NOT_FOUND; // created but not yet arrived — nothing to update
|
||||
};
|
||||
let mut new_modes = vec![Mode {
|
||||
width,
|
||||
height,
|
||||
refresh_rates: vec![refresh],
|
||||
}];
|
||||
union_modes(&mut new_modes, &m.modes);
|
||||
let old = core::mem::replace(&mut m.modes, new_modes.clone());
|
||||
(object, old, new_modes)
|
||||
};
|
||||
|
||||
// The OS's target-mode list for this monitor (the `*2`/HDR shape, like `monitor_query_modes2`).
|
||||
let mut targets: Vec<iddcx::IDDCX_TARGET_MODE2> = flatten(&new_modes)
|
||||
.map(|item| target_mode2(item.width, item.height, item.refresh_rate))
|
||||
.collect();
|
||||
let mut in_args = pod_init!(iddcx::IDARG_IN_UPDATEMODES2);
|
||||
in_args.Reason = iddcx::IDDCX_UPDATE_REASON::IDDCX_UPDATE_REASON_OTHER;
|
||||
in_args.TargetModeCount = targets.len() as u32;
|
||||
in_args.pTargetModes = targets.as_mut_ptr();
|
||||
// SAFETY: `object` is a live IddCx monitor handle (arrived — checked above; a concurrent REMOVE
|
||||
// is serialized by the host, which only ever resizes a monitor its own session holds a lease
|
||||
// on). `in_args` points at valid local storage (`targets` outlives the synchronous DDI call).
|
||||
let st = unsafe { wdk_iddcx::IddCxMonitorUpdateModes2(object, &in_args) };
|
||||
dbglog!(
|
||||
"[pf-vd] IddCxMonitorUpdateModes2(session={session_id}, {width}x{height}@{refresh}) -> {st:#x}"
|
||||
);
|
||||
if !wdk_iddcx::nt_success(st) {
|
||||
// Keep the stored list coherent with what the OS actually holds (the old one).
|
||||
let mut lock = lock_monitors();
|
||||
if let Some(m) = lock.iter_mut().find(|m| m.session_id == session_id) {
|
||||
m.modes = old_modes;
|
||||
}
|
||||
return st;
|
||||
}
|
||||
crate::STATUS_SUCCESS
|
||||
}
|
||||
|
||||
/// `IOCTL_REMOVE`: depart + drop the monitor for `session_id`. Returns true if one was removed.
|
||||
pub fn remove_monitor(session_id: u64) -> bool {
|
||||
// Pull out the IddCx handle AND the swap-chain processor under the lock, but drop the processor
|
||||
@@ -611,6 +724,9 @@ pub fn remove_monitor(session_id: u64) -> bool {
|
||||
return false;
|
||||
};
|
||||
let mut entry = lock.remove(pos);
|
||||
// Keep the departing monitor's advertised list for its id — the next same-id create
|
||||
// unions it back in (P2 mode history; see MODE_HISTORY).
|
||||
remember_modes(entry.id, &entry.modes);
|
||||
(entry.object, entry.swap_chain_processor.take())
|
||||
};
|
||||
// Drop the worker FIRST (it joins + deletes the swap-chain), THEN depart the monitor.
|
||||
|
||||
@@ -140,6 +140,16 @@ iddcx_ddi!(
|
||||
in_args: *const iddcx::IDARG_IN_ADAPTERSETRENDERADAPTER,
|
||||
) @ IddCxAdapterSetRenderAdapterTableIndex as PFN_IDDCXADAPTERSETRENDERADAPTER -> ()
|
||||
);
|
||||
iddcx_ddi!(
|
||||
/// Refresh a LIVE monitor's target-mode list (the HDR `*2` variant, IddCx 1.10 — the same API
|
||||
/// family as the `*2` mode/buffer DDIs this driver already requires): the OS re-evaluates which
|
||||
/// modes the target supports WITHOUT a monitor departure, so the host can then mode-set to a
|
||||
/// freshly-advertised mode in place (the mid-stream resize, latency plan P2).
|
||||
IddCxMonitorUpdateModes2(
|
||||
monitor: iddcx::IDDCX_MONITOR,
|
||||
in_args: *const iddcx::IDARG_IN_UPDATEMODES2,
|
||||
) @ IddCxMonitorUpdateModes2TableIndex as PFN_IDDCXMONITORUPDATEMODES2
|
||||
);
|
||||
iddcx_ddi!(
|
||||
/// Bind a D3D device to an assigned swap-chain. HRESULT-shaped (0x887A0026 → retry on monitor flap).
|
||||
IddCxSwapChainSetDevice(
|
||||
|
||||
Reference in New Issue
Block a user