[atari7800] sample 7800 source code using (ZP,X)

As per supercat's suggestion in my AtariAge blog entry, herein find an 
alternate version of my sample 7800 display list builder using the unloved 
(zp,x) addressing mode.

The main advantage is a surprising savings of 25 cycles per sprite (161 
versus 186), although some/all of this may be lost when wrap around is 
added. The disadvantage is the routine requires 2 bytes of zero page RAM per 
display list.

I've also added the dummy 5 byte sprite header to set Write Mode which was 
missing from the first skeleton.

7800 sprite to display list skeleton using (ZP,X)

YRES    EQU     192             ; screen resolution
DLHIGH  EQU     16              ; display list height
NUMDL   EQU     YRES/DLHIGH     ; number of display lists
DLLEN   EQU
NUMSPR  EQU
DLIFLAG DS      1

DLIDX   DS      NUMDL*2

SPRYPOS DS      NUMSPR          ; sprite Y postion, 0 = top
SPRXPOS DS      NUMSPR          ; sprite X position, 0 = left
SPRIDX  DS      NUMSPR          ; sprite table index
SPRPALW DS      NUMSPR          ; palette/width

DLWMSET DS      5               ; set WM=0
DLNULL  DS      2               ; null display list
DLLON   DS      (NUMDL+4)*3
DL00    DS      DLLEN
DL10    DS      DLLEN
DL20    DS      DLLEN
DL30    DS      DLLEN
DL40    DS      DLLEN
DL50    DS      DLLEN
DL60    DS      DLLEN
DL70    DS      DLLEN
DL80    DS      DLLEN
DL90    DS      DLLEN
DLA0    DS      DLLEN
DLB0    DS      DLLEN
DLC0    DS      DLLEN
DLD0    DS      DLLEN
DLE0    DS      DLLEN

; A flag in each DLL header causes an NMI when MARIA completes DMA for the
; previous zone.  This can be used for a variety of things (changing 
colours,
; resolutions, scrolling backgrounds).  Here it is just used to signal when 
MARIA is
; done displaying the visible screen so the display lists can be updated.

DLLNMI  INC     DLIFLAG
IRQRTI  RTI

; Start routine (stub)

START   SEI                     ; disable IRQ
        CLD                     ; binary arithmetic
        LDA     #$67
        STA     INPTCTRL        ; lock in 7800 mode
        STA     CTRL            ; disable DMA
        LDX     #$FF
        TXS                     ; set stack pointer

; Missing: Initialize MARIA/TIA/RIOT registers and zero page variables

; Build the DLL automatically.

CPYDLL  LDY     #0              ; copy DLL from ROM to RAM
1$      LDA     DLLTOP,Y        ; copy top from ROM
        STA     DLWMSET,Y
        INY
        CPY     #DLLBOT-DLLTOP
        BNE     1$
        LDX     #0              ; build active screen DLLs
2$      LDA     #$4F            ; 16 high zone, 4K (16 line) holey DMA
        STA     DLLON,Y
        INY
        LDA     DLPTRW+1,X      ; MSB
        STA     DLIDX+1,X
        STA     DLLON,Y
        INY
        LDA     DLPTRW,X        ; LSB
        STA     DLIDX,X
        STA     DLLON,Y
        INY
        INX
        INX
        CPX     #NUMDL*2
        BNE     2$
        LDX     #0
3$      LDA     DLLBOT,X        ; copy bottom from ROM
        STA     DLLON,Y
        INY
        INX
        CPX     #DLLEND-DLLBOT
        BNE     3$

; Wait for VBLANK to start, turn on DMA, and fall into the display
; list builder routine.

MSTAT0  BIT     MSTAT           ; wait for MARIA enabled
        BMI     MSTAT0
MSTAT1  BIT     MSTAT           ; wait for MARIA disabled
        BPL     MSTAT1
        LDA     #%01000000      ; color, DMA on, single char, black border, 
transparent, 160
        STA     CTRL

; This is the display list builder which could be considered the kernel of
; a 7800 game.  The code takes the X/Y positions (top left) of each sprite,
; figures out which display list they belong it and adds a sprite header to 
the
; end of the list.  There are several sections to this routine.
;
; DLINI         zeros the end of list index
; DLCPY         figures out which display list a sprite belongs in (via a
;               table lookup of Ypos/16), puts the end of list index in Y,
;               copies the data for each sprite into two display lists
; DLFIN         writes a zero one byte beyond the end of the current display
;               list.  This is the null header which tells MARIA it's
;               reached the end of the list.

DLINI   LDY     #NUMDL*2        ; reset DLIDX pointers
1$      LDA     DLPTRS-2,Y      ; reset LSB
        STA     DLIDX-2,Y
        DEY
        DEY
        BNE     1$              ; Y = 0

DLCPY   LDA     SPRYPOS,Y       ; determine display list
        AND     #$F0            ; (Y / DLHIGH) * 2
        LSR
        LSR
        LSR
        TAX
        LDA     SPRIDX,Y
        STA     (DLIDX,X)       ; low address
        INC     DLIDX,X
        LDA     SPRPALW,Y
        STA     (DLIDX,X)       ; palette + width
        INC     DLIDX,X
        LDA     SPRYPOS,Y
        AND     #$DLHIGH-1      ; mask
        ORA     #>SPRDATA       ; base address
        STA     (DLIDX,X)       ; high address
        INC     DLIDX,X
        LDA     SPRXPOS,Y
        STA     (DLIDX,X)       ; horizontal position
        INC     DLIDX,X
        CPX     #(NUMDL-1)*2    ; bottom of screen?
        BEQ     1$
        INX                     ; next display list
        INX
        LDA     SPRIDX,Y
        STA     (DLIDX,X)       ; low address
        INC     DLIDX,X
        LDA     SPRPALW,Y
        STA     (DLIDX,X)       ; palette + width
        INC     DLIDX,X
        AND     #$DLHIGH-1      ; mask
        ORA     #>SPRDATA-DLHIGH        ; base address
        STA     (DLIDX,X)       ; high address
        INC     DLIDX,X
        LDA     SPRXPOS,Y
        STA     (DLIDX,X)       ; horizontal position
        INC     DLIDX,X
1$      INY                     ; next sprite
        CPY     #NUMSPR
        BNE     DLCPY

DLFIN   LDX     #NUMDL*2        ; add end of display list
        LDA     #0
1$      INX     (DLIDX-2,X)
        STA     (DLIDX-2,X)     ; null header
        DEX
        DEX
        BNE     1$

; Now that the display list is built (hopefully before the active screen 
begins)
; it is now time to start preparing for the next screen.  Once this 
processing
; is done, the code waits for the DLL NMI flag to be tripped
; and returns to DLINI to rebuild the display lists again.

VWAIT   LDA     DLIFLAG
1$      CMP     DLIFLAG         ; wait for end of visible screen
        BEQ     1$
        JMP     DLINI

; This is a ROM version of the the non-visible part of the DLL. The onscreen
; portion is generated programatically.

DLLTOP  DC.B    <SPRDATA, %01000000, >SPRDATA, -1, 160, 0, 0    ; set WM=0
        DC.B    $4B, >DLWMSET, <DLWMSET ; 12 raster
        DC.B    $0C, >DLNULL, <DLNULL   ; +13 raster = 25 blank
DLLBOT  DC.B    $8C, >DLNULL, <DLNULL   ; NMI, 13 raster
        DC.B    $0C, >DLNULL, <DLNULL   ; +13 raster = 26 blank
DLLEND  EQU     .

; This lookup table is used by the display list builder routine
; to find the correct display list.  This table is also used
; to programatically generate the onscreen portion of the DLL.

DLPTRW  DC.W    DL00, DL10, DL20, DL30, DL40, DL50, DL60, DL70
        DC.W    DL80, DL90, DLA0, DLB0, DLC0, DLD0, DLE0

; The next chunk is the sprite graphics laid out in 7800 order - $Elnn, 
where
; l is the line number (so the sprites are upside down with each line on a
; separate page) and nn is the sprite index.

SPRDATA ORG     $E000           ; sprite table 

Other related posts: