Merge pull request 'h264: Intra_4x4 luma prediction — 9-mode C reference + spec gates' (#12) from noether/h264-intra-pred-4x4 into main

Reviewed-on: #12
This commit was merged in pull request #12.
This commit is contained in:
2026-05-24 22:28:39 +00:00
3 changed files with 493 additions and 0 deletions
+9
View File
@@ -531,6 +531,15 @@ add_executable(test_api_opportunistic_qpu tests/test_api_opportunistic_qpu.c)
target_link_libraries(test_api_opportunistic_qpu PRIVATE daedalus_core) target_link_libraries(test_api_opportunistic_qpu PRIVATE daedalus_core)
target_compile_options(test_api_opportunistic_qpu PRIVATE -O2) target_compile_options(test_api_opportunistic_qpu PRIVATE -O2)
# H.264 Intra_4x4 luma prediction (9 modes) — reference + tests.
# Pure CPU + spec-derived; no daedalus_core dependency yet (this is
# the bit-exact gate for the eventual shader / dispatch wiring).
add_executable(test_intra_pred_4x4
tests/test_intra_pred_4x4.c
tests/h264_intra_pred_4x4_ref.c
)
target_compile_options(test_intra_pred_4x4 PRIVATE -O2)
add_executable(bench_pool_overhead tests/bench_pool_overhead.c) add_executable(bench_pool_overhead tests/bench_pool_overhead.c)
target_link_libraries(bench_pool_overhead PRIVATE daedalus_core) target_link_libraries(bench_pool_overhead PRIVATE daedalus_core)
target_compile_options(bench_pool_overhead PRIVATE -O2) target_compile_options(bench_pool_overhead PRIVATE -O2)
+238
View File
@@ -0,0 +1,238 @@
/*
* Standalone bit-exact C reference for H.264 luma Intra_4x4
* prediction modes (per H.264 spec §8.3.1.4). All 9 modes.
*
* Mode index → name (per H.264 Table 8-2):
* 0 = Vertical
* 1 = Horizontal
* 2 = DC
* 3 = Diagonal_Down_Left
* 4 = Diagonal_Down_Right
* 5 = Vertical_Right
* 6 = Horizontal_Down
* 7 = Vertical_Left
* 8 = Horizontal_Up
*
* Calling convention matches FFmpeg's h264pred:
* pred_4x4_<mode>(uint8_t *dst, ptrdiff_t stride)
*
* `dst` points at row 0, col 0 of the 4x4 output block. Neighbour
* pixels come from the already-decoded surrounding pixels in the same
* buffer:
* top-left = dst[-stride - 1]
* top[0..3] = dst[-stride + 0 .. -stride + 3]
* top-right = dst[-stride + 4 .. -stride + 7] (DDL / VL only)
* left[0..3] = dst[ 0*stride - 1 .. 3*stride - 1]
*
* AVAILABILITY: this reference assumes ALL neighbours are available
* (the "interior MB" case). The H.264 spec defines fallback behaviour
* for unavailable neighbours (e.g. DC averages only the available
* side, top-right substitution from top[3] for DDL/VL near the right
* frame edge); those branches are NOT modelled here. Tests must
* exercise the kernel with all 13 neighbour bytes valid. The eventual
* libavcodec intercept handles availability before calling.
*
* License: BSD-2-Clause for the reference + tests; the underlying
* algorithm is from H.264/ITU-T H.264 (2003) and AVC standards, free
* to implement.
*/
#include <stdint.h>
#include <stddef.h>
/* Helper: 3-tap weighted average ((a + 2*b + c + 2) >> 2). */
static inline uint8_t avg3(int a, int b, int c)
{
return (uint8_t)((a + 2*b + c + 2) >> 2);
}
/* Helper: 2-tap mean ((a + b + 1) >> 1). */
static inline uint8_t avg2(int a, int b)
{
return (uint8_t)((a + b + 1) >> 1);
}
/* Mode 0 — Vertical: each col = top[col]. */
void daedalus_h264_pred_4x4_vertical_ref(uint8_t *dst, ptrdiff_t stride)
{
const uint8_t *top = dst - stride;
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) dst[r * stride + c] = top[c];
}
}
/* Mode 1 — Horizontal: each row = left[row]. */
void daedalus_h264_pred_4x4_horizontal_ref(uint8_t *dst, ptrdiff_t stride)
{
for (int r = 0; r < 4; r++) {
uint8_t l = dst[r * stride - 1];
for (int c = 0; c < 4; c++) dst[r * stride + c] = l;
}
}
/* Mode 2 — DC: mean of top 4 + left 4, broadcast. */
void daedalus_h264_pred_4x4_dc_ref(uint8_t *dst, ptrdiff_t stride)
{
const uint8_t *top = dst - stride;
int sum = 4; /* rounding for ((sum + 4) >> 3) */
for (int i = 0; i < 4; i++) sum += top[i];
for (int i = 0; i < 4; i++) sum += dst[i * stride - 1];
uint8_t v = (uint8_t)(sum >> 3);
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++) dst[r * stride + c] = v;
}
/* Mode 3 — Diagonal_Down_Left. Uses top[0..7] (incl. top-right). */
void daedalus_h264_pred_4x4_ddl_ref(uint8_t *dst, ptrdiff_t stride)
{
const uint8_t *top = dst - stride;
int t0 = top[0], t1 = top[1], t2 = top[2], t3 = top[3];
int t4 = top[4], t5 = top[5], t6 = top[6], t7 = top[7];
/* zz[7] = top filtered with 3-tap; spec table 8-7. */
uint8_t zz[7];
zz[0] = avg3(t0, t1, t2);
zz[1] = avg3(t1, t2, t3);
zz[2] = avg3(t2, t3, t4);
zz[3] = avg3(t3, t4, t5);
zz[4] = avg3(t4, t5, t6);
zz[5] = avg3(t5, t6, t7);
zz[6] = avg3(t6, t7, t7); /* spec: t7 doubled at the boundary */
/* dst[r][c] = zz[c + r] */
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++) dst[r * stride + c] = zz[c + r];
}
/* Mode 4 — Diagonal_Down_Right. Uses top-left + top[0..3] + left[0..3]. */
void daedalus_h264_pred_4x4_ddr_ref(uint8_t *dst, ptrdiff_t stride)
{
int tl = dst[-stride - 1];
int t0 = dst[-stride + 0], t1 = dst[-stride + 1];
int t2 = dst[-stride + 2], t3 = dst[-stride + 3];
int l0 = dst[ 0*stride - 1], l1 = dst[ 1*stride - 1];
int l2 = dst[ 2*stride - 1], l3 = dst[ 3*stride - 1];
/* zz indexed by (col - row): -3..+3 */
uint8_t zz_m3 = avg3(l1, l2, l3);
uint8_t zz_m2 = avg3(l0, l1, l2);
uint8_t zz_m1 = avg3(tl, l0, l1);
uint8_t zz_p0 = avg3(l0, tl, t0);
uint8_t zz_p1 = avg3(tl, t0, t1);
uint8_t zz_p2 = avg3(t0, t1, t2);
uint8_t zz_p3 = avg3(t1, t2, t3);
uint8_t zz[7] = { zz_m3, zz_m2, zz_m1, zz_p0, zz_p1, zz_p2, zz_p3 };
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++) dst[r * stride + c] = zz[(c - r) + 3];
}
/* Mode 5 — Vertical_Right. */
void daedalus_h264_pred_4x4_vr_ref(uint8_t *dst, ptrdiff_t stride)
{
int tl = dst[-stride - 1];
int t0 = dst[-stride + 0], t1 = dst[-stride + 1];
int t2 = dst[-stride + 2], t3 = dst[-stride + 3];
int l0 = dst[ 0*stride - 1], l1 = dst[ 1*stride - 1];
int l2 = dst[ 2*stride - 1];
/* H.264 §8.3.1.4.6: two patterns based on (2c - r) parity. */
dst[0*stride + 0] = avg2(tl, t0);
dst[0*stride + 1] = avg2(t0, t1);
dst[0*stride + 2] = avg2(t1, t2);
dst[0*stride + 3] = avg2(t2, t3);
dst[1*stride + 0] = avg3(l0, tl, t0);
dst[1*stride + 1] = avg3(tl, t0, t1);
dst[1*stride + 2] = avg3(t0, t1, t2);
dst[1*stride + 3] = avg3(t1, t2, t3);
dst[2*stride + 0] = avg3(tl, l0, l1);
dst[2*stride + 1] = dst[0*stride + 0];
dst[2*stride + 2] = dst[0*stride + 1];
dst[2*stride + 3] = dst[0*stride + 2];
dst[3*stride + 0] = avg3(l0, l1, l2);
dst[3*stride + 1] = dst[1*stride + 0];
dst[3*stride + 2] = dst[1*stride + 1];
dst[3*stride + 3] = dst[1*stride + 2];
}
/* Mode 6 — Horizontal_Down. */
void daedalus_h264_pred_4x4_hd_ref(uint8_t *dst, ptrdiff_t stride)
{
int tl = dst[-stride - 1];
int t0 = dst[-stride + 0], t1 = dst[-stride + 1], t2 = dst[-stride + 2];
int l0 = dst[ 0*stride - 1], l1 = dst[ 1*stride - 1];
int l2 = dst[ 2*stride - 1], l3 = dst[ 3*stride - 1];
dst[0*stride + 0] = avg2(tl, l0);
dst[0*stride + 1] = avg3(l0, tl, t0);
dst[0*stride + 2] = avg3(tl, t0, t1);
dst[0*stride + 3] = avg3(t0, t1, t2);
dst[1*stride + 0] = avg2(l0, l1);
dst[1*stride + 1] = avg3(tl, l0, l1);
dst[1*stride + 2] = dst[0*stride + 0];
dst[1*stride + 3] = dst[0*stride + 1];
dst[2*stride + 0] = avg2(l1, l2);
dst[2*stride + 1] = avg3(l0, l1, l2);
dst[2*stride + 2] = dst[1*stride + 0];
dst[2*stride + 3] = dst[1*stride + 1];
dst[3*stride + 0] = avg2(l2, l3);
dst[3*stride + 1] = avg3(l1, l2, l3);
dst[3*stride + 2] = dst[2*stride + 0];
dst[3*stride + 3] = dst[2*stride + 1];
}
/* Mode 7 — Vertical_Left. Uses top[0..7]. */
void daedalus_h264_pred_4x4_vl_ref(uint8_t *dst, ptrdiff_t stride)
{
const uint8_t *top = dst - stride;
int t0=top[0], t1=top[1], t2=top[2], t3=top[3];
int t4=top[4], t5=top[5], t6=top[6], t7=top[7];
dst[0*stride + 0] = avg2(t0, t1);
dst[0*stride + 1] = avg2(t1, t2);
dst[0*stride + 2] = avg2(t2, t3);
dst[0*stride + 3] = avg2(t3, t4);
dst[1*stride + 0] = avg3(t0, t1, t2);
dst[1*stride + 1] = avg3(t1, t2, t3);
dst[1*stride + 2] = avg3(t2, t3, t4);
dst[1*stride + 3] = avg3(t3, t4, t5);
dst[2*stride + 0] = avg2(t1, t2);
dst[2*stride + 1] = avg2(t2, t3);
dst[2*stride + 2] = avg2(t3, t4);
dst[2*stride + 3] = avg2(t4, t5);
dst[3*stride + 0] = avg3(t1, t2, t3);
dst[3*stride + 1] = avg3(t2, t3, t4);
dst[3*stride + 2] = avg3(t3, t4, t5);
dst[3*stride + 3] = avg3(t4, t5, t6);
(void) t6; (void) t7; /* t6 used; t7 unused in 4x4 VL */
}
/* Mode 8 — Horizontal_Up. Uses left[0..3] only. */
void daedalus_h264_pred_4x4_hu_ref(uint8_t *dst, ptrdiff_t stride)
{
int l0 = dst[ 0*stride - 1], l1 = dst[ 1*stride - 1];
int l2 = dst[ 2*stride - 1], l3 = dst[ 3*stride - 1];
dst[0*stride + 0] = avg2(l0, l1);
dst[0*stride + 1] = avg3(l0, l1, l2);
dst[0*stride + 2] = avg2(l1, l2);
dst[0*stride + 3] = avg3(l1, l2, l3);
dst[1*stride + 0] = avg2(l1, l2);
dst[1*stride + 1] = avg3(l1, l2, l3);
dst[1*stride + 2] = avg2(l2, l3);
dst[1*stride + 3] = avg3(l2, l3, l3);
dst[2*stride + 0] = avg2(l2, l3);
dst[2*stride + 1] = avg3(l2, l3, l3);
dst[2*stride + 2] = l3;
dst[2*stride + 3] = l3;
dst[3*stride + 0] = l3;
dst[3*stride + 1] = l3;
dst[3*stride + 2] = l3;
dst[3*stride + 3] = l3;
}
+246
View File
@@ -0,0 +1,246 @@
/*
* Tests the 9 H.264 Intra_4x4 luma prediction modes against
* spec-derived expected patterns. Goal: catch any mistake in
* the reference (sign / shift / table mapping) before it lands
* downstream. Each mode is exercised with a deterministic
* neighbour context and checked against a hand-computed (or
* spec-derived) expected 4x4 output.
*
* The test buffer layout reserves a 1-pixel top/left context border
* + a 4-pixel top-right (for modes 3 / 7):
*
* row 0: [tl][t0 t1 t2 t3 t4 t5 t6 t7] <- TOP_STRIDE = 9 bytes
* row 1: [l0][ 4x4 output goes here ]
* row 2: [l1][ ]
* row 3: [l2][ ]
* row 4: [l3][ ]
*
* dst (passed to the pred fns) points at row 1 col 1.
*/
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
extern void daedalus_h264_pred_4x4_vertical_ref(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_horizontal_ref(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_dc_ref(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_ddl_ref(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_ddr_ref(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_vr_ref(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_hd_ref(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_vl_ref(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_hu_ref(uint8_t *dst, ptrdiff_t stride);
#define STRIDE 9
typedef void (*pred_fn)(uint8_t *dst, ptrdiff_t stride);
/* Set up the buffer: 5 rows × STRIDE cols.
* top-left = tl, top[0..7] = t[0..7], left[0..3] = l[0..3].
* The 4x4 output region (rows 1..4, cols 1..4) is filled with 0xff
* sentinels so any unwritten cell shows up as 255 in the compare. */
static void set_ctx(uint8_t buf[5][STRIDE], int tl, const int t[8], const int l[4])
{
for (int r = 0; r < 5; r++) for (int c = 0; c < STRIDE; c++) buf[r][c] = 0xff;
buf[0][0] = (uint8_t) tl;
for (int c = 0; c < 8; c++) buf[0][1 + c] = (uint8_t) t[c];
for (int r = 0; r < 4; r++) buf[1 + r][0] = (uint8_t) l[r];
}
static int check(const uint8_t buf[5][STRIDE], const char *name,
const uint8_t expect[4][4])
{
int diff = 0;
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
uint8_t got = buf[1 + r][1 + c];
uint8_t exp = expect[r][c];
if (got != exp) {
if (diff == 0)
fprintf(stderr,
"%s: first mismatch r=%d c=%d got=%u exp=%u\n",
name, r, c, got, exp);
diff++;
}
}
}
if (diff == 0)
printf(" %-26s PASS\n", name);
else
printf(" %-26s FAIL (%d/16 bytes wrong)\n", name, diff);
return diff == 0 ? 0 : 1;
}
int main(void)
{
int fail = 0;
/* Mode 0 — Vertical: each col = top[col]. */
{
uint8_t buf[5][STRIDE];
int tl = 0;
int t[8] = { 10, 20, 30, 40, 0, 0, 0, 0 };
int l[4] = { 0, 0, 0, 0 };
set_ctx(buf, tl, t, l);
daedalus_h264_pred_4x4_vertical_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{10,20,30,40}, {10,20,30,40}, {10,20,30,40}, {10,20,30,40}
};
fail |= check(buf, "Vertical (mode 0)", exp);
}
/* Mode 1 — Horizontal: each row = left[row]. */
{
uint8_t buf[5][STRIDE];
int t[8] = { 0,0,0,0, 0,0,0,0 };
int l[4] = { 50, 60, 70, 80 };
set_ctx(buf, 0, t, l);
daedalus_h264_pred_4x4_horizontal_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{50,50,50,50}, {60,60,60,60}, {70,70,70,70}, {80,80,80,80}
};
fail |= check(buf, "Horizontal (mode 1)", exp);
}
/* Mode 2 — DC: all 8 neighbours valid → ((sum + 4) >> 3) broadcast.
* top sum = 4*1 = 4, left sum = 4*3 = 12, total 16, +4 = 20,
* >>3 = 2. */
{
uint8_t buf[5][STRIDE];
int t[8] = { 1,1,1,1, 0,0,0,0 };
int l[4] = { 3,3,3,3 };
set_ctx(buf, 99, t, l); /* tl unused for DC */
daedalus_h264_pred_4x4_dc_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{2,2,2,2}, {2,2,2,2}, {2,2,2,2}, {2,2,2,2}
};
fail |= check(buf, "DC (mode 2)", exp);
}
/* Mode 3 — Diagonal_Down_Left: zz[i] = avg3(t[i], t[i+1], t[i+2]);
* dst[r][c] = zz[c + r].
* With all t[]=100 → all zz=100 → all dst=100. */
{
uint8_t buf[5][STRIDE];
int t[8] = { 100,100,100,100, 100,100,100,100 };
int l[4] = { 0,0,0,0 };
set_ctx(buf, 0, t, l);
daedalus_h264_pred_4x4_ddl_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{100,100,100,100}, {100,100,100,100},
{100,100,100,100}, {100,100,100,100}
};
fail |= check(buf, "DiagDownLeft (mode 3)", exp);
}
/* Mode 4 — Diagonal_Down_Right: zz[c-r] with c-r ∈ {-3..+3}.
* If all 9 surrounding pixels = 200 → all zz = 200 → all dst = 200. */
{
uint8_t buf[5][STRIDE];
int t[8] = { 200,200,200,200, 0,0,0,0 };
int l[4] = { 200,200,200,200 };
set_ctx(buf, 200, t, l);
daedalus_h264_pred_4x4_ddr_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{200,200,200,200}, {200,200,200,200},
{200,200,200,200}, {200,200,200,200}
};
fail |= check(buf, "DiagDownRight (mode 4)", exp);
}
/* Mode 5 — Vertical_Right. With all neighbours = 80 the 3-tap
* (a+2b+c+2)>>2 and 2-tap (a+b+1)>>1 both yield 80. */
{
uint8_t buf[5][STRIDE];
int t[8] = { 80,80,80,80, 0,0,0,0 };
int l[4] = { 80,80,80,80 };
set_ctx(buf, 80, t, l);
daedalus_h264_pred_4x4_vr_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{80,80,80,80}, {80,80,80,80}, {80,80,80,80}, {80,80,80,80}
};
fail |= check(buf, "VerticalRight (mode 5)", exp);
}
/* Mode 6 — Horizontal_Down. Same uniform-context degenerate case. */
{
uint8_t buf[5][STRIDE];
int t[8] = { 120,120,120,120, 0,0,0,0 };
int l[4] = { 120,120,120,120 };
set_ctx(buf, 120, t, l);
daedalus_h264_pred_4x4_hd_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{120,120,120,120}, {120,120,120,120},
{120,120,120,120}, {120,120,120,120}
};
fail |= check(buf, "HorizontalDown (mode 6)", exp);
}
/* Mode 7 — Vertical_Left. Uniform context. */
{
uint8_t buf[5][STRIDE];
int t[8] = { 64,64,64,64, 64,64,64,64 };
int l[4] = { 0,0,0,0 };
set_ctx(buf, 0, t, l);
daedalus_h264_pred_4x4_vl_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{64,64,64,64}, {64,64,64,64}, {64,64,64,64}, {64,64,64,64}
};
fail |= check(buf, "VerticalLeft (mode 7)", exp);
}
/* Mode 8 — Horizontal_Up. Uniform context. */
{
uint8_t buf[5][STRIDE];
int t[8] = { 0,0,0,0, 0,0,0,0 };
int l[4] = { 200,200,200,200 };
set_ctx(buf, 0, t, l);
daedalus_h264_pred_4x4_hu_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{200,200,200,200}, {200,200,200,200},
{200,200,200,200}, {200,200,200,200}
};
fail |= check(buf, "HorizontalUp (mode 8)", exp);
}
/* Asymmetric Vertical_Right test: detects orientation /
* row-vs-col confusion. Top=10,20,30,40, Left=50,60,70,
* top-left=5. Spec-derived expected output computed by hand
* from §8.3.1.4.6.
*
* d[0][0] = (tl+t0+1)>>1 = (5+10+1)>>1 = 8
* d[0][1] = (t0+t1+1)>>1 = (10+20+1)>>1 = 15
* d[0][2] = (t1+t2+1)>>1 = (20+30+1)>>1 = 25
* d[0][3] = (t2+t3+1)>>1 = (30+40+1)>>1 = 35
* d[1][0] = avg3(l0,tl,t0) = (50+2*5+10+2)>>2 = 72/4 = 18
* d[1][1] = avg3(tl,t0,t1) = (5+20+20+2)>>2 = 47/4 = 11
* d[1][2] = avg3(t0,t1,t2) = (10+40+30+2)>>2 = 82/4 = 20
* d[1][3] = avg3(t1,t2,t3) = (20+60+40+2)>>2 = 122/4 = 30
* d[2][0] = avg3(tl,l0,l1) = (5+100+60+2)>>2 = 167/4 = 41
* d[2][1] = d[0][0] = 8
* d[2][2] = d[0][1] = 15
* d[2][3] = d[0][2] = 25
* d[3][0] = avg3(l0,l1,l2) = (50+120+70+2)>>2 = 242/4 = 60
* d[3][1] = d[1][0] = 18
* d[3][2] = d[1][1] = 11
* d[3][3] = d[1][2] = 20
*/
{
uint8_t buf[5][STRIDE];
int t[8] = { 10,20,30,40, 0,0,0,0 };
int l[4] = { 50,60,70,0 };
set_ctx(buf, 5, t, l);
daedalus_h264_pred_4x4_vr_ref(&buf[1][1], STRIDE);
uint8_t exp[4][4] = {
{ 8,15,25,35},
{18,11,20,30},
{41, 8,15,25},
{60,18,11,20},
};
fail |= check(buf, "VR asym (sanity)", exp);
}
if (fail == 0) printf("\nALL %d intra-4x4 mode references PASS\n", 10);
else fprintf(stderr, "\n%d test(s) FAILED\n", fail);
return fail ? 1 : 0;
}