feat(linux): zero-copy 4:4:4 — the EGL worker converts to planar YUV444 on the GPU
A 4:4:4 session no longer falls to the CPU path (SHM capture + swscale RGB→YUV444P + re-upload — the fps-ceiling triple tax). The zero-copy worker grows a Yuv444Blit: three full-res R8 GL passes (the proven BT.709 coefficients; studio or full range per PUNKTFUNK_444_FULLRANGE, read by both processes so pixels and VUI flip together) into ONE stacked 3-plane pitched CUDA allocation — which keeps the worker↔host wire and IPC single-plane. The encoder copies the planes into ffmpeg's yuv444p CUDA surface and hevc_nvenc emits Range-Extensions 4:4:4 natively. ImportKind::Tiled444 is APPENDED to the worker protocol (a worker outliving a replaced host binary must keep the old tags stable; an old worker just errors the import, which the fail machinery already handles). A 4:4:4 session on a LINEAR/gamescope capture — no convert wired there — fails with a clear message instead of letting hevc_nvenc silently subsample. caps().chroma_444 now keys off the session (it missed the GPU path when keyed off the swscale's existence). Live-verified on the CachyOS VM (RTX 5070 Ti): per-frame "imported to CUDA yuv444=true", stream Rext/yuv444p/bt709 in both tv and pc range, no CPU-path warning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -124,22 +124,26 @@ impl SessionPlan {
|
||||
pub fn output_format(&self) -> crate::capture::OutputFormat {
|
||||
let gpu = self.encoder.is_gpu();
|
||||
// Linux NVENC 4:4:4: libavcodec `hevc_nvenc` only emits 4:4:4 from a YUV444 *input* frame —
|
||||
// RGB-in is always subsampled to 4:2:0 (verified on the RTX 5070 Ti). So the encoder does an
|
||||
// RGB→YUV444P swscale and needs CPU-resident RGB frames; force the zero-copy GPU capture off
|
||||
// for a 4:4:4 NVENC session. (VAAPI 4:4:4, where the hardware supports it, keeps its dmabuf
|
||||
// path via `scale_vaapi`; Windows NVENC ingests ARGB directly and stays GPU.)
|
||||
// RGB-in is always subsampled to 4:2:0 (verified on the RTX 5070 Ti). With zero-copy
|
||||
// enabled the import worker produces that input ON the GPU (`ImportKind::Tiled444` — the
|
||||
// planar-YUV444 convert), so the session stays fully zero-copy at full chroma. Without
|
||||
// zero-copy the encoder swscales CPU RGB → YUV444P, which needs CPU-resident frames —
|
||||
// force the GPU capture off for that case only. (VAAPI 4:4:4, where the hardware supports
|
||||
// it, keeps its dmabuf path via `scale_vaapi`; Windows NVENC ingests BGRA directly.)
|
||||
#[cfg(target_os = "linux")]
|
||||
let gpu = {
|
||||
let force_cpu_for_nvenc_444 =
|
||||
self.chroma.is_444() && !crate::encode::linux_zero_copy_is_vaapi();
|
||||
let force_cpu_for_nvenc_444 = self.chroma.is_444()
|
||||
&& !crate::encode::linux_zero_copy_is_vaapi()
|
||||
&& !crate::zerocopy::enabled();
|
||||
if gpu && force_cpu_for_nvenc_444 {
|
||||
// Surface the trade loudly: this is the single biggest per-frame cost a 4:4:4
|
||||
// session adds (full-res CPU readback + swscale RGB→YUV444P every frame), and
|
||||
// it looks like an unexplained fps ceiling if you don't know it happened.
|
||||
tracing::warn!(
|
||||
"4:4:4 session on the NVENC path: zero-copy GPU capture DISABLED — every \
|
||||
frame is CPU RGB + swscale RGB→YUV444P; expect a lower fps ceiling than \
|
||||
4:2:0 at this mode"
|
||||
"4:4:4 session on the NVENC path without PUNKTFUNK_ZEROCOPY: zero-copy GPU \
|
||||
capture DISABLED — every frame is CPU RGB + swscale RGB→YUV444P; expect a \
|
||||
lower fps ceiling than 4:2:0 at this mode (set PUNKTFUNK_ZEROCOPY=1 for the \
|
||||
GPU 4:4:4 convert)"
|
||||
);
|
||||
}
|
||||
gpu && !force_cpu_for_nvenc_444
|
||||
|
||||
Reference in New Issue
Block a user