Measure the span: the mode survives, and it is an encoder format
FINDINGS 29 priced a literal-span mode at 4*(50 + 4L*9.08) cycles and labelled
the whole section DERIVED. Session 8 step 0 was to measure it before optimising
over the mode set it implies. Two variants in blit.s, one stream per span length
from prep_spans.py, timed by span.lua, driven by span.sh in ~25 s:
v5, handed (x, npix) and left to work the copy out: 97.9/span + 10.459/px
v6, handed an address and a jump displacement: 43.7/span + 9.152/px
29 assumed 50.0/span + 9.080/px
So 29's arithmetic was right about a format nobody had written. The difference
is not tuning: v5 spends ~122 cycles a span computing a destination, dividing
npix into bursts and handling a 0..15 remainder, all of which the encoder knows
at build time. v6's record is {u32 absolute GVRAM address, u16 jump
displacement} into an unrolled chain of 24-pixel copy units -- no loop, no
remainder, no arithmetic -- and it fits 11 span lengths to 0.3%.
Three things that measurement showed and derivation could not:
- The per-pixel cost is a function of REGISTER PRESSURE. FINDINGS 24's 9.08
was a fixed blit with 12 registers free; v5 can spare 8 and pays 10.46; v6
gets 12 back only because the encoder holds the state.
- Short spans die in the remainder path -- a 12-pixel span costs MORE than a
16-pixel one -- and the fix is padding, not avoidance.
- Odd-x alignment is free (259.0 vs 261.8 cycles/span), as a 16-bit bus
implies but nobody had checked.
Re-priced against the unchanged mode maps, sasi: median 74.4% -> 52.0% (29 said
43.0), misses 37 -> 10/120 (29 said 8), 448.0 KB/s. Break-even moved from runs
of 2 blocks to runs of 4. 29.4 survives: a scene cut needs x >= 0.196 of the
frame as spans and the bus allows x <= 0.373, so it fits at 12fps.
All 23 timing configs are also checked pixel-exact, so none of this was timed
against a decoder that quietly skipped work.
FINDINGS 30. Next: lever B, the cost-aware mode decision.
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -31,6 +31,43 @@
|
||||
; the block needs only one base pointer. V4 deliberately scrambles the
|
||||
; picture (it reads a row-linear source in block order); it is a timing
|
||||
; probe, which is why the correctness snapshot is taken after V1.
|
||||
; V5 ROW-LINEAR LITERAL SPANS, the mode priced in FINDINGS 29 and never
|
||||
; measured. Walks a stream of per-row span records
|
||||
; row: u16 nspans, then nspans * { u16 x, u16 npix, npix*u16 pixels }
|
||||
; for 192 rows, copying each span's word-expanded pixels straight from
|
||||
; the stream buffer into GVRAM. Unlike V1-V4 the work per call is set by
|
||||
; the STREAM, not by the code, so one variant measures every span length:
|
||||
; tools/bench/prep_spans.py generates a stream per span length and
|
||||
; tools/bench/span.lua times them and fits cycles = A*spans + B*pixels.
|
||||
; The point of the measurement is A -- the per-span overhead FINDINGS 29
|
||||
; guessed at 50 cycles -- and how much B degrades from V1's 9.08 when a
|
||||
; span is too short to burst. Every config covers the whole frame, so
|
||||
; V5 draws the SAME picture V1 does and can be verified, not just timed.
|
||||
;
|
||||
; Bursts are 8 registers (d0-d3/a3-a6 = 32 bytes = 16 pixels), not V1's
|
||||
; 12: a0/a1/a2 and d4-d7 are all live across a span (stream, row base,
|
||||
; destination, and three counters). The remainder is copied move.l at a
|
||||
; time with a leading move.w when it is odd, so a 4-pixel span never
|
||||
; reaches a movem at all -- which is exactly the case FINDINGS 29's
|
||||
; full-row-width extrapolation flatters.
|
||||
;
|
||||
; V6 the SAME spans with the arithmetic moved into the encoder. V5 measures
|
||||
; a decoder that is handed (x, npix) and has to work out how to copy it;
|
||||
; most of its per-span cost is that working-out, and an encoder can do it
|
||||
; once at build time instead of 12 times a second. V6's record is
|
||||
; { u32 absolute GVRAM address, u16 jump displacement } -- no row
|
||||
; structure, no counters, no remainder logic -- and the displacement
|
||||
; jumps into an unrolled chain of 24-pixel copy units, so a span of any
|
||||
; supported length is straight-line code with no loop at all.
|
||||
; GVRAM sits at a fixed $C00000 on every X68000, so absolute destinations
|
||||
; are a legitimate thing for an encoder to bake in.
|
||||
;
|
||||
; Two consequences of the format. Span lengths are multiples of 24
|
||||
; pixels, and a span may overrun the 256 visible pixels of its row by up
|
||||
; to 23 -- harmless, because the line stride is 1024 bytes and only the
|
||||
; first 512 are displayed, so the overrun lands in the invisible half.
|
||||
; And with row and remainder handling gone, 12 registers are free again
|
||||
; (d0-d6/a1/a3-a6), which is why the unit is 24 pixels and not V5's 16.
|
||||
;
|
||||
; 12 registers per movem burst (d0-d7/a2-a5 = 48 bytes) is the maximum
|
||||
; available: a0=src, a1=dst, a6=end sentinel. The row counter lives in the
|
||||
@@ -43,10 +80,14 @@
|
||||
FLAG = $18000 ; 0 idle / 1 running / $FF done
|
||||
VAR = $18004 ; variant selector, written by Lua
|
||||
ITER = $18008 ; iteration count, written by Lua
|
||||
SPTR = $1800C ; V5 span stream pointer, written by Lua
|
||||
SRCW = $60000 ; word-expanded frame 192*512 = 96KB
|
||||
SRCB = $80000 ; byte-per-pixel frame 192*256 = 48KB
|
||||
DST0 = $C08000 ; GVRAM + 32*1024 (first picture row)
|
||||
DSTE = $C38000 ; GVRAM + 224*1024 (one past last)
|
||||
ROWS = 192 ; picture rows a V5 stream describes
|
||||
V6UNIT = 12 ; bytes of code per V6 chain unit
|
||||
V6MAX = 11 ; chain units = 11*24 = 264 pixels >= one row
|
||||
|
||||
org $10000
|
||||
start:
|
||||
@@ -58,6 +99,10 @@ start:
|
||||
beq v2
|
||||
cmp.l #4,d0
|
||||
beq v4
|
||||
cmp.l #5,d0
|
||||
beq v5
|
||||
cmp.l #6,d0
|
||||
beq v6
|
||||
bra v3
|
||||
|
||||
; ---------------------------------------------------------------- V1
|
||||
@@ -152,5 +197,90 @@ v4blk: movem.l (a0)+,d0-d7 ; 32 bytes = one 4x4 block, expanded
|
||||
bne v4
|
||||
bra done
|
||||
|
||||
; ---------------------------------------------------------------- V5
|
||||
; a0 stream, a1 row base, a2 span destination, d7 rows, d6 spans, d5 pixels,
|
||||
; d4 burst/tail counter. Everything else (d0-d3/a3-a6) is burst payload.
|
||||
v5: move.l SPTR.l,a0
|
||||
lea DST0,a1
|
||||
move.w #ROWS-1,d7
|
||||
v5row: move.w (a0)+,d6 ; spans in this row
|
||||
subq.w #1,d6
|
||||
bmi.s v5eor ; a row may legitimately have none
|
||||
v5span: move.w (a0)+,d0 ; x, in pixels
|
||||
add.w d0,d0 ; one pixel = one word
|
||||
lea 0(a1,d0.w),a2
|
||||
move.w (a0)+,d5 ; pixels in this span
|
||||
move.w d5,d4
|
||||
lsr.w #4,d4 ; 16-pixel bursts
|
||||
beq.s v5tail
|
||||
subq.w #1,d4
|
||||
v5burst: movem.l (a0)+,d0-d3/a3-a6 ; 32 bytes straight out of the stream
|
||||
movem.l d0-d3/a3-a6,(a2)
|
||||
lea 32(a2),a2
|
||||
dbra d4,v5burst
|
||||
v5tail: moveq #15,d4
|
||||
and.w d5,d4 ; 0..15 pixels left
|
||||
beq.s v5eos
|
||||
lsr.w #1,d4 ; C = odd pixel count
|
||||
bcc.s v5t2
|
||||
move.w (a0)+,(a2)+
|
||||
v5t2: subq.w #1,d4
|
||||
bmi.s v5eos
|
||||
v5tl: move.l (a0)+,(a2)+
|
||||
dbra d4,v5tl
|
||||
v5eos: dbra d6,v5span
|
||||
v5eor: lea 1024(a1),a1
|
||||
dbra d7,v5row
|
||||
subq.l #1,ITER.l
|
||||
bne v5
|
||||
bra done
|
||||
|
||||
; ---------------------------------------------------------------- V6
|
||||
; a0 stream, a2 destination, d7 spans remaining; everything else is payload.
|
||||
v6: move.l SPTR.l,a0
|
||||
move.w (a0)+,d7 ; total spans in the frame
|
||||
subq.w #1,d7
|
||||
v6span: move.l (a0)+,a2 ; absolute GVRAM destination
|
||||
move.w (a0)+,d0 ; (V6MAX - units) * V6UNIT, from the encoder
|
||||
jmp v6ch(pc,d0.w)
|
||||
v6ch:
|
||||
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
|
||||
dbra d7,v6span
|
||||
subq.l #1,ITER.l
|
||||
bne v6
|
||||
bra done
|
||||
|
||||
done: move.l #$FF,FLAG.l ; timer stops here
|
||||
halt: bra.s halt
|
||||
|
||||
Reference in New Issue
Block a user