feat(host/linux): cursor-as-metadata — pointer in gamescope streams, no perf hit
ci / web (push) Successful in 46s
ci / docs-site (push) Successful in 1m8s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 18s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (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 8s
windows-host / package (push) Successful in 8m37s
ci / bench (push) Successful in 5m47s
ci / rust (push) Failing after 8m24s
docker / deploy-docs (push) Successful in 22s
arch / build-publish (push) Successful in 11m26s
android / android (push) Successful in 12m54s
deb / build-publish (push) Successful in 12m8s
apple / swift (push) Successful in 5m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m30s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m31s
apple / screenshots (push) Successful in 19m34s
ci / web (push) Successful in 46s
ci / docs-site (push) Successful in 1m8s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 18s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (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 8s
windows-host / package (push) Successful in 8m37s
ci / bench (push) Successful in 5m47s
ci / rust (push) Failing after 8m24s
docker / deploy-docs (push) Successful in 22s
arch / build-publish (push) Successful in 11m26s
android / android (push) Successful in 12m54s
deb / build-publish (push) Successful in 12m8s
apple / swift (push) Successful in 5m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m30s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m31s
apple / screenshots (push) Successful in 19m34s
gamescope draws its pointer on a hardware DRM cursor plane that never enters the framebuffer feeding its PipeWire capture node, so captured frames arrive cursorless. Rather than force the producer's Embedded full-frame composite, request the pointer as PipeWire SPA_META_Cursor and composite it ourselves — a ≤256×256 blit into the encoder-OWNED surface, never the compositor's read-only dmabuf. Capture (capture/linux/mod.rs, capture.rs): - choose_cursor_mode() gates on available_cursor_modes(): Metadata > Embedded > Hidden (defaults Embedded on query error — never silently lose the cursor). Applied on both the plain and remote-desktop portal paths. - build_cursor_meta_param() adds a SPA_PARAM_Meta pod requesting SPA_META_Cursor (bitmap up to 256x256) to the connect params on every path. - CursorState parses spa_meta_cursor (id 0 = hidden; position - hotspot; bitmap re-read only when bitmap_offset != 0), normalizing RGBA/BGRA/ARGB/ABGR. Updated in .process before the corrupted/size-0 skip so cursor-only Mutter buffers still track movement. - CapturedFrame gains cursor: Option<CursorOverlay> (Arc rgba + serial) riding the GPU (Dmabuf/Cuda) payloads; the CPU de-pad path composites inline. GPU composite into each zero-copy backend's owned surface: - Vulkan Video + PyroWave: folded into the shared rgb2yuv.comp CSC shader — cursor sampled and alpha-mixed over RGB before the YUV convert (correct chroma, no extra pass). binding 3 (combined image sampler) + 16B push constant, per-slot cursor image uploaded only on serial change. spv regenerated. - CUDA/NVENC: real on-GPU kernel (cursor_blend.cu -> cursor_blend.ptx, compute_75 Turing baseline, JIT-forward) with blend_argb/blend_yuv444/ blend_nv12 (BT.709 limited, matching the shader). Loaded via the hand-rolled libcuda fn-table; blended into the ring InputSurface after copy, degrading to no-cursor on any failure — never drops a frame. VAAPI (AMD/Intel fallback) deferred: Vulkan Video already covers those GPUs; blind libva struct-layout FFI shouldn't ship unverified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -114,6 +114,26 @@ pub(crate) fn gpu_encode() -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// A mouse-cursor overlay to composite onto a frame at encode time (cursor-as-metadata). Rides on
|
||||
/// [`CapturedFrame::cursor`] for the GPU zero-copy payloads (Cuda/Dmabuf), whose pixels never touch
|
||||
/// the CPU — the encoder blends this small bitmap into its owned surface (Vulkan CSC image / CUDA
|
||||
/// devbuf / VA surface). The CPU de-pad path composites the cursor inline instead, so it leaves
|
||||
/// this `None`. `rgba` is `Arc` so attaching the (unchanged) bitmap to every frame is a refcount
|
||||
/// bump, not a copy; `serial` bumps only when the bitmap image changes, so the encoder re-uploads
|
||||
/// its small GPU texture on change and just moves a push-constant otherwise.
|
||||
#[derive(Clone)]
|
||||
pub struct CursorOverlay {
|
||||
/// Top-left in frame pixels where the bitmap is drawn (already = reported position − hotspot).
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: u32,
|
||||
pub h: u32,
|
||||
/// Straight-alpha RGBA pixels, `w*h*4` (bytes R,G,B,A).
|
||||
pub rgba: std::sync::Arc<Vec<u8>>,
|
||||
/// Bumps whenever `rgba`/`w`/`h` change; stable across position-only moves.
|
||||
pub serial: u64,
|
||||
}
|
||||
|
||||
/// A captured frame. [`format`](Self::format)/dimensions describe the pixels regardless of
|
||||
/// where they live — [`payload`](Self::payload) is either a CPU buffer (the spike/fallback path)
|
||||
/// or a GPU buffer already on the device (the zero-copy path, plan §9).
|
||||
@@ -124,6 +144,10 @@ pub struct CapturedFrame {
|
||||
/// Pixel layout of the payload.
|
||||
pub format: PixelFormat,
|
||||
pub payload: FramePayload,
|
||||
/// Cursor overlay to blend at encode time (GPU zero-copy payloads only); `None` when there's no
|
||||
/// visible cursor or the pixels were already composited on the CPU de-pad path. See
|
||||
/// [`CursorOverlay`].
|
||||
pub cursor: Option<CursorOverlay>,
|
||||
}
|
||||
|
||||
/// A captured frame still living in a single-plane packed-RGB dmabuf (the VAAPI zero-copy path).
|
||||
@@ -289,6 +313,7 @@ impl Capturer for SyntheticCapturer {
|
||||
pts_ns,
|
||||
format: PixelFormat::Bgrx,
|
||||
payload: FramePayload::Cpu(self.buf.clone()),
|
||||
cursor: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -356,6 +381,7 @@ impl Capturer for FastSyntheticCapturer {
|
||||
pts_ns: 0,
|
||||
format: PixelFormat::Bgrx,
|
||||
payload: FramePayload::Cpu(self.buf.clone()),
|
||||
cursor: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user