Commit Graph
6 Commits
Author SHA1 Message Date
enricobuehlerandClaude Opus 5 4063ddc93d perf(encode/nvenc-linux): swscale the packed 3-bpp expand instead of a per-pixel loop
`submit_cpu`'s RGB24/BGR24 → rgb0/bgr0 expand ran `w*h` iterations per frame, each
building two bounds-checked sub-slices for a 3-byte copy plus a pad-byte store — a
shape LLVM will not vectorise into the byte shuffle it actually is. At 3840x2160
that is 8.3M iterations on the encode thread, per frame.

It is not an edge case. The module header says the portal commonly negotiates
packed 24-bit RGB, and pf-capture offers `VideoFormat::RGB` first because wlroots
commonly fixates it — so this was the mainstream CPU path, running roughly an order
of magnitude slower than the 4-bpp sibling branch (a plain row memcpy) purely
because of how it was written.

swscale's packed-RGB expanders are SIMD, the sibling VAAPI backend already routes
RGB24/BGR24 through them, and this file already owned the `sws_getContext` /
`sws_freeContext` lifecycle — so this is net subtractive rather than new machinery:
the existing CSC condition widens to include `expand`, and the hand-written loop
goes away. No new field, no second context, nothing added to `Drop`: `expand` is
false whenever `want_444` (see the `nvenc_pixel`/`expand` binding), so the three
users are mutually exclusive and one context serves whichever applies. The `expand`
field itself is gone with its only reader.

`sws_setColorspaceDetails` is now applied ONLY for the 4:4:4/HDR users. The expand
is a pure byte shuffle — NVENC does the RGB→YUV itself downstream — and handing it
a matrix and range would have silently range-converted every packed-RGB session,
which is exactly what the module header promises does not happen.

Verified `-D warnings --all-targets` + tests on Linux (default; shipped
nvenc+vulkan-encode+pyrowave). The pixel path itself is unverifiable without an
NVIDIA box: `nvenc_hdr10_smoke` and friends are #[ignore]d, so the channel order
and the pad byte want an on-glass check on the CachyOS 5070 Ti.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 00:32:37 +02:00
enricobuehlerandClaude Opus 5 c7081b2a82 fix(encode/nvenc-linux): stop leaking the SwsContext on open's early returns
`sws_getContext` hands back a raw pointer whose only `sws_freeContext` lives in
`Drop for NvencEncoder` — and `Drop` needs a CONSTRUCTED `Self`, which does not
exist on either of `open`'s early returns: the intra-refresh-unsupported path
(which recurses into `Self::open`) and the plain error return. Both sit between
the context's creation and the `Ok(NvencEncoder { … })` that would give it an
owner, so every failed open leaked one.

That is not a once-per-process wart. `open_nvenc_probed` walks an EINVAL bitrate
ladder — requested rate, then the codec-level cap, then ×3/4 down to 50 Mb/s —
re-entering `open` at each step, so a GPU that refuses the requested bitrate leaks
a context per step (up to ~10). The intra-refresh retry adds one more.

Fixed structurally rather than with a guard: the block depends only on values known
before the encoder open (`want_444`, `want_hdr10`, `cuda`, `format`, `nvenc_pixel`,
the dimensions, `full_range_444`), so moving it BELOW `video.open_with(opts)` — to
just above the struct construction, with nothing fallible in between — makes the
leak unrepresentable instead of merely handled. No behaviour change: same context,
same colourspace details, same field.

Found while designing the WP1.4 swscale expand, which would have promoted this from
a 4:4:4/HDR-only leak (the only sessions that build a context today) to one on every
packed-RGB session. Fixing it first is the prerequisite for that change.

Verified `-D warnings --all-targets` + tests on Linux (default; shipped
nvenc+vulkan-encode+pyrowave).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 00:32:37 +02:00
enricobuehlerandClaude Fable 5 cd36c46388 perf(encode/nvenc-linux): LN2 v1 — stream-ordered submit via NvEncSetIOCudaStreams
Bind the session's IO streams to the encode thread's high-priority copy
stream: in sync-retrieve depth-1 use the per-frame input copy and cursor
blend now enqueue with NO cuStreamSynchronize and encode_picture orders
after them on the stream. Same stream both directions, so the encode's
completion is inserted into the stream and later work (the next frame's
copy into a reused ring slot) waits for it.

Soundness gate: the fast path engages only when pending is empty (true
depth-1 usage) — every prior encode was drained by a blocking poll, and
the caller holds the frame payload across the matching poll (contract now
documented on Encoder::submit; both host loops already comply). Pipelined
callers and PUNKTFUNK_NVENC_ASYNC mode keep the blocking copies.

True zero-copy input registration (registering the worker-owned IPC
buffer directly) stays the LN2 v2 follow-up — it needs a contiguous
worker-pool NV12 layout and a registration<->IPC-mapping lifetime tie.

PUNKTFUNK_NVENC_STREAM_ORDERED=0 restores the old blocking behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 19:38:22 +02:00
enricobuehlerandClaude Opus 4.8 00f759ec72 style(pf-encode): clear the clippy gate on the HDR/PyroWave additions
`cargo clippy --workspace --all-targets --locked -- -D warnings` was red on
main — three lints landed with the GNOME 50 HDR + PyroWave 4:4:4 work:

* pyrowave_wire.rs: `aw / 2 >> level` tripped clippy::precedence. Rust already
  binds `/` tighter than `>>`, so this always parsed as `(aw / 2) >> level`
  (subband dim at half res, then one halving per DWT level) — the parens are
  purely explicit, no change in behaviour.
* linux/mod.rs: `probe_can_encode_10bit` sat after `mod hdr_tests`
  (clippy::items_after_test_module) — moved above the test module, unchanged.

Lint-only; no functional change. fmt/clippy/test all green afterwards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 10:23:47 +02:00
enricobuehlerandClaude Fable 5 0e977817f9 feat(hdr): GNOME 50 HDR screencast capture + Linux Main10 encode
ci / docs-site (push) Successful in 58s
ci / rust (push) Failing after 1m3s
ci / web (push) Successful in 1m4s
apple / swift (push) Successful in 1m22s
decky / build-publish (push) Successful in 19s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 46s
windows-host / package (push) Failing after 5m38s
ci / bench (push) Successful in 6m12s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6m38s
apple / screenshots (push) Successful in 6m25s
docker / deploy-docs (push) Successful in 29s
deb / build-publish-host (push) Failing after 7m30s
deb / build-publish (push) Successful in 11m22s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 8m59s
arch / build-publish (push) Successful in 11m57s
android / android (push) Successful in 15m26s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m46s
GNOME 50 (Mutter MR 4928, PipeWire >= 1.6) added HDR screen sharing for
monitor streams: 10-bit PQ formats (xRGB_210LE/xBGR_210LE) with MANDATORY
BT.2020 + SMPTE-2084 colorimetry props, advertised while the mirrored
monitor is in BT.2100 colour mode. Wire the Linux host into it end-to-end
on the GameStream desktop-mirror path (PUNKTFUNK_VIDEO_SOURCE=portal):

* pf-frame: PixelFormat::X2Rgb10/X2Bgr10 (DRM XR30/XB30; X2Bgr10 is the
  Windows Rgb10a2 layout) + fourccs.
* pf-capture: want_hdr portal offer — HDR-only LINEAR-dmabuf pods with
  MANDATORY PQ/BT.2020 props (SHM excluded: Mutter's SHM record path
  paints 8-bit ARGB32 regardless of format; tiled excluded: the EGL
  de-tile blit is 8-bit RGBA8), negotiated-colorimetry parse, generic
  HDR10 hdr_meta(), packed-10-bit CPU cursor blend, a process-wide SDR
  downgrade latch on negotiation timeout, and a DisplayConfig BT.2100
  colour-mode probe (gnome_hdr_monitor_active).
* pf-encode: libav NVENC X2RGB10->P010 swscale (BT.2020 limited) ->
  HEVC Main10 / 10-bit AV1 with PQ VUI; VAAPI 10-bit on both paths (CPU
  P010 upload + dmabuf XR30 scale_vaapi p010/bt2020); can_encode_10bit
  now probes for real on Linux; 10-bit sessions route around the
  8-bit-only Vulkan-video/direct-NVENC backends.
* GameStream: host_hdr_capable() Linux arm, live monitor-HDR check at
  RTSP honor time, capturer-pool reuse keyed on HDR-ness, gs_bit_depth
  covers the new formats. New `punktfunk-host hdr-probe` diagnostic and
  a PUNKTFUNK_SPIKE_HDR spike lever.
* Native plane stays honestly 8-bit via capturer_supports_hdr(): Mutter
  RecordVirtual streams are SDR-only upstream (GNOME 50 and 51-dev), so
  virtual-display sources cannot deliver HDR yet.

Validated on the RTX 5070 Ti (GNOME 50.3 / PipeWire 1.6.8): the Main10
probes pass and the ignored nvenc_hdr10_smoke GPU test emits an IDR that
ffprobe reads as Main 10 / yuv420p10le / bt2020nc / smpte2084 / limited.
Live HDR capture negotiation still needs an HDR monitor on glass; VAAPI
10-bit needs the AMD box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:30:55 +02:00
enricobuehlerandClaude Opus 4.8 9a36ea2132 refactor(host/W6.2): extract the video encode backends into the pf-encode crate
encode.rs + encode/* (NVENC, VAAPI, native AMF, AMF/QSV ffmpeg, direct-SDK
NVENC/CUDA, raw Vulkan-Video, PyroWave, openh264) move into crates/pf-encode
behind one Encoder trait + open_video selector (plan §W6). The crate speaks the
shared frame vocabulary (pf-frame: CapturedFrame/PixelFormat + the DXGI identity
D3d11Frame/make_device) and pf-zerocopy (CUDA context/buffers), and NEVER
pf-capture — the capture→encode edge is one-way (ZeroCopyPolicy, prior commit).

Dep moves: the heavy encoder deps (ffmpeg-next, the NVENC SDK, openh264,
pyrowave-sys) move from the host to pf-encode; the host's
nvenc/amf-qsv/vulkan-encode/pyrowave features now FORWARD to pf-encode/*. The
host keeps a mod-encode shim (pub use pf_encode) so every crate::encode::* path
(negotiator + GameStream/native/mgmt planes) is unchanged.

resolve_render_adapter_luid moves from the host's windows/win_adapter.rs into
pf-gpu (both pf-encode and pf-capture need it as a peer of GPU selection); its 5
call sites (encode amf/nvenc, capture idd_push/synthetic_nv12, vdisplay manager)
rewire to pf_gpu::resolve_render_adapter_luid and win_adapter.rs is deleted.
pf-frame's make_device gains a # Safety section (public-unsafe-fn lint, latent
since the pf-frame carve — a full-workspace -D warnings clippy catches it).

Verified: Linux clippy -D warnings (pf-encode + host nvenc,vulkan-encode,pyrowave
--all-targets) + 13/13 pf-encode + 299/299 host tests; Windows clippy -D warnings
(pf-encode nvenc,amf-qsv --all-targets + host nvenc,amf-qsv --all-targets)
Finished exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:42:51 +02:00