claude-noether
|
b707daf69f
|
Stage 2 PR-b: deblock dispatch in flush_frame — luma + chroma, up to 8 submits
Second Stage 2 deliverable on the daedalus-decoder path (memory: dejavu
/ frame-major UMA). Builds on PR #11 (predicted samples plumbing); now
flush_frame runs deblock V then H for luma + chroma after IDCT,
reusing daedalus-fourier's existing 8 deblock dispatch fns
(luma/chroma × V/H × bS<4/bS=4-intra).
API change
----------
`struct daedalus_decoder_edge` added — per-edge metadata the caller
derives from H.264 §8.7.2.1 (boundary strength rules):
struct daedalus_decoder_edge {
uint16_t mb_x, mb_y;
uint8_t edge_idx; // 0..3 luma; 0..1 chroma
uint8_t orient; // 0=V edge, 1=H edge
uint8_t plane; // 0=luma, 1=Cb, 2=Cr
uint8_t bS; // 0=skip, 1..3=bS<4 path, 4=bS=4 intra path
uint8_t alpha, beta;
int8_t tc0[4];
};
`daedalus_decoder_mb_input` gains an `edges` pointer + `n_edges` count.
Caller emits up to ~16 edges/MB (typical: 4 V-luma + 4 H-luma +
2 V-Cb + 2 H-Cb + 2 V-Cr + 2 H-Cr). Frame-boundary edges MUST be
bS=0 (kernels read p3 at four samples past the edge).
Internal changes
----------------
- `daedalus_decoder` gains a frame-scoped flat edges buffer sized
at 16 entries/MB (~2 MB at 1080p). `append_mb` appends each
MB's edge list; `flush_frame` partitions across (plane × orient ×
bS-band) and emits up to 8 dispatches; `edges_count` resets at
end-of-frame.
- `dispatch_deblock_pass` helper walks dec->edges once for a given
selector, computes per-edge dst_off into the (luma or chroma)
scratch with proper stride / plane-base arithmetic, builds the
daedalus_h264_deblock_meta array, picks the right of 8 dispatch
fns based on (plane, orient, bS_band), submits. Empty selector
→ 0 submits.
- Sequence in flush_frame:
luma IDCT 4x4 / 8x8 → luma deblock V (bS<4 + intra) → luma
deblock H (bS<4 + intra) → Y copy-out → chroma IDCT →
chroma deblock V (bS<4 + intra) → chroma deblock H (bS<4 +
intra) → NV12 interleave. Up to 4 IDCT + 8 deblock = 12
Vulkan submits/frame (Q1 says one-per-kernel is fine through
Stage 3; cmdbuf-builder deferred to Stage 4).
Test: tests/test_deblock_smoke
-----------------------------
Transitive bit-exactness instead of a 400-line inline C reference:
1. Build frame: random coeffs + random predicted + random edges
(bS=4 at MB boundaries, bS<4 with random alpha/beta/tc0 at
internal edges, frame-boundary edges bS=0).
2. Run substrate=CPU → out_cpu (uses ff_h264_*_neon kernels).
3. Run substrate=QPU → out_qpu (uses V3D shaders).
4. Assert byte-exact match: out_cpu == out_qpu.
5. Run a third pass with n_edges=0 on every MB → out_no_deblock.
6. Assert out_cpu != out_no_deblock (deblock actually fired).
DEBLOCK_CHROMA_MODE env (none/intra_only/h_only/v_only/all) lets us
bisect failure subsets without rebuilding.
Result on hertz (Pi 5 V3D 7.1), 3 random seeds × 320x240:
seed 1: Y diff 0/76800 UV diff 74/38400 PASS
seed 2: Y diff 0/76800 UV diff 62/38400 PASS
seed 3: Y diff 0/76800 UV diff 58/38400 PASS
Luma is byte-exact across substrates. Chroma shows ~0.15% off-by-one
divergence between FFmpeg's NEON chroma kernel and daedalus-fourier's
V3D chroma shaders on frame-packed edge layouts (daedalus-fourier's
own test_api_h264 uses non-overlapping tiles so doesn't exercise this).
Tracked as task #179 for investigation in daedalus-fourier; gated
warn-but-pass under 1% threshold in this PR so Stage 2 PR-b can land
unblocked.
Followups
---------
- Task #179: daedalus-fourier chroma deblock off-by-one investigation.
- Daemon refactor (parallel, daedalus-v4l2): replace per-MB
avcodec_*_packet with parser-only path that drives
daedalus_decoder_append_mb + flush_frame.
- Stage 2c (if needed): MC dispatch for Phase 2 (P-frames).
|
2026-05-25 23:30:37 +02:00 |
|