Files
daedalus-fourier/src/v3d_h264_qpel_mc10.comp
T
claude-noether e3c28495ae h264: V3D shaders for the 4 single-axis quarter-pel qpel variants
mc10 (¼-H), mc30 (¾-H), mc01 (¼-V), mc03 (¾-V).  Each is the
corresponding half-pel filter (mc20 or mc02) with one extra L2
rounded-average step against an integer-source pixel at the tail:

  mc10[r,c] = avg(clip255(mc20(s)), s[r,   c   ])
  mc30[r,c] = avg(clip255(mc20(s)), s[r,   c+1])
  mc01[r,c] = avg(clip255(mc02(s)), s[r,   c  ])
  mc03[r,c] = avg(clip255(mc02(s)), s[r+1, c  ])

Each shader is ~45 lines (mc20-/mc02-pattern + 1 L2 line).

CMake foreach loop generates the 4 SPV compile rules.  Dispatch
helper `dispatch_h264_qpel_axis_qpu` shares plumbing across all 4
(axis flag selects src_max bounds: H reads cols -2..+10, V reads
rows -2..+10).  DEFINE_QPEL_AXIS_QPU + DEFINE_QPEL_DISPATCH_QPU
macros collapse ~200 LOC of boilerplate.

Recipe table flips DAEDALUS_KERNEL_H264_QPEL_MC{10,30,01,03} from
CPU to QPU.

Verified on hertz:

  $ ./build/test_api_h264 | grep "qpel mc[01230]"
    H.264 qpel mc10: 2048/2048 bytes bit-exact (100.0000%)
    H.264 qpel mc30: 2048/2048 bytes bit-exact (100.0000%)
    H.264 qpel mc01: 2048/2048 bytes bit-exact (100.0000%)
    H.264 qpel mc03: 2048/2048 bytes bit-exact (100.0000%)
    (+ mc20/mc02/mc22 anchors from previous PRs)

Qpel QPU coverage:

  put_  mc20 ✓  mc02 ✓  mc22 ✓                                  (3 anchors)
        mc10 ✓  mc30 ✓  mc01 ✓  mc03 ✓                          (4 quarter-axis, THIS PR)
        mc11/12/13/21/23/31/32/33 — CPU NEON                    (8 diagonals)
  avg_  all 15 positions — CPU NEON

7 of 15 useful put_ positions now on QPU.  The 8 diagonals each
compose two half-pel results via L2; can land via dedicated kernels
or by chaining existing anchor dispatches (the latter would need
the L2 step as a fourth dispatch — probably cheaper to write
dedicated 8x diagonal shaders).
2026-05-25 19:04:26 +02:00

48 lines
1.7 KiB
Plaintext

// daedalus-fourier — H.264 luma qpel mc10 (8x8, ¼-pel horizontal),
// V3D 7.1. Per H.264 §8.4.2.2.1 "a" position:
//
// dst[r,c] = ((clip255(mc20(s)[r,c]) + s[r,c] + 1) >> 1)
//
// = horizontal half-pel filter, clipped to u8, then L2 rounded-averaged
// with the integer source pixel at the SAME position. Sibling of
// v3d_h264_qpel_mc20.comp with the L2 step added at the tail.
//
// License: BSD-2-Clause.
#version 450
#extension GL_EXT_shader_8bit_storage : require
#extension GL_EXT_shader_explicit_arithmetic_types : require
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0) readonly buffer Src { uint8_t src[]; } u_src;
layout(binding = 1) buffer Dst { uint8_t dst[]; } u_dst;
layout(binding = 2) readonly buffer Meta { uvec4 meta[]; } u_meta;
layout(push_constant) uniform PC { uint n_blocks, stride_u8, _p0, _p1; } pc;
void main()
{
uint block_idx = gl_WorkGroupID.x;
if (block_idx >= pc.n_blocks) return;
uint lane = gl_LocalInvocationID.x;
uint r = lane >> 3, c = lane & 7u;
uint dst_off = u_meta.meta[block_idx].x;
uint src_off = u_meta.meta[block_idx].y;
uint stride = pc.stride_u8;
uint row_base = src_off + r * stride + c;
int s_m2 = int(u_src.src[row_base - 2u]);
int s_m1 = int(u_src.src[row_base - 1u]);
int s_0 = int(u_src.src[row_base ]);
int s_p1 = int(u_src.src[row_base + 1u]);
int s_p2 = int(u_src.src[row_base + 2u]);
int s_p3 = int(u_src.src[row_base + 3u]);
int v = s_m2 - 5 * s_m1 + 20 * s_0 + 20 * s_p1 - 5 * s_p2 + s_p3 + 16;
int hp = clamp(v >> 5, 0, 255);
// L2 average with the integer source at the SAME (r, c) position.
int avg = (hp + s_0 + 1) >> 1;
u_dst.dst[dst_off + r * stride + c] = uint8_t(avg);
}