Files
punktfunk/clients/session
enricobuehler e4d8573475 feat(client): the native rung now decodes HEVC as well as H.264
The last piece of M3 WP-2 — VkH265Decoder was built and hardware-gated
but nothing drove it. video_vk_native.rs holds a two-arm codec enum and
forwards to it; the ledger, release tokens, status-query settling and
timeline waits are byte-for-byte what they were, since they were always
codec-agnostic over one DecodedVkFrame contract. The forwarders are
written out per arm rather than macro'd so the unchanged H.264 arm is
visible to a reviewer.

The picture's own format now reaches the presenter, which picks bit
depth and MSB packing from it instead of assuming the H.264 envelope.
That incidentally fixes a live bug on the SHIPPING FFmpeg-Vulkan path:
it derived ten-bit-ness by comparing against the 10-bit 4:2:0 format
alone, so a 10-bit two-plane 4:4:4 surface — which its own format table
accepts, and which NVIDIA reports for HEVC RExt — got 8-bit range and
transfer maths. Reachable today with Full chroma plus 10-bit: decoded
correctly, displayed wrong.

Review round 11 caught a regression this WP would otherwise have
shipped. pf-vkdecode refuses a stream whose (chroma, depth) pair has no
picture format on the device, but the session is built lazily from the
first SPS, so the refusal arrived AFTER construction — past the point
where a native init failure falls through to FFmpeg-Vulkan. It burned
the error streak instead and demoted to VAAPI/D3D11VA, which on
NVIDIA/Linux means software. Turning on Full chroma on any non-NVIDIA
GPU was enough: a 4K HEVC session that ran on FFmpeg-Vulkan before this
branch would have landed on software decode.

Both halves are fixed. The negotiated chroma and bit depth — already at
the call site, the PyroWave arm four lines up uses them — are threaded
into the backend, which probes the same caps path ensure_state would
run, so the whole class refuses at CONSTRUCTION where the fall-through
already exists. For the legs no negotiation can carry (a level above
maxLevelIdc, an SPS that disagrees with the Welcome) the decoder latches
'never delivered a frame' and routes that first streak to FFmpeg-Vulkan
rather than down the hardware ladder. H.264 is deliberately not probed:
its envelope is fixed, so a probe would only add a profile guess on the
bit-exact path; it gets the latch as its backstop.

Two more from the round. Planner warnings are typed again rather than
Debug strings — pf-vkdecode simply lacked the h265 re-export its h264
twin already had — which restores the H.264 log rendering exactly and
unblocks M4, whose job is counting concealment by kind. And concealment
is now the integrity set only: NonZeroReorder is documented spec-legal
and fully planned, but the client treated every warning as damage, so
the opening IDR and every ABR renegotiation's IDR were released unshown
and re-anchored — a visible hitch on a healthy stream.

Also: a raw-format newtype so a neighbouring i32 field cannot be passed
to the colour maths, the presenter's depth table now pinned against
pf-vkdecode's actual output vocabulary rather than the FFmpeg lane's,
a per-format warn latch, and four stale docs.

Gates: fmt clean; container clippy -D warnings zero across
pf-client-core + pf-presenter + pf-vkdecode; tests 69/125/108/40 green;
cargo check --workspace clean.
2026-08-06 03:02:09 +02:00
..

punktfunk-session

The Vulkan session binary: one stream per invocation in an SDL3 window — no UI toolkit, no widgets, terminal stats. The power-user / gamescope stream client, and the stage-2 presenter of the Linux client re-architecture (punktfunk-planning: linux-client-rearchitecture.md).

This binary is deliberately dumb: a renderer the front-ends call INTO — the GTK shell (punktfunk-client), the WinUI shell, and the punktfunk CLI all spawn it through the same brain (pf_client_core::orchestrate), which resolves policy (profiles, settings, wake) and hands the result down, normally as a --resolved-spec file. It reads the shared stores only as the compat fallback for a bare hand-launched invocation.

punktfunk-session --connect host[:port] [--fp HEX] [--launch id] [--fullscreen] [--stats]
punktfunk-session --browse host[:port] [--mgmt PORT] [--fullscreen]

--browse opens the console game library (the Skia coverflow over the animated aurora) instead of connecting: A launches the focused title as a stream in the same window, session end returns to the library, B quits (Gaming Mode returns). Paired hosts only — pairing is the desktop client / Decky plugin's job. PUNKTFUNK_FAKE_LIBRARY=<file.json> feeds canned entries with no host (portrait paths starting with / load from disk).

Reads the same identity / known-hosts / settings stores as the desktop client (punktfunk-client), so enrolling on either side makes the other work; this binary never connects to a host it has no pinned fingerprint for (--fp HEX overrides the store).

Pairing is punktfunk pair <host> — the CLI, which ships alongside this binary in every package and needs no window and no toolkit either. punktfunk-session --pair still works for one release (someone's provisioning script calls it today) but prints a deprecation notice: pairing is a trust ceremony and belongs to the brain, not a renderer.

Stdout is the machine interface: {"ready":true} after the first presented frame, stats: … once per second while the overlay tier isn't Off (always the full detailed text, whatever the OSD shows; --stats forces the overlay on), one {"error"|"ended": …} JSON line on the way out. Logs go to stderr. Exit codes: 0 clean end, 2 connect failed, 3 trust rejected / pairing required, 4 presenter init failed.

In-stream keys match the desktop client: click captures input (Ctrl+Alt+Shift+Q releases), Ctrl+Alt+Shift+D disconnects, F11 toggles fullscreen; the controller escape chord (L1+R1+Start+Select, hold to disconnect) works the same.

The default build carries the Skia console UI (ui feature): the stats OSD and capture hint render in-window. Ctrl+Alt+Shift+S cycles the OSD tier live — Off → Compact (one line: fps · latency · Mb/s) → Normal (mode + end-to-end percentiles) → Detailed (decoder path + per-stage latency equation); any tier but Off also emits the stdout mirror. --no-default-features is the ~5 MB power-user build — same streaming, stats on stdout only, no Skia anywhere in the dependency tree.

Decode follows the Settings preference (auto is vendor-ordered: hardware Vulkan Video → VAAPI → software on Linux, hardware Vulkan Video → D3D11VA → software on Windows, with VAAPI/D3D11VA first on Intel; on H.264 and HEVC the native pf-vkdecode Vulkan decoder is tried immediately before FFmpeg-Vulkan): the Vulkan decoders run on the presenter's own device where the stack supports it (every vendor, zero copy); VAAPI dmabufs import per-plane elsewhere (D3D11VA textures on Windows); software is the universal fallback. 10-bit Main10 and HDR10 are advertised (VIDEO_CAP_10BIT|HDR): P010 decodes through the native, FFmpeg-Vulkan, VAAPI/D3D11VA and software paths alike, and PQ streams present on an HDR10/ST.2084 swapchain when the desktop offers one (KDE HDR, gamescope) or tone-map in-shader to SDR when it doesn't (PUNKTFUNK_TONEMAP_PEAK tunes the rolloff, default ≈1000 nits). The host still gates the upgrade behind its PUNKTFUNK_10BIT policy.

Debug/bisect knobs: PUNKTFUNK_DECODER=native-vulkan|vulkan|vaapi|d3d11va|software, PUNKTFUNK_PRESENT_MODE= mailbox|fifo|immediate|fifo_relaxed (default MAILBOX, FIFO where the surface offers no MAILBOX — AMD on Windows), PUNKTFUNK_VK_DEVICE=<index> (multi-GPU), and PUNKTFUNK_HW_FAULT=import (fault every VAAPI dmabuf import — proves the three-strike demotion to software on healthy hardware).