Sibling of cycle 9's v3d_h264_qpel_mc20.comp. Same 6-tap H.264 luma
half-pel filter, transposed to vertical orientation: filter reads
rows [-2..+3] of source per output pixel instead of cols.
Shader is ~58 lines (vs mc20's 86) — same WG geometry (64 lanes /
1 block per WG / 1 lane per output pixel). The address arithmetic
flips: row_base = src_off + r*stride + c (mc20) → col_base =
src_off + c, then col_base + (r±N)*stride (mc02).
dispatch_h264_qpel_mc02_qpu mirrors the mc20 QPU dispatch; src_max
calculation differs since the V kernel reads rows -2..+10 of source
(13 rows × stride wide) vs mc20's cols -2..+10 (8 rows × stride+11).
For 8x8 blocks: src_max = src_off + 10*stride + 8.
Recipe table flips DAEDALUS_KERNEL_H264_QPEL_MC02 from CPU to QPU.
Verified on hertz:
$ ./build/test_api_h264 | grep qpel
H.264 qpel mc20: 1024/1024 bytes bit-exact (100.0000%)
H.264 qpel mc02: 2048/2048 bytes bit-exact (100.0000%)
QPU coverage for the 30 qpel positions:
put_ mc20 ✓ (cycle 9) mc02 ✓ (this PR)
all 13 other put_ CPU NEON
avg_ all 15 positions CPU NEON
Next-priority candidates by per-frame impact (per PR #24 bench):
mc22 (2D half-pel) — 71.5 ns/block NEON × 32 640 blocks worst
case = 2.33 ms/frame at 1080p. Most-used
qpel position in real H.264 streams.
mc11/mc13/mc31/mc33 — corner ¼-pel positions, structurally similar
to mc20 + mc02 with L2 averaging.
The cascaded H+V structure of mc22 means it can either share the
existing mc20 + mc02 shaders' L2 (compute mc20 into tmp, then mc02
on tmp) or get a dedicated 2-stage pipeline. Follow-up.