feat(windows): parallel virtual displays — proto v3 ring binding, manager slot map, group topology (W0–W3)
windows-drivers / probe-and-proto (push) Successful in 41s
ci / web (push) Successful in 1m2s
ci / docs-site (push) Successful in 1m7s
apple / swift (push) Successful in 1m11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
decky / build-publish (push) Successful in 15s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 38s
windows-drivers / driver-build (push) Successful in 1m46s
ci / bench (push) Successful in 6m35s
docker / deploy-docs (push) Successful in 20s
windows-host / package (push) Successful in 9m37s
deb / build-publish (push) Successful in 13m48s
arch / build-publish (push) Successful in 14m21s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m34s
android / android (push) Successful in 15m45s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m20s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m18s
ci / rust (push) Successful in 18m48s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m41s
release / apple (push) Successful in 19m30s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m49s
flatpak / build-publish (push) Failing after 2m9s
apple / screenshots (push) Successful in 19m3s
windows-drivers / probe-and-proto (push) Successful in 41s
ci / web (push) Successful in 1m2s
ci / docs-site (push) Successful in 1m7s
apple / swift (push) Successful in 1m11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
decky / build-publish (push) Successful in 15s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 38s
windows-drivers / driver-build (push) Successful in 1m46s
ci / bench (push) Successful in 6m35s
docker / deploy-docs (push) Successful in 20s
windows-host / package (push) Successful in 9m37s
deb / build-publish (push) Successful in 13m48s
arch / build-publish (push) Successful in 14m21s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m34s
android / android (push) Successful in 15m45s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m20s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m18s
ci / rust (push) Successful in 18m48s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m41s
release / apple (push) Successful in 19m30s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m49s
flatpak / build-publish (push) Failing after 2m9s
apple / screenshots (push) Successful in 19m3s
design/windows-parallel-virtual-displays.md (display-management Stage 7 / §6.6): N simultaneously-live pf-vdisplay monitors, one sealed ring each, every idd-push-security invariant preserved per-ring. - proto v3: SharedHeader._pad → target_id — the ring NAMES its monitor, host-stamped before the magic; the driver publisher refuses a cross-bound ring via the shared, unit-tested frame::check_attach (new DRV_STATUS_BIND_FAIL — the gamepad pad_index validation applied to frames, invariant #10); the host's wait_for_attach surfaces the refusal loudly and self-checks its own stamp. - manager: the one-monitor MgrState becomes a slot map keyed by the client's identity slot (0 = anonymous/GameStream); per-slot reconnect + dead-WUDFHost preempts, slot-scoped begin_idd_setup (a different identity is an admission question, never a preempt), ONE device-level watchdog pinger, per-slot /display/state + /display/release. - group topology: isolate_displays_ccd takes the managed target SET (a sibling slot is never deactivated); SavedConfig + the DDC/PnP axes move to the group record (first-in captures, last-out restores); desktop layout via CCD source origins from the pure layout::arrange (auto-row default, manual pins win), re-applied on create + reconfigure. - admission: the Windows separate→reject override now sits behind the PUNKTFUNK_WIN_SEPARATE=1 validation hatch (the wedge it guarded is structurally gone — a second identity gets its own monitor + ring; default flips in W5 after soak); max_displays and NVENC session-unit budgets decline an unaffordable display AT admission; kick_dwm_compose is process-globally throttled and per-display — cursor jump + 35 ms dwell (a sub-tick jump composes nothing; DWM reads dirties from current state at the next vsync tick). On-glass on the RTX box: V1/V2/V4/V5/V6/V9 green — two paired clients on two monitors streaming ~60 fps each with zero mismatches and zero bind failures, churn-hammer clean (no 0x80070490), per-ring mode-change recreate leaves the sibling untouched, typed budget rejection, fault-injected cross-bind refused loudly with the sibling undisturbed. V7: WUDFHost-kill shared fate is clean; in-process device recovery is a known follow-up (the retired-never-closed control handles block the adapter cycle — reset-pf-vdisplay.ps1 recovers). DWM composes two IDD monitors concurrently at 60 fps — the plan's load-bearing unknown, answered yes. Also carries the client-HDR EDID forwarding that shared this working tree (Hello::display_hdr → AddRequest luminance tail → the monitor's CTA-861.3 HDR block, PUNKTFUNK_CLIENT_PEAK_NITS hatch) and the Deck client fixes (40 ms rumble keep-alive with 1-LSB jitter, HDR self-diagnosing presenter warn, flatpak HDR env). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,8 @@ use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||
|
||||
use pf_driver_proto::control::SetFrameChannelRequest;
|
||||
use pf_driver_proto::frame::{
|
||||
DRV_STATUS_NO_DEVICE1, DRV_STATUS_OPENED, DRV_STATUS_TEX_FAIL, FrameToken, MAGIC, RING_LEN,
|
||||
SharedHeader,
|
||||
AttachReject, DRV_STATUS_BIND_FAIL, DRV_STATUS_NO_DEVICE1, DRV_STATUS_OPENED,
|
||||
DRV_STATUS_TEX_FAIL, FrameToken, RING_LEN, SharedHeader, check_attach,
|
||||
};
|
||||
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
||||
use windows::Win32::Graphics::Direct3D11::{
|
||||
@@ -157,9 +157,12 @@ impl FramePublisher {
|
||||
/// re-delivers on the next recreate — there is nothing to poll, so failure is terminal for THIS
|
||||
/// delivery (the host's `wait_for_attach` sees the status code and fails the session open). All
|
||||
/// early-return paths clean up explicitly (raw-handle style, no RAII — matches the rest of this
|
||||
/// driver).
|
||||
/// driver). `target_id` is the OWNING monitor's OS target id: the mapped ring must name it
|
||||
/// (proto v3 binding validation — see step 3), so a cross-delivered ring can never carry this
|
||||
/// monitor's frames into another client's stream.
|
||||
pub fn from_channel(
|
||||
mut channel: FrameChannel,
|
||||
target_id: u32,
|
||||
render_luid_low: u32,
|
||||
render_luid_high: i32,
|
||||
device: &ID3D11Device,
|
||||
@@ -203,35 +206,70 @@ impl FramePublisher {
|
||||
(*header).driver_render_luid_high = render_luid_high;
|
||||
}
|
||||
|
||||
// 3. The host stamps magic==MAGIC BEFORE delivering the channel, and this channel's generation
|
||||
// must match the header's CURRENT generation — a mismatch means the host recreated the ring
|
||||
// again before we attached (a fresh delivery is on its way); drop this stale one.
|
||||
// 3. The host stamps magic==MAGIC BEFORE delivering the channel, this channel's generation
|
||||
// must match the header's CURRENT generation (a mismatch means the host recreated the ring
|
||||
// again before we attached — a fresh delivery is on its way; drop this stale one), and —
|
||||
// proto v3, `design/idd-push-security.md` invariant #10 — the mapped ring must NAME THIS
|
||||
// MONITOR: the host stamps `target_id` before the magic, so with parallel displays a
|
||||
// host-side stash cross-wire fails CLOSED here instead of publishing this monitor's frames
|
||||
// into another client's ring. The shared `check_attach` (unit-tested in pf-driver-proto)
|
||||
// owns the precedence: staleness first, binding second.
|
||||
// SAFETY: `header` is the mapped host header; `magic`/`generation` live within it and are read
|
||||
// atomically (Acquire) to pair with the host's Release publishes.
|
||||
let (magic, header_gen) = unsafe {
|
||||
// atomically (Acquire) to pair with the host's Release publishes; `target_id` is a plain
|
||||
// in-bounds u32 read, stamped before the magic the Acquire load ordered us behind.
|
||||
let (magic, header_gen, header_target) = unsafe {
|
||||
(
|
||||
(*(core::ptr::addr_of!((*header).magic) as *const AtomicU32))
|
||||
.load(Ordering::Acquire),
|
||||
(*(core::ptr::addr_of!((*header).generation) as *const AtomicU32))
|
||||
.load(Ordering::Acquire),
|
||||
(*header).target_id,
|
||||
)
|
||||
};
|
||||
if magic != MAGIC || header_gen != channel.generation {
|
||||
dbglog!(
|
||||
"[pf-vd] frame-push(driver): dropping channel delivery (magic ok: {}, channel gen {} vs header gen {header_gen})",
|
||||
magic == MAGIC,
|
||||
channel.generation
|
||||
);
|
||||
// SAFETY: `header`/`map` are the live mapped view + taken handle; unmapped + closed once on
|
||||
// this path.
|
||||
unsafe {
|
||||
let _ = UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
|
||||
Value: header.cast(),
|
||||
});
|
||||
let _ = CloseHandle(map);
|
||||
match check_attach(
|
||||
magic,
|
||||
header_gen,
|
||||
header_target,
|
||||
channel.generation,
|
||||
target_id,
|
||||
) {
|
||||
Ok(()) => {}
|
||||
Err(AttachReject::Stale) => {
|
||||
dbglog!(
|
||||
"[pf-vd] frame-push(driver): dropping channel delivery (channel gen {} vs header gen {header_gen}) — superseded",
|
||||
channel.generation
|
||||
);
|
||||
// SAFETY: `header`/`map` are the live mapped view + taken handle; unmapped + closed once
|
||||
// on this path.
|
||||
unsafe {
|
||||
let _ = UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
|
||||
Value: header.cast(),
|
||||
});
|
||||
let _ = CloseHandle(map);
|
||||
}
|
||||
// E_BOUNDS — stand-in for "stale delivery"; the caller only drops the attempt.
|
||||
return Err(windows::core::HRESULT(0x8000_000Bu32 as i32).into());
|
||||
}
|
||||
Err(AttachReject::BindMismatch) => {
|
||||
dbglog!(
|
||||
"[pf-vd] frame-push(driver): REFUSING attach — ring names target {header_target}, this monitor is {target_id} (host stash cross-wire?)"
|
||||
);
|
||||
// Report the refusal through the header so the host's wait_for_attach fails the open
|
||||
// LOUDLY (DRV_STATUS_BIND_FAIL) instead of timing out mute; the detail carries the
|
||||
// target id the ring claims.
|
||||
// SAFETY: `header`/`map` are the live mapped view + taken handle; the status writes are
|
||||
// in-bounds scalar writes, then both are released exactly once on this path.
|
||||
unsafe {
|
||||
(*header).driver_status_detail = header_target;
|
||||
(*header).driver_status = DRV_STATUS_BIND_FAIL;
|
||||
let _ = UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
|
||||
Value: header.cast(),
|
||||
});
|
||||
let _ = CloseHandle(map);
|
||||
}
|
||||
// E_INVALIDARG — the delivery itself is wrong; the caller only drops the attempt.
|
||||
return Err(windows::core::HRESULT(0x8007_0057u32 as i32).into());
|
||||
}
|
||||
// E_BOUNDS — stand-in for "stale delivery"; the caller only drops the attempt.
|
||||
return Err(windows::core::HRESULT(0x8000_000Bu32 as i32).into());
|
||||
}
|
||||
|
||||
// 4. The frame-ready event (duplicated with the host handle's full access, so SetEvent works).
|
||||
|
||||
Reference in New Issue
Block a user