| Atari ST word-interleaved planar jlFillCircle -- 68000 hand-rolled.
|
| Exact-disk span walk, bit-identical to the chunky reference span
| loop in src/core/draw.c jlFillCircle: for each y offset in [0, r]
| the largest x with x*x + y*y <= r*r is found incrementally (no
| in-loop multiply), then the span (cx-x, cx+x) is drawn at rows
| cy+y and cy-y. leftMask/rightMask are computed once per row pair.
|
| Caller MUST guarantee the bounding box (cx-r, cy-r) (cx+r, cy+r)
| is fully on-surface (so r <= 99 and r*r <= 9801 fits a word).
| Off-surface circles fall back to the C walker.
|
| Phase 10 final: 16-way color dispatch at the OUTER loop. Each color
| variant has its own span-walk body where SPAN_BODY inlines a hard-
| coded 4-plane mask RMW (no btst, no bsr/rts). Saves ~120 cyc per
| applyMask call (was ~180 via bsr applyMask with runtime btst on d7).
|
| ABI: cdecl. d2-d7/a2-a6 callee-save.
|
|   void surface68kStFillCircle(uint8_t *base,
|                               uint16_t cx, uint16_t cy,
|                               uint16_t r,  uint8_t  color);

                .text


                .equ    SP_FC_SAVED, 44
                .equ    SP_FC_LOCAL, 8
                .equ    SP_FC_OFF,         (SP_FC_SAVED + 4 + SP_FC_LOCAL)
                .equ    SP_FC_BASE,    SP_FC_OFF + 0
                .equ    SP_FC_CX,      SP_FC_OFF + 4 + 2
                .equ    SP_FC_CY,      SP_FC_OFF + 8 + 2
                .equ    SP_FC_R,       SP_FC_OFF + 12 + 2
                .equ    SP_FC_COLOR,   SP_FC_OFF + 16 + 3


| ---- COMPUTE_PAIR_MASKS macro -----------------------------------
| Input:  d0.w = left, d1.w = right
| Output: 0(sp) leftMask, 2(sp) rightMask, 4(sp) numGroups,
|         6(sp) groupFirstByteOff
| Trashes: d0, d1

                .macro  COMPUTE_PAIR_MASKS
                move.w  %d0,0(%sp)             | stash left
                move.w  %d1,2(%sp)             | stash right
                move.w  %d0,%d1
                lsr.w   #4,%d1                 | groupFirst
                move.w  %d1,%d0
                lsl.w   #3,%d0                 | groupFirstByteOff
                move.w  %d0,6(%sp)
                move.w  2(%sp),%d0
                lsr.w   #4,%d0                 | groupLast
                sub.w   %d1,%d0                | numGroups
                move.w  %d0,4(%sp)
                | leftMask via LUT[bitFirst]; a5 = _gStLeftMaskLut base
                move.w  0(%sp),%d0
                and.w   #15,%d0
                add.w   %d0,%d0
                move.w  (%a5,%d0.w),%d1
                move.w  %d1,0(%sp)
                | rightMask via LUT[bitLast]; a6 = _gStRightMaskLut base
                move.w  2(%sp),%d0
                and.w   #15,%d0
                add.w   %d0,%d0
                move.w  (%a6,%d0.w),%d1
                move.w  %d1,2(%sp)
                .endm


| ---- APPLY_MASK_INLINE macro ------------------------------------
| 4-plane mask RMW with HARDCODED color. a4 advances by 8 (postinc).
| Inputs:  d0.w = mask, a4 = group ptr
| Trashes: d1 (notMask scratch)

                .macro  APPLY_MASK_INLINE  color
                move.w  %d0,%d1
                not.w   %d1
                .if  ((\color) & 1)
                or.w    %d0,(%a4)+
                .else
                and.w   %d1,(%a4)+
                .endif
                .if  ((\color) & 2)
                or.w    %d0,(%a4)+
                .else
                and.w   %d1,(%a4)+
                .endif
                .if  ((\color) & 4)
                or.w    %d0,(%a4)+
                .else
                and.w   %d1,(%a4)+
                .endif
                .if  ((\color) & 8)
                or.w    %d0,(%a4)+
                .else
                and.w   %d1,(%a4)+
                .endif
                .endm


| ---- SPAN_BODY macro --------------------------------------------
| Render one row span. Color hardcoded.
| Input:  d0.w = y (signed)
|         a3 = base, d5 = loLong, d6 = hiLong
|         masks at 0..7(sp): leftMask, rightMask, numGroups, groupFirstByteOff
| Trashes: d0, d1, a4

                .macro  SPAN_BODY  color
                | a4 = base + y*160 + groupFirstByteOff
                | y*160 via shared _gStRowOffsetLut (a2 holds lut base).
                | byteOff (y*160 + groupFirstByteOff) fits in 16 bits
                | (max 31992), so word-only ops + .w-indexed lea.
                add.w   %d0,%d0                | y * 2 (word index)
                move.w  (%a2,%d0.w),%d0        | d0 = y * 160
                add.w   6(%sp),%d0             | + groupFirstByteOff
                lea     0(%a3,%d0.w),%a4
                | numGroups in d1
                move.w  4(%sp),%d1
                tst.w   %d1
                bne.s   .Lsb_multi\@
                | single-group: combinedMask = leftMask & rightMask
                move.w  0(%sp),%d0
                and.w   2(%sp),%d0
                APPLY_MASK_INLINE \color
                bra.w   .Lsb_done\@
.Lsb_multi\@:
                | leading mask. APPLY_MASK_INLINE postinc-advances a4 by 8.
                | APPLY trashes d1, so reload numGroups after.
                move.w  0(%sp),%d0
                APPLY_MASK_INLINE \color
                move.w  4(%sp),%d1
                subq.w  #1,%d1                 | d1 = numMid
                beq.s   .Lsb_skipMid\@
.Lsb_midLoop\@:
                move.l  %d5,(%a4)+
                move.l  %d6,(%a4)+
                subq.w  #1,%d1
                bne.s   .Lsb_midLoop\@
.Lsb_skipMid\@:
                | trailing mask
                move.w  2(%sp),%d0
                APPLY_MASK_INLINE \color
.Lsb_done\@:
                .endm


| ---- CO_BODY macro: per-color exact-disk span loop body ---------
|
| Mirrors src/core/draw.c jlFillCircle's on-surface span loop:
|   for y = 0 .. r:
|       while xx > r2 - yy: xx -= 2x - 1; x--
|       span (cx-x, cx+x) at rows cy+y and cy-y
|       yy += 2y + 1
| The y == 0 pass writes row cy twice with identical bytes, which is
| cheaper than a per-color skip branch.
|
| Regs: d2 = x, d3 = y, d4 = xx, d7 = yy, a0 = r2 (loop-invariant).
| d0/d1/a4 are macro scratch; d5/d6 hold the full-group longs.

                .macro  CO_BODY  color
.Lfc_loop_\color:
                | walk x down: while xx > r2 - yy { xx -= 2x - 1; x-- }
                move.w  %a0,%d0
                sub.w   %d7,%d0                | d0 = xxLimit = r2 - yy
.Lfc_walk_\color:
                cmp.w   %d0,%d4
                bls.s   .Lfc_walkDone_\color   | xx <= xxLimit
                sub.w   %d2,%d4
                sub.w   %d2,%d4
                addq.w  #1,%d4                 | xx -= 2x - 1
                subq.w  #1,%d2                 | x--
                bra.s   .Lfc_walk_\color
.Lfc_walkDone_\color:

                | masks for x range (cx - x, cx + x), shared by both rows
                move.w  SP_FC_CX(%sp),%d0
                move.w  %d0,%d1
                sub.w   %d2,%d0
                add.w   %d2,%d1
                COMPUTE_PAIR_MASKS

                | span at row cy + y
                move.w  SP_FC_CY(%sp),%d0
                add.w   %d3,%d0
                SPAN_BODY  \color

                | span at row cy - y
                move.w  SP_FC_CY(%sp),%d0
                sub.w   %d3,%d0
                SPAN_BODY  \color

                | yy += 2y + 1; y++; loop while y <= r
                add.w   %d3,%d7
                add.w   %d3,%d7
                addq.w  #1,%d7
                addq.w  #1,%d3
                cmp.w   SP_FC_R(%sp),%d3
                bls.w   .Lfc_loop_\color
                bra.w   .Lfc_done
                .endm


                .globl  _surface68kStFillCircle

_surface68kStFillCircle:
                movem.l %d2-%d7/%a2-%a6,-(%sp)
                lea     -SP_FC_LOCAL(%sp),%sp

                | base, color
                move.l  SP_FC_BASE(%sp),%a3
                moveq   #0,%d7
                move.b  SP_FC_COLOR(%sp),%d7

                | LUT bases. a5/a6 = mask LUTs (used by COMPUTE_PAIR_MASKS).
                | a2 = shared _gStRowOffsetLut (used by SPAN_BODY for y*160).
                | All three LUTs are the shared definitions in lineSpan.s;
                | reference them absolute (single source of truth).
                lea     _gStLeftMaskLut,%a5
                lea     _gStRightMaskLut,%a6
                lea     _gStRowOffsetLut,%a2

                | loLong = ((c&1)?0xFFFF0000:0) | ((c&2)?0x0000FFFF:0)
                moveq   #0,%d5
                btst    #1,%d7
                beq.s   .Lfc_lo1
                move.w  #-1,%d5
.Lfc_lo1:
                btst    #0,%d7
                beq.s   .Lfc_lo0
                ori.l   #0xFFFF0000,%d5
.Lfc_lo0:
                | hiLong = ((c&4)?0xFFFF0000:0) | ((c&8)?0x0000FFFF:0)
                moveq   #0,%d6
                btst    #3,%d7
                beq.s   .Lfc_hi3
                move.w  #-1,%d6
.Lfc_hi3:
                btst    #2,%d7
                beq.s   .Lfc_hi2
                ori.l   #0xFFFF0000,%d6
.Lfc_hi2:

                | Exact-disk init: x = r, y = 0, xx = r2, yy = 0.
                | r <= 99 (caller's on-surface guarantee), so r2 fits a
                | word and mulu runs once, outside the loop.
                move.w  SP_FC_R(%sp),%d2
                moveq   #0,%d3
                move.w  %d2,%d4
                mulu.w  %d2,%d4                | d4 = xx = r * r
                move.w  %d4,%a0                | a0 = r2 (loop-invariant)

                | Dispatch on color (low 4 bits) -> 16 specialized loops.
                | Use a4 (gets overwritten in SPAN_BODY's first lea) as
                | dispatch scratch since a2 now holds yLut for the body.
                and.w   #0x0F,%d7
                move.w  %d7,%d0
                add.w   %d0,%d0
                add.w   %d0,%d0                | * 4 for bra.w table
                moveq   #0,%d7                 | d7 = yy (color consumed)
                lea     .Lfc_table(%pc),%a4
                jmp     0(%a4,%d0.w)

.Lfc_table:
                bra.w   .Lfc_loop_0
                bra.w   .Lfc_loop_1
                bra.w   .Lfc_loop_2
                bra.w   .Lfc_loop_3
                bra.w   .Lfc_loop_4
                bra.w   .Lfc_loop_5
                bra.w   .Lfc_loop_6
                bra.w   .Lfc_loop_7
                bra.w   .Lfc_loop_8
                bra.w   .Lfc_loop_9
                bra.w   .Lfc_loop_10
                bra.w   .Lfc_loop_11
                bra.w   .Lfc_loop_12
                bra.w   .Lfc_loop_13
                bra.w   .Lfc_loop_14
                bra.w   .Lfc_loop_15

                CO_BODY  0
                CO_BODY  1
                CO_BODY  2
                CO_BODY  3
                CO_BODY  4
                CO_BODY  5
                CO_BODY  6
                CO_BODY  7
                CO_BODY  8
                CO_BODY  9
                CO_BODY  10
                CO_BODY  11
                CO_BODY  12
                CO_BODY  13
                CO_BODY  14
                CO_BODY  15


.Lfc_done:
                lea     SP_FC_LOCAL(%sp),%sp
                movem.l (%sp)+,%d2-%d7/%a2-%a6
                rts


| (leftMaskLut/rightMaskLut now live in lineSpan.s as the shared
|  _gStLeftMaskLut / _gStRightMaskLut, referenced absolute above.)
