c95db8eebca4b5267bcbfd5811f588ddf9f3969a
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a9a1b923a2 |
fix(pf-capture): withhold zero-copy buffers from the producer until the encoder is done reading them
ci / bun-nix (pull_request) Successful in 29s
ci / web (pull_request) Successful in 1m8s
apple / swift (pull_request) Successful in 1m50s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 4m14s
ci / rust-arm64 (pull_request) Successful in 4m52s
ci / rust (pull_request) Successful in 4m57s
android / android (pull_request) Successful in 7m6s
The raw-dmabuf passthrough handed the SPA buffer back to gamescope at .process return while the encode thread had not yet imported - let alone read - its dmabuf, and nothing ordered the producer's writes against the consumer's read (no explicit sync; the implicit-fence wait measures NoFence on every compositor x vendor pairing we have). On the direct-VCN arms (native NV12, RGB-direct EFC) the captured buffer IS the encode source for the whole ring-2-deep encode plus the phase-lock hold, so at 120 fps gamescope cycles back into the buffer mid-encode and the stream ships torn frames: luma/chroma desync (magenta tint) plus block corruption propagating through the P-chain until the next intra. Field report: Nobara, gamescope mode at 120 fps - KDE sessions were clean because cursor_blend routes them to the compute-CSC copy arm whose read window is microseconds. The fix defers the requeue: a published passthrough frame carries a FrameHold (new on DmabufFrame), and the buffer rejoins the producer's pool only when the last clone drops. The Vulkan encoder clones the hold into the ring slot at submit and releases it when the slot's fence retires (poll/backpressure/reset), extending "the producer must not rewrite this" across exactly the GPU read. The host loop's repeat path is fixed by the same mechanism: a re-submitted frame now aliases a buffer the producer never got back, instead of whatever gamescope last composited into it. Bookkeeping lives in a per-stream HoldBook (loop-thread mutations only): holds release through a pw channel onto the loop thread, a generation tag keeps a stale release from requeueing a renegotiated pool's reused address, and at most pool_depth - HOLD_POOL_RESERVE buffers are ever withheld - a pool at the old floor of 2 cannot spare any and falls back (with one warn) to the previous racy contract. PUNKTFUNK_ZEROCOPY_HOLD=0 restores the old behavior outright for field bisects. Gates (.25): cargo check + clippy --all-targets -D warnings on pf-frame/pf-capture/pf-encode/punktfunk-host; pf-capture 68/68 (4 new HoldBook tests), pf-encode 75/75 (+15 ignored, host-feature set); workspace cargo fmt --check clean. punktfunk-host's hooks::prep_runs_do_in_order_and_undo_in_reverse fails on that box on pristine main too (pre-existing; crate untouched here). |
||
|
|
f373dffb5e |
chore: migrate the main workspace and pf-vkhdr-layer to edition 2024 (WP20)
The safety half of the rust-safety programme's §8.4: `std::env::set_var`/`remove_var` are
`unsafe fn` in edition 2024, converting the class of bug the programme found the hard way
(the
|
||
|
|
dfebb9dfbb |
chore(safety): hoist the unsafe lints into the workspace tables (WP2c hoist)
undocumented_unsafe_blocks joins unsafe_op_in_unsafe_fn in
[workspace.lints], and the ~100 scattered per-file #![deny(...)] attributes
(85 files) are deleted — a new crate, or a new module in an old one, is now
covered on creation rather than on remembering. The per-file form is how
pf-vkhdr-layer, wdk-probe and half of pf-clipboard stayed uncovered.
There are THREE workspaces, so the claim is made three times: the main
Cargo.toml, packaging/windows/drivers (workspace table + [lints]
workspace = true in all seven members), and packaging/windows/pf-vkhdr-layer
(its [lints] table, previous commit). pf-update now opts into workspace
lints; the two vendored member snapshots (cros-codecs, usbip-sim) stay out
deliberately and now both say so.
Newly-covered fallout was two link-sanity tests (pyrowave-sys, libvpl-sys)
— proofs written. Stale prose that claimed the workspace held
unsafe_op_in_unsafe_fn at "warn" (it has been deny) or pointed at the
deleted attributes is corrected.
nvenc_core.rs is carved OUT of the unsafe_op_in_unsafe_fn fence: its
exemption rationale ("raw entry-table calls almost line for line") was
false — the file makes zero FFI calls. Its unsafe surface is C-union writes
whose soundness hangs on which codec arm is active, and its own 4:4:4 note
records the shipped bug (hevcConfig bytes stamped onto an AV1 config) that
per-operation blocks make visible. It now runs the strictest discipline in
the crate: clippy::multiple_unsafe_ops_per_block at deny, one union access
per block, each naming its codec guard.
Verified here: cargo fmt clean in all three workspaces; native clippy
-D warnings clean for everything that compiles on macOS (the three
pre-existing mac-native failures — pf-client-core wol.rs, pf-encode
dead-code/closure-call, probe mic_burst — reproduce on the clean tree).
Linux/Windows legs ride the .25/.133 gate.
|
||
|
|
4a4118e3ce |
feat(pf-encode): encode PyroWave in a capability-carrying worker, so the host never holds a capability
PyroWave encodes on the same GPU shader cores the game saturates, and an elevated VK_KHR_global_priority queue is the compute-preemption lever for it — measured on .21 (RTX 5070 Ti, GRID 2 loop): encode p99 6.4 -> 4.4 ms. Every driver refuses every priority class without CAP_SYS_NICE, on NVIDIA and on RADV alike, so the lever is decoration on a packaged host. 0.26.0-1 granted that capability to punktfunk-host and killed desktop streaming on every KDE box: KWin identifies a client by resolving /proc/<pid>/exe and matching an installed .desktop's Exec=, the kernel refuses that readlink to a reader whose effective set is not a superset of the target's PERMITTED set (cap_ptrace_access_check), and KWin holds no capabilities. #136 revoked it everywhere. The capability therefore cannot live in the process that fronts KWin. It lives in a new, deliberately small binary — punktfunk-encode-worker — which owns the priority-elevated Vulkan device and talks to nothing but the socket its parent spawned it on: no Wayland, no D-Bus, no network, no plugins. It is a SEPARATE FILE and must stay one; a hardlink or a hidden host subcommand shares the inode, hence the capability, and silently re-creates the incident. That rule is written where someone would break it, in the worker crate's own Cargo.toml. `open_inner` is reused verbatim in the worker — the same REALTIME->HIGH->none ladder, the same refusal-never-fails-open invariant, the same PUNKTFUNK_PERF split — so the A/B stays comparable with PW1. The only in-process change is a flag for whether THIS process prints the INERT warn, plus an out-parameter reporting the class that was granted. Three things the design did not anticipate: * An AU cannot ride in the message body. MAX_MSG is 64 KiB and bodies are serde_json, which renders a Vec<u8> as one decimal per byte: a 1080p60 AU is ~333 KB of JSON and 4K ~3.3 MB, and the minimum per-frame budget is already 64 KiB. So the AU crosses on a memfd the worker creates once and pwrites each frame; the fd crosses once, in Ready. A test pins the arithmetic so nobody "simplifies" the memfd away. Cursor bitmaps take the same route, only when their serial changes. * set_wire_chunking has to cross the wire even though poll_chunk does not. Chunking changes the AU BYTES, not merely how they are handed out — it feeds rate_budget()'s deflation and build_au's windowed framing — so a proxy-local copy would have the host cutting dense AUs at boundaries that are not window boundaries. Forwarded and mirrored. poll_chunk itself needs no protocol: the identical AuChunker runs host-side on the whole AU the worker returns. * CPU-backed frames really do reach this encoder (force_cpu_for_nvenc_444, and the raw-dmabuf degrade latch), and a 1080p BGRA frame is ~8 MB. The first non-dmabuf frame pins the session in-process with one warn rather than putting 480 MB/s on a socket. Every rung falls back to the in-process encoder exactly as today with one warn and never a dead session: PUNKTFUNK_ENCODE_WORKER=off, binary missing, spawn failure, handshake timeout, proto or workspace-version skew (host and worker are different files now, so that check is load-bearing), InitErr, a refused frame, and socket EOF mid-session — which respawns once, then pins inline. Also: recv retries EINTR with the REMAINING deadline, not a fresh one. With SO_RCVTIMEO the kernel returns EINTR rather than restarting, so a signal would otherwise read as a dead worker; re-arming with the full budget would instead let a steady signal rate defer a real hang forever. |