Commit Graph
5 Commits
Author SHA1 Message Date
enricobuehlerandClaude Fable 5 e92a0aaa00 fix(zerocopy/vkslot): a timed-out blend can't corrupt its slot, and NV12 cursor chroma sits on the grid
Two [GPU]-class defects from design/pf-zerocopy-sweep-handoff.md; the code
is compile- and unit-verified, the visual halves still owe on-glass time.

- blend_ref reused a fence and command buffer after a wait timeout: on
  TIMEOUT it reset a fence that still had a pending signal and re-recorded
  a command buffer the GPU might still be executing — reached exactly in
  the contended case the CPU-synced path exists for (no timeline export +
  visible cursor + a >1 s GPU stall). A failed wait now drains the device
  before the reset (the VkBridge::import_linear precedent), and free_slots
  quiesces unconditionally instead of only when a timeline exists, so
  teardown after a timed-out sync blend no longer frees objects an
  outstanding submission references. (C1)

- cursor_blend.comp anchored NV12 chroma blocks to the cursor's oy, not
  the surface chroma grid: for odd oy every UV sample averaged luma rows
  one below the rows it covers (a one-row colour fringe on the cursor's
  edges, ~half of all cursor positions) and the cursor's last row's chroma
  was never written. Block rows now anchor to the chroma grid the same way
  spans anchor to the word grid — y0 = floor(oy/2)*2, per-row cy guards
  for the straddle block — and blend_geometry counts blocks from the same
  anchor (one extra block when oy is odd; covered by new tests, including
  negative oy). cursor_blend.spv rebuilt (glslangValidator, spirv-val
  clean, drift gate passes). (C6)

Owed on-glass: a stalled-GPU cursor session for C1; an odd-oy cursor
colour check for C6 (subtle fringe on the cursor's top/bottom edge).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 13:01:46 +02:00
enricobuehlerandClaude Fable 5 fa083f50d3 fix(zerocopy): unsafe fn bodies join the unsafe-proof program
#![deny(clippy::undocumented_unsafe_blocks)] never inspected the body of an
unsafe fn — operations there are not "unsafe blocks" — so roughly 20
functions' worth of raw GL/CUDA/Vulkan driver calls sat outside the
invariant the crate advertises. Concretely, that blind spot is why the
constructor-leak and teardown-ordering shapes fixed earlier in this series
could ship without ever prompting a reviewer.

#![deny(unsafe_op_in_unsafe_fn)] now closes the gap: every unsafe fn body
is an explicit unsafe block carrying a SAFETY comment that names the
caller contract it relies on (the dlopen'd CUDA wrapper table gets a
uniform forward-to-live-table proof). Mechanical; no behavior change.

(V2 from design/pf-zerocopy-sweep-handoff.md.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 12:59:15 +02:00
enricobuehlerandClaude Fable 5 143a707f76 fix(zerocopy): the mechanical sweep batch — truthful fence waits, forgiving env flags, no leaked planes
From the pf-zerocopy review sweep (design/pf-zerocopy-sweep-handoff.md), the
compile-verifiable batch:

- dmabuf_fence: the blocking poll's result was discarded — EINTR silently
  skipped the wait (reopening the stale-frame race a SIGCHLD away) and a
  timeout reported as waited. Now EINTR retries with the remaining budget
  and the caller gets Signaled/TimedOut/NoFence, so the one diagnostic
  operators have about implicit fencing stops lying. (C2)
- env flags: PUNKTFUNK_ZEROCOPY=TRUE meant *off* — values are case-folded
  now, and an unrecognised spelling falls back to the flag's default with a
  one-shot warning instead of silently inverting the operator's intent. (C3)
- cuda: alloc_pitched_nv12 leaked the Y plane when the UV allocation failed
  (per-frame under VRAM pressure — the worst possible time to leak); a failed
  async-copy enqueue now drains the stream before returning, so a recycled
  pool buffer can't race an orphaned in-flight copy. (C4, C5)
- worker: --fd is validated (>= 3, fstat + S_ISSOCK) before OwnedFd adoption
  — 'zerocopy-worker --fd -1' was constructing OwnedFd's niche value. (V1)
- docs: all 15 rustdoc warnings fixed, including the link to the renamed
  note_raw_dmabuf_negotiation_failed. (D1)
- CI: a SPIR-V drift gate — the committed .spv blobs are include_bytes!'d and
  rebuilt by hand; the gate diffs disassembly (filtering only the
  shaderc/glslang generator difference) so a forgotten rebuild fails CI
  instead of shipping the old kernel. Both blobs verified in sync. (S1)
- tests: bt709_limited pinned to external BT.709 anchors (it is the sole
  oracle for the GPU colour self-test); blend_geometry gets its first tests —
  empty rect, CURSOR_MAX clamp, per-format group counts, and negative-ox
  floor alignment. (T2, T3)

Verified on .25: clippy -D warnings clean, 27/27 tests, cargo doc 0 warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 12:40:37 +02:00
enricobuehlerandClaude Fable 5 dff63b2a29 fix(encode/nvenc): a visible cursor no longer serializes submit — the blend goes stream-ordered
windows-host / winget-source (push) Canceled after 0s
windows-host / package (push) Canceled after 2m32s
android / android (push) Canceled after 39s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 34s
ci / rust (push) Canceled after 24s
ci / rust-arm64 (push) Canceled after 24s
ci / bench (push) Canceled after 5s
ci / web (push) Canceled after 24s
ci / docs-site (push) Canceled after 8s
deb / build-publish-client-arm64 (push) Canceled after 0s
deb / build-publish (push) Canceled after 2s
deb / build-publish-host (push) Canceled after 1s
decky / build-publish (push) Canceled after 4s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 5s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 5s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 2s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 2s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 2s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 1s
Report: iPad on a gamescope/NVIDIA 120 fps session capped at ~80 fps with
repeat_fps 0, zero loss, capture 0 µs, ASIC 15 µs — and submit p50 at
10.2 ms, ~81 % of the loop period. Under gamescope the host composites
the live pointer into EVERY frame, and a cursor-bearing frame forced the
CPU-synced submit path: a blocking CUDA copy plus a fence-waited Vulkan
blend, both exposed to the running game's GPU load. The "games hide the
cursor" assumption the gate relied on does not hold under gamescope.

The blend is now stream-ordered end to end. VkSlotBlend exports a
timeline semaphore (VK_KHR_timeline_semaphore + external_semaphore_fd)
into CUDA (cuImportExternalSemaphore, new dlopen entries): the enqueued
copy signals it on the encode thread's copy stream, the blend submission
waits for and advances it on the Vulkan queue, and a CUDA-side wait
orders the encode after the blend on the session's bound IO stream — no
CPU sync anywhere, so cursor frames keep the stream-ordered fast path.
Each ring slot gets its own command buffer + descriptor set (written
once) so several ordered blends can be in flight; cursor-bitmap uploads
and teardown quiesce through the timeline. Drivers without the timeline
export keep the previous CPU-synced blend, and any bring-up or per-frame
failure still degrades to "no cursor", never a dropped frame.

Also: the blocking multi-plane copies (the escalated/pipelined mode and
the non-stream-ordered fallback) now enqueue every plane and pay ONE
stream sync instead of one per plane (NV12 2→1, YUV444 3→1) — each
exposed wait costs scheduling latency under GPU contention, which is
what makes the escalation's blocking copies self-reinforcing.

Verified on the RTX 5070 Ti box (driver 610.43.03): all 12 nvenc_cuda
on-hardware smokes green, including the new
nvenc_cuda_cursor_blend_stream_ordered (6 cursor AUs, all ordered,
across a bitmap-serial flip); host suite 301/301; clippy --all-targets
-D warnings clean; struct layouts of the hand-flattened cuda.h params
asserted in tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 20:05:27 +02:00
enricobuehlerandClaude Fable 5 d2c46eaf3c feat(encode/nvenc): SPIR-V cursor blend over Vulkan-allocated input slots — retire the PTX kernels
A vendored PTX blob is JIT'd against the driver's ISA ceiling, so the
cursor-blend module silently dies on drivers older than the generating
toolkit (CUDA_ERROR_UNSUPPORTED_PTX_VERSION/INVALID_PTX, 222/218 — the
KWin leg's invisible composite cursor on driver 595/CUDA 13.2 vs a
CUDA 13.3 blob). SPIR-V has no such coupling, and the in-tree precedent
already exists twice (vulkan_video's CSC blend, VkBridge's exportable
OPAQUE_FD → cuImportExternalMemory bridge).

New pf_zerocopy::vkslot::VkSlotBlend: the direct-SDK NVENC encoder now
allocates its input ring as exportable Vulkan buffers CUDA-imports (same
contiguous InputSurface layouts, pitch = row bytes rounded to 256), and
the cursor composite is a compute dispatch over the cursor's rectangle
(cursor_blend.comp, vendored .spv; spec-constant selects ARGB/NV12/YUV444;
BT.709 limited, matching the retired .cu). The surface SSBO is uint[] with
every invocation owning whole words — no 8-bit-storage device dependency.
Cursor-bearing frames force the existing CPU-synced submit path so the
CUDA copy → Vulkan dispatch (fence-waited) → NVENC encode ordering is
CPU-established; cursorless frames keep the stream-ordered fast path
untouched. Any bring-up/alloc/registration failure falls back wholesale
to plain pitched CUDA surfaces (never a mixed or short ring): sessions
always encode, composite mode just loses the cursor, warned once.

cursor_blend.cu / cursor_blend.ptx and the CursorBlend PTX loader are
deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 23:47:53 +02:00