wip: predicted samples plumbing

This commit is contained in:
2026-05-25 22:58:00 +02:00
parent 0b6482bc8f
commit 6ed40dc001
4 changed files with 170 additions and 24 deletions
+63 -15
View File
@@ -1,12 +1,16 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* test_idct_bitexact — phase1 stage1 bit-exact gate for the frame-
* scaled luma IDCT 4×4 dispatch.
* scaled luma + chroma IDCT 4×4 / 8×8 dispatch + Stage 2 predicted-
* samples plumbing.
*
* Generates a frame of random coefficients, runs daedalus_decoder
* (with predicted=0 by the scaffold's flush_frame contract), and
* compares every output byte against an inline C reference that
* mirrors the H.264 §8.5.12.1 1D butterfly.
* Generates a frame of random coefficients AND random predicted
* samples per MB, runs daedalus_decoder (which writes the predicted
* samples into its frame-scoped predicted_y/_uv buffers via
* append_mb, then pre-fills the IDCT dispatch scratch from them in
* flush_frame), and compares every output byte against an inline C
* reference that mirrors the H.264 §8.5.12.1 1D butterfly applied
* to the same predicted+coeffs inputs.
*
* Why "bit-exact": the GPU shader and the C reference apply the same
* integer arithmetic. Any rounding / sign / overflow disagreement is
@@ -30,7 +34,7 @@
* Not in scope (covered by other tests / future PRs):
* - Chroma DC / Intra16x16 DC Hadamard pre-pass
* - bit-exactness against real H.264 streams (test-vector PR)
* - non-zero predicted pixels (intra prediction lands in Stage 2a)
* - deblock (lands in Stage 2 PR-b after this one)
*/
#include "daedalus_decoder.h"
@@ -202,15 +206,25 @@ int main(int argc, char **argv)
/* Build the per-MB inputs. Each MB gets 16 luma 4×4 blocks of
* random coeffs in [-512, 511] — same range as the daedalus-fourier
* cycle-6 M1 gate uses. */
int16_t (*per_mb_coeffs)[384] = malloc((size_t) n_mbs * sizeof(*per_mb_coeffs));
if (!per_mb_coeffs) { fprintf(stderr, "alloc fail\n"); return 1; }
* cycle-6 M1 gate uses. Plus random predicted samples (uint8 each)
* to exercise the Stage 2 predicted-samples plumbing — when this
* is non-zero, flush_frame must pre-fill the IDCT-dispatch scratch
* from dec->predicted_y / dec->predicted_uv (Stage 2 PR-a) rather
* than from calloc-zero (the Stage 1 scaffold contract). The
* reference path mirrors this by pre-filling ref_y / ref_cb / ref_cr
* from the same predicted bytes BEFORE the per-block ref_idct*_add
* calls — so the test catches any mismatch between caller-supplied
* predicted and what reaches the GPU's IDCT-add starting state. */
int16_t (*per_mb_coeffs)[384] = malloc((size_t) n_mbs * sizeof(*per_mb_coeffs));
uint8_t (*per_mb_predicted)[384] = malloc((size_t) n_mbs * sizeof(*per_mb_predicted));
if (!per_mb_coeffs || !per_mb_predicted) { fprintf(stderr, "alloc fail\n"); return 1; }
for (int mb = 0; mb < n_mbs; mb++) {
for (int i = 0; i < 384; i++) {
/* Random coeffs in [-512, 511] for all of luma + Cb + Cr.
* Same range as the daedalus-fourier cycle-6 M1 gate. */
/* Random coeffs in [-512, 511] for all of luma + Cb + Cr. */
per_mb_coeffs[mb][i] = (int16_t)((int)(xs64() % 1024) - 512);
/* Random predicted samples in [0, 255]. */
per_mb_predicted[mb][i] = (uint8_t)(xs64() & 0xff);
}
}
@@ -230,6 +244,7 @@ int main(int argc, char **argv)
mb.mb_x = (uint16_t) mx;
mb.mb_y = (uint16_t) my;
mb.coeffs = per_mb_coeffs[idx];
mb.predicted = per_mb_predicted[idx];
mb.transform_8x8 = mb_8x8[idx];
if (mb_8x8[idx]) n_8x8_mbs++; else n_4x4_mbs++;
if (daedalus_decoder_append_mb(dec, &mb) != 0) {
@@ -256,9 +271,26 @@ int main(int argc, char **argv)
}
/* Compute the reference output: same per-MB → flat raster block
* layout as flush_frame uses. Branch per MB on transform_8x8. */
uint8_t *ref_y = calloc(1, y_size);
* layout as flush_frame uses. Branch per MB on transform_8x8.
*
* ref_y is pre-filled with each MB's 16×16 luma predicted samples
* at raster (my*16, mx*16), then ref_idct4_add/8_add overlay the
* residual via FFmpeg `idct_add` semantics (dst += idct(coeffs);
* clip255). This mirrors what flush_frame does on the GPU side:
* scratch_y starts from dec->predicted_y, IDCT-add writes back. */
uint8_t *ref_y = malloc(y_size);
if (!ref_y) return 1;
for (int my = 0; my < mb_h; my++) {
for (int mx = 0; mx < mb_w; mx++) {
int mb_idx = my * mb_w + mx;
const uint8_t *p_y = per_mb_predicted[mb_idx]; /* [0..256) */
for (int r = 0; r < 16; r++) {
memcpy(&ref_y[((size_t) my * 16 + r) * (size_t) width
+ (size_t) mx * 16],
&p_y[r * 16], 16);
}
}
}
int16_t block_scratch[64]; /* large enough for 8x8 */
for (int my = 0; my < mb_h; my++) {
for (int mx = 0; mx < mb_w; mx++) {
@@ -302,9 +334,24 @@ int main(int argc, char **argv)
size_t chroma_w = (size_t) width / 2;
size_t chroma_h = (size_t) height / 2;
size_t chroma_plane_size = chroma_w * chroma_h;
uint8_t *ref_cb = calloc(1, chroma_plane_size);
uint8_t *ref_cr = calloc(1, chroma_plane_size);
uint8_t *ref_cb = malloc(chroma_plane_size);
uint8_t *ref_cr = malloc(chroma_plane_size);
if (!ref_cb || !ref_cr) return 1;
/* Pre-fill ref_cb / ref_cr with per-MB 8x8 chroma predicted samples
* (mirrors the predicted-samples plumbing on the chroma path). */
for (int my = 0; my < mb_h; my++) {
for (int mx = 0; mx < mb_w; mx++) {
int mb_idx = my * mb_w + mx;
const uint8_t *p_cb = per_mb_predicted[mb_idx] + 256;
const uint8_t *p_cr = per_mb_predicted[mb_idx] + 256 + 64;
for (int r = 0; r < 8; r++) {
memcpy(&ref_cb[((size_t) my * 8 + r) * chroma_w + (size_t) mx * 8],
&p_cb[r * 8], 8);
memcpy(&ref_cr[((size_t) my * 8 + r) * chroma_w + (size_t) mx * 8],
&p_cr[r * 8], 8);
}
}
}
for (int my = 0; my < mb_h; my++) {
for (int mx = 0; mx < mb_w; mx++) {
int mb_idx = my * mb_w + mx;
@@ -386,6 +433,7 @@ int main(int argc, char **argv)
free(gpu_uv);
free(gpu_y);
free(mb_8x8);
free(per_mb_predicted);
free(per_mb_coeffs);
daedalus_decoder_destroy(dec);