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:
@@ -1370,9 +1370,15 @@ async fn serve_session(
|
||||
// GetDesc1) as soon as capture starts and re-sends it on keyframes; the client applies the
|
||||
// latest it receives. This baseline covers the synthetic source and the pre-capture gap.
|
||||
if welcome.color.is_hdr() {
|
||||
let meta = crate::hdr::generic_hdr10();
|
||||
// Prefer the CLIENT's own display volume (Hello::display_hdr): the virtual display's EDID
|
||||
// now advertises it, so host apps tone-map to exactly that volume — echoing it here keeps
|
||||
// the mastering metadata honest end-to-end. Generic HDR10 only for older clients.
|
||||
let meta = hello.display_hdr.unwrap_or_else(crate::hdr::generic_hdr10);
|
||||
let _ = conn.send_datagram(punktfunk_core::quic::encode_hdr_meta_datagram(&meta).into());
|
||||
tracing::info!("sent HDR10 static metadata (0xCE; generic baseline)");
|
||||
tracing::info!(
|
||||
client_volume = hello.display_hdr.is_some(),
|
||||
"sent HDR10 static metadata (0xCE baseline)"
|
||||
);
|
||||
}
|
||||
|
||||
// Test hook (synthetic source only): a scripted feedback burst on the host→client
|
||||
@@ -1445,6 +1451,10 @@ async fn serve_session(
|
||||
};
|
||||
let stop_stream = stop.clone();
|
||||
let quit_stream = quit.clone();
|
||||
// The client display's HDR volume (Hello): the virtual display's EDID advertises it (host apps
|
||||
// tone-map to the client's real panel) and the 0xCE mastering metadata echoes it. `None` =
|
||||
// older client / no HDR display → the built-in defaults everywhere.
|
||||
let client_hdr = hello.display_hdr;
|
||||
let fec_target_dp = fec_target.clone(); // data-plane handle to the adaptive-FEC target
|
||||
let conn_stream = conn.clone(); // for sending the source's real HDR metadata (0xCE) mid-stream
|
||||
// Per-AU host-timing emission (0xCF): only when the client advertised the cap bit. All
|
||||
@@ -1533,6 +1543,7 @@ async fn serve_session(
|
||||
stats: stats_dp,
|
||||
client_label,
|
||||
launch: launch_for_dp,
|
||||
client_hdr,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3178,6 +3189,11 @@ struct SessionContext {
|
||||
/// command already resolved against the host's own library — nested into gamescope's bare spawn
|
||||
/// via `set_launch_command`, or spawned into the live session once capture is up.
|
||||
launch: Option<String>,
|
||||
/// The client display's HDR colour volume (`Hello::display_hdr`; `None` = older client / SDR).
|
||||
/// Threaded into the vdisplay backend before `create` (→ the pf-vdisplay EDID's CTA HDR block,
|
||||
/// so host apps tone-map to the client's real panel) and preferred over the generic baseline
|
||||
/// for the 0xCE mastering metadata.
|
||||
client_hdr: Option<punktfunk_core::quic::HdrMeta>,
|
||||
}
|
||||
|
||||
fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
@@ -3217,6 +3233,7 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
stats,
|
||||
client_label,
|
||||
launch,
|
||||
client_hdr,
|
||||
} = ctx;
|
||||
tracing::info!(
|
||||
compositor = compositor.id(),
|
||||
@@ -3234,6 +3251,10 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
// reapplies the client's saved per-monitor config (DPI scaling) on reconnect. No-op on Linux backends
|
||||
// and for anonymous/GameStream clients (no fingerprint → the driver auto-allocates).
|
||||
vd.set_client_identity(endpoint::peer_fingerprint(&conn));
|
||||
// The client display's HDR volume (Hello) → a freshly created virtual monitor's EDID CTA HDR
|
||||
// block (pf-vdisplay), so host apps + the OS tone-map to the client's real panel instead of the
|
||||
// driver's built-in ~1000-nit placeholder. No-op on Linux backends and for older/SDR clients.
|
||||
vd.set_client_hdr(client_hdr);
|
||||
// Deliberate-quit wiring (Windows pf-vdisplay; no-op elsewhere): every lease the backend mints —
|
||||
// the retry-hold below AND the capturer's — carries the session's quit flag, so a user "stop"
|
||||
// (⌘D → the QUIT close code) tears the virtual monitor down the moment the pipeline drops instead
|
||||
@@ -3253,9 +3274,17 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
// register THIS session's stop. The returned guard holds the setup lock across the pipeline build;
|
||||
// dropping it lets the next reconnect begin (and preempt us). Held BEFORE the monitor is created
|
||||
// (build_pipeline → vd.create), so the preempt still precedes this session's monitor creation.
|
||||
// SLOT-scoped (Stage W1): the preempt targets only a prior session holding THIS client's slot —
|
||||
// a different identity's session is an admission question, never a preempt.
|
||||
#[cfg(target_os = "windows")]
|
||||
let _idd_setup_guard = (plan.capture == crate::session_plan::CaptureBackend::IddPush)
|
||||
.then(|| crate::vdisplay::manager::vdm().begin_idd_setup(stop.clone()));
|
||||
let _idd_setup_guard =
|
||||
(plan.capture == crate::session_plan::CaptureBackend::IddPush).then(|| {
|
||||
let slot = crate::vdisplay::manager::slot_id_for(
|
||||
endpoint::peer_fingerprint(&conn),
|
||||
(mode.width, mode.height),
|
||||
);
|
||||
crate::vdisplay::manager::vdm().begin_idd_setup(slot, stop.clone())
|
||||
});
|
||||
let (mut capturer, mut enc, mut frame, mut interval, mut cur_node_id) =
|
||||
build_pipeline_with_retry(&mut vd, mode, bitrate_kbps, bit_depth, plan, &quit, &stop)?;
|
||||
// Setup done — release the IDD-push setup lock so the next reconnect can begin (and preempt us).
|
||||
@@ -3791,11 +3820,15 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
diag_repeat = 0;
|
||||
diag_at = std::time::Instant::now();
|
||||
}
|
||||
// The source's static HDR mastering metadata (Windows GetDesc1; None on Linux/SDR) is the
|
||||
// single source of truth: hand it to the encoder (in-band SEI on keyframes) and, when it
|
||||
// changes, to the client (0xCE). Re-sent on each keyframe below so a dropped best-effort
|
||||
// datagram converges within a GOP.
|
||||
let hdr_meta = capturer.hdr_meta();
|
||||
// The source's static HDR mastering metadata is the single source of truth: hand it to the
|
||||
// encoder (in-band SEI on keyframes) and, when it changes, to the client (0xCE). Re-sent on
|
||||
// each keyframe below so a dropped best-effort datagram converges within a GOP. PRESENCE is
|
||||
// the capturer's call (Some iff the virtual display is in HDR mode); the VALUE prefers the
|
||||
// client's own display volume when it sent one — the virtual display's EDID advertises
|
||||
// exactly that volume, so host apps already tone-mapped the content into it and the honest
|
||||
// mastering description IS the client's panel. (The IDD capturer only knows the generic
|
||||
// baseline; if the driver ever forwards per-content IDDCX_HDR10_METADATA, prefer that here.)
|
||||
let hdr_meta = capturer.hdr_meta().map(|m| client_hdr.unwrap_or(m));
|
||||
enc.set_hdr_meta(hdr_meta);
|
||||
let mut resend_meta = hdr_meta != last_hdr_meta;
|
||||
if resend_meta {
|
||||
@@ -4840,6 +4873,7 @@ mod tests {
|
||||
2, // audio_channels (stereo)
|
||||
0, // video_codecs (0 → HEVC-only)
|
||||
0, // preferred_codec (auto)
|
||||
None, // display_hdr
|
||||
None, // launch
|
||||
None, // pin: TOFU — the operator's approval (not a PIN) authorizes this client
|
||||
Some((cert, key)),
|
||||
@@ -4906,6 +4940,7 @@ mod tests {
|
||||
2, // audio_channels (stereo)
|
||||
0, // video_codecs
|
||||
0, // preferred_codec
|
||||
None, // display_hdr
|
||||
None, // launch
|
||||
None,
|
||||
None,
|
||||
@@ -4934,6 +4969,7 @@ mod tests {
|
||||
2, // audio_channels (stereo)
|
||||
0, // video_codecs
|
||||
0, // preferred_codec
|
||||
None, // display_hdr
|
||||
None, // launch
|
||||
Some(host_fp),
|
||||
Some((cert.clone(), key.clone())),
|
||||
|
||||
Reference in New Issue
Block a user