[atari7800] sample 7800 source code using (ZP,X)
- From: "Eric Ball" <ek-ball@xxxxxxxxxx>
- To: <atari7800@xxxxxxxxxxxxx>
- Date: Sun, 1 Apr 2007 15:06:08 -0400
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:
- » [atari7800] sample 7800 source code using (ZP,X)