Commit Graph
6 Commits
Author SHA1 Message Date
enricobuehlerandClaude Opus 5 bf9fb3fb22 fix(pf-encode): enable VK_EXT_queue_family_foreign for the dmabuf acquires (Phase 8)
Both Linux Vulkan encode backends named QUEUE_FAMILY_FOREIGN_EXT as the
acquire barriers' src family without ever enabling the extension —
spec-invalid on every device, tolerated by RADV. The audit filed
vulkan_video's three sites; pyrowave's fresh-import acquire had the
identical defect on its own device (critic catch).

Enable when advertised (a fresh open-time enumerate — the rgb probe's
is a probe-local and skipped entirely on native-NV12, so there was
nothing to reuse; pf-presenter/dmabuf.rs is the in-repo precedent that
already enables this extension). Not advertised → the core-1.1
QUEUE_FAMILY_EXTERNAL conservative substitute, chosen once at open and
warn-logged (no fleet hardware takes that arm; such devices were never
valid targets before). All four sites are acquire-only (src=FOREIGN,
EXCLUSIVE images, oldLayout=UNDEFINED) — the swap is index-only.

On-glass: 780M under validation layers — vulkan smokes + pyrowave
smokes green, FOREIGN advertised and enabled, no fallback engaged.
Shared ext_advertised helper in vk_util (cfg = the union of both
consumers) with a unit test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:08:44 +02:00
enricobuehlerandClaude Fable 5 47a23bec12 fix(encode/vulkan): unwind every open/import leak, and serve 24-bpp CPU instead of dying on it
Phase 5's Linux half (audit WP5.1 + WP5.4), each item shaped by the
review that rejected the obvious fix:

Dmabuf import unwind (vk_util): every failure after create_image leaked
the VkImage, and the dup'd dmabuf fd leaked as a raw i32. The sharp edge
is that a SUCCESSFUL vkAllocateMemory transfers fd ownership to Vulkan
(vkFreeMemory closes it), so the naive close-on-error is a double close
that clobbers whatever unrelated descriptor recycled the number. The dup
now lives in an OwnedFd released exactly in the allocate-success arm;
every other path drops it once, and bind/view failures free image+memory.

PyroWave open unwind: open_inner had ~20 fallible steps that each leaked
everything before them (instance, device, pyrowave objects, the whole
CSC pipeline). Rather than a parallel teardown guard — whose reviewed
hazards were a null-unsafe pyrowave_encoder_destroy and a drifting
duplicate of Drop — Self is now constructed right after create_device
with every later resource null, and the existing Drop (wait-idle first,
pw_enc null-guarded, delete-nullptr and VK_NULL_HANDLE destroys are
no-ops) is the single unwind path for error and normal teardown alike.
The ensure_cpu_rgb staging twins (create/allocate/bind, both backends)
and the RGB-direct make_view pair get the same discipline via a shared
make_host_buffer. Observed on hardware: 32 forced import failures, zero
fd drift (the new import_failure_leaks_no_fds smoke on RADV).

24-bpp CPU service (WP5.4): pixel_to_vk had no mapping for the packed
Rgb/Bgr the PipeWire portal negotiates, so a session committed to a path
the backend could not serve and died at its first frame. The filed
open-gate was rejected as a half-mirror — the dmabuf axis is keyed by
fourcc at submit, unknowable at open — so instead the CPU axis is
SERVED: a 3-to-4 expand at the staging upload (normalize_cpu_rgb, the
CPU twin of WP1.4's swscale expand), order-preserving for the CSC
samplers and BGRA-forced for the RGB-direct encode source, whose session
pictureFormat is B8G8R8A8 — the on-glass run caught R-first sources
violating VUID-vkCmdEncodeVideoKHR-pEncodeInfo-08207, a mismatch that
predates this change for plain Rgbx CPU sources. The dmabuf axis feeds
pf-zerocopy's raw-dmabuf degrade latch (3efbe416) from both Vulkan
import caches — deterministic refusals flip capture to CPU delivery,
which now serves every format capture produces; transient OOM and
native-NV12 sessions are excluded.

RECORDING-state hygiene: the fallible recording prefix (cursor prep,
import, staging) now resets the command buffer on error instead of
leaving it RECORDING for the next begin to trip
VUID-vkBeginCommandBuffer-commandBuffer-00049; PyroWave's submit-level
blanket reset is replaced by the same scoped shape, closing its
fence-timeout hole where a reset hit a PENDING buffer
(VUID-vkResetCommandBuffer-commandBuffer-00045).

Verified: docker linux/amd64 4-leg gate green; RADV 780M on-glass under
the validation layers at 1920x1080 — all 10 smokes pass (including the
new 24-bpp CSC + RGB-direct legs and both channel orders), decoded
colour truth checked with ffmpeg, and the only remaining validation
messages are the two documented VALVE-extension layer gaps plus a
pre-existing pyrowave shader/feature mismatch now filed in the planning
doc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 16:51:27 +02:00
enricobuehlerandClaude Fable 5 1d4795666e feat(linux/vulkan-encode): opt-in gamescope producer-native NV12 encode source
With PUNKTFUNK_PIPEWIRE_NV12=1 (bring-up gate), the PipeWire negotiation
offers an NV12 LINEAR DMA-BUF pod (BT.709 limited pinned MANDATORY) ahead
of the BGRx one, and gamescope's producer-side RGB->YUV pass replaces the
host CSC entirely: the encoder imports the two-plane buffer as a profiled
VIDEO_ENCODE_SRC image and the VCN encodes it directly. Contributed
measurements: encode p99 2.9 ms (from ~4-4.5 ms via EFC RGB-direct),
60 fps capture, 0 send drops.

Hardening on top of the contributed patch:
- unaligned modes (1080p!) stage through a padded aligned NV12 copy (edge
  rows/columns duplicated, transfer-only) instead of direct-importing the
  visible-size buffer -- a direct import would make the VCN read past the
  producer allocation, the exact OOB class behind the 2026-07-20 field GPU
  reset; the encode extents return to the aligned coded extent everywhere
- the UV plane layout honors the producer's plane-1 chunk (offset/stride)
  when the SPA buffer carries one (same-BO verified by inode), with the
  contiguous-plane contract as fallback
- PyroWave sessions are excluded from the gate (their Vulkan compute CSC
  ingests packed RGB), and a native-NV12 session that resolves to libav
  VAAPI (H264 codec, PUNKTFUNK_VULKAN_ENCODE=0, feature off, or a failed
  Vulkan open) refuses at open instead of streaming garbage chroma
- pad staging images carry TRANSFER_SRC (the width-padding pass self-copies
  the staging image -- previously missing on 1366-wide modes)
- metadata-cursor one-shot warn (parity with RGB-direct) and a padded-NV12
  PUNKTFUNK_PERF split label; the padded RGB copy gets its timestamps too

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 11:57:42 +02:00
256244430b feat(encode): Vulkan Video B1 — RGB-direct encode source via VCN EFC (PUNKTFUNK_VULKAN_RGB_DIRECT=1)
design/vulkan-rgb-direct-encode.md B1: when the probe passes AND
PUNKTFUNK_VULKAN_RGB_DIRECT=1 (default OFF until the B2 on-glass A/B),
the session opens with pictureFormat=B8G8R8A8 and the captured RGB frame
becomes the encode source — the VCN EFC front-end does the 709-narrow CSC
inline during encode. Deleted from the per-frame path: the compute CSC
dispatch, both NV12 plane copies, the semaphore hop and one queue submit
(dmabuf frames are ONE encode-queue submit; ~17 MB/frame of GPU traffic
gone). The DPB stays NV12; RFI/RC/quality-level machinery is untouched.

Shape:
- probe_rgb_direct now returns the chroma-siting bits to create the
  session with (midpoint preferred, else cosited-even per axis); the open
  verdict line gains "active" / "available(off; ...)" states.
- RgbProfileStack: the rgb-chained video profile rebuilt on the stack per
  profiled-image creation after open (profile identity is by value) —
  dmabuf imports become profiled VIDEO_ENCODE_SRC images (import cache
  unchanged), the CPU staging image likewise (concurrent encode+compute).
- record_submit_rgb: steps 2–4 twin (dmabuf: single submit; CPU: staging
  copy on the compute queue, semaphore-ordered); shared step-1/bookkeeping.
- begin_encode_cmd takes a SrcAcquire (CSC general / fresh-import FOREIGN
  QFOT / cached visibility-only / staging TRANSFER_DST) so both paths
  share the encode recording.
- RGB-direct frames skip the CSC per-slot resources entirely (make_frame
  split into csc/common halves); cursor bitmaps warn once (EFC cannot
  composite; gamescope — the flagship — embeds the cursor itself).

On-glass (780M RADV PHOENIX, host Mesa 26.0.4): all four smokes pass
(vulkan_smoke{,_av1,_rgb,_rgb_av1}); the EFC-encoded H265 stream decodes
clean and matches the CSC-encoded stream at 49.9 dB average PSNR (min
48.9) — within the design's ±1-code-value tolerance. (Raw-OBU ffmpeg
probing of the tiny AV1 dumps fails identically for BOTH paths —
pre-existing dump quirk, not a stream defect.) check+clippy+full unit
suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 20:05:56 +02:00
enricobuehlerandClaude Fable 5 28491acf2a fix(encode): unwind Vulkan Video open failure instead of leaking every prior object
VulkanVideoEncoder::open_inner creates ~20 Vulkan objects across ~15
fallible steps, but all cleanup lived in the encoder's Drop — which only
runs once the value exists at the final Ok(Self). Any earlier ?/bail!
leaked everything built so far (a VkDevice + GPU memory per retried
open, and this backend is the default encode path on AMD/Intel Linux
hosts where open can fail transiently).

Factor the entire teardown sequence — unchanged — into a VkTeardown
guard whose Drop destroys any prefix of the build (vkDestroy*/vkFree*
are defined no-ops on VK_NULL_HANDLE): open_inner mirrors each object
into the guard as it is created and disarms it only at Ok(Self); the
encoder's own Drop rebuilds one from its fields, so both paths share
one sequence and cannot drift. make_frame now builds in place into a
guard-parked null Frame so a mid-build failure unwinds its partial
handles too, and make_video_image / vk_util::make_plain_image (also
used by the PyroWave backend) / build_parameters_h265 no longer leak
their own partially-created objects on failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 22:15:44 +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