6 Commits
Author SHA1 Message Date
LibretroAdmin ca3f5c4380 IPU: a coefficient split by a suspension resumes from ipu_cmd
When the bitstream runs dry between the run/level code of an AC
coefficient and its sign or escape bits, the block decoders suspend and
are called again. The second half needs only whether the code was an
escape and its level; both now travel in ipu_cmd.pos[5], which the
savestate carries, and the table entry is local to the call. A state
loaded into a fresh process resumes the coefficient from that.

pos[5] holds bit 1 to say the resume data is there, bit 0 for the
escape, and the level from bit 8 up. A state written by an older build
holds 1 there with nothing to go on, and that block ends at the
suspension.

tests/ipu/slice_hash.cpp decodes every fixture again with the input
running dry on every other quadword, so that hundreds of coefficients
are split across a suspension, and requires the same hash as the full
feed. It also resumes a split coefficient, and a state in the older
form, before anything has been decoded in the process.
2026-10-08 16:11:09 +02:00
LibretroAdmin 72e5461eaa IPU: let the slice harness link at -O0
The IDEC and CSC paths reach EE memory through vtlb_memRead128 and
vtlb_memWrite128. They are not on the route this drives, so the harness
stubs them rather than depending on the optimiser to drop the calls, which
is what a build below -O1 cannot do.

The pinned hash then holds from -O0 to -Os on both compilers, and an -O0
build with -fno-sanitize-recover=all runs clean.
2026-09-21 23:11:11 +02:00
LibretroAdmin 30f3d30e60 IPU: run the slice harness on aarch64
The NEON inverse DCT, colour conversion and dither had only been checked
on synthetic blocks. This lane decodes the same MPEG-2 streams with them
and holds the result to the same pinned hash, which is where the whole
chain is shown to agree rather than each kernel on its own.

The tier line names aarch64 now instead of falling through to sse2.
2026-09-21 22:54:08 +02:00
LibretroAdmin 4c2bd24bf5 IPU: make the bit pointer a C struct with free functions
tIPU_BP moves to ipu_bp.h as plain data plus ipu_bp_fill_buffer,
ipu_bp_advance and ipu_bp_align, which both languages can call. They stay
in the header so they keep inlining into the decoder, which steps the
position once per VLC symbol. The FIFO read behind them is reached through
ipu_fifo_in_read, provided by IPU_Fifo.cpp, which owns the FIFO.

IPUStatus moves alongside into ipu_status.h, since the refill marks the
core as waiting and both languages now read it.

The struct is written whole into savestates, so its layout is the point:
size, alignment and all four offsets are unchanged.

The decoder comes out smaller for it -- 4086 instructions and 1438 memory
operands become 3839 and 1335 at SSE4.1 -- and the slice harness holds its
pinned hash on every tier, which is what says the rewrite is bit-exact.
2026-09-21 21:35:40 +02:00
LibretroAdmin da05193de5 IPU: cover the picture-level switches the slice decoder branches on
The fixture set grows to sixteen streams so every switch mpeg2_slice reads
is exercised: the alternate intra VLC table, the non-linear quantiser
table, all three wider DC precisions, and the alternate scan. Each arrives
as a real stream with ffmpeg's decode of the same frame beside it, the way
the rest already do.

Alternate scan comes with frame_pred_frame_dct clear, which puts a
dct_type bit after macroblock_type; make_stream_fixtures.py and the
harness's macroblock layer now read it, and the fielddct fixture is coded
so the encoder picks field DCT and the interlaced block layout runs.

The two layouts write the same six blocks at different strides, so a
macroblock coded as frame DCT can also be decoded a second time with the
interlaced layout forced and the two results must differ by exactly the
field interleave. That holds over 61440 pixels and rejects a wrong
DCT_offset or DCT_stride in either branch.

The per-stream bound is now two grades. Flat and smooth fixtures carry
almost no high-frequency energy, so there the IPU and ffmpeg have to agree
closely -- a flat frame is bit-exact under both scan orders -- and that is
where the decode is shown to be right. Detailed fixtures keep a loose
bound, since what separates them there is the two inverse DCTs.
2026-09-21 21:28:30 +02:00
LibretroAdmin b0a5476fde IPU: check the slice decoder against ffmpeg on real streams
The macroblock layer had no test: reaching it means producing a legal
bitstream, which the synthetic blocks the other IPU harnesses use cannot
do. tests/ipu/slice_hash.cpp drives real MPEG-2 slices through the decoder
and compares each macroblock against ffmpeg's decode of the same frame,
covering the address increment, the coded block pattern, the intra VLC
tables, the dequantiser, the scan order, DC prediction and the inverse DCT
at once.

The comparison is a bound rather than an equality, because the two
decoders use different inverse DCTs and the gap tracks how much
high-frequency content a block carries -- the row pass here stores its
intermediate back to s16. On these fixtures a frame quantised to DC only
comes out bit-exact, a gradient sits at 0.27 mean, and detailed frames run
4 to 5 mean with peaks near 60, so the per-stream limits sit just above
that. A wrong scan order or a mis-stepped VLC table puts the mean in the
tens and the peak past 200. Bit-exactness against this tree is the pinned
hash, which every tier and both compilers agree on -- SSE2 takes the
scalar inverse-DCT column pass and SSE4.1 the vector one, so real streams
now exercise both.

The fixtures are committed so the check does not depend on the local
ffmpeg; make_stream_fixtures.py regenerates them and carries the sequence,
picture and slice header parsing the EE does on hardware.
2026-09-21 21:18:25 +02:00