perf(host): session-transition trace + Welcome-time display prep (native path)

Latency plan P0.1 + P1.1/P1.2 (design/first-frame-and-resize-latency.md):

P0.1 — every native session runs a bringup::Trace (hello -> welcome -> start
-> punch_done -> display_acquired -> capture_attached -> first_frame ->
encoder_open -> first_au -> first_packet), one summary info! line when the
first video packet leaves; each accepted resize runs its own trace
(reconfigure -> pipeline_rebuilt). Totals surface per session as
time_to_first_frame_ms / last_resize_ms in session_status -> mgmt /status,
so every subsequent latency change is measured, not vibed. (The Windows
manager logs its own activation/settle deltas — correlate by wall clock.)

P1.1/P1.2 — on the Windows native path the display bring-up no longer
serializes behind the Start round-trip and the up-to-2.5 s hole-punch wait:
a prep thread kicks off at Welcome (mode is final there) and runs monitor
create -> activation -> verified settle -> capture attach -> first frame ->
encoder open while the network waits are in flight; the data plane hands it
the post-punch SessionContext and it becomes the stream thread on a warm
pipeline. Abort between Welcome and Start drops the hand-off channel and the
prep result releases into the keep-alive machinery (stop/quit + watcher are
created pre-handshake so a vanished client also aborts the build retries).
Same slot-scoped begin_idd_setup serialization as the inline path. Linux
keeps the inline bring-up (launch semantics bind before create); GameStream
untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 16:45:34 +02:00
parent e62cd5448e
commit 8374dfedf3
5 changed files with 451 additions and 74 deletions
@@ -36,6 +36,11 @@ struct LiveSession {
/// One-shot force-keyframe flag ([`force_idr_all`] → mgmt `POST /session/idr`); the encode loop
/// drains it alongside a client's decode-recovery keyframe request.
force_idr: Arc<AtomicBool>,
/// Completed bring-up total (hello → first packet), ms; 0 until the first packet left. Written
/// once by the session's [`crate::bringup::Trace`] (latency plan P0.1).
ttff_ms: Arc<AtomicU32>,
/// Most recent completed mid-stream resize (reconfigure → pipeline rebuilt), ms; 0 = none yet.
last_resize_ms: Arc<AtomicU32>,
}
/// A resolved read of one live session, for the `/status` view.
@@ -46,6 +51,10 @@ pub struct SessionSnapshot {
pub fps: u32,
pub bitrate_kbps: u32,
pub codec: Codec,
/// Bring-up total (hello → first packet), ms; 0 while still bringing up (latency plan P0.1).
pub time_to_first_frame_ms: u32,
/// Most recent mid-stream resize total, ms; 0 = no resize this session.
pub last_resize_ms: u32,
}
fn registry() -> &'static Mutex<Vec<LiveSession>> {
@@ -65,6 +74,8 @@ pub fn register(
codec: Codec,
stop: Arc<AtomicBool>,
force_idr: Arc<AtomicBool>,
ttff_ms: Arc<AtomicU32>,
last_resize_ms: Arc<AtomicU32>,
) -> LiveSessionGuard {
let id = next_id();
registry().lock().unwrap().push(LiveSession {
@@ -74,6 +85,8 @@ pub fn register(
codec,
stop,
force_idr,
ttff_ms,
last_resize_ms,
});
LiveSessionGuard { id }
}
@@ -109,6 +122,8 @@ pub fn snapshot() -> Vec<SessionSnapshot> {
fps,
bitrate_kbps: s.bitrate_kbps.load(Ordering::Relaxed),
codec: s.codec,
time_to_first_frame_ms: s.ttff_ms.load(Ordering::Relaxed),
last_resize_ms: s.last_resize_ms.load(Ordering::Relaxed),
}
})
.collect()