| Atari ST word-interleaved planar jlDrawLine -- 68000 hand-rolled.
|
| Bresenham line walker with 16-way color dispatch. Per pixel:
|   * 4-plane word RMW with branchless OR/AND chosen at compile time
|   * bit mask via 16-entry word table; group offset via (x>>4)<<3
|   * y*160 = (y<<5)+(y<<7)
|
| Caller MUST guarantee the entire line lies on-surface (full clip
| precheck). Partial-clip lines fall back to the C walker.
|
| ABI: cdecl. d2-d7/a2-a6 callee-save.
|
|   void surface68kStDrawLine(uint8_t *base,
|                             int16_t x0, int16_t y0,
|                             int16_t x1, int16_t y1,
|                             uint8_t color);
|
| Register allocation in the inner loop:
|   d2.w   = x  (current pixel)
|   d3.w   = y  (current pixel)
|   d4.w   = err
|   d5.w   = dx       (>= 0)
|   d6.w   = -dy_abs  (<= 0; "Bresenham uses -dy")
|   d7     = sx (long; moveq #1 or #-1, low word used for .w add)
|   a4     = sy (long; sign-extended)
|   a3     = base
|   a5     = _gStBitMaskWordLut
|   a2     = scratch (per-pixel: base + byteOff)
|   d0,d1  = scratch
|
| Stack scratch:
|   sp+0..1  iter counter (max(dx, dy_abs) + 1)

                .text


                .equ    SP_SAVED, 44
                .equ    SP_LOCAL, 4
                .equ    SP_OFF,         (SP_SAVED + 4 + SP_LOCAL)
                .equ    SP_BASE,    SP_OFF + 0
                .equ    SP_X0,      SP_OFF + 4 + 2
                .equ    SP_Y0,      SP_OFF + 8 + 2
                .equ    SP_X1,      SP_OFF + 12 + 2
                .equ    SP_Y1,      SP_OFF + 16 + 2
                .equ    SP_COLOR,   SP_OFF + 20 + 3


| ---- DL_PLOT: 4-plane word RMW for hardcoded color ----
| Inputs:  d2.w = x, d3.w = y, a3 = base, a5 = _gStBitMaskWordLut
| Trashes: d0, d1, a2

                .macro  DL_PLOT  color
                | byteOff = y*160 + (x>>4)*8 (fits in 16 bits since
                | surface is 32000 bytes < 32K). Skip ext.l + .l add
                | + .l indexed lea -- all word-sized ops save 14 cyc/pixel.
                move.w  %d3,%d0
                add.w   %d0,%d0                | y * 2 (word index)
                move.w  (%a6,%d0.w),%d0        | d0 = y * 160
                move.w  %d2,%d1
                lsr.w   #4,%d1
                lsl.w   #3,%d1                 | (x>>4) * 8
                add.w   %d1,%d0                | d0 = byteOff (fits in 16 bits)
                lea     0(%a3,%d0.w),%a2       | a2 = base + byteOff
                | d1 = bitMask, d0 = notMask
                move.w  %d2,%d1
                and.w   #15,%d1
                add.w   %d1,%d1
                move.w  (%a5,%d1.w),%d1
                move.w  %d1,%d0
                not.w   %d0
                | per-plane RMW with postinc (drops 4 cyc per RMW vs
                | displacement (d8,An) = 16 cyc, plain (An)+ = 12 cyc).
                .if  ((\color) & 1)
                or.w    %d1,(%a2)+
                .else
                and.w   %d0,(%a2)+
                .endif
                .if  ((\color) & 2)
                or.w    %d1,(%a2)+
                .else
                and.w   %d0,(%a2)+
                .endif
                .if  ((\color) & 4)
                or.w    %d1,(%a2)+
                .else
                and.w   %d0,(%a2)+
                .endif
                .if  ((\color) & 8)
                or.w    %d1,(%a2)+
                .else
                and.w   %d0,(%a2)+
                .endif
                .endm


| ---- DL_BODY: full Bresenham loop body for hardcoded color ----

                .macro  DL_BODY  color
.LdlStLoop_\color:
                DL_PLOT  \color
                | e2 = 2 * err
                move.w  %d4,%d0
                add.w   %d0,%d0                | d0 = e2
                | if (e2 >= dy) { err += dy; x += sx; }
                cmp.w   %d6,%d0
                blt.s   .LdlStNoX_\color
                add.w   %d6,%d4
                add.w   %d7,%d2
.LdlStNoX_\color:
                | if (e2 <= dx) { err += dx; y += sy; }
                cmp.w   %d5,%d0
                bgt.s   .LdlStNoY_\color
                add.w   %d5,%d4
                add.w   %a4,%d3                | sy.w from a4
.LdlStNoY_\color:
                subq.w  #1,0(%sp)
                bne.w   .LdlStLoop_\color
                bra.w   .LdlStDone
                .endm


                .globl  _surface68kStDrawLine

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

                | Load base & luts.
                move.l  SP_BASE(%sp),%a3
                lea     _gStBitMaskWordLut(%pc),%a5
                | a6 = yLut base (yp -> yp*160) for use in DL_PLOT.
                lea     _gStRowOffsetLut(%pc),%a6

                | x = x0, y = y0
                move.w  SP_X0(%sp),%d2
                move.w  SP_Y0(%sp),%d3

                | dx = abs(x1 - x0), sx = sign(x1 - x0)
                move.w  SP_X1(%sp),%d5
                sub.w   %d2,%d5                | d5 = x1 - x0
                bge.s   .LdlSxPos
                neg.w   %d5
                moveq   #-1,%d7
                bra.s   .LdlSxDone
.LdlSxPos:
                moveq   #1,%d7
.LdlSxDone:

                | dy_abs in d6, sy in d0 (-> a4)
                move.w  SP_Y1(%sp),%d6
                sub.w   %d3,%d6                | d6 = y1 - y0
                bge.s   .LdlSyPos
                neg.w   %d6
                moveq   #-1,%d0
                bra.s   .LdlSyDone
.LdlSyPos:
                moveq   #1,%d0
.LdlSyDone:
                ext.l   %d0
                movea.l %d0,%a4                | a4 = sy

                | iter counter = max(dx, dy_abs) + 1
                move.w  %d5,%d0
                cmp.w   %d6,%d0
                bge.s   .LdlNitDone
                move.w  %d6,%d0
.LdlNitDone:
                addq.w  #1,%d0
                move.w  %d0,0(%sp)

                | err = dx - dy_abs (== dx + dy where dy negative)
                move.w  %d5,%d4
                sub.w   %d6,%d4                | d4 = err
                neg.w   %d6                    | d6 = -dy_abs (negative)

                | Dispatch on color (low 4 bits) -> 16 specialized loops.
                moveq   #0,%d0
                move.b  SP_COLOR(%sp),%d0
                and.w   #0x0F,%d0
                add.w   %d0,%d0
                add.w   %d0,%d0                | * 4 for bra.w table
                lea     .LdlStTable(%pc),%a2   | a2 scratch (a6 holds yLut)
                jmp     0(%a2,%d0.w)

.LdlStTable:
                bra.w   .LdlStLoop_0
                bra.w   .LdlStLoop_1
                bra.w   .LdlStLoop_2
                bra.w   .LdlStLoop_3
                bra.w   .LdlStLoop_4
                bra.w   .LdlStLoop_5
                bra.w   .LdlStLoop_6
                bra.w   .LdlStLoop_7
                bra.w   .LdlStLoop_8
                bra.w   .LdlStLoop_9
                bra.w   .LdlStLoop_10
                bra.w   .LdlStLoop_11
                bra.w   .LdlStLoop_12
                bra.w   .LdlStLoop_13
                bra.w   .LdlStLoop_14
                bra.w   .LdlStLoop_15

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

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


                .align  2
| 16 word entries: _gStBitMaskWordLut[i] = 1 << (15 - i), for i in
| 0..15. Exported (like _gStRowOffsetLut) so circle.s can reference
| it via absolute addressing -- single source of truth for the per-
| pixel bit mask shared by jlDrawLine and jlDrawCircle.
                .globl  _gStBitMaskWordLut
_gStBitMaskWordLut:
                .word   0x8000, 0x4000, 0x2000, 0x1000
                .word   0x0800, 0x0400, 0x0200, 0x0100
                .word   0x0080, 0x0040, 0x0020, 0x0010
                .word   0x0008, 0x0004, 0x0002, 0x0001


| ---- surface68kStFillSpan ---------------------------------------
|
| Single-row span fill: leading-mask group + middle long-fills +
| trailing-mask group, all in one frame. Caller pre-clips so the
| span is fully on-surface.
|
|   void surface68kStFillSpan(uint8_t *base,
|                             int16_t left, int16_t right,
|                             int16_t y, uint8_t color);
|
|   Caller guarantees: 0 <= left <= right < 320, 0 <= y < 200.
|
| Register layout:
|   a3 = base
|   a4 = current group pointer
|   d2.w = leftMask (then trailing trampoline target)
|   d3.w = rightMask
|   d4.w = numGroups - 1 (middle iter count when > 0)
|   d5.l = loLong (planes 0+1 long template)
|   d6.l = hiLong (planes 2+3 long template)
|   d7.b = color (low nibble; tested via btst)
|   d0,d1 = scratch

                .equ    SP_FS_SAVED, 44
                .equ    SP_FS_OFF,         (SP_FS_SAVED + 4)
                .equ    SP_FS_BASE,    SP_FS_OFF + 0
                .equ    SP_FS_LEFT,    SP_FS_OFF + 4 + 2
                .equ    SP_FS_RIGHT,   SP_FS_OFF + 8 + 2
                .equ    SP_FS_Y,       SP_FS_OFF + 12 + 2
                .equ    SP_FS_COLOR,   SP_FS_OFF + 16 + 3


                .globl  _surface68kStFillSpan

_surface68kStFillSpan:
                movem.l %d2-%d7/%a2-%a6,-(%sp)

                move.l  SP_FS_BASE(%sp),%a3
                moveq   #0,%d7
                move.b  SP_FS_COLOR(%sp),%d7   | d7 = color

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

                | rowBase = base + y*160 -> a4
                move.w  SP_FS_Y(%sp),%d0
                ext.l   %d0
                move.l  %d0,%d1
                lsl.l   #5,%d0
                lsl.l   #7,%d1
                add.l   %d1,%d0                | d0 = y*160
                lea     0(%a3,%d0.l),%a4

                | left in d0, right in d1
                move.w  SP_FS_LEFT(%sp),%d0
                move.w  SP_FS_RIGHT(%sp),%d1

                | bitFirst in d2, bitLast in d3
                move.w  %d0,%d2
                and.w   #15,%d2
                move.w  %d1,%d3
                and.w   #15,%d3

                | a4 += groupFirst * 8
                | numGroups = groupLast - groupFirst (in d4)
                move.w  %d0,%d4
                lsr.w   #4,%d4                 | d4 = groupFirst
                move.w  %d4,%d0                | save groupFirst into d0
                lsl.w   #3,%d0                 | d0 = groupFirst*8
                ext.l   %d0
                add.l   %d0,%a4
                move.w  %d1,%d0
                lsr.w   #4,%d0                 | d0 = groupLast
                sub.w   %d4,%d0                | d0 = groupLast - groupFirst
                move.w  %d0,%d4                | d4 = numGroups

                | leftMask  = (1 << (16 - bitFirst)) - 1
                moveq   #16,%d0
                sub.w   %d2,%d0                | d0 = 16 - bitFirst (1..16)
                moveq   #1,%d2
                lsl.l   %d0,%d2                | 1 << (16 - bitFirst)
                subq.l  #1,%d2                 | d2.w = leftMask

                | rightMask = ~((1 << (15 - bitLast)) - 1)
                moveq   #15,%d0
                sub.w   %d3,%d0                | d0 = 15 - bitLast (0..15)
                moveq   #1,%d3
                lsl.l   %d0,%d3                | 1 << (15 - bitLast)
                subq.l  #1,%d3                 | inverse mask
                not.w   %d3                    | d3.w = rightMask

                | If numGroups == 0, single-group: mask = leftMask & rightMask
                tst.w   %d4
                bne.s   .LfsMulti

                and.w   %d2,%d3                | d3 = combinedMask
                move.w  %d3,%d2
                bsr.s   .LfsApplyMask
                bra.w   .LfsDone

.LfsMulti:
                | Leading mask (d2 already = leftMask)
                bsr.s   .LfsApplyMask
                addq.l  #8,%a4                 | next group

                | numMid = numGroups - 1
                subq.w  #1,%d4
                beq.s   .LfsTrailing

.LfsMidLoop:
                move.l  %d5,(%a4)+
                move.l  %d6,(%a4)+
                subq.w  #1,%d4
                bne.s   .LfsMidLoop

.LfsTrailing:
                move.w  %d3,%d2                | d2 = rightMask
                bsr.s   .LfsApplyMask

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


| Apply 4-plane word RMW at (a4) using mask in d2 (or notMask in d0).
| Plane N: if (color bit N) OR mask else AND notMask.
| Inputs: a4, d2.w = mask, d7.b = color
| Trashes: d0
| Returns via rts.

.LfsApplyMask:
                move.w  %d2,%d0
                not.w   %d0                    | d0 = notMask
                btst    #0,%d7
                beq.s   .LfsAm0a
                or.w    %d2,(%a4)
                bra.s   .LfsAm1
.LfsAm0a:
                and.w   %d0,(%a4)
.LfsAm1:
                btst    #1,%d7
                beq.s   .LfsAm1a
                or.w    %d2,2(%a4)
                bra.s   .LfsAm2
.LfsAm1a:
                and.w   %d0,2(%a4)
.LfsAm2:
                btst    #2,%d7
                beq.s   .LfsAm2a
                or.w    %d2,4(%a4)
                bra.s   .LfsAm3
.LfsAm2a:
                and.w   %d0,4(%a4)
.LfsAm3:
                btst    #3,%d7
                beq.s   .LfsAm3a
                or.w    %d2,6(%a4)
                rts
.LfsAm3a:
                and.w   %d0,6(%a4)
                rts


| ---- surface68kStFillRectSingleGroup -----------------------------
|
| Fill rect when groupFirst == groupLast (thin/single-column rect).
| Caller pre-computes firstGroupPtr = base + y*160 + groupFirst*8
| and the mask = leftMask & rightMask.
|
|   void surface68kStFillRectSingleGroup(uint8_t *firstGroupPtr,
|                                        uint16_t mask,
|                                        uint16_t h,
|                                        uint8_t color);
|
| Dispatched on color (low nibble) -> 16 specialized loops with
| hardcoded OR/AND per plane. Inner loop is 4 plane word RMWs +
| advance row + branch.
|
| jlDrawLine V routes to jlFillRect 1xH which lands here.

                .equ    SP_FRG_SAVED, 24            | d2-d5/a2-a3 = 6 longs
                .equ    SP_FRG_OFF,         (SP_FRG_SAVED + 4)
                .equ    SP_FRG_PTR,    SP_FRG_OFF + 0
                .equ    SP_FRG_MASK,   SP_FRG_OFF + 4 + 2
                .equ    SP_FRG_H,      SP_FRG_OFF + 8 + 2
                .equ    SP_FRG_COLOR,  SP_FRG_OFF + 12 + 3


                .macro  FRG_LOOP color
.Lfrg_loop_\color:
                .if  ((\color) & 1)
                or.w    %d3,(%a3)+
                .else
                and.w   %d4,(%a3)+
                .endif
                .if  ((\color) & 2)
                or.w    %d3,(%a3)+
                .else
                and.w   %d4,(%a3)+
                .endif
                .if  ((\color) & 4)
                or.w    %d3,(%a3)+
                .else
                and.w   %d4,(%a3)+
                .endif
                .if  ((\color) & 8)
                or.w    %d3,(%a3)+
                .else
                and.w   %d4,(%a3)+
                .endif
                lea     152(%a3),%a3            | a3 now at row start; advance to next row (160-8)
                subq.w  #1,%d5
                bne.w   .Lfrg_loop_\color
                bra.w   .Lfrg_done
                .endm


                .globl  _surface68kStFillRectSingleGroup

_surface68kStFillRectSingleGroup:
                movem.l %d2-%d5/%a2-%a3,-(%sp)

                move.l  SP_FRG_PTR(%sp),%a3
                move.w  SP_FRG_MASK(%sp),%d3
                move.w  SP_FRG_H(%sp),%d5
                tst.w   %d5
                beq.w   .Lfrg_done
                move.w  %d3,%d4
                not.w   %d4                        | d4 = notMask

                | Color dispatch
                moveq   #0,%d2
                move.b  SP_FRG_COLOR(%sp),%d2
                and.w   #0x0F,%d2
                add.w   %d2,%d2
                add.w   %d2,%d2                    | * 4 for bra.w table
                lea     .Lfrg_table(%pc),%a2
                jmp     0(%a2,%d2.w)

.Lfrg_table:
                bra.w   .Lfrg_loop_0
                bra.w   .Lfrg_loop_1
                bra.w   .Lfrg_loop_2
                bra.w   .Lfrg_loop_3
                bra.w   .Lfrg_loop_4
                bra.w   .Lfrg_loop_5
                bra.w   .Lfrg_loop_6
                bra.w   .Lfrg_loop_7
                bra.w   .Lfrg_loop_8
                bra.w   .Lfrg_loop_9
                bra.w   .Lfrg_loop_10
                bra.w   .Lfrg_loop_11
                bra.w   .Lfrg_loop_12
                bra.w   .Lfrg_loop_13
                bra.w   .Lfrg_loop_14
                bra.w   .Lfrg_loop_15

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

.Lfrg_done:
                movem.l (%sp)+,%d2-%d5/%a2-%a3
                rts


| ---- surface68kStTileFill8x8 ---------------------------------------
|
| Specialized 8x8 single-group fill: 16-way color dispatch + 8 rows
| fully unrolled. Drops the per-row subq+bne overhead that the
| generic FRG_LOOP pays. Used by jlpTileFill.
|
|   void surface68kStTileFill8x8(uint8_t *firstGroupPtr,
|                                uint16_t mask,
|                                uint8_t color);
|
| Per row body: 4 plane RMW with postinc + lea 152(a3),a3 to next
| row. Row 7 skips the trailing lea (a3 not used after).

                .equ    SP_TF_SAVED, 16        | d3-d4/a2-a3 = 4 longs
                .equ    SP_TF_OFF,         (SP_TF_SAVED + 4)
                .equ    SP_TF_PTR,    SP_TF_OFF + 0
                .equ    SP_TF_MASK,   SP_TF_OFF + 4 + 2
                .equ    SP_TF_COLOR,  SP_TF_OFF + 8 + 3


                .macro  TF8_ROW_BARE  color
                .if  ((\color) & 1)
                or.w    %d3,(%a3)+
                .else
                and.w   %d4,(%a3)+
                .endif
                .if  ((\color) & 2)
                or.w    %d3,(%a3)+
                .else
                and.w   %d4,(%a3)+
                .endif
                .if  ((\color) & 4)
                or.w    %d3,(%a3)+
                .else
                and.w   %d4,(%a3)+
                .endif
                .if  ((\color) & 8)
                or.w    %d3,(%a3)+
                .else
                and.w   %d4,(%a3)+
                .endif
                .endm


                .macro  TF8_ROW  color
                TF8_ROW_BARE  \color
                lea     152(%a3),%a3
                .endm


                .macro  TF8_BODY  color
.Ltf8_body_\color:
                TF8_ROW       \color           | row 0
                TF8_ROW       \color           | row 1
                TF8_ROW       \color           | row 2
                TF8_ROW       \color           | row 3
                TF8_ROW       \color           | row 4
                TF8_ROW       \color           | row 5
                TF8_ROW       \color           | row 6
                TF8_ROW_BARE  \color           | row 7 (no trailing lea)
                bra.w         .Ltf8_done
                .endm


                .globl  _surface68kStTileFill8x8

_surface68kStTileFill8x8:
                movem.l %d3-%d4/%a2-%a3,-(%sp)

                move.l  SP_TF_PTR(%sp),%a3
                move.w  SP_TF_MASK(%sp),%d3
                move.w  %d3,%d4
                not.w   %d4

                | Color dispatch
                moveq   #0,%d0
                move.b  SP_TF_COLOR(%sp),%d0
                and.w   #0x0F,%d0
                add.w   %d0,%d0
                add.w   %d0,%d0                | * 4 for bra.w table
                lea     .Ltf8_table(%pc),%a2
                jmp     0(%a2,%d0.w)

.Ltf8_table:
                bra.w   .Ltf8_body_0
                bra.w   .Ltf8_body_1
                bra.w   .Ltf8_body_2
                bra.w   .Ltf8_body_3
                bra.w   .Ltf8_body_4
                bra.w   .Ltf8_body_5
                bra.w   .Ltf8_body_6
                bra.w   .Ltf8_body_7
                bra.w   .Ltf8_body_8
                bra.w   .Ltf8_body_9
                bra.w   .Ltf8_body_10
                bra.w   .Ltf8_body_11
                bra.w   .Ltf8_body_12
                bra.w   .Ltf8_body_13
                bra.w   .Ltf8_body_14
                bra.w   .Ltf8_body_15

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

.Ltf8_done:
                movem.l (%sp)+,%d3-%d4/%a2-%a3
                rts


| ---- surface68kStFillRectMulti -------------------------------------
|
| Multi-group jlFillRect: groupFirst != groupLast. Caller pre-clips.
| Dispatched on color (low nibble) -> 16 specialized H-row loops.
|
|   void surface68kStFillRectMulti(uint8_t *base,
|                                  int16_t x, int16_t y,
|                                  uint16_t w, uint16_t h,
|                                  uint8_t color);
|
| Per row body (per color C):
|   1. Leading mask: 4 hardcoded plane RMW with leftMask
|   2. Middle: numMid groups of 2 long-writes (loLong, hiLong)
|   3. Trailing mask: 4 hardcoded plane RMW with rightMask
|   4. Advance rowBase by 160; decrement h; loop.
|
| Register layout in inner loop:
|   d2.w = leftMask       d3.w = rightMask
|   d4.w = ~leftMask      d5.w = ~rightMask
|   d6.l = loLong         d7.l = hiLong
|   a3   = rowBase (advances by 160 each iter)
|   a4   = a_grp (per-row scratch)
|   d0,d1 = scratch
|
| Stack scratch (4 bytes at sp+0):
|   0..1  numMid   (word, reload per row for mid loop)
|   2..3  h        (word, decrement per row)

                .equ    SP_FRM_SAVED, 44
                .equ    SP_FRM_LOCAL, 4
                .equ    SP_FRM_OFF,         (SP_FRM_SAVED + 4 + SP_FRM_LOCAL)
                .equ    SP_FRM_BASE,    SP_FRM_OFF + 0
                .equ    SP_FRM_X,       SP_FRM_OFF + 4 + 2
                .equ    SP_FRM_Y,       SP_FRM_OFF + 8 + 2
                .equ    SP_FRM_W,       SP_FRM_OFF + 12 + 2
                .equ    SP_FRM_H,       SP_FRM_OFF + 16 + 2
                .equ    SP_FRM_COLOR,   SP_FRM_OFF + 20 + 3


                .macro  FRM_LOOP color
.LfrM_loop_\color:
                | Leading mask at (a4)+, walking from row start
                move.l  %a3,%a4                | a4 = current row's groupFirst byte
                .if  ((\color) & 1)
                or.w    %d2,(%a4)+
                .else
                and.w   %d4,(%a4)+
                .endif
                .if  ((\color) & 2)
                or.w    %d2,(%a4)+
                .else
                and.w   %d4,(%a4)+
                .endif
                .if  ((\color) & 4)
                or.w    %d2,(%a4)+
                .else
                and.w   %d4,(%a4)+
                .endif
                .if  ((\color) & 8)
                or.w    %d2,(%a4)+
                .else
                and.w   %d4,(%a4)+
                .endif
                | a4 now points to next group (8 bytes past row start).
                | Middle long-fill
                move.w  0(%sp),%d0
                tst.w   %d0
                beq.s   .LfrM_skipMid_\color
.LfrM_midLoop_\color:
                move.l  %d6,(%a4)+
                move.l  %d7,(%a4)+
                subq.w  #1,%d0
                bne.s   .LfrM_midLoop_\color
.LfrM_skipMid_\color:
                | Trailing mask at (a4)+
                .if  ((\color) & 1)
                or.w    %d3,(%a4)+
                .else
                and.w   %d5,(%a4)+
                .endif
                .if  ((\color) & 2)
                or.w    %d3,(%a4)+
                .else
                and.w   %d5,(%a4)+
                .endif
                .if  ((\color) & 4)
                or.w    %d3,(%a4)+
                .else
                and.w   %d5,(%a4)+
                .endif
                .if  ((\color) & 8)
                or.w    %d3,(%a4)+
                .else
                and.w   %d5,(%a4)+
                .endif
                | Advance row (a3 unchanged through the body)
                lea     160(%a3),%a3
                subq.w  #1,2(%sp)
                bne.w   .LfrM_loop_\color
                bra.w   .LfrM_done
                .endm


                .globl  _surface68kStFillRectMulti

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

                | Load color, build loLong (d6) and hiLong (d7)
                moveq   #0,%d0
                move.b  SP_FRM_COLOR(%sp),%d0
                moveq   #0,%d6
                btst    #1,%d0
                beq.s   .LfrM_lo1
                move.w  #-1,%d6
.LfrM_lo1:
                btst    #0,%d0
                beq.s   .LfrM_lo0
                ori.l   #0xFFFF0000,%d6
.LfrM_lo0:
                moveq   #0,%d7
                btst    #3,%d0
                beq.s   .LfrM_hi3
                move.w  #-1,%d7
.LfrM_hi3:
                btst    #2,%d0
                beq.s   .LfrM_hi2
                ori.l   #0xFFFF0000,%d7
.LfrM_hi2:

                | Compute group ptrs and masks
                | groupFirst = x >> 4; groupFirstByteOff = groupFirst * 8
                | bitFirst = x & 15
                move.w  SP_FRM_X(%sp),%d0
                move.w  SP_FRM_W(%sp),%d1
                add.w   %d0,%d1
                subq.w  #1,%d1                 | d1 = x + w - 1 (last pixel)

                | leftMask via LUT[bitFirst]
                move.w  %d0,%d2
                and.w   #15,%d2
                add.w   %d2,%d2
                lea     _gStLeftMaskLut(%pc),%a2
                move.w  (%a2,%d2.w),%d2        | d2 = leftMask
                move.w  %d2,%d4
                not.w   %d4                    | d4 = notLeftMask

                | rightMask via LUT[bitLast]
                move.w  %d1,%d3
                and.w   #15,%d3
                add.w   %d3,%d3
                lea     _gStRightMaskLut(%pc),%a2
                move.w  (%a2,%d3.w),%d3        | d3 = rightMask
                move.w  %d3,%d5
                not.w   %d5                    | d5 = notRightMask

                | numMid = (last >> 4) - (x >> 4) - 1
                move.w  %d1,%a2                | a2.w = lastPixel (temp)
                move.l  %a2,%d1
                lsr.w   #4,%d1                 | groupLast (low word)
                move.w  %d0,%a2
                move.l  %a2,%d0
                lsr.w   #4,%d0                 | groupFirst
                move.w  %d0,%a4                | a4.w = groupFirst (save for byteOff calc)
                sub.w   %d0,%d1                | d1 = groupLast - groupFirst
                subq.w  #1,%d1                 | d1 = numMid (>= 0 since multi-group caller)
                move.w  %d1,0(%sp)             | numMid -> stack

                | h -> stack
                move.w  SP_FRM_H(%sp),%d1
                move.w  %d1,2(%sp)

                | a3 = base + y*160 + groupFirst*8
                move.w  SP_FRM_Y(%sp),%d0
                ext.l   %d0
                move.l  %d0,%d1
                lsl.l   #5,%d0
                lsl.l   #7,%d1
                add.l   %d1,%d0                | y*160
                move.l  SP_FRM_BASE(%sp),%a3
                add.l   %d0,%a3                | rowBase = base + y*160
                move.l  %a4,%d0                | groupFirst
                lsl.w   #3,%d0                 | * 8
                ext.l   %d0
                add.l   %d0,%a3                | + groupFirst*8

                | Dispatch on color
                moveq   #0,%d0
                move.b  SP_FRM_COLOR(%sp),%d0
                and.w   #0x0F,%d0
                add.w   %d0,%d0
                add.w   %d0,%d0
                lea     .LfrM_table(%pc),%a2
                jmp     0(%a2,%d0.w)

.LfrM_table:
                bra.w   .LfrM_loop_0
                bra.w   .LfrM_loop_1
                bra.w   .LfrM_loop_2
                bra.w   .LfrM_loop_3
                bra.w   .LfrM_loop_4
                bra.w   .LfrM_loop_5
                bra.w   .LfrM_loop_6
                bra.w   .LfrM_loop_7
                bra.w   .LfrM_loop_8
                bra.w   .LfrM_loop_9
                bra.w   .LfrM_loop_10
                bra.w   .LfrM_loop_11
                bra.w   .LfrM_loop_12
                bra.w   .LfrM_loop_13
                bra.w   .LfrM_loop_14
                bra.w   .LfrM_loop_15

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

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


                .align  2
| Leading/trailing edge-mask LUTs, exported (like _gStRowOffsetLut)
| as the single source of truth shared with jlFillCircle.s via
| absolute addressing.
|   _gStLeftMaskLut[i]  = (1 << (16 - i)) - 1,    indexed by bitFirst
|   _gStRightMaskLut[i] = ~((1 << (15 - i)) - 1), indexed by bitLast
                .globl  _gStLeftMaskLut
_gStLeftMaskLut:
                .word   0xFFFF, 0x7FFF, 0x3FFF, 0x1FFF
                .word   0x0FFF, 0x07FF, 0x03FF, 0x01FF
                .word   0x00FF, 0x007F, 0x003F, 0x001F
                .word   0x000F, 0x0007, 0x0003, 0x0001

                .globl  _gStRightMaskLut
_gStRightMaskLut:
                .word   0x8000, 0xC000, 0xE000, 0xF000
                .word   0xF800, 0xFC00, 0xFE00, 0xFF00
                .word   0xFF80, 0xFFC0, 0xFFE0, 0xFFF0
                .word   0xFFF8, 0xFFFC, 0xFFFE, 0xFFFF


                .align  2
| ST surface stride. Single asm-side source of truth, mirroring
| include/joey/surface.h SURFACE_BYTES_PER_ROW (160). A future stride
| change must touch both; the guard below fails the assemble if this
| drifts from 160.
                .equ    ST_BYTES_PER_ROW, 160
                .if     ST_BYTES_PER_ROW != 160
                .error  "ST_BYTES_PER_ROW must stay 160 (== surface.h SURFACE_BYTES_PER_ROW)"
                .endif
| Shared y -> y*160 LUT. Used by jlDrawLine (DL_PLOT), jlDrawCircle
| (YP_REC), jlFillCircle (SPAN_BODY). 200 words = 400 bytes.
| Replaces a 44-cyc lsl.w #5 + lsl.w #7 + add.w shift chain with
| a 14-cyc indexed-word load. Exported so circle.s and jlFillCircle.s
| can reference it via absolute addressing without duplication.
                .globl  _gStRowOffsetLut
_gStRowOffsetLut:
                .set    li_y, 0
                .rept   200
                .word   li_y * ST_BYTES_PER_ROW
                .set    li_y, li_y + 1
                .endr


| ---- surface68kStLongFill ----------------------------------------
|
| Bulk long-fill helper for full-row fills (jlSurfaceClear, jlFillRect
| 320x200). Writes numGroups groups of 8 bytes (loLong, hiLong)
| starting at dst. Register-pumps movem.l d2-d7/a1-a6 (12 longs =
| 6 groups = 48 bytes per batch) so the per-batch lea/subq/bne loop
| overhead is amortised over twice the data of the old 24-byte batch.
| a1 is caller-saved scratch (free); a2-a6 and d2-d7 are callee-saved
| and restored on exit.
|
|   void surface68kStLongFill(uint8_t *dst,
|                             uint16_t numGroups,
|                             uint32_t loLong,
|                             uint32_t hiLong);
|
| STF has no blitter, so this CPU movem.l burst is the fast path; the
| fill is memory-bandwidth bound, so the win is in cutting loop
| overhead (one lea/subq/bne per 48 bytes instead of per 24).

                .equ    SP_LF_SAVED, 44           | d2-d7 + a2-a6 = 11 longs
                .equ    SP_LF_OFF,         (SP_LF_SAVED + 4)
                .equ    SP_LF_DST,     SP_LF_OFF + 0
                .equ    SP_LF_NGROUPS, SP_LF_OFF + 4 + 2
                .equ    SP_LF_LO,      SP_LF_OFF + 8
                .equ    SP_LF_HI,      SP_LF_OFF + 12


                .globl  _surface68kStLongFill

_surface68kStLongFill:
                movem.l %d2-%d7/%a2-%a6,-(%sp)

                move.l  SP_LF_DST(%sp),%a0
                move.l  SP_LF_LO(%sp),%d2
                move.l  SP_LF_HI(%sp),%d3
                move.w  SP_LF_NGROUPS(%sp),%d0

                | Spread the {lo,hi} pattern across 12 registers in the
                | exact order movem.l writes them (d2-d7 then a1-a6, low
                | to high address) = 6 consecutive 8-byte groups:
                |   lo -> d2,d4,d6,a1,a3,a5 ; hi -> d3,d5,d7,a2,a4,a6
                move.l  %d2,%d4
                move.l  %d2,%d6
                move.l  %d2,%a1
                move.l  %d2,%a3
                move.l  %d2,%a5
                move.l  %d3,%d5
                move.l  %d3,%d7
                move.l  %d3,%a2
                move.l  %d3,%a4
                move.l  %d3,%a6

                | numBatches = numGroups / 6 (quotient), tail = remainder
                ext.l   %d0
                divu.w  #6,%d0
                move.l  %d0,%d1
                swap    %d1                        | d1.w = remainder (0..5)
                tst.w   %d0                        | quotient
                beq.s   .Llf_tail
.Llf_loop:
                movem.l %d2-%d7/%a1-%a6,(%a0)       | 48 bytes = 6 groups
                lea     48(%a0),%a0
                subq.w  #1,%d0
                bne.s   .Llf_loop

.Llf_tail:
                | Remainder: 0..5 groups of 8 bytes
                tst.w   %d1
                beq.s   .Llf_done
.Llf_tailLoop:
                move.l  %d2,(%a0)+
                move.l  %d3,(%a0)+
                subq.w  #1,%d1
                bne.s   .Llf_tailLoop

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