feat(host): enable GPU zero-copy by default on all backends
apple / swift (push) Failing after 45s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m15s
android / android (push) Successful in 4m56s
windows-host / package (push) Failing after 3m31s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
arch / build-publish (push) Successful in 5m39s
release / apple (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
apple / screenshots (push) Has been skipped
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
decky / build-publish (push) Has been cancelled
ci / web (push) Successful in 1m3s
ci / docs-site (push) Successful in 1m23s
ci / rust (push) Successful in 4m46s
ci / bench (push) Successful in 4m57s
deb / build-publish (push) Successful in 4m22s

Now that the per-capture worker subprocess makes an NVENC EGL/CUDA driver
fault survivable (design/zerocopy-worker-isolation.md), the reason the NVENC
zero-copy path stayed opt-in is gone. zerocopy::enabled() now defaults ON for
both GPU backends (was ON VAAPI / OFF NVENC). Fallbacks are intact: VAAPI's
one-shot CPU auto-downgrade (VAAPI-gated, never trips for NVENC) and NVENC's
per-capture fallback + worker-death latch.

Reframe the shipped host.env examples and setup guides to rely on the default
rather than force PUNKTFUNK_ZEROCOPY=1 (an explicit =1 skips the VAAPI
auto-downgrade).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 10:33:30 +00:00
parent a947f48d29
commit 2f9e61d191
11 changed files with 33 additions and 27 deletions
@@ -51,18 +51,21 @@ pub fn vaapi_dmabuf_forced() -> bool {
flag_opt("PUNKTFUNK_ZEROCOPY") == Some(true) flag_opt("PUNKTFUNK_ZEROCOPY") == Some(true)
} }
/// Whether the zero-copy path is on. `PUNKTFUNK_ZEROCOPY` decides when set (truthy = on, else /// Whether the zero-copy path is on. `PUNKTFUNK_ZEROCOPY` decides when set (truthy = on, else off).
/// off). Unset defaults **on for the VAAPI (AMD/Intel) backend** — the stock AMD/Intel install /// **Unset defaults ON for both GPU backends** — the stock install gets the GPU dmabuf path, not
/// gets the GPU dmabuf path, not three full-frame CPU touches — unless a failed negotiation /// three full-frame CPU touches. This includes NVENC (previously opt-in): the EGL→CUDA (tiled) and
/// downgraded it ([`note_vaapi_dmabuf_failed`]); and **off for NVENC**, whose EGL→CUDA import /// Vulkan (LINEAR) imports now run in a per-capture worker subprocess
/// stays opt-in (Mutter+NVIDIA has known dmabuf-capture races; see `PUNKTFUNK_FORCE_SHM`). /// (`design/zerocopy-worker-isolation.md`), so a driver fault on a producer-invalidated dmabuf kills
/// the worker and the host degrades to its capture-loss rebuild instead of dying — the reason the
/// NVENC path stayed opt-in is gone. Fallbacks stay in place: VAAPI has a one-shot CPU downgrade if
/// the LINEAR-dmabuf offer never negotiates ([`note_vaapi_dmabuf_failed`]); NVENC falls back per
/// capture when no importer/importable modifier is available and latches the import off after
/// repeated worker deaths. `PUNKTFUNK_ZEROCOPY=0` opts out; `PUNKTFUNK_FORCE_SHM` forces the
/// race-free SHM path.
pub fn enabled() -> bool { pub fn enabled() -> bool {
match flag_opt("PUNKTFUNK_ZEROCOPY") { match flag_opt("PUNKTFUNK_ZEROCOPY") {
Some(v) => v, Some(v) => v,
None => { None => !VAAPI_DMABUF_FAILED.load(Ordering::Relaxed),
crate::encode::linux_zero_copy_is_vaapi()
&& !VAAPI_DMABUF_FAILED.load(Ordering::Relaxed)
}
} }
} }
+2 -2
View File
@@ -85,13 +85,13 @@ cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env
The template is deliberately minimal — it does **not** force a compositor, because the host The template is deliberately minimal — it does **not** force a compositor, because the host
auto-detects Gaming Mode (gamescope) vs Desktop (KWin) on every connect and follows the switch auto-detects Gaming Mode (gamescope) vs Desktop (KWin) on every connect and follows the switch
mid-stream. The only settings that matter are the session anchors plus zero-copy: mid-stream. The only settings that matter are the session anchors (GPU zero-copy is on by default):
```sh ```sh
XDG_RUNTIME_DIR=/run/user/1000 XDG_RUNTIME_DIR=/run/user/1000
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
PUNKTFUNK_VIDEO_SOURCE=virtual PUNKTFUNK_VIDEO_SOURCE=virtual
PUNKTFUNK_ZEROCOPY=1 # GPU zero-copy (dmabuf → CUDA → NVENC); auto-falls back to CPU # GPU zero-copy (dmabuf → CUDA → NVENC) is ON by default; auto-falls back to CPU. Set =0 to force CPU.
PUNKTFUNK_GAMESCOPE_ATTACH=1 # Gaming Mode = attach to the box's own session (see below) PUNKTFUNK_GAMESCOPE_ATTACH=1 # Gaming Mode = attach to the box's own session (see below)
``` ```
+1 -1
View File
@@ -36,7 +36,7 @@ On Linux the host **rewrites `WAYLAND_DISPLAY` / `XDG_CURRENT_DESKTOP` / `XDG_RU
|---|---|---| |---|---|---|
| `PUNKTFUNK_COMPOSITOR` | `kwin` · `mutter` · `gamescope` · `wlroots` (aliases: `kde`/`plasma`, `gnome`, `sway`/`hyprland`) | Which backend creates the virtual display. **Leave unset to auto-detect;** set only to force one. | | `PUNKTFUNK_COMPOSITOR` | `kwin` · `mutter` · `gamescope` · `wlroots` (aliases: `kde`/`plasma`, `gnome`, `sway`/`hyprland`) | Which backend creates the virtual display. **Leave unset to auto-detect;** set only to force one. |
| `PUNKTFUNK_VIDEO_SOURCE` | `virtual` · `portal` | `virtual` creates a per-client display at the client's exact mode (the normal choice). `portal` captures an existing monitor instead. | | `PUNKTFUNK_VIDEO_SOURCE` | `virtual` · `portal` | `virtual` creates a per-client display at the client's exact mode (the normal choice). `portal` captures an existing monitor instead. |
| `PUNKTFUNK_ZEROCOPY` | `1` · `0` | GPU zero-copy capture→encode (dmabuf → CUDA → NVENC, or D3D11 on Windows). Leave on; it falls back to a CPU path automatically. | | `PUNKTFUNK_ZEROCOPY` | `1` · `0` *(default on)* | GPU zero-copy capture→encode (dmabuf → CUDA → NVENC, or D3D11 on Windows). **On by default** — no need to set it; it falls back to a CPU path automatically. Set `0` to force the CPU path. |
| `PUNKTFUNK_INPUT_BACKEND` | `libei` · `gamescope` · `wlr` · `uinput` | How input is injected. `libei` for GNOME/KDE, `gamescope` for Bazzite/gamescope, `wlr` for Sway/wlroots. Auto-detected with the compositor. | | `PUNKTFUNK_INPUT_BACKEND` | `libei` · `gamescope` · `wlr` · `uinput` | How input is injected. `libei` for GNOME/KDE, `gamescope` for Bazzite/gamescope, `wlr` for Sway/wlroots. Auto-detected with the compositor. |
| `PUNKTFUNK_ENCODER` | `auto` · `nvenc` · `vaapi` (Linux) · `amf` · `qsv` (Windows) · `software` | Encoder backend. `auto` (default) detects the GPU vendor: NVIDIA→NVENC, AMD→VAAPI/AMF, Intel→VAAPI/QSV. `software` (aliases `sw`/`openh264`) is the GPU-less H.264 path on both platforms — on Windows `auto` falls back to it when no GPU is found; on Linux it is **explicit-only** (`auto` never picks it). | | `PUNKTFUNK_ENCODER` | `auto` · `nvenc` · `vaapi` (Linux) · `amf` · `qsv` (Windows) · `software` | Encoder backend. `auto` (default) detects the GPU vendor: NVIDIA→NVENC, AMD→VAAPI/AMF, Intel→VAAPI/QSV. `software` (aliases `sw`/`openh264`) is the GPU-less H.264 path on both platforms — on Windows `auto` falls back to it when no GPU is found; on Linux it is **explicit-only** (`auto` never picks it). |
| `PUNKTFUNK_RENDER_NODE` | path | Linux DRM render node for zero-copy (default `/dev/dri/renderD128`). Set on multi-GPU boxes to pick the right GPU. | | `PUNKTFUNK_RENDER_NODE` | path | Linux DRM render node for zero-copy (default `/dev/dri/renderD128`). Set on multi-GPU boxes to pick the right GPU. |
+1 -1
View File
@@ -21,7 +21,7 @@ WAYLAND_DISPLAY=wayland-0
XDG_CURRENT_DESKTOP=GNOME XDG_CURRENT_DESKTOP=GNOME
PUNKTFUNK_COMPOSITOR=mutter PUNKTFUNK_COMPOSITOR=mutter
PUNKTFUNK_VIDEO_SOURCE=virtual PUNKTFUNK_VIDEO_SOURCE=virtual
PUNKTFUNK_ZEROCOPY=1 # GPU zero-copy (dmabuf → CUDA → NVENC) is ON by default; auto-falls back to CPU. Set =0 to force CPU.
PUNKTFUNK_INPUT_BACKEND=libei PUNKTFUNK_INPUT_BACKEND=libei
``` ```
+1 -1
View File
@@ -20,7 +20,7 @@ WAYLAND_DISPLAY=wayland-0
XDG_CURRENT_DESKTOP=KDE XDG_CURRENT_DESKTOP=KDE
PUNKTFUNK_COMPOSITOR=kwin PUNKTFUNK_COMPOSITOR=kwin
PUNKTFUNK_VIDEO_SOURCE=virtual PUNKTFUNK_VIDEO_SOURCE=virtual
PUNKTFUNK_ZEROCOPY=1 # GPU zero-copy (dmabuf → CUDA → NVENC) is ON by default; auto-falls back to CPU. Set =0 to force CPU.
PUNKTFUNK_INPUT_BACKEND=libei PUNKTFUNK_INPUT_BACKEND=libei
``` ```
+1 -1
View File
@@ -27,7 +27,7 @@ these in `~/.config/punktfunk/host.env`:
PUNKTFUNK_COMPOSITOR=wlroots # aliases: sway, hyprland PUNKTFUNK_COMPOSITOR=wlroots # aliases: sway, hyprland
PUNKTFUNK_INPUT_BACKEND=wlr PUNKTFUNK_INPUT_BACKEND=wlr
PUNKTFUNK_VIDEO_SOURCE=virtual PUNKTFUNK_VIDEO_SOURCE=virtual
PUNKTFUNK_ZEROCOPY=1 # GPU zero-copy capture→encode; auto-falls back to CPU # GPU zero-copy capture→encode is ON by default; auto-falls back to CPU. Set PUNKTFUNK_ZEROCOPY=0 to force CPU.
``` ```
See [Configuration](/docs/configuration) for the full reference. See [Configuration](/docs/configuration) for the full reference.
+6 -5
View File
@@ -242,8 +242,9 @@ PUNKTFUNK_GAMESCOPE_APP=steam -gamepadui
# gamescope hosts its own EIS input socket — input lands in the nested session. # gamescope hosts its own EIS input socket — input lands in the nested session.
PUNKTFUNK_INPUT_BACKEND=gamescope PUNKTFUNK_INPUT_BACKEND=gamescope
# GPU zero-copy capture (dmabuf -> CUDA -> NVENC). Auto-falls back to CPU if unavailable. # GPU zero-copy capture (dmabuf -> CUDA -> NVENC) is ON by default and auto-falls back to CPU if
PUNKTFUNK_ZEROCOPY=1 # unavailable. No need to set it. Set to 0 only to force the CPU path.
# PUNKTFUNK_ZEROCOPY=0
#RUST_LOG=info #RUST_LOG=info
``` ```
@@ -257,7 +258,7 @@ PUNKTFUNK_ZEROCOPY=1
| `PUNKTFUNK_VIDEO_SOURCE` | `virtual` | Create a per-client virtual output at the client's exact WxH@Hz (the flagship "native resolution, no scaling" mode), vs. `portal` which captures an existing monitor. | | `PUNKTFUNK_VIDEO_SOURCE` | `virtual` | Create a per-client virtual output at the client's exact WxH@Hz (the flagship "native resolution, no scaling" mode), vs. `portal` which captures an existing monitor. |
| `PUNKTFUNK_GAMESCOPE_APP` | `steam -gamepadui` | The command launched **inside** the nested gamescope — here, a SteamOS-style couch UI. Set it to whatever you want the session to run. | | `PUNKTFUNK_GAMESCOPE_APP` | `steam -gamepadui` | The command launched **inside** the nested gamescope — here, a SteamOS-style couch UI. Set it to whatever you want the session to run. |
| `PUNKTFUNK_INPUT_BACKEND` | `gamescope` | Inject mouse/keyboard/gamepad into the nested gamescope via its own EIS socket. | | `PUNKTFUNK_INPUT_BACKEND` | `gamescope` | Inject mouse/keyboard/gamepad into the nested gamescope via its own EIS socket. |
| `PUNKTFUNK_ZEROCOPY` | `1` | GPU zero-copy capture (dmabuf → CUDA → NVENC). Falls back to CPU automatically if unavailable. | | `PUNKTFUNK_ZEROCOPY` | `on` *(default)* | GPU zero-copy capture (dmabuf → CUDA → NVENC), on by default. Falls back to CPU automatically if unavailable; set `0` to force the CPU path. |
| `RUST_LOG` | (commented) | Uncomment `RUST_LOG=info` for verbose logs while debugging. | | `RUST_LOG` | (commented) | Uncomment `RUST_LOG=info` for verbose logs while debugging. |
**Optional — a real DualSense for clients holding one:** add `PUNKTFUNK_GAMEPAD=dualsense` to present **Optional — a real DualSense for clients holding one:** add `PUNKTFUNK_GAMEPAD=dualsense` to present
@@ -463,8 +464,8 @@ desktop viewer.
after an `rpm-ostree`/`bootc` update, confirm the NVIDIA driver still loads (`nvidia-smi`) before after an `rpm-ostree`/`bootc` update, confirm the NVIDIA driver still loads (`nvidia-smi`) before
blaming punktfunk. blaming punktfunk.
- **`PUNKTFUNK_ZEROCOPY=1` but it falls back to CPU.** The zero-copy path needs working EGL/CUDA from - **Zero-copy falls back to CPU.** The zero-copy path (on by default) needs working EGL/CUDA from the
the NVIDIA driver. The code falls back to CPU automatically; check the log for the fallback line and NVIDIA driver. The code falls back to CPU automatically; check the log for the fallback line and
verify the `-nvidia` image / driver is healthy. verify the `-nvidia` image / driver is healthy.
- **Wrong UID in `host.env`.** `XDG_RUNTIME_DIR=/run/user/1000` and the bus path assume UID 1000. Run - **Wrong UID in `host.env`.** `XDG_RUNTIME_DIR=/run/user/1000` and the bus path assume UID 1000. Run
+3 -2
View File
@@ -10,8 +10,9 @@ DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
PUNKTFUNK_VIDEO_SOURCE=virtual PUNKTFUNK_VIDEO_SOURCE=virtual
# GPU zero-copy capture (dmabuf -> CUDA -> NVENC). Auto-falls back to CPU if unavailable. # GPU zero-copy capture (dmabuf -> CUDA -> NVENC) is ON by default and auto-falls back to CPU if
PUNKTFUNK_ZEROCOPY=1 # unavailable. No need to set it. Set to 0 only to force the CPU path.
# PUNKTFUNK_ZEROCOPY=0
#RUST_LOG=info #RUST_LOG=info
+1 -1
View File
@@ -8,7 +8,7 @@ WAYLAND_DISPLAY=wayland-kde
XDG_CURRENT_DESKTOP=KDE XDG_CURRENT_DESKTOP=KDE
PUNKTFUNK_COMPOSITOR=kwin PUNKTFUNK_COMPOSITOR=kwin
PUNKTFUNK_VIDEO_SOURCE=virtual PUNKTFUNK_VIDEO_SOURCE=virtual
PUNKTFUNK_ZEROCOPY=1 # GPU zero-copy (dmabuf → CUDA → NVENC) is ON by default; auto-falls back to CPU. Set =0 to force CPU.
PUNKTFUNK_INPUT_BACKEND=libei PUNKTFUNK_INPUT_BACKEND=libei
# UDP Generic Segmentation Offload on the send path: coalesce a frame's equal-size packets into # UDP Generic Segmentation Offload on the send path: coalesce a frame's equal-size packets into
# kernel super-buffers (one sendmsg per ~64 packets instead of one per packet) — the dominant # kernel super-buffers (one sendmsg per ~64 packets instead of one per packet) — the dominant
+2 -2
View File
@@ -13,8 +13,8 @@
# bash scripts/headless/run-headless-kde.sh [WxH] # default 1920x1080 # bash scripts/headless/run-headless-kde.sh [WxH] # default 1920x1080
# #
# Then in another shell: # Then in another shell:
# WAYLAND_DISPLAY=wayland-kde XDG_CURRENT_DESKTOP=KDE PUNKTFUNK_ZEROCOPY=1 \ # WAYLAND_DISPLAY=wayland-kde XDG_CURRENT_DESKTOP=KDE \
# punktfunk-host punktfunk1-host --source virtual --seconds 14400 # punktfunk-host punktfunk1-host --source virtual --seconds 14400 # zero-copy is on by default
set -euo pipefail set -euo pipefail
RES="${1:-1920x1080}" RES="${1:-1920x1080}"
+3 -2
View File
@@ -16,8 +16,9 @@ XDG_CURRENT_DESKTOP=KDE
# resolution+refresh (the flagship mode); `portal` captures an existing monitor. # resolution+refresh (the flagship mode); `portal` captures an existing monitor.
PUNKTFUNK_VIDEO_SOURCE=virtual PUNKTFUNK_VIDEO_SOURCE=virtual
# GPU zero-copy capture (EGL/Vulkan → CUDA → NVENC). Falls back to CPU automatically. # GPU zero-copy capture (dmabuf → CUDA → NVENC / VAAPI / Vulkan) is ON by default and falls back to
PUNKTFUNK_ZEROCOPY=1 # CPU automatically. No need to set it. Set to 0 only to force the CPU path.
# PUNKTFUNK_ZEROCOPY=0
# --- Bazzite / SteamOS-like host: host-managed Steam-Deck-UI session ----------------------- # --- Bazzite / SteamOS-like host: host-managed Steam-Deck-UI session -----------------------
# The host LAUNCHES gamescope-session-plus headless AT THE CLIENT'S mode (so games see the # The host LAUNCHES gamescope-session-plus headless AT THE CLIENT'S mode (so games see the