Fully integration simple (8x8) sprites into the render pipeline

This commit is contained in:
Lucas Scharenbroich 2021-11-01 23:36:53 -05:00
parent e83e8d8a0e
commit bb5f4493d9
6 changed files with 227 additions and 299 deletions

View File

@ -40,16 +40,13 @@ DOWN_ARROW equ $0A
; Set up our level data
jsr BG0SetUp
; jsr TileAnimInit
jsr SetLimits
; Allocate room to load data
; jsr MovePlayerToOrigin ; Put the player at the beginning of the map
jsr InitOverlay ; Initialize the status bar
stz frameCount
ldal OneSecondCounter
sta oldOneSecondCounter
jsr UdtOverlay
; Initialize the sprite's global position (this is tracked outside of the tile engine)
lda #16
@ -65,7 +62,7 @@ DOWN_ARROW equ $0A
; Add a sprite to the engine and save it's sprite ID
jsr UpdatePlayerLocal
lda #3 ; 8x8 sprite, tile ID = 3
lda #64 ; 8x8 sprite, tile ID = 64
ldx PlayerX
ldy PlayerY
jsl AddSprite
@ -151,6 +148,7 @@ EvtLoop
cmp #$FFFA
bcc :not_j
:pos_xvel dec
dec
sta PlayerXVel
bra :do_render
:not_j
@ -162,6 +160,7 @@ EvtLoop
cmp #6
bcs :not_l
:neg_xvel inc
inc
sta PlayerXVel
bra :do_render
:not_l
@ -169,35 +168,14 @@ EvtLoop
; Update the camera position
:do_render
; jsr UpdatePlayerPos ; Moves in global cordinates
; jsr UpdateCameraPos ; Moves the screen
; jsr UpdatePlayerLocal ; Gets local sprite coordinates
jsr UpdatePlayerPos ; Moves in global cordinates
jsr UpdateCameraPos ; Moves the screen
jsr UpdatePlayerLocal ; Gets local sprite coordinates
; lda PlayerID
; ldx PlayerX
; ldy PlayerY
; jsl UpdateSprite ; Move the sprite to this local position
; Draw the sprite in the sprite plane
; ldx PlayerX
; ldy PlayerY
; jsl GetSpriteVBuffAddr
; tax ; put in X
; ldy #3*128 ; draw the 3rd tile as a sprite
; stx PlayerLastPos ; save for erasure
; jsl DrawTileSprite
; Now the sprite has been drawn. Enqueue the dirty tiles. We blindly add the potential
; dirty tiles and rely on PushDirtyTile to elimate duplicates quickly
; ldx PlayerX
; ldy PlayerY
; jsr MakeDirtySprite8x8
; The dirty tile queue has been written to; apply it to the code field
; jsl ApplyTiles
lda PlayerID
ldx PlayerX
ldy PlayerY
jsl UpdateSprite ; Move the sprite to this local position
; Let's see what it looks like!
@ -213,26 +191,6 @@ EvtLoop
jsr UdtOverlay
stz frameCount
:noudt
; Erase the sprites that moved
; ldx PlayerLastPos ; Delete the sprite because it moved
; jsl EraseTileSprite
; Add the tiles that the sprite was previously at as well.
; ldx PlayerXOld
; ldy PlayerYOld
; jsr MakeDirtyTile8x8
; tax
; ldy PlayerY
; lda PlayerID
; jsl UpdateSprite
; jsl DoTimers
; jsl Render
brl EvtLoop
; Exit code
@ -343,13 +301,13 @@ UpdatePlayerLocal
lda PlayerGlobalX
sec
sbc StartX
jsr AdjustLocalX
; jsr AdjustLocalX
sta PlayerX
lda PlayerGlobalY
sec
sbc StartY
jsr AdjustLocalY
; jsr AdjustLocalY
sta PlayerY
rts
@ -411,6 +369,9 @@ UpdatePlayerPos
:no_dxv
sta PlayerXVel
lda PlayerStanding
bne :too_fast
lda PlayerYVel
inc
bmi :is_neg
@ -421,72 +382,35 @@ UpdatePlayerPos
:too_fast
rts
; Takes a signed playfield position (including off-screen coordinates) and a size and marks
; the tiles that are impacted by this shape. The main job of this subroutine is to ensure
; that all of the tile coordinate s are within the valid bounds [0 - 40], [0 - 25].
;
; X = signed integer
; Y = signed integer
; A = sprite size (0 - 7)
SpriteWidths dw 4,4,8,8,12,8,12,16
SpriteHeights dw 8,16,8,16,16,24,24,24
; 000 - 8x8 (1x1 tile)
; 001 - 8x16 (1x2 tiles)
; 010 - 16x8 (2x1 tiles)
; 011 - 16x16 (2x2 tiles)
; 100 - 24x16 (3x2 tiles)
; 101 - 16x24 (2x3 tiles)
; 110 - 24x24 (3x3 tiles)
; 111 - 32x24 (4x3 tiles)
MarkTilesOut
ply
plx
sec
rts
MarkTiles
phx
phy
and #$0007
asl
tax
; First, do a bound check against the whole sprite. It it's totally off-screen, do nothing because
; there are no physical tiles to mark.
lda 1,s ; load the Y coordinate
bpl :y_pos
eor #$FFFF ; for a negative coordinate, see if it's equal to or larger than the sprite height
inc
cmp SpriteHeights,x
bcs MarkTilesOut
bra :y_ok
:y_pos cmp ScreenHeight
bcc :y_ok
bra MarkTilesOut
:y_ok
rts
; X = coordinate
; Y = coordinate
GetTileAt
txa
bmi :out
clc
adc StartXMod164
cmp #164
bcc *+5
sbc #164
lsr
lsr
tax
tya
bmi :out
clc
adc StartYMod208
cmp #208
bcc *+5
sbc #208
lsr
lsr
lsr
tay
jsl GetTileStoreOffset
jsl GetTileStoreOffset
tax
ldal TileStore+TS_TILE_ID,x
rts
@ -495,142 +419,6 @@ GetTileAt
lda #EMPTY_TILE
rts
; X = coordinate
; Y = coordinate
MakeDirtySprite8x8
phx
phy
txa ; need to do a signed shift...
lsr
lsr
tax
tya
lsr
lsr
lsr
tay
jsr MakeDirtySpriteTile ; top-left
lda 3,s
clc
adc #3
lsr
lsr
tax
jsr MakeDirtySpriteTile ; top-right
lda 1,s
clc
adc #7
lsr
lsr
lsr
tay
jsr MakeDirtySpriteTile ; bottom-right
lda 3,s
lsr
lsr
tax
jsr MakeDirtySpriteTile ; bottom-left
ply
plx
rts
; X = coordinate
; Y = coordinate
MakeDirtyTile8x8
phx
phy
txa
lsr
lsr
tax
tya
lsr
lsr
lsr
tay
jsr MakeDirtyTile ; top-left
lda 3,s
clc
adc #3
lsr
lsr
tax
jsr MakeDirtyTile ; top-right
lda 1,s
clc
adc #7
lsr
lsr
lsr
tay
jsr MakeDirtyTile ; bottom-right
lda 3,s
lsr
lsr
tax
jsr MakeDirtyTile ; bottom-left
ply
plx
rts
MakeDirtyTile
phx
phy
jsl GetTileStoreOffset
jsl PushDirtyTile
ply
plx
rts
MakeDirtySpriteTile
phx
phy
txa
asl
asl
tax
tya
asl
asl
asl
tay
jsl GetSpriteVBuffAddr
pha
lda 3,s
tay
lda 5,s
tax
jsl GetTileStoreOffset
tax
lda #TILE_SPRITE_BIT
stal TileStore+TS_SPRITE_FLAG,x
pla
stal TileStore+TS_SPRITE_ADDR,x
txa
jsl PushDirtyTile
ply
plx
rts
; Position the screen with the botom-left corner of the tilemap visible
MovePlayerToOrigin
lda #0 ; Set the player's position

View File

@ -139,6 +139,15 @@ _TileStoreOffset mac
adc TileStoreYTable,y
<<<
_TileStoreOffsetX mac
lda ]2
asl
tax
lda ]1
asl ; Assume in range, so asl puts a 0 bit into the carry
adc TileStoreYTable,x
<<<
_; Macro variant to calculate inline from any source
_SpriteVBuffAddr mac
lda ]2

View File

@ -8,7 +8,7 @@
use .\Defs.s
; Feature flags
NO_INTERRUPTS equ 1 ; turn off for crossrunner debugging
NO_INTERRUPTS equ 0 ; turn off for crossrunner debugging
NO_MUSIC equ 1 ; turn music + tool loading off
; External data provided by the main program segment

View File

@ -23,6 +23,14 @@ InitSprites
cpx #$FFFE
bne :loop2
; Clear values in the sprite array
ldx #{MAX_SPRITES-1}*2
:loop3 stz _Sprites+TILE_STORE_ADDR_1,x
dex
dex
bpl :loop3
rts
@ -49,12 +57,12 @@ _RenderSprites
sta forceSpriteFlag
:no_chng_y
; Second step is to scan the list of spries. 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,
; then its position had changed, so we need to add tiles to the dirty queue to make sure the
; playfield gets update. If it's clean, we can skip eerything.
; playfield gets update. If it's clean, we can skip everything.
ldx #0
:loop lda _Sprites+SPRITE_STATUS,x ; If the sttus is zero, that's the sentinel value
:loop lda _Sprites+SPRITE_STATUS,x ; If the status is zero, that's the sentinel value
beq :out
ora forceSpriteFlag
bit #SPRITE_STATUS_DIRTY ; If the dirty flag is set, do the things....
@ -72,8 +80,32 @@ _RenderSprites
stx tmp0 ; stash the X register
txy ; switch to the Y register
; ldx _Sprites+OLD_VBUFF_ADDR,y
; jsr _EraseTileSprite ; erase from the old position
; Run through the list of tile store offsets that this sprite was last drawn into and mark
; those tiles as dirty. The most tiles that a sprite could possibly cover is 20 (a 4x3 sprite)
; that is offset, covering a 5x4 area of play field tiles.
;
; For now, we limit ourselved to 4 tiles until things are working....
lda _Sprites+TILE_STORE_ADDR_1,y
beq :erase_done
jsr _PushDirtyTile
lda _Sprites+TILE_STORE_ADDR_2,y
beq :erase_done
jsr _PushDirtyTile
lda _Sprites+TILE_STORE_ADDR_3,y
beq :erase_done
jsr _PushDirtyTile
lda _Sprites+TILE_STORE_ADDR_4,y
beq :erase_done
jsr _PushDirtyTile
:erase_done
; Really, we should only be erasing and redrawing a sprite if its local coordinateds change. Look into this
; as a future optimization. Ideally, all of the sprites will be rendered into the sprite plane in a separate
; pass from this function, which is primarily concerned with flagging dirty tiles in the Tile Store.
ldx _Sprites+OLD_VBUFF_ADDR,y
jsr _EraseTileSprite ; erase from the old position
; Draw the sprite into the sprint plane buffer(s)
@ -88,6 +120,8 @@ _RenderSprites
ldx tmp0 ; Restore the index into the sprite array
jsr _MarkDirtySprite8x8 ; Eventually will have routines for all sprite sizes
ldx tmp0 ; Restore the index again
bra :next
; Marks a 8x8 square as dirty. The work here is mapping from local screen coordinates to the
@ -112,9 +146,12 @@ _RenderSprites
; ...
;
; For the Y-coordinate, we just use "mod 8" instead of "mod 4"
;
; On input, X register = Sprite Array Index
_MarkDirtySprite8x8
stz _Sprites+TILE_STORE_ADDR_1,x ; Clear the Dirty Tiles in case of an early exit
; First, bounds check the X and Y coodinates of the sprite and, if they pass, pre-calculate some
; values that we can use later
@ -194,28 +231,94 @@ _MarkDirtySprite8x8
lsr
sta tmp4
; tmp5 = X mod 4
; tmp6 = Y mod 8
;
; Look at these values to determine, up front, exactly which tiles will need to be put into the
; dirty tile queue.
;
; tmp5 tmp6
; ------------+
; 0 0 | top-left only (1 tile)
; !0 0 | top row (2 tiles)
; 0 !0 | left column (2 tiles)
; !0 !0 | square (4 tiles)
txy
ldx #0
lda tmp6
beq :hop_y
ldx #4
:hop_y
lda tmp5
beq :hop_x
inx
inx
:hop_x
lda #0 ; shared value
jmp (:mark,x) ; pick the appropriate marking routine
:mark dw :mark1x1,:mark1x2,:mark2x1,:mark2x2
; At this point we have the top-left corner in the sprite plane (tmp1, tmp3) and the corresponding
; column and row in the tile store (tmp2, tmp4). The next step is to add these tile locations to
; the dirty queue and set the sprite flag along with the VBUFF location. We try to incrementally
; calculate new values to avoid re-doing work.
:mark1x1
sta _Sprites+TILE_STORE_ADDR_2,y ; Terminate the list after one item
_SpriteVBuffAddr tmp3;tmp1
pha
_TileStoreOffset tmp4;tmp2
tax
lda #TILE_SPRITE_BIT
sta TileStore+TS_SPRITE_FLAG,x
pla
sta TileStore+TS_SPRITE_ADDR,x
txa
jsr :top_left
sta _Sprites+TILE_STORE_ADDR_1,y ; Returns the tile store offset
jmp _PushDirtyTile
:mark1x2
sta _Sprites+TILE_STORE_ADDR_3,y ; Terminate the list after two items
jsr :calc_col1 ; Calculate the values for the next column
jsr :top_left
sta _Sprites+TILE_STORE_ADDR_1,y
jsr _PushDirtyTile
; Now see if we need to extend to other tiles. If the mod values are not equal to zero, then
; the width of the sprite will extend into the adjacent code field tiles.
jsr :top_right
sta _Sprites+TILE_STORE_ADDR_2,y
jmp _PushDirtyTile
lda tmp5
beq :no_x_oflow
:mark2x1
sta _Sprites+TILE_STORE_ADDR_3,y ; Terminate the list after two items
jsr :calc_row1 ; Calculate the values for the next row
jsr :top_left
sta _Sprites+TILE_STORE_ADDR_1,y
jsr _PushDirtyTile
jsr :bottom_left
sta _Sprites+TILE_STORE_ADDR_2,y
jmp _PushDirtyTile
; This is the maximum value, so no need to terminate the list early
:mark2x2
jsr :calc_col1 ; Calculate the next row and column values
jsr :calc_row1
jsr :top_left
sta _Sprites+TILE_STORE_ADDR_1,y
jsr _PushDirtyTile
jsr :bottom_left
sta _Sprites+TILE_STORE_ADDR_2,y
jsr _PushDirtyTile
jsr :top_right
sta _Sprites+TILE_STORE_ADDR_3,y
jsr _PushDirtyTile
jsr :bottom_right
sta _Sprites+TILE_STORE_ADDR_4,y
jmp _PushDirtyTile
; Functions to advance to the right, or down and cache the values in the direct page
; temporary space for re-use. col0 and row0 is the original tile
:calc_col1
lda tmp3
clc
adc #4
@ -226,60 +329,59 @@ _MarkDirtySprite8x8
bcc *+5
lda #0
sta tmp8
rts
_SpriteVBuffAddr tmp7;tmp1
pha
_TileStoreOffset tmp8;tmp2
tax
lda #TILE_SPRITE_BIT
sta TileStore+TS_SPRITE_FLAG,x
pla
sta TileStore+TS_SPRITE_ADDR,x
txa
jsr _PushDirtyTile
:no_x_oflow
lda tmp6
beq :no_y_oflow
:calc_row1
lda tmp1
clc
adc #8
sta tmp1
sta tmp9
lda tmp2
inc
cmp #26
bcc *+5
lda #0
sta tmp2
sta tmp10
rts
_SpriteVBuffAddr tmp3;tmp1
pha
_TileStoreOffset tmp4;tmp2
:top_left
_TileStoreOffsetX tmp4;tmp2 ; Overwrites X
tax
_SpriteVBuffAddr tmp3;tmp1 ; Does not affect X, Y
sta TileStore+TS_SPRITE_ADDR,x
lda #TILE_SPRITE_BIT
sta TileStore+TS_SPRITE_FLAG,x
pla
sta TileStore+TS_SPRITE_ADDR,x
txa
jsr _PushDirtyTile
lda tmp5
beq :no_y_oflow
rts
:top_right
_TileStoreOffsetX tmp8;tmp2
tax
_SpriteVBuffAddr tmp7;tmp1
pha
_TileStoreOffset tmp8;tmp2
tax
sta TileStore+TS_SPRITE_ADDR,x
lda #TILE_SPRITE_BIT
sta TileStore+TS_SPRITE_FLAG,x
pla
sta TileStore+TS_SPRITE_ADDR,x
txa
jsr _PushDirtyTile
rts
:no_y_oflow
ldx tmp0 ; Restore X register
:bottom_left
_TileStoreOffsetX tmp4;tmp10
tax
_SpriteVBuffAddr tmp3;tmp9
sta TileStore+TS_SPRITE_ADDR,x
lda #TILE_SPRITE_BIT
sta TileStore+TS_SPRITE_FLAG,x
txa
rts
:bottom_right
_TileStoreOffsetX tmp8;tmp10
tax
_SpriteVBuffAddr tmp7;tmp9
sta TileStore+TS_SPRITE_ADDR,x
lda #TILE_SPRITE_BIT
sta TileStore+TS_SPRITE_FLAG,x
txa
rts
; _GetTileAt
@ -565,6 +667,12 @@ _UpdateSprite
lda _Sprites+VBUFF_ADDR,x ; Save the previous draw location for erasing
sta _Sprites+OLD_VBUFF_ADDR,x
; lda _Sprites+SPRITE_X,x
; sta _Sprites+OLD_SPRITE_X,x
; lda _Sprites+SPRITE_Y,x
; sta _Sprites+OLD_SPRITE_Y,x
lda tmp0 ; Update the X coordinate
sta _Sprites+SPRITE_X,x
@ -589,8 +697,8 @@ _UpdateSprite
; Number of "off-screen" lines above logical (0,0)
NUM_BUFF_LINES equ 24
MAX_SPRITES equ 64
SPRITE_REC_SIZE equ 12
MAX_SPRITES equ 16
SPRITE_REC_SIZE equ 20
SPRITE_STATUS_EMPTY equ 0
SPRITE_STATUS_CLEAN equ 1
@ -602,5 +710,9 @@ VBUFF_ADDR equ {MAX_SPRITES*4}
SPRITE_X equ {MAX_SPRITES*6}
SPRITE_Y equ {MAX_SPRITES*8}
OLD_VBUFF_ADDR equ {MAX_SPRITES*10}
TILE_STORE_ADDR_1 equ {MAX_SPRITES*12}
TILE_STORE_ADDR_2 equ {MAX_SPRITES*14}
TILE_STORE_ADDR_3 equ {MAX_SPRITES*16}
TILE_STORE_ADDR_4 equ {MAX_SPRITES*18}
_Sprites ds SPRITE_REC_SIZE*MAX_SPRITES

View File

@ -650,7 +650,7 @@ PushDirtyTile ENT
plb
rtl
_PushDirtyTile
_PushDirtyTileOld
tay ; check if this already marked immediately
lda TileStore+TS_DIRTY,y ; If the lookup === $FFFF (<$8000), it is free.
bpl :occupied
@ -671,6 +671,25 @@ _PushDirtyTile
:occupied
rts
; alternate version that is very slightly slower, but preserves the y-register
_PushDirtyTile
tax
lda TileStore+TS_DIRTY,x
bpl :occupied2
lda DirtyTileCount
sta TileStore+TS_DIRTY,x
pha ; Would be nice to have an "exchange a and x" instruction
txa
plx
sta DirtyTiles,x
inx
inx
stx DirtyTileCount
:occupied2
rts
; Remove a dirty tile from the list and return it in state ready to be rendered. It is important
; that the core rendering functions *only* use _PopDirtyTile to get a list of tiles to update,
; because this routine merges the tile IDs stored in the Tile Store with the Sprite

View File

@ -5,7 +5,7 @@
; there is no way to do everything inline, so a composite tile is created on the fly and written to
; a direct page buffer. This direct page buffer is then used to render the tile.
_TBSolidSpriteTile_00
ldx #45*128
; ldx #45*128
jsr _TBCopyTileDataToCBuff ; Copy the tile into the compositing buffer (using correct x-register)
jsr _TBApplySpriteData ; Overlay the data form the sprite plane (and copy into the code field)
jmp _TBFillPEAOpcode ; Fill in the code field opcodes