Two sessions, unrecorded until now, committed together because their edits share files and cannot be split cleanly after the fact. Session 28 (FINDINGS 60): the container is DLX5 -- every record sector-aligned, 120/120 starting on a boundary where 3/120 did, +0.48% on the wire and zero clocks -- and the ring's release rounds to RECALN so no pad is stranded. Two encoder levers measured and refused: `--spans all` buys +0.19 dB for +67% of the wire, and joint span/lam selection emits byte-identical containers because `lam` never leaves its floor on any of 120 frames. Session 29 (FINDINGS 61): the packed full-frame blit is 27.3% of a 12 fps frame, a channel fills GVRAM in buffer mode off the disc with the CPU halted, and it walks the 1,024 B line stride itself through array chaining. At the 9 clk/B dual-address floor the codec is 110.4% of a frame and a decoder-free packed literal player is 55.2%, at +4.89 dB -- 2.75 dB past a ceiling the codec's scene-wide palette cannot cross. Encoder work is parked; the codec is kept and not built on. check.sh is ALL GREEN before and after, plus one new stage that gates the ORDER of the measured paint costs rather than their values. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
523 lines
22 KiB
ArmAsm
523 lines
22 KiB
ArmAsm
; Full-frame GVRAM blit cost on a stock 68000 @ 10MHz.
|
|
;
|
|
; Answers: what fraction of a 12fps frame budget (833,333 cycles) does simply
|
|
; PUTTING a decoded 256x192 frame on screen cost, before any decoding?
|
|
;
|
|
; Geometry (tools/bench/crtc_mode.lua): 256-colour page, one pixel per WORD of
|
|
; CPU address space, 1024-byte line stride, picture in rows 32..223 of a
|
|
; 256-row page. So a row is 512 contiguous bytes of writes, then a 512-byte
|
|
; skip. 192 rows = 98,304 bytes of GVRAM write traffic per frame.
|
|
;
|
|
; Confirmed from MAME 0.277 x68k_crtc.cpp:501 (gvram_w, case 0x0100): a CPU
|
|
; write in 256-colour mode is masked to 0x00ff, so the HIGH byte of every word
|
|
; written is discarded by the hardware. V1 exploits this -- it never has to
|
|
; clear the odd bytes of its source.
|
|
;
|
|
; Three variants, selected by VAR, each looped ITER times:
|
|
; V1 movem.l blit from a word-expanded RAM frame (96KB). The realistic
|
|
; "decode to RAM, then blit" design. Reads 96KB, writes 96KB.
|
|
; V2 naive byte-source expansion (move.b / move.w per pixel). The obvious
|
|
; implementation, kept as the baseline V1 has to beat.
|
|
; V3 write-only floor: registers preloaded once, no source read at all.
|
|
; Nothing that puts this many pixels on screen can beat V3. The gap
|
|
; V1-V3 is the price of reading a source frame at all.
|
|
; V4 the SAME 96KB of writes, but issued in 4x4 BLOCK order instead of
|
|
; row-linear order. This is the access pattern a decoder that writes
|
|
; codewords straight into GVRAM actually has, and it is the number that
|
|
; picks the decoder architecture: compose-in-RAM-then-blit (V1) versus
|
|
; decode-direct-to-GVRAM (V4 scaled by the fraction of non-SKIP blocks).
|
|
; Each block is 4 rows of 8 bytes at a 1024-byte stride, so the
|
|
; destination displacements 0/1024/2048/3072 all fit a 16-bit offset and
|
|
; 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.
|
|
;
|
|
; V7 v6 with a SECOND, finer chain for the tail (FINDINGS 39.4). v6 pays for
|
|
; its 24-pixel quantum in padding: an average span wastes ~11 pixels, and
|
|
; FINDINGS 39.3 attributes 86% of the DMAC array-chain's advantage over v6
|
|
; to exactly that. V7 keeps the 24-pixel coarse chain and appends a chain
|
|
; of 2-pixel units, so a span is 24*c + 2*f pixels and the padding is at
|
|
; most one pixel -- ZERO for the real case, where a span is a run of 4x4
|
|
; blocks and its length is a multiple of 4.
|
|
;
|
|
; The fine unit is `move.l (a0)+,(a2)+` (20 cycles, 2 pixels), NOT a
|
|
; 2-register movem: movem.l (a0)+,d0-d1 plus movem.l d0-d1,(a2) plus the
|
|
; lea is 52+8 cycles for 4 pixels, so the obvious "smaller movem" tail is
|
|
; 50% dearer per pixel than the plainest instruction on the machine.
|
|
;
|
|
; The second entry point costs a second dispatch, and the trick that pays
|
|
; for it is that the fine displacement is NOT in the span record: it sits
|
|
; in the STREAM, after the coarse pixels and before the fine ones. The
|
|
; coarse chain falls out into `move.w (a0)+,d0 / jmp`, by which point d0
|
|
; is dead payload and a0 is pointing exactly at it. So v7 holds nothing
|
|
; extra across the copy and keeps all 12 payload registers -- a record is
|
|
; still {u32 address, u16 displacement}, with one more u16 mid-span.
|
|
;
|
|
; 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
|
|
; a1-vs-a6 compare rather than a d-register for exactly this reason.
|
|
; 512 = 10*48 + 32, hence ten 12-register bursts and one 8-register tail.
|
|
; Destination uses (d16,a1) displacement rather than post-increment because
|
|
; movem cannot post-increment a destination; the displacement costs 4 cycles
|
|
; per burst but saves an 8-cycle lea, so it is the cheaper of the two.
|
|
|
|
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
|
|
SRCP = $90000 ; PACKED frame 192*256 = 48KB (V8): two picture
|
|
; bytes per word, already interleaved by the
|
|
; encoder, so the blit is a straight copy
|
|
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
|
|
V7CU = 12 ; bytes of code per V7 COARSE unit (24 px)
|
|
V7CN = 11 ; coarse units: 11*24 = 264 px >= one row
|
|
V7FU = 2 ; bytes of code per V7 FINE unit (2 px)
|
|
V7FN = 11 ; fine units: 11*2 = 22 px > one coarse unit
|
|
|
|
org $10000
|
|
start:
|
|
move.l VAR.l,d0
|
|
move.l #1,FLAG.l ; timer starts here
|
|
cmp.l #1,d0
|
|
beq v1
|
|
cmp.l #2,d0
|
|
beq v2
|
|
cmp.l #4,d0
|
|
beq v4
|
|
cmp.l #5,d0
|
|
beq v5
|
|
cmp.l #6,d0
|
|
beq v6
|
|
cmp.l #7,d0
|
|
beq v7
|
|
cmp.l #8,d0
|
|
beq v8
|
|
cmp.l #9,d0
|
|
beq v9
|
|
cmp.l #10,d0
|
|
beq v10
|
|
bra v3
|
|
|
|
; ---------------------------------------------------------------- V1
|
|
v1: lea SRCW,a0
|
|
lea DST0,a1
|
|
lea DSTE,a6
|
|
v1row: movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,48(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,96(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,144(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,192(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,240(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,288(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,336(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,384(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,432(a1)
|
|
movem.l (a0)+,d0-d7
|
|
movem.l d0-d7,480(a1)
|
|
lea 1024(a1),a1
|
|
cmpa.l a6,a1
|
|
bne v1row
|
|
subq.l #1,ITER.l
|
|
bne v1
|
|
bra done
|
|
|
|
; ---------------------------------------------------------------- V2
|
|
v2: lea SRCB,a0
|
|
lea DST0,a1
|
|
lea DSTE,a6
|
|
v2row: move.w #255,d1
|
|
v2px: move.b (a0)+,d0
|
|
move.w d0,(a1)+ ; high byte is discarded by gvram_w
|
|
dbra d1,v2px
|
|
lea 512(a1),a1 ; skip the unused half of the line
|
|
cmpa.l a6,a1
|
|
bne v2row
|
|
subq.l #1,ITER.l
|
|
bne v2
|
|
bra done
|
|
|
|
; ---------------------------------------------------------------- V3
|
|
v3: lea SRCW,a0
|
|
movem.l (a0),d0-d7/a2-a5 ; load the burst once, outside the loop
|
|
lea DST0,a1
|
|
lea DSTE,a6
|
|
v3row: movem.l d0-d7/a2-a5,(a1)
|
|
movem.l d0-d7/a2-a5,48(a1)
|
|
movem.l d0-d7/a2-a5,96(a1)
|
|
movem.l d0-d7/a2-a5,144(a1)
|
|
movem.l d0-d7/a2-a5,192(a1)
|
|
movem.l d0-d7/a2-a5,240(a1)
|
|
movem.l d0-d7/a2-a5,288(a1)
|
|
movem.l d0-d7/a2-a5,336(a1)
|
|
movem.l d0-d7/a2-a5,384(a1)
|
|
movem.l d0-d7/a2-a5,432(a1)
|
|
movem.l d0-d7,480(a1)
|
|
lea 1024(a1),a1
|
|
cmpa.l a6,a1
|
|
bne v3row
|
|
subq.l #1,ITER.l
|
|
bne v3
|
|
bra done
|
|
|
|
; ---------------------------------------------------------------- V4
|
|
v4: lea SRCW,a0
|
|
lea DST0,a3 ; base of the current block row
|
|
lea DSTE,a4 ; one past the last block row
|
|
v4brow: move.l a3,a1
|
|
lea 512(a3),a5 ; 64 blocks * 8 bytes
|
|
v4blk: movem.l (a0)+,d0-d7 ; 32 bytes = one 4x4 block, expanded
|
|
movem.l d0-d1,(a1)
|
|
movem.l d2-d3,1024(a1)
|
|
movem.l d4-d5,2048(a1)
|
|
movem.l d6-d7,3072(a1)
|
|
addq.l #8,a1
|
|
cmpa.l a5,a1
|
|
bne.s v4blk
|
|
lea 4096(a3),a3 ; next block row is 4 picture lines
|
|
cmpa.l a4,a3
|
|
bne v4brow
|
|
subq.l #1,ITER.l
|
|
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
|
|
|
|
; ---------------------------------------------------------------- V7
|
|
; a0 stream, a2 destination, d7 spans remaining; everything else is payload.
|
|
; Stream per span: u32 dest, u16 coarse disp, c*48 B pixels,
|
|
; u16 fine disp, f*4 B pixels.
|
|
v7: move.l SPTR.l,a0
|
|
move.w (a0)+,d7 ; total spans in the frame
|
|
subq.w #1,d7
|
|
v7span: move.l (a0)+,a2 ; absolute GVRAM destination
|
|
move.w (a0)+,d0 ; (V7CN - coarse) * V7CU
|
|
jmp v7ch(pc,d0.w)
|
|
v7ch:
|
|
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
|
|
v7cx: move.w (a0)+,d0 ; (V7FN - fine) * V7FU, from mid-stream
|
|
jmp v7fh(pc,d0.w)
|
|
v7fh:
|
|
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,v7span
|
|
subq.l #1,ITER.l
|
|
bne v7
|
|
bra done
|
|
|
|
; ---------------------------------------------------------------- V8
|
|
; THE PACKED FULL-FRAME BLIT (FINDINGS 46.6/47.2). Identical in shape to V1 --
|
|
; a row-linear movem.l chain out of a RAM frame into GVRAM -- and different in
|
|
; exactly one thing: a row is 128 WORDS, not 256, because R20 bit 11 lets one
|
|
; word carry two picture bytes. 256 = 5*48 + 16, so five 12-register bursts
|
|
; and a 4-register tail, against V1's ten and one.
|
|
;
|
|
; TIMING ONLY, and it does not set bit 11. MAME's gvram_w carries no timing in
|
|
; either arm (blit.lua's header), so the bit cannot move a cycle here; what it
|
|
; moves is the PICTURE, and the picture is what tools/bench/show_frame256_packed.lua
|
|
; and tools/bench/gvpack already verify pixel-exactly. Setting it here would
|
|
; make this variant's snapshot right and its measurement no different, and
|
|
; would put a display-mode change inside a timing loop for no gain.
|
|
;
|
|
; The source is PRE-INTERLEAVED by the host, which is the honest half of the
|
|
; claim: the packing is an encoder-side transform (46.3's argument for the text
|
|
; plane, and the same one here), so the decoder-free player's per-frame work is
|
|
; this copy and nothing else. If the interleave had to happen at run time this
|
|
; variant would be V2, not V1.
|
|
v8: lea SRCP,a0
|
|
lea DST0,a1
|
|
lea DSTE,a6
|
|
v8row: movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,48(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,96(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,144(a1)
|
|
movem.l (a0)+,d0-d7/a2-a5
|
|
movem.l d0-d7/a2-a5,192(a1)
|
|
movem.l (a0)+,d0-d3
|
|
movem.l d0-d3,240(a1)
|
|
lea 1024(a1),a1
|
|
cmpa.l a6,a1
|
|
bne v8row
|
|
subq.l #1,ITER.l
|
|
bne v8
|
|
bra done
|
|
|
|
; ---------------------------------------------------------------- V9
|
|
; WHAT THE PACKED LAYOUT COSTS A BLOCK DECODER (FINDINGS 47.6.4, open).
|
|
;
|
|
; V4 is the access pattern of a decoder that writes 4x4 codewords straight into
|
|
; GVRAM: 4 rows of 8 contiguous bytes at a 1024-byte stride, so each row is one
|
|
; `movem.l` of two registers. Under the packed layout that pattern is GONE.
|
|
; A block at columns x..x+3 owns the LOW bytes of four consecutive words -- four
|
|
; bytes at STRIDE 2 -- and the high bytes of those same words belong to the
|
|
; block 128 columns away. There is no burst that writes every other byte, so
|
|
; the block is sixteen `move.b`s.
|
|
;
|
|
; V9 does the pair together, low block then high block off one base, so it
|
|
; writes every byte it touches and covers the same 49,152 pixels V1/V4/V8 do.
|
|
; It is the HONEST version of "keep the codec and pack the screen": the mode
|
|
; map is unchanged, SKIP still works per block, and the writes go byte at a
|
|
; time. V10 below is the other option, and the comparison is the point.
|
|
v9: lea SRCB,a0
|
|
lea DST0,a3
|
|
lea DSTE,a4
|
|
v9brow: move.l a3,a1
|
|
lea 256(a3),a5 ; 32 block PAIRS * 8 bytes
|
|
v9blk:
|
|
move.b (a0)+,(a1)
|
|
move.b (a0)+,2(a1)
|
|
move.b (a0)+,4(a1)
|
|
move.b (a0)+,6(a1)
|
|
move.b (a0)+,1024(a1)
|
|
move.b (a0)+,1026(a1)
|
|
move.b (a0)+,1028(a1)
|
|
move.b (a0)+,1030(a1)
|
|
move.b (a0)+,2048(a1)
|
|
move.b (a0)+,2050(a1)
|
|
move.b (a0)+,2052(a1)
|
|
move.b (a0)+,2054(a1)
|
|
move.b (a0)+,3072(a1)
|
|
move.b (a0)+,3074(a1)
|
|
move.b (a0)+,3076(a1)
|
|
move.b (a0)+,3078(a1)
|
|
move.b (a0)+,1(a1)
|
|
move.b (a0)+,3(a1)
|
|
move.b (a0)+,5(a1)
|
|
move.b (a0)+,7(a1)
|
|
move.b (a0)+,1025(a1)
|
|
move.b (a0)+,1027(a1)
|
|
move.b (a0)+,1029(a1)
|
|
move.b (a0)+,1031(a1)
|
|
move.b (a0)+,2049(a1)
|
|
move.b (a0)+,2051(a1)
|
|
move.b (a0)+,2053(a1)
|
|
move.b (a0)+,2055(a1)
|
|
move.b (a0)+,3073(a1)
|
|
move.b (a0)+,3075(a1)
|
|
move.b (a0)+,3077(a1)
|
|
move.b (a0)+,3079(a1)
|
|
addq.l #8,a1
|
|
cmpa.l a5,a1
|
|
bne v9blk
|
|
lea 4096(a3),a3
|
|
cmpa.l a4,a3
|
|
bne v9brow
|
|
subq.l #1,ITER.l
|
|
bne v9
|
|
bra done
|
|
|
|
; ---------------------------------------------------------------- V10
|
|
; THE OTHER OPTION: PAIR THE BLOCKS IN THE ENCODER. If the codec codes the
|
|
; block at x and the block at x+128 as ONE unit, the destination is whole words
|
|
; again and V4's `movem.l` shape comes straight back -- the same instructions,
|
|
; the same 32 bytes of source per unit, and TWICE the pixels, because a word now
|
|
; carries two of them. So V10 is V4's inner loop run half as many times.
|
|
;
|
|
; WHAT IT COSTS IS NOT IN THIS MEASUREMENT. A pair skips only if BOTH of its
|
|
; blocks skip, and the two are 128 columns apart with nothing in the picture
|
|
; relating them. That is a CONTAINER question -- what fraction of the mode map
|
|
; survives pairing -- and 08_mode_map.py has the data to answer it. V10 prices
|
|
; the paint; it does not price the SKIPs the pairing loses.
|
|
v10: lea SRCP,a0
|
|
lea DST0,a3
|
|
lea DSTE,a4
|
|
v10brow: move.l a3,a1
|
|
lea 256(a3),a5 ; 32 block PAIRS * 8 bytes
|
|
v10blk: movem.l (a0)+,d0-d7 ; 32 bytes = one PAIR of 4x4 blocks
|
|
movem.l d0-d1,(a1)
|
|
movem.l d2-d3,1024(a1)
|
|
movem.l d4-d5,2048(a1)
|
|
movem.l d6-d7,3072(a1)
|
|
addq.l #8,a1
|
|
cmpa.l a5,a1
|
|
bne.s v10blk
|
|
lea 4096(a3),a3
|
|
cmpa.l a4,a3
|
|
bne v10brow
|
|
subq.l #1,ITER.l
|
|
bne v10
|
|
bra done
|
|
|
|
done: move.l #$FF,FLAG.l ; timer stops here
|
|
halt: bra.s halt
|