One stat model everywhere (design/stats-unification.md): four measurement
points (capture/received/decoded/displayed), three stages that tile the
interval exactly, and a HUD that shows the addition explicitly —
end-to-end 14.2 ms p50 · 19.8 p95 · capture→on-glass
= host+network 9.8 + decode 2.1 + display 2.3
replacing each client's ad-hoc mix of overlapping absolutes (the Apple HUD's
three arrow lines that looked sequential but weren't), mean-vs-median decode
times (Windows/Linux), missing same-host-clock flags (Windows/Linux), and
three different names for the same capture→received measurement (probe's
"reassembled", Apple/Android's "client", Windows/Linux's post-decode "lat").
Per client: Apple threads receivedNs through the VT decode via the frame
refcon bit pattern so the decode stage exists at all (stage-1 fallback
honestly degrades to a capture→received headline); Windows carries
FrameTimes through the existing frame channel to the render thread and adds
e2e p50/p95 post-Present; Linux stamps received at AU pop and rides
decoded_ns on DecodedFrame to the paintable-set site; Android pairs receipt
stamps with MediaCodec output buffers via the codec's pts round-trip (JNI
stats array 14→16 doubles, indexes 0-13 unchanged). fps now uniformly counts
received AUs; lost/(received+lost) per window, hidden at zero.
docs-site gains "Understanding the Stats Overlay": what each line means, why
the equation only approximately sums (percentiles), and a line-by-line
Moonlight/Sunshine matrix — including that Moonlight has no end-to-end
number and its "network latency" is an ENet control RTT, so punktfunk's
headline must not be compared against any single Moonlight line.
Verified here: linux client + probe + core check/clippy/fmt green, android
native cargo-ndk arm64 check green. Pending: Windows CI + on-glass, swift
test on the mac, on-device Android.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bump windows-reactor + windows to a4f7b2cb (from b4129fcc) for the new
PointerEntered/PointerExited events; migration is mechanical renames only
(SymbolGlyph->Symbol, placeholder->placeholder_text, on_changed->
on_text_changed/on_toggled, on_menu_item_clicked->on_item_clicked,
on_ready->on_mounted). New runtime model: reactor lost its build.rs, so the
client build.rs stages the WinAppSDK bootstrap via
windows-reactor-setup::as_framework_dependent() and main calls
windows_reactor::bootstrap() (missing either = 0x80040154 at launch);
staged filenames unchanged, so pack-msix and the MSIX manifest are untouched.
- Host tiles: WinUI pointer-over fill (ControlFillSecondary) via the new
pointer enter/exit events, hover id in root state (backend-wired handlers
bypass the reconciler flush, like the flyout clicks).
- Settings: stock NavigationView sidebar (Windows-Settings pattern) with
Display/Video/Input/Audio/About panes, built-in back arrow, wide content
column, and a per-section content slide-up tween. The section card is
KEYED by section: an in-place diff across sections re-sets a reused
ComboBox's items (clearing WinUI's selection) but skips selected_index
when the values compare equal, rendering a blank selection - the key
forces a remount. Card titles/descriptions dropped; per-control guidance
moved to hover tooltips (ToolTipService).
- New "Show the stats overlay (HUD)" setting (show_hud, default on),
honored mid-stream via the 400 ms HUD re-render.
- Add-host modal: entrance fade + slide-up tween (scrim fades with it).
- Self-initiated disconnect (Ctrl+Alt+Shift+D -> Ended(None)) returns to
the host list silently instead of raising the error banner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Settings gains a GPU selector (shown only on multi-GPU boxes): the picked
DXGI adapter drives decode + present, persisted as Settings.adapter and
applied at the next stream - gpu.rs now caches the shared device keyed by
the resolved preference (env PUNKTFUNK_ADAPTER > Settings > the window's
monitor's adapter) so a change needs no app restart.
- Ctrl+Alt+Shift+D disconnects the session (consumed locally, captured or
released): the hook releases capture and trips the session stop flag,
plumbed through the stream-page handoff; the pump winds down and the UI
navigates back to the host list.
- Stream HUD extended: codec chip (HEVC/H.264/AV1), display-side line from
the render thread (presents/s + capture-to-decoded vs capture-to-on-glass
p50), session line (host name, duration, network-lost frames, skipped
backlog frames), and both shortcut hints incl. the new disconnect.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Decode+present rewrite (first real pixels on glass for this client):
- Decode: FFmpeg D3D11VA on NVIDIA/AMD/Intel. get_format now only returns
AV_PIX_FMT_D3D11 and lets libavcodec build the decode pool from
hw_device_ctx (hand-built frames contexts failed three different ways:
NVIDIA rejects DECODER|SHADER_RESOURCE arrays, BindFlags=0 fails texture
creation, Intel rejects non-128-aligned HEVC surfaces at the first
SubmitDecoderBuffers). A DXVA profile probe before the hwdevice commits
hardware-vs-software up front instead of burning the opening IDR;
extra_hw_frames covers the frames the client holds.
- Present: the decoded slice is copied with ONE display-size-boxed
CopySubresourceRegion (a planar slice is a single subresource in D3D11;
the old two-copy D3D12-style code silently no-opped - the black screen)
into a sampleable NV12/P010 texture, per-plane SRVs + YUV->RGB shaders.
- New dedicated render thread (render.rs): presenting is decoupled from the
XAML thread; frame-latency-waitable swapchain + SetMaximumFrameLatency(1),
newest-wins drain after the wait, crossbeam frame channel with pts for a
capture->presented p50 log.
- HiDPI: pixel-sized buffers + SetMatrixTransform(96/dpi) - was blurry at
125/150 % scaling.
- Software fallback now feeds the same shaders (swscale -> NV12/P010 planes
-> two dynamic plane textures); ps_rgba/X2BGR10 path deleted, hw/sw colour
math identical.
- Adapter selection for hybrid boxes: PUNKTFUNK_ADAPTER > the window's
monitor's adapter > default; PUNKTFUNK_D3D_DEBUG=1 debug layer.
- Session pump: request_keyframe at start and on hw->sw demotion (infinite
GOP would otherwise sit on a black screen).
Validated live on the Arc Pro + RTX 3500 Ada laptop against the local
Windows host: 60 fps D3D11VA on both vendors, software path, GUI on glass.
Also: embedded app icon (build.rs winresource + WM_SETICON, MSIX
Square44x44 targetsize assets, pack-msix stages them) and the hosts-page
tile rework (tap-to-connect tiles with sibling overflow menu - fixes
forget-also-connects - in-tile rename editor, add-host modal via root state).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Structure: split the 1400-line app.rs into per-screen app/ modules (mod=root/
router, hosts, connect, pair, speed, settings, licenses, stream, style) with
shared card/header/busy-page builders and setting_combo/toggle helpers; the
re-render rule (thread-driven state lives in root use_async_state, flows down
as props) is now documented at the module root.
Parity features the other clients already had:
- "Native display" resolves the real monitor mode at connect
(MonitorFromWindow -> EnumDisplaySettingsW; was a hardcoded 1080p60)
- per-host network speed test: saved-host card button + a results screen
(probe burst -> goodput/loss -> ~70% recommended bitrate applied in one
tap; stale runs invalidated by generation) and `--headless --speed-test`;
the bitrate setting becomes a free-form NumberBox so the recommendation
round-trips
- forget host (ContentDialog confirm -> KnownHosts::remove_by_fp)
- settings: forwarded-controller picker (pads/pinned/set_pinned now wired),
gamepad type, host compositor, capture-system-shortcuts; the previously
dead Settings.compositor / inhibit_shortcuts are honored (shortcuts off =
Alt+Tab/Alt+Esc/Ctrl+Esc/Win act locally)
- click-to-recapture after a Ctrl+Alt+Shift+Q release; the HUD hint tracks
the live capture state
Perf: the input hook caches lock geometry (clip rect + contain-fit scale) at
engage instead of GetClientRect per WM_MOUSEMOVE; the audio jitter ring trims
via drain() and reuses the render scratch buffer.
Validated on the bare-metal box: --discover, synthetic-host loopback E2E
(TOFU -> clock skew -> HEVC negotiate -> D3D11VA init -> session end),
speed-test E2E, and the WinUI shell rendering in the console session via
PsExec (SSH/session-0 cannot create windows, pre-existing 0x80070005).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>