503 lines
20 KiB
ArmAsm
503 lines
20 KiB
ArmAsm
| Shared 68000 separate-plane area fills -- hand-rolled.
|
|
|
|
|
| Used by BOTH separate-plane 68k ports, the Amiga and the X68000: a 320px
|
|
| 4bpp surface is four plane buffers of 40 bytes per row on either machine, so
|
|
| a span of pixels is the same bytes in the same places. (The Atari ST is
|
|
| word-interleaved and keeps its own St* routines.)
|
|
|
|
|
| Home of the span-filling primitives that write a run of pixels into all
|
|
| four plane buffers at once. Currently:
|
|
|
|
|
| surface68kFillCircle jlFillCircle: the whole exact-disk walk
|
|
| surface68kFillRect jlFillRect: a rect that does not span whole rows
|
|
|
|
|
| A span is a partial left byte, a run of whole middle bytes, and a partial
|
|
| right byte, in each of four planes. Three things make this much cheaper
|
|
| than the general case, and the PLANEFILL macro below is built around them:
|
|
|
|
|
| * A PLANE'S FILL VALUE IS ONLY EVER ALL-ONES OR ALL-ZEROS. It is one bit
|
|
| of the colour smeared across a byte. So a masked edge is not a read,
|
|
| two ANDs, an OR and a store; it is `or.b mask,(a0)` for a set plane and
|
|
| `and.b ~mask,(a0)` for a clear one -- one instruction, one bus cycle.
|
|
| * THE COLOUR IS FIXED FOR THE WHOLE SHAPE, so which of those two it is
|
|
| need not be decided at run time. The body is emitted once per colour
|
|
| value and dispatched to through a jump table, exactly as
|
|
| src/x68000/circle.s does. Set planes then run as a group with d6 = -1
|
|
| and clear planes as a group with d6 = 0 and the masks complemented
|
|
| once, so the middle run is the same code for both.
|
|
| * THE MIDDLE RUN IS WHOLE BYTES of that value, so it goes out a long at
|
|
| a time through a dbra loop once the pointer is aligned.
|
|
|
|
|
| ABI: cdecl (gcc-amigaos / elf2x68k), d2-d7/a2-a6 callee-save. 16-bit
|
|
| arguments are sign-extended to 4 bytes on the call stack.
|
|
|
|
|
| Every entry point carries BOTH symbol spellings at the same address:
|
|
| gcc-amigaos and gcc-atari-mint prepend an underscore to C names and
|
|
| elf2x68k does not, and one extra unused symbol is cheaper than forking the
|
|
| file over a naming convention.
|
|
|
|
.text
|
|
.even
|
|
.globl surface68kFillCircle
|
|
.globl _surface68kFillCircle
|
|
.globl surface68kFillRect
|
|
.globl _surface68kFillRect
|
|
|
|
| Bytes per plane row: SURFACE_WIDTH / 8, which is AMIGA_BYTES_PER_ROW and
|
|
| X68K_BYTES_PER_ROW both. Every planar surface on either port is
|
|
| SURFACE_WIDTH x SURFACE_HEIGHT, so it is a constant here as well as in the
|
|
| C, and both ports' C guards the radius bound that depends on it.
|
|
.equ ROW_BYTES, 40
|
|
|
|
| Stack locals. The walk state lives here rather than in registers because it
|
|
| is touched once per row while the span body runs twice, so the registers are
|
|
| worth more to the span.
|
|
.equ FC_XX, 0 | x*x tracker, word
|
|
.equ FC_XLIMIT, 2 | r*r - y*y, word
|
|
.equ FC_X, 4 | current x, word
|
|
.equ FC_T, 6 | 2*y + 1, word
|
|
.equ FC_COUNT, 8 | rows left, word
|
|
.equ FC_DOWN, 10 | (cy + y) * 40, word
|
|
.equ FC_UP, 12 | (cy - y) * 40, word
|
|
.equ FC_FIRST, 14 | span's first byte in a row
|
|
.equ FC_LOCAL, 16
|
|
| 16 locals + 44 saved registers + 4 return address
|
|
.equ FC_ARG, (FC_LOCAL + 44 + 4)
|
|
.equ FC_PLANES, FC_ARG + 0
|
|
.equ FC_CX, FC_ARG + 4 + 2
|
|
.equ FC_CY, FC_ARG + 8 + 2
|
|
.equ FC_R, FC_ARG + 12 + 2
|
|
.equ FC_COLOR, FC_ARG + 16 + 2
|
|
|
|
|
|
| ---- PLANEFILL: one span in one plane ----
|
|
| \areg = that plane's base, \op = or (set) or and (clear).
|
|
| d7.w = span's byte offset, d2 = left mask, d3 = right mask, d4 = middle
|
|
| byte count, d6 = the fill value (0 or -1, matching \op).
|
|
| Trashes d0, d1, d5, a0.
|
|
.macro PLANEFILL areg, op
|
|
lea (\areg,%d7.w),%a0
|
|
\op\().b %d2,(%a0)+ | left edge
|
|
move.w %d4,%d5
|
|
beq.s 9f
|
|
1: move.l %a0,%d1 | byte-align, then longs
|
|
and.w #3,%d1
|
|
beq.s 2f
|
|
move.b %d6,(%a0)+
|
|
subq.w #1,%d5
|
|
bne.s 1b
|
|
bra.s 9f
|
|
2: move.w %d5,%d0
|
|
and.w #3,%d5 | d5 = trailing bytes
|
|
lsr.w #2,%d0 | d0 = whole longs
|
|
beq.s 5f
|
|
subq.w #1,%d0
|
|
3: move.l %d6,(%a0)+
|
|
dbra %d0,3b
|
|
5: tst.w %d5
|
|
beq.s 9f
|
|
6: move.b %d6,(%a0)+
|
|
subq.w #1,%d5
|
|
bne.s 6b
|
|
9: \op\().b %d3,(%a0) | right edge
|
|
.endm
|
|
|
|
|
|
| ---- SPAN: one span in all four planes, for a known colour ----
|
|
| Set planes first with d6 = -1, then the clear planes with d6 = 0 and the
|
|
| masks complemented, so `and.b %d2` is a single instruction there too.
|
|
.macro SPAN colour
|
|
.if (\colour) != 0
|
|
moveq #-1,%d6
|
|
.if ((\colour) & 1)
|
|
PLANEFILL %a2, or
|
|
.endif
|
|
.if ((\colour) & 2)
|
|
PLANEFILL %a3, or
|
|
.endif
|
|
.if ((\colour) & 4)
|
|
PLANEFILL %a4, or
|
|
.endif
|
|
.if ((\colour) & 8)
|
|
PLANEFILL %a5, or
|
|
.endif
|
|
.endif
|
|
.if (\colour) != 15
|
|
moveq #0,%d6
|
|
not.b %d2
|
|
not.b %d3
|
|
.if !((\colour) & 1)
|
|
PLANEFILL %a2, and
|
|
.endif
|
|
.if !((\colour) & 2)
|
|
PLANEFILL %a3, and
|
|
.endif
|
|
.if !((\colour) & 4)
|
|
PLANEFILL %a4, and
|
|
.endif
|
|
.if !((\colour) & 8)
|
|
PLANEFILL %a5, and
|
|
.endif
|
|
not.b %d2 | the row's second span wants
|
|
not.b %d3 | the masks back
|
|
.endif
|
|
.endm
|
|
|
|
|
|
| ---- CIRCLEBODY: the whole exact-disk walk for one colour ----
|
|
|
|
|
| Bit-identical to the chunky reference loop in src/core/draw.c jlFillCircle
|
|
| and to the C walk this replaces: for each y in [0, r] find the largest x
|
|
| with x*x + y*y <= r*r incrementally, then fill the span (cx-x, cx+x) at
|
|
| rows cy+y and cy-y. Each row is filled exactly once -- no octant overlap.
|
|
|
|
|
| The arithmetic is 16-bit throughout. r*r must fit a word, which holds for
|
|
| r <= 181; the caller's contract (the bounding box is on-surface, and a
|
|
| surface is 200 rows tall) caps r at 99, and halPlanar.c has a build-time
|
|
| check on that.
|
|
.macro CIRCLEBODY colour
|
|
.Lrow\colour:
|
|
| walk x down until x*x + y*y <= r*r again
|
|
move.w FC_XX(%sp),%d0
|
|
move.w FC_XLIMIT(%sp),%d1
|
|
cmp.w %d1,%d0
|
|
bls.s .Lgeom\colour
|
|
move.w FC_X(%sp),%d4
|
|
.Lshrink\colour:
|
|
move.w %d4,%d5
|
|
add.w %d5,%d5
|
|
subq.w #1,%d5 | 2x - 1
|
|
sub.w %d5,%d0 | xx -= 2x - 1
|
|
subq.w #1,%d4 | x--
|
|
cmp.w %d1,%d0
|
|
bhi.s .Lshrink\colour
|
|
move.w %d0,FC_XX(%sp)
|
|
move.w %d4,FC_X(%sp)
|
|
.Lgeom\colour:
|
|
| span geometry, computed once for the row's two spans
|
|
move.w FC_X(%sp),%d0
|
|
move.w %a6,%d1
|
|
sub.w %d0,%d1 | left = cx - x
|
|
add.w %a6,%d0 | right = cx + x
|
|
move.w %d1,%d4
|
|
lsr.w #3,%d4 | first byte
|
|
move.w %d4,FC_FIRST(%sp)
|
|
and.w #7,%d1
|
|
move.b (%a1,%d1.w),%d2 | left mask
|
|
move.w %d0,%d1
|
|
lsr.w #3,%d1 | last byte
|
|
and.w #7,%d0
|
|
move.b 8(%a1,%d0.w),%d3 | right mask
|
|
sub.w %d4,%d1
|
|
bne.s .Lmulti\colour
|
|
| Both ends in one byte. Combining the masks covers it, and
|
|
| zeroing the right mask makes the right edge write the NEXT
|
|
| byte back unchanged instead of needing its own code path.
|
|
| (The plane allocation carries a spare byte for this.)
|
|
and.b %d3,%d2
|
|
moveq #0,%d3
|
|
moveq #0,%d4
|
|
bra.s .Ldown\colour
|
|
.Lmulti\colour:
|
|
subq.w #1,%d1
|
|
move.w %d1,%d4 | middle byte count
|
|
.Ldown\colour:
|
|
move.w FC_DOWN(%sp),%d7
|
|
add.w FC_FIRST(%sp),%d7
|
|
SPAN \colour
|
|
| the mirrored row, unless y is zero and it IS this row
|
|
move.w FC_UP(%sp),%d7
|
|
cmp.w FC_DOWN(%sp),%d7
|
|
beq.w .Lstep\colour
|
|
add.w FC_FIRST(%sp),%d7
|
|
SPAN \colour
|
|
.Lstep\colour:
|
|
addi.w #ROW_BYTES,FC_DOWN(%sp)
|
|
subi.w #ROW_BYTES,FC_UP(%sp)
|
|
| y*y grows by 2y+1, so the x*x limit shrinks by it
|
|
move.w FC_T(%sp),%d0
|
|
sub.w %d0,FC_XLIMIT(%sp)
|
|
addq.w #2,%d0
|
|
move.w %d0,FC_T(%sp)
|
|
subq.w #1,FC_COUNT(%sp)
|
|
bne.w .Lrow\colour
|
|
bra.w fcDone
|
|
.endm
|
|
|
|
|
|
| void surface68kFillCircle(uint8_t *const *planes, uint16_t cx, uint16_t cy,
|
|
| uint16_t r, uint16_t color);
|
|
surface68kFillCircle:
|
|
_surface68kFillCircle:
|
|
movem.l %d2-%d7/%a2-%a6,%sp@- | 11 registers = 44 bytes
|
|
sub.l #FC_LOCAL,%sp
|
|
move.l FC_PLANES(%sp),%a0
|
|
move.l (%a0),%a2
|
|
move.l 4(%a0),%a3
|
|
move.l 8(%a0),%a4
|
|
move.l 12(%a0),%a5
|
|
move.w FC_CX(%sp),%a6
|
|
move.w FC_CY(%sp),%d0
|
|
move.w FC_R(%sp),%d1
|
|
|
|
move.w %d1,%d2
|
|
mulu.w %d2,%d2 | r*r, a word for r <= 181
|
|
move.w %d2,FC_XX(%sp)
|
|
move.w %d2,FC_XLIMIT(%sp)
|
|
move.w %d1,FC_X(%sp)
|
|
move.w #1,FC_T(%sp)
|
|
addq.w #1,%d1
|
|
move.w %d1,FC_COUNT(%sp) | r + 1 rows
|
|
| cy * 40 as (cy << 5) + (cy << 3)
|
|
move.w %d0,%d1
|
|
lsl.w #5,%d0
|
|
lsl.w #3,%d1
|
|
add.w %d1,%d0
|
|
move.w %d0,FC_DOWN(%sp)
|
|
move.w %d0,FC_UP(%sp)
|
|
lea gFillMaskLut,%a1
|
|
|
|
move.w FC_COLOR(%sp),%d0
|
|
and.w #15,%d0
|
|
add.w %d0,%d0
|
|
move.w fcJump(%pc,%d0.w),%d0
|
|
jmp fcJump(%pc,%d0.w)
|
|
|
|
fcJump:
|
|
.word fcC0-fcJump, fcC1-fcJump, fcC2-fcJump, fcC3-fcJump
|
|
.word fcC4-fcJump, fcC5-fcJump, fcC6-fcJump, fcC7-fcJump
|
|
.word fcC8-fcJump, fcC9-fcJump, fcC10-fcJump, fcC11-fcJump
|
|
.word fcC12-fcJump, fcC13-fcJump, fcC14-fcJump, fcC15-fcJump
|
|
|
|
fcC0: CIRCLEBODY 0
|
|
fcC1: CIRCLEBODY 1
|
|
fcC2: CIRCLEBODY 2
|
|
fcC3: CIRCLEBODY 3
|
|
fcC4: CIRCLEBODY 4
|
|
fcC5: CIRCLEBODY 5
|
|
fcC6: CIRCLEBODY 6
|
|
fcC7: CIRCLEBODY 7
|
|
fcC8: CIRCLEBODY 8
|
|
fcC9: CIRCLEBODY 9
|
|
fcC10: CIRCLEBODY 10
|
|
fcC11: CIRCLEBODY 11
|
|
fcC12: CIRCLEBODY 12
|
|
fcC13: CIRCLEBODY 13
|
|
fcC14: CIRCLEBODY 14
|
|
fcC15: CIRCLEBODY 15
|
|
|
|
fcDone:
|
|
add.l #FC_LOCAL,%sp
|
|
movem.l %sp@+,%d2-%d7/%a2-%a6
|
|
rts
|
|
|
|
|
|
|
|
| ---- RECTBODY: every row of the rect, for one colour ----
|
|
|
|
|
| A rect is one span repeated, so the masks and the middle byte count are
|
|
| computed once at entry and stay in d2/d3/d4 for the whole shape; the row
|
|
| loop only walks the offset. d7 counts up by a row's worth of bytes until it
|
|
| meets a6, which is one row past the last.
|
|
.macro RECTBODY colour
|
|
.Lrect\colour:
|
|
SPAN \colour
|
|
addi.w #ROW_BYTES,%d7
|
|
cmp.w %a6,%d7
|
|
bne.w .Lrect\colour
|
|
bra.w frDone
|
|
.endm
|
|
|
|
|
|
| ---- THINPLANE / THINBODY: a rect only one byte wide ----
|
|
|
|
|
| Worth its own body rather than folding into SPAN. A vertical line is this
|
|
| shape (draw.c turns one into a 1px-wide rect), and it has no middle run and
|
|
| only one masked byte per row, so the generic span pays for a middle-run test
|
|
| and a second edge it does not need. Here the plane loop is outside the row
|
|
| loop, which leaves a dbra over a single read-modify-write: d2 holds the one
|
|
| combined mask and d4 the row count less one.
|
|
.macro THINPLANE areg, op
|
|
move.w %d4,%d5
|
|
lea (\areg,%d7.w),%a0
|
|
1: \op\().b %d2,(%a0)
|
|
lea ROW_BYTES(%a0),%a0
|
|
dbra %d5,1b
|
|
.endm
|
|
|
|
|
|
.macro THINBODY colour
|
|
.if (\colour) != 0
|
|
.if ((\colour) & 1)
|
|
THINPLANE %a2, or
|
|
.endif
|
|
.if ((\colour) & 2)
|
|
THINPLANE %a3, or
|
|
.endif
|
|
.if ((\colour) & 4)
|
|
THINPLANE %a4, or
|
|
.endif
|
|
.if ((\colour) & 8)
|
|
THINPLANE %a5, or
|
|
.endif
|
|
.endif
|
|
.if (\colour) != 15
|
|
not.b %d2
|
|
.if !((\colour) & 1)
|
|
THINPLANE %a2, and
|
|
.endif
|
|
.if !((\colour) & 2)
|
|
THINPLANE %a3, and
|
|
.endif
|
|
.if !((\colour) & 4)
|
|
THINPLANE %a4, and
|
|
.endif
|
|
.if !((\colour) & 8)
|
|
THINPLANE %a5, and
|
|
.endif
|
|
.endif
|
|
bra.w frDone
|
|
.endm
|
|
|
|
|
|
| 44 saved registers + 4 return address
|
|
.equ FR_ARG, 48
|
|
.equ FR_PLANES, FR_ARG + 0
|
|
.equ FR_X, FR_ARG + 4 + 2
|
|
.equ FR_Y, FR_ARG + 8 + 2
|
|
.equ FR_W, FR_ARG + 12 + 2
|
|
.equ FR_H, FR_ARG + 16 + 2
|
|
.equ FR_COLOR, FR_ARG + 20 + 2
|
|
|
|
| void surface68kFillRect(uint8_t *const *planes, uint16_t x, uint16_t y,
|
|
| uint16_t w, uint16_t h, uint16_t color);
|
|
|
|
|
| w and h must both be at least 1 -- draw.c clips before it calls, so an empty
|
|
| rect never arrives. A rect covering whole rows is better served by one flat
|
|
| run of words per plane and halPlanar.c keeps that case to itself; everything
|
|
| else, partial pixel edges included, lands here.
|
|
surface68kFillRect:
|
|
_surface68kFillRect:
|
|
movem.l %d2-%d7/%a2-%a6,%sp@- | 11 registers = 44 bytes
|
|
move.l FR_PLANES(%sp),%a0
|
|
move.l (%a0),%a2
|
|
move.l 4(%a0),%a3
|
|
move.l 8(%a0),%a4
|
|
move.l 12(%a0),%a5
|
|
lea gFillMaskLut,%a1
|
|
|
|
| span geometry, once for the whole rect
|
|
move.w FR_X(%sp),%d0 | left
|
|
move.w FR_W(%sp),%d1
|
|
add.w %d0,%d1
|
|
subq.w #1,%d1 | right
|
|
move.w %d0,%d5
|
|
lsr.w #3,%d5 | first byte
|
|
and.w #7,%d0
|
|
move.b (%a1,%d0.w),%d2 | left mask
|
|
move.w %d1,%d0
|
|
lsr.w #3,%d0 | last byte
|
|
and.w #7,%d1
|
|
move.b 8(%a1,%d1.w),%d3 | right mask
|
|
sub.w %d5,%d0
|
|
bne.s 1f
|
|
| One byte wide: one combined mask, and d4 becomes the dbra
|
|
| row count for THINBODY instead of a middle-run length.
|
|
and.b %d3,%d2
|
|
move.w FR_H(%sp),%d4
|
|
subq.w #1,%d4
|
|
moveq #1,%d6 | take the thin dispatch
|
|
bra.s 2f
|
|
1: subq.w #1,%d0
|
|
move.w %d0,%d4 | middle byte count
|
|
moveq #0,%d6
|
|
2: | d7 = y * ROW_BYTES + firstByte, a6 = one row past the last
|
|
move.w FR_Y(%sp),%d0
|
|
move.w %d0,%d1
|
|
lsl.w #5,%d0
|
|
lsl.w #3,%d1
|
|
add.w %d1,%d0 | y * 40, as (y << 5) + (y << 3)
|
|
add.w %d5,%d0
|
|
move.w %d0,%d7
|
|
move.w FR_H(%sp),%d0
|
|
move.w %d0,%d1
|
|
lsl.w #5,%d0
|
|
lsl.w #3,%d1
|
|
add.w %d1,%d0 | h * 40
|
|
add.w %d7,%d0
|
|
move.w %d0,%a6
|
|
|
|
move.w FR_COLOR(%sp),%d0
|
|
and.w #15,%d0
|
|
add.w %d0,%d0
|
|
| The jump table goes through a1: a PC-relative index has only
|
|
| an 8-bit displacement, and the thin table is 4 KB out.
|
|
tst.w %d6
|
|
bne.s .Lthin
|
|
lea frJump(%pc),%a1
|
|
bra.s .Ldispatch
|
|
.Lthin:
|
|
lea ftJump(%pc),%a1
|
|
.Ldispatch:
|
|
move.w (%a1,%d0.w),%d1
|
|
jmp (%a1,%d1.w)
|
|
|
|
frJump:
|
|
.word frC0-frJump, frC1-frJump, frC2-frJump, frC3-frJump
|
|
.word frC4-frJump, frC5-frJump, frC6-frJump, frC7-frJump
|
|
.word frC8-frJump, frC9-frJump, frC10-frJump, frC11-frJump
|
|
.word frC12-frJump, frC13-frJump, frC14-frJump, frC15-frJump
|
|
|
|
frC0: RECTBODY 0
|
|
frC1: RECTBODY 1
|
|
frC2: RECTBODY 2
|
|
frC3: RECTBODY 3
|
|
frC4: RECTBODY 4
|
|
frC5: RECTBODY 5
|
|
frC6: RECTBODY 6
|
|
frC7: RECTBODY 7
|
|
frC8: RECTBODY 8
|
|
frC9: RECTBODY 9
|
|
frC10: RECTBODY 10
|
|
frC11: RECTBODY 11
|
|
frC12: RECTBODY 12
|
|
frC13: RECTBODY 13
|
|
frC14: RECTBODY 14
|
|
frC15: RECTBODY 15
|
|
|
|
ftJump:
|
|
.word ftC0-ftJump, ftC1-ftJump, ftC2-ftJump, ftC3-ftJump
|
|
.word ftC4-ftJump, ftC5-ftJump, ftC6-ftJump, ftC7-ftJump
|
|
.word ftC8-ftJump, ftC9-ftJump, ftC10-ftJump, ftC11-ftJump
|
|
.word ftC12-ftJump, ftC13-ftJump, ftC14-ftJump, ftC15-ftJump
|
|
|
|
ftC0: THINBODY 0
|
|
ftC1: THINBODY 1
|
|
ftC2: THINBODY 2
|
|
ftC3: THINBODY 3
|
|
ftC4: THINBODY 4
|
|
ftC5: THINBODY 5
|
|
ftC6: THINBODY 6
|
|
ftC7: THINBODY 7
|
|
ftC8: THINBODY 8
|
|
ftC9: THINBODY 9
|
|
ftC10: THINBODY 10
|
|
ftC11: THINBODY 11
|
|
ftC12: THINBODY 12
|
|
ftC13: THINBODY 13
|
|
ftC14: THINBODY 14
|
|
ftC15: THINBODY 15
|
|
|
|
frDone:
|
|
movem.l %sp@+,%d2-%d7/%a2-%a6
|
|
rts
|
|
|
|
|
|
.data
|
|
.even
|
|
| Eight left-edge masks (bits at and to the right of x&7), then eight
|
|
| right-edge masks (bits at and to the left of it). The span body reaches the
|
|
| second half at 8(%a1,%dn.w), so the two must stay adjacent.
|
|
gFillMaskLut:
|
|
.byte 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01
|
|
.byte 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF
|