Files
daedalus-fourier/tests/test_intra_pred_4x4.c
T
claude-noether df9e1c9d78 h264: promote Intra_4x4 luma prediction (9 modes) to public API
PR #12 added the 9 Intra_4x4 luma intra prediction modes as test-only
spec references in tests/.  This PR promotes them to public src/
symbols so consumers (the eventual marfrit-packages substitution-arc
patch 0014) can link against them.

  Moved: tests/h264_intra_pred_4x4_ref.c → src/h264_intra_pred_4x4.c
  Renamed: daedalus_h264_pred_4x4_<mode>_ref → daedalus_h264_pred_4x4_<mode>
           (9 functions: vertical/horizontal/dc/ddl/ddr/vr/hd/vl/hu)

The src/ implementation is byte-for-byte the same code as the
test-only ref; this PR is plain plumbing.  The test binary now
links against daedalus_core to pull in the public symbols (instead
of compiling the ref file directly), exercising the path that real
consumers will use.

Same promotion shape as PR #25 (chroma DC Hadamard).

Verified on hertz:

  $ ./build/test_intra_pred_4x4
    Vertical (mode 0)          PASS
    Horizontal (mode 1)        PASS
    DC (mode 2)                PASS
    DiagDownLeft (mode 3)      PASS
    DiagDownRight (mode 4)     PASS
    VerticalRight (mode 5)     PASS
    HorizontalDown (mode 6)    PASS
    VerticalLeft (mode 7)      PASS
    HorizontalUp (mode 8)      PASS
    VR asym (sanity)           PASS

  ALL 10 intra-4x4 mode references PASS

  $ nm -g build/libdaedalus_core.a | grep "T daedalus_h264_pred_4x4"
  (9 symbols exported)

Follow-ups (same promotion pattern, can land in parallel):
  - Intra_16x16 luma (4 modes, PR #13)
  - Intra_8x8 chroma (4 modes, PR #14)
  - Intra_8x8 luma (9 modes, PRs #21 + #22)

Once all 26 intra modes are in the public API, the marfrit-packages
substitution arc can route H264PredContext's pred function pointer
tables through daedalus alongside the IDCT / deblock / qpel / DC
Hadamard substitutions already in place.
2026-05-25 14:53:37 +02:00

247 lines
8.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_horizontal(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_dc(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_ddl(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_ddr(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_vr(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_hd(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_vl(uint8_t *dst, ptrdiff_t stride);
extern void daedalus_h264_pred_4x4_hu(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(&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(&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(&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(&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(&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(&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(&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(&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(&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(&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;
}