wip: predicted samples plumbing
This commit is contained in:
@@ -89,6 +89,26 @@ struct daedalus_decoder_mb_input {
|
|||||||
* column-major within each 4x4 or 8x8 block (matches FFmpeg
|
* column-major within each 4x4 or 8x8 block (matches FFmpeg
|
||||||
* convention). Caller-owned; copied during append. */
|
* convention). Caller-owned; copied during append. */
|
||||||
const int16_t *coeffs; /* points at exactly 384 int16_t */
|
const int16_t *coeffs; /* points at exactly 384 int16_t */
|
||||||
|
|
||||||
|
/* Reconstructed predicted samples for this MB, planar order:
|
||||||
|
* [ 0 .. 256) — 16×16 luma, ROW-MAJOR raster (row 0 cols 0..15,
|
||||||
|
* row 1 cols 0..15, ..., row 15 cols 0..15)
|
||||||
|
* [256 .. 320) — 8×8 Cb, ROW-MAJOR raster
|
||||||
|
* [320 .. 384) — 8×8 Cr, ROW-MAJOR raster
|
||||||
|
*
|
||||||
|
* The caller (libavcodec's CPU intra-prediction kernels for Phase 1
|
||||||
|
* I-frames; MC fallback for Phase 2 P-frames before GPU MC lands)
|
||||||
|
* populates this from neighbour samples per H.264 §8.3 / §8.4.
|
||||||
|
* `flush_frame()`'s reconstruction step is `clip255(predicted +
|
||||||
|
* idct(coeffs))` — the IDCT shader reads dst, adds the inverse
|
||||||
|
* transform, writes clipped — so a non-zero `predicted` here makes
|
||||||
|
* the output pixel a valid H.264 reconstruction; zero means
|
||||||
|
* residual-only (used by IDCT-isolation tests).
|
||||||
|
*
|
||||||
|
* NULL is legal and means "all-zero predicted samples" for this MB
|
||||||
|
* (the per-frame predicted buffer is zeroed at flush time so a NULL
|
||||||
|
* is indistinguishable from explicit zeros). */
|
||||||
|
const uint8_t *predicted; /* NULL or exactly 384 uint8_t */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------------------------------
|
/* -------------------------------------------------------------------
|
||||||
|
|||||||
+73
-9
@@ -54,7 +54,18 @@ daedalus_decoder *daedalus_decoder_create(int width, int height)
|
|||||||
|
|
||||||
dec->mb_descs = calloc((size_t) dec->n_mbs, sizeof(*dec->mb_descs));
|
dec->mb_descs = calloc((size_t) dec->n_mbs, sizeof(*dec->mb_descs));
|
||||||
dec->coeffs = calloc((size_t) dec->n_mbs * 384, sizeof(int16_t));
|
dec->coeffs = calloc((size_t) dec->n_mbs * 384, sizeof(int16_t));
|
||||||
if (!dec->mb_descs || !dec->coeffs) {
|
|
||||||
|
/* Predicted-samples buffers — zero-initialised so a frame where
|
||||||
|
* every append_mb gets NULL `predicted` decodes residual-only
|
||||||
|
* (the Stage 1 scaffold contract). flush_frame zeroes these at
|
||||||
|
* end-of-frame to maintain that invariant for the next frame. */
|
||||||
|
const size_t pred_y_size = (size_t) width * (size_t) height;
|
||||||
|
const size_t pred_uv_size = pred_y_size / 2;
|
||||||
|
dec->predicted_y = calloc(1, pred_y_size);
|
||||||
|
dec->predicted_uv = calloc(1, pred_uv_size);
|
||||||
|
|
||||||
|
if (!dec->mb_descs || !dec->coeffs ||
|
||||||
|
!dec->predicted_y || !dec->predicted_uv) {
|
||||||
daedalus_decoder_destroy(dec);
|
daedalus_decoder_destroy(dec);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -66,6 +77,8 @@ void daedalus_decoder_destroy(daedalus_decoder *dec)
|
|||||||
{
|
{
|
||||||
if (!dec)
|
if (!dec)
|
||||||
return;
|
return;
|
||||||
|
free(dec->predicted_uv);
|
||||||
|
free(dec->predicted_y);
|
||||||
free(dec->coeffs);
|
free(dec->coeffs);
|
||||||
free(dec->mb_descs);
|
free(dec->mb_descs);
|
||||||
if (dec->dctx)
|
if (dec->dctx)
|
||||||
@@ -153,6 +166,34 @@ int daedalus_decoder_append_mb(daedalus_decoder *dec,
|
|||||||
mb->coeffs,
|
mb->coeffs,
|
||||||
384 * sizeof(int16_t));
|
384 * sizeof(int16_t));
|
||||||
|
|
||||||
|
/* Splat predicted samples into frame-scoped planes at raster
|
||||||
|
* (mb_y*16, mb_x*16) for luma, (mb_y*8, mb_x*8) for each chroma
|
||||||
|
* component. NULL → leave buffers as-is (zeroed at create + at
|
||||||
|
* end of each flush_frame); that's the zero-predictor contract. */
|
||||||
|
if (mb->predicted) {
|
||||||
|
const size_t y_stride = (size_t) dec->width;
|
||||||
|
const size_t uv_stride = (size_t) dec->width / 2;
|
||||||
|
const size_t uv_plane = uv_stride * ((size_t) dec->height / 2);
|
||||||
|
|
||||||
|
const uint8_t *p_y = mb->predicted;
|
||||||
|
const uint8_t *p_cb = mb->predicted + 256;
|
||||||
|
const uint8_t *p_cr = mb->predicted + 256 + 64;
|
||||||
|
|
||||||
|
uint8_t *dst_y = &dec->predicted_y[
|
||||||
|
(size_t) mb->mb_y * 16 * y_stride + (size_t) mb->mb_x * 16];
|
||||||
|
uint8_t *dst_cb = &dec->predicted_uv[
|
||||||
|
(size_t) mb->mb_y * 8 * uv_stride + (size_t) mb->mb_x * 8];
|
||||||
|
uint8_t *dst_cr = &dec->predicted_uv[uv_plane +
|
||||||
|
(size_t) mb->mb_y * 8 * uv_stride + (size_t) mb->mb_x * 8];
|
||||||
|
|
||||||
|
for (int r = 0; r < 16; r++)
|
||||||
|
memcpy(&dst_y[(size_t) r * y_stride], &p_y[r * 16], 16);
|
||||||
|
for (int r = 0; r < 8; r++) {
|
||||||
|
memcpy(&dst_cb[(size_t) r * uv_stride], &p_cb[r * 8], 8);
|
||||||
|
memcpy(&dst_cr[(size_t) r * uv_stride], &p_cr[r * 8], 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dec->mbs_appended++;
|
dec->mbs_appended++;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -174,14 +215,18 @@ int daedalus_decoder_append_mb(daedalus_decoder *dec,
|
|||||||
* int16 (64 Cb + 64 Cr); dispatch into a planar Cb||Cr scratch
|
* int16 (64 Cb + 64 Cr); dispatch into a planar Cb||Cr scratch
|
||||||
* buffer (W*H/4 each, concatenated W*H/2 total); CPU-interleave
|
* buffer (W*H/4 each, concatenated W*H/2 total); CPU-interleave
|
||||||
* into the caller's NV12 UV plane post-dispatch.
|
* into the caller's NV12 UV plane post-dispatch.
|
||||||
* - Both dispatches use predicted=0 (the scratch buffers are
|
* - Both dispatches pre-fill the scratch from the per-frame
|
||||||
* calloc'd); the shader does clip255(predicted + idct(coeffs)).
|
* predicted_y / predicted_uv buffers (accumulated by append_mb's
|
||||||
|
* per-MB predicted-samples splat). The IDCT shader's
|
||||||
|
* `dst += idct(coeffs)` + clip255 then folds reconstruction into
|
||||||
|
* the IDCT pass — no separate Stage 3 dispatch needed.
|
||||||
*
|
*
|
||||||
* What's NOT done yet (follow-on Phase 1 sub-PRs):
|
* What's NOT done yet (follow-on Phase 1 sub-PRs):
|
||||||
* - Intra prediction (Stage 2a wavefront): predicted is forced to 0,
|
* - Intra prediction: caller-driven (Q2 decision 2026-05-25, CPU
|
||||||
* so output pixels are residual-only and not a valid frame decode.
|
* intra-pred via FFmpeg NEON kernels). Caller writes the
|
||||||
* Sufficient for Vulkan round-trip validation, not for bit-exact
|
* intra-predicted samples into mb_input.predicted; this dispatch
|
||||||
* against FFmpeg.
|
* consumes them as the IDCT-add starting state. GPU wavefront
|
||||||
|
* intra-pred (DESIGN.md Stage 2a) is no longer planned.
|
||||||
* - Motion compensation (Stage 2b): inter MBs not handled.
|
* - Motion compensation (Stage 2b): inter MBs not handled.
|
||||||
* - High-profile IDCT 8x8 (Stage 1 extension).
|
* - High-profile IDCT 8x8 (Stage 1 extension).
|
||||||
* - Chroma DC / luma Intra16x16 DC Hadamard pre-pass (currently we
|
* - Chroma DC / luma Intra16x16 DC Hadamard pre-pass (currently we
|
||||||
@@ -222,9 +267,17 @@ int daedalus_decoder_flush_frame(daedalus_decoder *dec,
|
|||||||
* transform_8x8_size_flag per MB), so we allocate worst-case for
|
* transform_8x8_size_flag per MB), so we allocate worst-case for
|
||||||
* each and track actual counts.
|
* each and track actual counts.
|
||||||
*/
|
*/
|
||||||
|
/* Pre-fill the dispatch scratch with the per-MB predicted samples
|
||||||
|
* accumulated by append_mb. daedalus-fourier's IDCT 4x4/8x8
|
||||||
|
* shaders implement FFmpeg `idct_add` semantics — dst += idct(coeffs)
|
||||||
|
* with clip255 — so a non-zero predicted dst becomes the
|
||||||
|
* reconstruction step (residual + predicted → clip) "for free",
|
||||||
|
* collapsing DESIGN.md's Stage 3 into Stage 1's existing dispatch. */
|
||||||
const size_t y_stride_int = (size_t) dec->width;
|
const size_t y_stride_int = (size_t) dec->width;
|
||||||
const size_t y_size = y_stride_int * (size_t) dec->height;
|
const size_t y_size = y_stride_int * (size_t) dec->height;
|
||||||
uint8_t *scratch_y = calloc(1, y_size);
|
uint8_t *scratch_y = malloc(y_size);
|
||||||
|
if (scratch_y)
|
||||||
|
memcpy(scratch_y, dec->predicted_y, y_size);
|
||||||
|
|
||||||
const size_t worst_4x4 = (size_t) dec->n_mbs * 16;
|
const size_t worst_4x4 = (size_t) dec->n_mbs * 16;
|
||||||
const size_t worst_8x8 = (size_t) dec->n_mbs * 4;
|
const size_t worst_8x8 = (size_t) dec->n_mbs * 4;
|
||||||
@@ -349,7 +402,9 @@ int daedalus_decoder_flush_frame(daedalus_decoder *dec,
|
|||||||
const size_t cb_plane_size = chroma_w * chroma_h;
|
const size_t cb_plane_size = chroma_w * chroma_h;
|
||||||
const size_t uv_scratch_size = 2 * cb_plane_size;
|
const size_t uv_scratch_size = 2 * cb_plane_size;
|
||||||
|
|
||||||
scratch_uv = calloc(1, uv_scratch_size);
|
scratch_uv = malloc(uv_scratch_size);
|
||||||
|
if (scratch_uv)
|
||||||
|
memcpy(scratch_uv, dec->predicted_uv, uv_scratch_size);
|
||||||
chroma_coeffs = malloc(n_chroma_blocks * 16 * sizeof(int16_t));
|
chroma_coeffs = malloc(n_chroma_blocks * 16 * sizeof(int16_t));
|
||||||
chroma_meta = malloc(n_chroma_blocks *
|
chroma_meta = malloc(n_chroma_blocks *
|
||||||
sizeof(daedalus_h264_block_meta));
|
sizeof(daedalus_h264_block_meta));
|
||||||
@@ -427,6 +482,15 @@ cleanup:
|
|||||||
free(coeffs8);
|
free(coeffs8);
|
||||||
free(coeffs4);
|
free(coeffs4);
|
||||||
free(scratch_y);
|
free(scratch_y);
|
||||||
|
|
||||||
|
/* Zero the predicted-samples buffers so the next frame starts from
|
||||||
|
* the all-zero-predictor baseline; MBs whose append_mb gets NULL
|
||||||
|
* for `predicted` then decode residual-only. */
|
||||||
|
if (dec->predicted_y)
|
||||||
|
memset(dec->predicted_y, 0, (size_t) dec->width * (size_t) dec->height);
|
||||||
|
if (dec->predicted_uv)
|
||||||
|
memset(dec->predicted_uv, 0, (size_t) dec->width * (size_t) dec->height / 2);
|
||||||
|
|
||||||
dec->mbs_appended = 0;
|
dec->mbs_appended = 0;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,20 @@ struct daedalus_decoder {
|
|||||||
int16_t *coeffs; /* n_mbs * 384 */
|
int16_t *coeffs; /* n_mbs * 384 */
|
||||||
int mbs_appended; /* per-frame count */
|
int mbs_appended; /* per-frame count */
|
||||||
|
|
||||||
|
/* Per-frame predicted samples, accumulated by append_mb(), consumed
|
||||||
|
* by flush_frame() as the initial dst content for the IDCT-add
|
||||||
|
* dispatch (predicted + idct → clip → final pixel). Zeroed at end
|
||||||
|
* of each flush_frame so NULL `mb->predicted` is indistinguishable
|
||||||
|
* from explicit zeros.
|
||||||
|
*
|
||||||
|
* predicted_y: width × height, row-major (stride = width)
|
||||||
|
* predicted_uv: PLANAR Cb||Cr, each (width/2) × (height/2), so
|
||||||
|
* size = width × height / 2, with Cb plane at
|
||||||
|
* offset 0 and Cr at offset (width/2)*(height/2).
|
||||||
|
* Matches scratch_uv layout in flush_frame. */
|
||||||
|
uint8_t *predicted_y;
|
||||||
|
uint8_t *predicted_uv;
|
||||||
|
|
||||||
/* Output format. */
|
/* Output format. */
|
||||||
daedalus_decoder_output_format output_fmt;
|
daedalus_decoder_output_format output_fmt;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user