Build v7 into the player, and find the cost model 18% wrong on the block it made commonest
src/player/decode.s now paints v7 literal spans, pixel-exact under MAME and px68k's C68K core over a container where every frame carries 128-216 spans covering up to 38% of the picture. The span pass is blit.s v7 verbatim: the 66.0/9.143/9.978 fit was measured on that instruction sequence. The container is DLX3 -- a span section between the mode header and the block payload, since that is the only place the 68000 can reach without first parsing something of variable length. 16_span_roundtrip.py gates it in check.sh, and asserts it emitted enough spans to have tested anything. Two synthetic all-SPAN anchors price v7 inside decode.s at 151.2 and 225.6 clocks per 4x4 block, against FINDINGS 40's table of 151 and 226 -- 0.2% on both emulators. The measured mode costs what it was said to cost. Two things that were not on the list: TWO BYTE BUDGETS. FINDINGS 40's 18/120 was scored against the 488 KB/s PIPE, not the 280 KB/s profile, and at the profile rate the lam search has already spent the allowance -- spans fired on 5 frames of 120 and looked like a regression. The profile is a chosen quality rate point; the pipe is hardware. --kbps and --span-kbps are now separate and spans run before mu, because a span pays in bytes and mu pays in picture. Delivered: 86/120 over budget without spans, 77/120 at the profile budget, 34/120 on the pipe for +0.36 dB. C_SKIP_MIXED WAS NEVER MEASURED, and it was 18% low -- 45.0, now 55.0. It is the one constant in the table that came from a derivation, because the synthetic frame that would measure it cannot exist: a byte needs a coded block for its SKIP to be mixed. Four bracketing anchors measure it on both emulators with the header byte rotated through all four positions, and the partner mode solves back to its own anchored value to 0.2%. With it corrected the model predicts a real spanned decode to -0.06% mean / 0.09% worst, against -2.99% / 4.30%. It matters because a span marks its run SKIP, so mixed SKIPs dominate exactly the frames spans are judged on. Also: the rig had been writing its synthetic timing frames 26 KB past the top of a 2 MB machine, and got away with it because the modes it overran are data-independent. A span's jump displacements come out of the stream, so it is not. And frames-over-budget is no longer a safe headline -- the controller aims at the deadline, so 55 of 120 frames sit within 5% of it and a 1% cost shift moves 22 frames. FINDINGS 41. check.sh ALL GREEN, now gating on a span-heavy DLX3 container. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
+114
-1
@@ -38,6 +38,39 @@
|
||||
; paid only by blocks that are NOT all-SKIP: a header byte of zero clears four
|
||||
; blocks with one tst.b, and SKIP is the median block.
|
||||
;
|
||||
; LITERAL SPANS (v7, FINDINGS 40). A run of horizontally adjacent dirty blocks
|
||||
; is cheaper to paint as four ROW-LINEAR runs of word-expanded literal pixels
|
||||
; than as blocks: 226 clocks per 4x4 block at a run of 4, against V1's 299.9,
|
||||
; and the break-even is a run of 2. The run's blocks read SKIP in the mode
|
||||
; header and the span section paints them instead, so the block loop below is
|
||||
; unchanged -- it sees a SKIP and advances, exactly as it does for a genuinely
|
||||
; held block.
|
||||
;
|
||||
; The section sits BETWEEN the mode header and the block payload because that is
|
||||
; the only place the 68000 can reach without first parsing something of variable
|
||||
; length: the header is a fixed 768 bytes. Per span the record is {u32 absolute
|
||||
; GVRAM address, u16 coarse displacement}, then the coarse pixels, then a u16
|
||||
; FINE displacement, then the fine pixels.
|
||||
;
|
||||
; The two displacements are jumps into two unrolled copy chains -- 24 pixels per
|
||||
; coarse unit (a 12-register movem pair) and 2 per fine unit (one
|
||||
; `move.l (a0)+,(a2)+`) -- so a span of any length is straight-line code with no
|
||||
; loop, no remainder and no address arithmetic. A run of 4x4 blocks is always a
|
||||
; multiple of 4 pixels long, and 4 is a multiple of the 2-pixel fine quantum, so
|
||||
; NOTHING is padded (FINDINGS 40.3).
|
||||
;
|
||||
; The fine displacement is in the STREAM rather than in the span record because
|
||||
; that is what pays for the second dispatch: when the coarse chain falls out
|
||||
; into `move.w (a0)+,d0 / jmp`, d0 is dead payload and a0 is already pointing at
|
||||
; it, so the decoder holds nothing extra across the copy and keeps all twelve
|
||||
; payload registers (FINDINGS 40.4). Twelve is why the coarse unit is 24 pixels
|
||||
; and not V5's 16, and it is the whole reason the per-pixel cost is 9.143 rather
|
||||
; than 10.459 (FINDINGS 30.4).
|
||||
;
|
||||
; a1 (the mode header cursor) is one of those twelve, so it goes on the stack
|
||||
; across the span pass. Two long accesses per frame, against the 24 pixels a
|
||||
; register buys per chain unit.
|
||||
;
|
||||
; ALIGNMENT. Frame records are [u32 length][768-byte mode header][payload] laid
|
||||
; end to end, and payload lengths are arbitrary -- so record boundaries land on
|
||||
; odd addresses, and `move.l (a0)+,d0` on an odd address is an ADDRESS ERROR on
|
||||
@@ -63,6 +96,10 @@ DSTE = $C38000 ; GVRAM + 224*1024 (one past last)
|
||||
BROW = 4096 ; bytes per block row (4 picture rows)
|
||||
ROWLEN = 512 ; bytes per block row of blocks (64 * 8)
|
||||
MODEB = 768 ; packed mode header, 3072 blocks * 2 bits
|
||||
SPCU = 12 ; bytes of code per COARSE span unit (24 px)
|
||||
SPCN = 11 ; coarse units: 11*24 = 264 px >= one row
|
||||
SPFU = 2 ; bytes of code per FINE span unit (2 px)
|
||||
SPFN = 11 ; fine units: 11*2 = 22 px > one coarse unit
|
||||
|
||||
org $10000
|
||||
start:
|
||||
@@ -75,7 +112,8 @@ frameloop:
|
||||
lea 0(a0,d0.l),a1
|
||||
move.l a1,SCR_END.l ; where the payload must end
|
||||
move.l a0,a1 ; a1 = packed mode header
|
||||
lea MODEB(a0),a0 ; a0 = payload
|
||||
lea MODEB(a0),a0 ; a0 = span section
|
||||
bsr paint_spans ; -> a0 = block payload, a1 preserved
|
||||
bsr decode_frame
|
||||
cmpa.l SCR_END.l,a0 ; bitstream desync is silent otherwise
|
||||
bne desync
|
||||
@@ -169,6 +207,81 @@ RAWPAIR macro
|
||||
move.l d0,\1(a4)
|
||||
endm
|
||||
|
||||
; ------------------------------------------------------- the span section
|
||||
; in: a0 = span section, a1 = mode header (preserved across the call)
|
||||
; out: a0 = one past the section, i.e. the block payload
|
||||
;
|
||||
; This is tools/bench/blit.s v7 verbatim, and deliberately so: the 66.0 clocks
|
||||
; per span + 9.143 per coarse pixel + 9.978 per fine pixel of FINDINGS 40 were
|
||||
; measured on exactly this instruction sequence, over thirteen span lengths, and
|
||||
; a "tidier" rewrite here would silently invalidate every span figure in
|
||||
; FINDINGS 39/40 and in tools/analysis/14_dmac_chain.py.
|
||||
;
|
||||
; The fine chain is entered by FALLING OUT of the coarse one, so a span with no
|
||||
; coarse units enters at v7cx with d0 already reloaded -- which is why the
|
||||
; coarse displacement for c=0 is SPCN*SPCU, one past the last coarse unit,
|
||||
; rather than a special case.
|
||||
paint_spans:
|
||||
move.w (a0)+,d7 ; spans in this frame
|
||||
subq.w #1,d7
|
||||
bmi spnone ; a frame may legitimately have none (the
|
||||
; chain is far past a short branch)
|
||||
move.l a1,-(sp) ; a1 is a payload register below
|
||||
spspan: move.l (a0)+,a2 ; absolute GVRAM destination
|
||||
move.w (a0)+,d0 ; (SPCN - coarse) * SPCU
|
||||
jmp spch(pc,d0.w)
|
||||
spch:
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||
movem.l d0-d6/a1/a3-a6,(a2)
|
||||
lea 48(a2),a2
|
||||
spcx: move.w (a0)+,d0 ; (SPFN - fine) * SPFU, from mid-stream
|
||||
jmp spfh(pc,d0.w)
|
||||
spfh:
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
move.l (a0)+,(a2)+
|
||||
dbra d7,spspan
|
||||
move.l (sp)+,a1
|
||||
spnone: rts
|
||||
|
||||
; ------------------------------------------------------------- one frame
|
||||
; in: a0 = payload, a1 = packed mode header
|
||||
; out: a0 = one past the last payload byte consumed
|
||||
|
||||
Reference in New Issue
Block a user