Streamline sprite functions; untested conceptual changes

This commit is contained in:
Lucas Scharenbroich 2022-02-03 08:50:11 -06:00
parent 16a3a385a9
commit 1b9425b620
4 changed files with 138 additions and 116 deletions

View File

@ -78,7 +78,7 @@ BG1TileMapWidth equ 82
BG1TileMapHeight equ 84 BG1TileMapHeight equ 84
BG1TileMapPtr equ 86 BG1TileMapPtr equ 86
SCBArrayPtr equ 90 ; USed for palette binding SCBArrayPtr equ 90 ; Used for palette binding
Next equ 94 Next equ 94
BankLoad equ 128 BankLoad equ 128

View File

@ -1,10 +1,17 @@
; Functions for sprie handling. Mostly maintains the sprite list and provides ; Functions for sprite handling. Mostly maintains the sprite list and provides
; utility functions to calculate sprite/tile intersections ; utility functions to calculate sprite/tile intersections
; ;
; The sprite plane actually covers two banks so that more than 32K can be used as a virtual ; The sprite plane actually covers two banks so that more than 32K can be used as a virtual
; screen buffer. In order to be able to draw sprites offscreen, the virtual screen must be ; screen buffer. In order to be able to draw sprites offscreen, the virtual screen must be
; wider and taller than the physical graphics screen. ; wider and taller than the physical graphics screen.
; ;
; Sprite State Machine
;
; EMPTY -> DIRTY <-> CLEAN
; ^ |
; | |
; +------ FREE <-----+
; Initialize the sprite plane data and mask banks (all data = $0000, all masks = $FFFF) ; Initialize the sprite plane data and mask banks (all data = $0000, all masks = $FFFF)
InitSprites InitSprites
ldx #$FFFE ldx #$FFFE
@ -113,25 +120,26 @@ _ClearSpriteFromTileStore
; This function looks at the sprite list and renders the sprite plane data into the appropriate ; This function looks at the sprite list and renders the sprite plane data into the appropriate
; tiles in the code field. There are a few phases to this routine. The assumption is that ; tiles in the code field. There are a few phases to this routine. The assumption is that
; any sprite that needs to be re-drawn has been marked as dirty. ; any sprite that needs to be re-drawn has been marked as DIRTY.
; ;
; In the first phase, we run through the list of dirty sprites and erase them from their ; In the first phase, we run through the list of DIRTY and FREE sprites and erase them from their
; OLD_VBUFF_ADDR. This clears the sprite plane buffers. We also interate through the ; OLD_VBUFF_ADDR. This clears the sprite plane buffers. We also interate through the
; TILE_STORE_ADDR_X array and mark all of the tile store location that this sprite had occupied ; TILE_STORE_ADDR_X array and mark all of the tile store location that this sprite had occupied
; as dirty, as well as removing this sprite from the TS_SPRITE_FLAG bitfield. ; as dirty, as well as removing this sprite from the TS_SPRITE_FLAG bitfield.
; ;
; A final aspect is that any of the sprites idicated in the TS_SPRITE_FLAG are marked to be ; A final aspect is that any of the sprites indicated in the TS_SPRITE_FLAG are marked to be
; drawn in the next phase (since a portion of their content may have been erased if they overlap) ; drawn in the next phase (since a portion of their content may have been erased if they overlap)
; ;
; In the second phase, the sprite is re-drawn into the sprite plane buffers and the appropriate ; In the second phase, the sprite is re-drawn into the sprite plane buffers and the appropriate
; Tile Store locations are marked as dirty ; Tile Store locations are marked as dirty
; ;
; ; IF a sprite is marked as FREE, it is transitioned to a free slot after being erased from the
; the scene and its slot index is returned to the open list.
forceSpriteFlag ds 2 forceSpriteFlag ds 2
_RenderSprites _RenderSprites
; First step is to look at the StartX and StartY values. If the offsets have changed from the ; First step is to look at the StartX and StartY values. If the offsets have changed from the
; last time that the frame was redered, then we need to mark all of the sprites as dirty so that ; last time that the frame was rendered, then we need to mark all of the sprites as dirty so that
; the tiles on which they were located at the previous frame will be refreshed ; the tiles on which they were located at the previous frame will be refreshed
stz forceSpriteFlag stz forceSpriteFlag
@ -152,19 +160,36 @@ _RenderSprites
ldy #0 ldy #0
:loop1 lda _Sprites+SPRITE_STATUS,y ; If the status is zero, that's the sentinel value :loop1 lda _Sprites+SPRITE_STATUS,y ; If the status is zero, that's the sentinel value
beq :phase2 beq :phase2
bit #SPRITE_STATUS_DIRTY bit #SPRITE_STATUS_DIRTY+SPRITE_STATUS_FREE
beq :next1 beq :next1
; Erase the sprite from the Sprite Plane buffers ; Erase the sprite from the Sprite Plane buffers
jsr _EraseSpriteY jsr _EraseSpriteY
; Mark all of the tile store indices that thie sprite was drawn at as dirty and clear ; Mark all of the tile store indices that this sprite was drawn at as dirty and clear
; it's bit flag in the TS_SPRITE_FLAG ; it's bit flag in the TS_SPRITE_FLAG
jsr _ClearSpriteFromTileStore jsr _ClearSpriteFromTileStore
; Check to see if this was a FREE sprite. If so, then it's index can be returned to the
; open list
lda _Sprites+SPRITE_STATUS,y
bit #SPRITE_STATUS_FREE
beq :next1
ldx #SPRITE_STATUS_EMPTY ; Mark as empty
stx _Sprites+SPRITE_STATUS,y
ldx _OpenListHead
dex
dex
stx _OpenListHead
sty _OpenList,x
sty _NextOpenSlot
:next1 iny :next1 iny
iny iny
bra :loop1 cpy #2*MAX_SPRITES
bcc :loop1
:phase2 :phase2
; Second step is to scan the list of sprites. A sprite is either clean or dirty. If it's dirty, ; Second step is to scan the list of sprites. A sprite is either clean or dirty. If it's dirty,
@ -250,26 +275,6 @@ _GetTileAt
clc clc
rts rts
; _DrawSprites
;
; Draw the sprites on the _Sprite list into the Sprite Plane data and mask buffers. This is using the
; tile data right now, but could be replaced with compiled sprite routines.
_DrawSprites
ldx #0
:loop lda _Sprites+SPRITE_STATUS,x
beq :out ; The first open slot is the end of the list
cmp #SPRITE_STATUS_DIRTY
bne :skip
phx
jsr _DrawSprite
plx
:skip
inx
inx
bra :loop
:out rts
; X = _Sprites array offset ; X = _Sprites array offset
_EraseSprite _EraseSprite
txy txy
@ -827,6 +832,38 @@ _EraseTileSprite16x16
pla pla
plb plb
rts rts
; A = x coordinate
; Y = y coordinate
GetSpriteVBuffAddr ENT
jsr _GetSpriteVBuffAddr
rtl
; A = x coordinate
; Y = y coordinate
_GetSpriteVBuffAddr
pha
tya
clc
adc #NUM_BUFF_LINES ; The virtual buffer has 24 lines of off-screen space
xba ; Each virtual scan line is 256 bytes wide for overdraw space
clc
adc 1,s
sta 1,s
pla
rts
; Version that uses temporary space (tmp15)
_GetSpriteVBuffAddrTmp
sta tmp15
tya
clc
adc #NUM_BUFF_LINES ; The virtual buffer has 24 lines of off-screen space
xba ; Each virtual scan line is 256 bytes wide for overdraw space
clc
adc tmp15
rts
; Add a new sprite to the rendering pipeline ; Add a new sprite to the rendering pipeline
; ;
; The tile id ithe range 0 - 511. The top 7 bits are used as sprite control bits ; The tile id ithe range 0 - 511. The top 7 bits are used as sprite control bits
@ -861,66 +898,47 @@ AddSprite ENT
rtl rtl
_AddSprite _AddSprite
phx ; Save the horizontal position and tile ID phx ; Save the horizontal position
pha
ldx #0 ldx _NextOpenSlot ; Get the next free sprite slot index
:loop lda _Sprites+SPRITE_STATUS,x ; Look for an open slot bpl :open ; A negative number means we are full
beq :open
inx
inx
cpx #MAX_SPRITES*2
bcc :loop
pla ; Early out plx ; Early out
pla sec ; Signal that no sprite slot was available
sec ; Signal that no sprite slot was available
rts rts
:open lda #SPRITE_STATUS_DIRTY :open
sta _Sprites+SPRITE_STATUS,x ; Mark this sprite slot as occupied and that it needs to be drawn
pla
sta _Sprites+SPRITE_ID,x ; Keep a copy of the full descriptor sta _Sprites+SPRITE_ID,x ; Keep a copy of the full descriptor
jsr _GetTileAddr ; This applies the TILE_ID_MASK jsr _GetTileAddr ; This applies the TILE_ID_MASK
sta _Sprites+TILE_DATA_OFFSET,x sta _Sprites+TILE_DATA_OFFSET,x
tya ; Y coordinate lda #SPRITE_STATUS_DIRTY
sta _Sprites+SPRITE_Y,x sta _Sprites+SPRITE_STATUS,x ; Mark this sprite slot as occupied and that it needs to be drawn
sty _Sprites+SPRITE_Y,x ; Y coordinate
pla ; X coordinate pla ; X coordinate
sta _Sprites+SPRITE_X,x sta _Sprites+SPRITE_X,x
jsr _GetSpriteVBuffAddr ; Preserves X-register jsr _GetSpriteVBuffAddrTmp ; Preserves X-register
sta _Sprites+VBUFF_ADDR,x sta _Sprites+VBUFF_ADDR,x
clc ; Mark that the sprite was successfully added
txa ; And return the sprite ID txa ; And return the sprite ID
clc ; Mark that the sprite was successfully added
; We can only get to this point if there was an open slot, so we know we're not at the
; end of the list yet.
ldx _OpenListHead
inx
inx
stx _OpenListHead
ldy _OpenList,x ; If this is the end, then the sentinel value will
sty _NextOpenSlot ; get stored into _NextOpenSlot
rts rts
; X = x coordinate
; Y = y coordinate
GetSpriteVBuffAddr ENT
jsr _GetSpriteVBuffAddr
rtl
; A = x coordinate
; Y = y coordinate
_GetSpriteVBuffAddr
pha
tya
clc
adc #NUM_BUFF_LINES ; The virtual buffer has 24 lines of off-screen space
xba ; Each virtual scan line is 256 bytes wide for overdraw space
clc
adc 1,s
sta 1,s
pla
rts
; Remove a sprite from the list. Just mark its STATUS as FREE and it will be ; Remove a sprite from the list. Just mark its STATUS as FREE and it will be
; picked up in the next AddSprite. We have to be carful not to set it to zero ; picked up in the next AddSprite.
; as that will truncate the sprite list
; ;
; A = Sprite ID ; A = Sprite ID
RemoveSprite ENT RemoveSprite ENT
@ -931,23 +949,15 @@ RemoveSprite ENT
plb plb
rtl rtl
_RemoveSprite _RemoveSprite
cmp #MAX_SPRITES*2 ; Make sure we're in bounds tax
bcc :ok
_RemoveSpriteX
lda #SPRITE_STATUS_FREE ; This will tell the renderer to erase the sprite,
sta _Sprites+SPRITE_STATUS,x ; but then remove it from the list
rts rts
:ok ; Update the sprite's flags. We do not allow the size of a sprite to be changed. That requires
lda #SPRITE_STATUS_FREE
sta _Sprites+SPRITE_STATUS,x
lda tmp0 ; Update the Tile ID
sta _Sprites+SPRITE_ID,x ; Keep a copy of the full descriptor
jsr _GetTileAddr ; This applies the TILE_ID_MASK
sta _Sprites+TILE_DATA_OFFSET,x
rts
; Update the sprite's flags. We do not allow the size fo a sprite to be changed. That required
; the sprite to be removed and re-added. ; the sprite to be removed and re-added.
; ;
; A = Sprite ID ; A = Sprite ID
@ -960,24 +970,25 @@ UpdateSprite ENT
plb plb
rtl rtl
_UpdateSprite _UpdateSprite
cmp #MAX_SPRITES*2 ; Make sure we're in bounds phx ; swap X/A to be more efficient
tax
pla
_UpdateSpriteX
cpx #MAX_SPRITES*2 ; Make sure we're in bounds
bcc :ok bcc :ok
rts rts
:ok :ok
stx tmp0 ; Save the horizontal position _UpdateSpriteXnc
and #$FFFE ; Defensive sta _Sprites+SPRITE_ID,x ; Keep a copy of the full descriptor
tax ; Get the sprite index jsr _GetTileAddr ; This applies the TILE_ID_MASK
sta _Sprites+TILE_DATA_OFFSET,x
lda #SPRITE_STATUS_DIRTY ; Content is changing, mark as dirty lda #SPRITE_STATUS_DIRTY ; Content is changing, mark as dirty
sta _Sprites+SPRITE_STATUS,x sta _Sprites+SPRITE_STATUS,x
lda tmp0 ; Update the Tile ID
sta _Sprites+SPRITE_ID,x ; Keep a copy of the full descriptor
jsr _GetTileAddr ; This applies the TILE_ID_MASK
sta _Sprites+TILE_DATA_OFFSET,x
rts rts
; Move a sprite to a new location. If the tile ID of the sprite needs to be changed, then ; Move a sprite to a new location. If the tile ID of the sprite needs to be changed, then
@ -995,31 +1006,28 @@ MoveSprite ENT
rtl rtl
_MoveSprite _MoveSprite
cmp #MAX_SPRITES*2 ; Make sure we're in bounds phx ; swap X/A to be more efficient
tax
pla
_MoveSpriteX
cpx #MAX_SPRITES*2 ; Make sure we're in bounds
bcc :ok bcc :ok
rts rts
:ok :ok
stx tmp0 ; Save the horizontal position _MoveSpriteXnc
and #$FFFE ; Defensive sta _Sprites+SPRITE_X,x ; Update the X coordinate
tax ; Get the sprite index sty _Sprites+SPRITE_Y,x ; Update the Y coordinate
jsr _GetSpriteVBuffAddrTmp ; A = x-coord, Y = y-coord
ldy _Sprites+VBUFF_ADDR,x ; Save the previous draw location for erasing
sty _Sprites+OLD_VBUFF_ADDR,x
sta _Sprites+VBUFF_ADDR,x ; Overwrite with the new location
lda #SPRITE_STATUS_DIRTY ; Position is changing, mark as dirty lda #SPRITE_STATUS_DIRTY ; Position is changing, mark as dirty
sta _Sprites+SPRITE_STATUS,x ; Mark this sprite slot as occupied and that it needs to be drawn sta _Sprites+SPRITE_STATUS,x ; Mark this sprite slot as occupied and that it needs to be drawn
lda _Sprites+VBUFF_ADDR,x ; Save the previous draw location for erasing
sta _Sprites+OLD_VBUFF_ADDR,x
lda tmp0 ; Update the X coordinate
sta _Sprites+SPRITE_X,x
tya ; Update the Y coordinate
sta _Sprites+SPRITE_Y,x
lda tmp0
jsr _GetSpriteVBuffAddr
sta _Sprites+VBUFF_ADDR,x
rts rts
; Sprite data structures. We cache quite a few pieces of information about the sprite ; Sprite data structures. We cache quite a few pieces of information about the sprite
@ -1060,4 +1068,10 @@ TILE_STORE_ADDR_8 equ {MAX_SPRITES*28}
TILE_STORE_ADDR_9 equ {MAX_SPRITES*30} TILE_STORE_ADDR_9 equ {MAX_SPRITES*30}
TILE_STORE_ADDR_10 equ {MAX_SPRITES*32} TILE_STORE_ADDR_10 equ {MAX_SPRITES*32}
; Maintain the index of the next open sprite slot. This allows us to have amortized
; constant sprite add performance. A negative value means no slots are available.
_NextOpenSlot dw 0
_OpenListHead dw 0
_OpenList dw 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,$FFFF ; List with sentinel at the end
_Sprites ds SPRITE_REC_SIZE*MAX_SPRITES _Sprites ds SPRITE_REC_SIZE*MAX_SPRITES

View File

@ -91,7 +91,7 @@ _MarkDirtySprite
dec dec
:ok_y sta Bottom :ok_y sta Bottom
; At this point we know that we have to update the tile that overlap the sprite plane rectangle defined ; At this point we know that we have to update the tiles that overlap the sprite plane rectangle defined
; by (Top, Left), (Bottom, Right). The general process is to figure out the top-left coordinate in the ; by (Top, Left), (Bottom, Right). The general process is to figure out the top-left coordinate in the
; sprite plane that matches up with the code field and then calculate the number of tiles in each direction ; sprite plane that matches up with the code field and then calculate the number of tiles in each direction
; that need to be dirtied to cover the sprite. ; that need to be dirtied to cover the sprite.
@ -146,7 +146,7 @@ _MarkDirtySprite
sta ColLeft sta ColLeft
; Sneak a pre-calculation here. Calculate the upper-left corder of the sprite in the sprite plane. ; Sneak a pre-calculation here. Calculate the upper-left corder of the sprite in the sprite plane.
; We can reuse this in all of the routines below ; We can reuse this in all of the routines below
clc clc
lda TileTop lda TileTop
@ -191,6 +191,15 @@ _MarkDirtySprite
sta _Sprites+TILE_STORE_ADDR_2,y sta _Sprites+TILE_STORE_ADDR_2,y
rts rts
; NOTE: If we rework the _PushDirtyTile to use the Y register instead of the X register, we can
; optimize all of these :mark routines as
;
; :mark1x1
; jsr :mark_0_0
; sty _Sprites+TILE_STORE_ADDR_1,x
; stz _Sprites+TILE_STORE_ADDR_2,y
; rts
:mark1x2 :mark1x2
jsr :mark_0_0 jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y sta _Sprites+TILE_STORE_ADDR_1,y

View File

@ -681,9 +681,8 @@ _PushDirtyTileX
lda DirtyTileCount lda DirtyTileCount
sta TileStore+TS_DIRTY,x sta TileStore+TS_DIRTY,x
pha ; Would be nice to have an "exchange a and x" instruction
txa txa
plx ldx DirtyTileCount
sta DirtyTiles,x sta DirtyTiles,x
inx inx