Skip to content

60fps HDMI pipeline: zero-copy VPU encode + RGA hardware CSC + kmssink double-vsync fix#29

Open
NickEngmann wants to merge 5 commits into
mainfrom
fix/display-fps
Open

60fps HDMI pipeline: zero-copy VPU encode + RGA hardware CSC + kmssink double-vsync fix#29
NickEngmann wants to merge 5 commits into
mainfrom
fix/display-fps

Conversation

@NickEngmann

Copy link
Copy Markdown
Contributor

Problem

The HDMI passthrough pipeline capped at 27-30fps at every resolution (dipping to 23 during shows) despite the VPU being rated for 4K60, making media playback visibly janky.

Root causes and fixes

Four stacked bottlenecks, found and fixed in order:

Encoder side (companion branch: garagehq/ustreamerfix/fps-early-buffer-release, commit 32ab470)

  1. Deferred V4L2 buffer release starved capture to half rate. Encode workers held their capture buffer until the dispatcher next picked them (~100ms), permanently pinning 4 of 5 V4L2 buffers — captured_fps dropped 60→30 the moment a client attached. Buffers are now released inside the worker right after encode.
  2. The CPU read every pixel of uncached V4L2 mmap memory per frame (~90ms per 1080p NV24 frame vs ~5ms of actual VPU encode). Replaced with hardware paths: NV12 sources import the V4L2 DMABUF directly into MPP (true zero-copy); NV24 (Roku 4:4:4) converts via two RGA passes using an RGBA plane-reinterpretation trick (~2.4ms); BGR24 (Google TV) via RGA imcvtcolor. Any hardware-path failure falls back to the legacy CPU path permanently with one log line. Pixel-modifying features (blocking composite, notification overlay) still route through the CPU path while active — all overlays verified working.

Display side (this branch)

  1. videobalance omitted when color settings are neutral — CPU per-pixel color correction caps 4K at ~56fps and breaks the DMABuf zero-copy path into kmssink. The pipeline now rebuilds with/without the element when settings cross neutral.
  2. kmssink double-vsync — with the TV attached, display pinned at exactly 30.00fps on a genuine 60Hz mode. Measured proof: the rockchip BSP's legacy drmModeSetPlane ioctl blocks until the flip latches at vblank (16.67ms/call), and kmssink then waits for a second vblank internally → 2 vblanks/frame. Fixed with skip-vsync=true (upstream property intended exactly for this); frame pacing remains vsync-locked by the blocking SetPlane, so no tearing.

Results (measured live)

Metric Before After
Stream served (1080p60 NV24 source) 29.4 fps 60.0 fps
Display output (TV attached, 4K60 mode) 30.00 fps 59.99 fps
Grab-to-expose latency 110 ms 17 ms
4K encode headroom (EXT_DMA, 4 workers, 10s soak) 66.7 fps aggregate

4K resilience validated with real 4K NV12 content in CMA dma-heap buffers (same memory class as V4L2 capture buffers); display decode benchmarks 120fps at 4K.

Preserved features (verified live)

Blocking overlay (header/vocab/preview/stats/OCR snippet), notification overlay, /snapshot + /snapshot/raw, OCR + VLM detection, autonomous mode — all confirmed working; stream returns to 60fps after a block ends.

Follow-up documented in CLAUDE.md

Color settings are currently neutral (fastest path). The VOP2 plane interprets the video as BT.601/Limited matching the data the RGA path preserves, so neutral should look correct; the old sat=1.4/bri=0.2 likely compensated for the pre-RGA CPU conversion. A COLOR_RANGE lead is documented if color needs revisiting.

🤖 Generated with Claude Code

Cyril Engmann added 5 commits July 2, 2026 18:23
…o-copy display path); document 4K resilience validation (EXT_DMA 66.7fps aggregate, decode 120fps, videobalance 4K ceiling 56fps, COLOR_RANGE lead)
…y blocks until vblank, kmssink's own vsync wait made every frame take 2 vblanks (display pinned at 30fps on 60Hz mode); verified 30.00 -> 59.99 fps live at 4K60
…s categories when tree exceeds MINUS_SCREENSHOTS_MAX_GB (10) or free disk drops below MINUS_SCREENSHOTS_MIN_FREE_GB (4); unlimited-count training collection was filling the 59GB root (~1.5GB/day, 13GB accumulated, 84% full)
…onomousMode: escalate Roku-home OCR fallback past the audio veto after 6 consecutive vetoed matches — home promo audio held a stuck-on-home loop for 50 min when the digits-only regex silenced the ECP recovery
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant