| 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