Nearly complete integration of sprites into rendering function

This commit is contained in:
Lucas Scharenbroich 2021-10-31 15:42:59 -05:00
parent 8f0e82cb04
commit 631f40da23
6 changed files with 271 additions and 199 deletions

View File

@ -129,6 +129,25 @@ asr8 mac
ror
<<<
; Inline macros for fast calculation of some internal values
_TileStoreOffset mac
lda ]2
asl
tay
lda ]1
asl ; Assume in range, so asl puts a 0 bit into the carry
adc TileStoreYTable,y
<<<
_; Macro variant to calculate inline from any source
_SpriteVBuffAddr mac
lda ]2
clc
adc #NUM_BUFF_LINES
xba
adc ]1
<<<
; Macro to define script steps
ScriptStep MAC
IF #=]5

View File

@ -8,7 +8,7 @@
use .\Defs.s
; Feature flags
NO_INTERRUPTS equ 0 ; turn off for crossrunner debugging
NO_INTERRUPTS equ 1 ; turn off for crossrunner debugging
NO_MUSIC equ 1 ; turn music + tool loading off
; External data provided by the main program segment
@ -342,7 +342,7 @@ ClearKbdStrobe sep #$20
rep #$20
rts
; Read the keyboard and paddle controls and return in a game-cotroller-like format
; Read the keyboard and paddle controls and return in a game-controller-like format
ReadControl ENT
pea $0000 ; low byte = key code, high byte = %------AB
@ -352,8 +352,8 @@ ReadControl ENT
beq :BNotDown
lda #1
ora 1,s
sta 1,s
ora 2,s
sta 2,s
:BNotDown
ldal COMMAND_KEY_REG
@ -361,8 +361,8 @@ ReadControl ENT
beq :ANotDown
lda #2
ora 1,s
sta 1,s
ora 2,s
sta 2,s
:ANotDown
ldal KBD_STROBE_REG ; read the keyboard

View File

@ -78,8 +78,8 @@ _Render
jsr _ApplyBG0XPosPre
jsr _ApplyBG1XPosPre
; jsr _RenderSprites ; Once the BG0 X and Y positions are committed, update sprite data
; nop
jsr _RenderSprites ; Once the BG0 X and Y positions are committed, update sprite data
jsr _UpdateBG0TileMap ; and the tile maps. These subroutines build up a list of tiles
jsr _UpdateBG1TileMap ; that need to be updated in the code field

View File

@ -28,13 +28,37 @@ InitSprites
; This function looks at the sprite list and renders the sprite plane data into the appropriate
; tiles in the code field
forceSpriteFlag ds 2
_RenderSprites
; First step is to look at the StartX and StartY values. If the offsets have changed from the
; last time that the frame was rederer, then we need to mark all of the sprites as dirty so that
; the tiles that they were located at on the previous frame will be refreshed
stz forceSpriteFlag
lda StartX
cmp OldStartX
beq :no_chng_x
lda #SPRITE_STATUS_DIRTY
sta forceSpriteFlag
:no_chng_x
lda StartY
cmp OldStartY
beq :no_chng_y
lda #SPRITE_STATUS_DIRTY
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,
; 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.
ldx #0
:loop lda _Sprites+SPRITE_STATUS,x
:loop lda _Sprites+SPRITE_STATUS,x ; If the sttus is zero, that's the sentinel value
beq :out
; cmp #SPRITE_STATUS_DIRTY
; beq :render
bra :render
ora forceSpriteFlag
bit #SPRITE_STATUS_DIRTY ; If the dirty flag is set, do the things....
bne :render
:next inx
inx
bra :loop
@ -46,194 +70,216 @@ _RenderSprites
; from.
:render
stx tmp0 ; stash the X register
txy ; switch to the Y register
txy
ldx _Sprites+OLD_VBUFF_ADDR,y
jsr _EraseTileSprite ; erase from the old position
; ldx _Sprites+OLD_VBUFF_ADDR,y
; jsr _EraseTileSprite ; erase from the old position
ldx _Sprites+VBUFF_ADDR,y
lda _Sprites+TILE_DATA_OFFSET,y
; Draw the sprite into the sprint plane buffer(s)
ldx _Sprites+VBUFF_ADDR,y ; Get the address in the sprite plane to draw at
lda _Sprites+TILE_DATA_OFFSET,y ; and the tile address of the tile
tay
jsr _DrawTileSprite ; draw the sprite into the sprite plane
ldx tmp0
ldy #0 ; flags to mark if the sprite is aligned to the code field grid or not
; Mark the appropriate tiles as dirty and as occupied by a sprite so that the ApplyTiles
; subroutine will get the drawn data from the sprite plane into the code field where it
; can be drawn to the screen
lda _Sprites+SPRITE_X,x ; Will need some special handling for X < 0
sta tmp3
ldx tmp0 ; Restore the index into the sprite array
jsr _MarkDirtySprite8x8 ; Eventually will have routines for all sprite sizes
bra :next
; Marks a 8x8 square as dirty. The work here is mapping from local screen coordinates to the
; tile store indices. The first step is to adjust the sprite coordinates based on the current
; code field offsets and then cache variations of this value needed in the rest of the subroutine
;
; The SpritX is always the MAXIMUM value of the corner coordinates. We subtract (SpriteX + StartX) mod 4
; to find the coordinate in the sprite plane that match up with the tile in the play field and
; then use that to calculate the VBUFF address to copy sprite data from.
;
; StartX SpriteX z = * mod 4 (SprietX - z)
; ----------------------------------------------
; 0 8 0 8
; 1 8 1 7
; 2 8 2 6
; 3 8 3 5
; 4 9 1 8
; 5 9 2 7
; 6 9 3 6
; 7 9 0 9
; 8 10 2 8
; ...
;
; For the Y-coordinate, we just use "mod 8" instead of "mod 4"
_MarkDirtySprite8x8
; First, bounds check the X and Y coodinates of the sprite and, if they pass, pre-calculate some
; values that we can use later
lda _Sprites+SPRITE_Y,x ; This is a signed value
bpl :y_is_pos
cmp #$FFF9 ; If a tile is <= -8 do nothing, it's off-screen
bcs :y_is_ok
rts
:y_is_pos cmp ScreenHeight ; Is a tile is > ScreenHeight, it's off-screen
bcc :y_is_ok
rts
:y_is_ok
; The sprite's Y coordinate is in a range that it will impact the visible tiles that make up the play
; field. Figure out what tile(s) they are and what part fo the sprite plane data/mask need to be
; accessed to overlay with the tile pixels
clc
adc StartYMod208 ; Adjust for the scroll offset (could be a negative number!)
tay ; Save this value
and #$0007 ; Get (StartY + SpriteY) mod 8. For negative, this is ok because 65536 mod 8 = 0.
sta tmp6
eor #$FFFF
inc
clc
adc _Sprites+SPRITE_Y,x ; subtract from the SpriteY position
sta tmp1 ; This position will line up with the tile that the sprite overlaps with
tya ; Get back the position of the sprite in the code field
bpl :ty_is_pos
clc
adc #208 ; wrap around if we are slightly off-screen
bra :ty_is_ok
:ty_is_pos cmp #208 ; check if we went too far positive
bcc :ty_is_ok
sbc #208
:ty_is_ok
lsr
lsr
lsr ; This is the row in the Tile Store for top-left corner of the sprite
sta tmp2
; Same code, except for the X coordiante
lda _Sprites+SPRITE_X,x
bpl :x_is_pos
cmp #$FFFD ; If a tile is <= -4 do nothing, it's off-screen
bcs :x_is_ok
rts
:x_is_pos cmp ScreenWidth ; Is a tile is > ScreeWidth, it's off-screen
bcc :x_is_ok
rts
:x_is_ok
clc
adc StartXMod164
tay
and #$0003
sta tmp5 ; save the mod value to test for alignment later
bit #$0003 ; If the botton bit are zero, then we're aligned
beq :aligned_x
ldy #4
:aligned_x
eor #$FFFF
inc
clc
adc _Sprites+SPRITE_X,x
sta tmp3
cmp #164
bcc *+5
tya
bpl :tx_is_pos
clc
adc #164
bra :tx_is_ok
:tx_is_pos cmp #164
bcc :tx_is_ok
sbc #164
:tx_is_ok
lsr
lsr
pha ; Save the tile column
lda _Sprites+SPRITE_Y,x
sta tmp2
clc
adc StartYMod208
bit #$0007
beq :aligned_y
iny
iny
:aligned_y
cmp #208
bcc *+5
sbc #208
lsr
lsr
lsr
tyx ; stash the alignment in the x register for dispatch
jmp (:mark_dirty,x)
; :mark_dirty dw :corner,:column,:row,:square
:mark_dirty dw :corner,:corner,:corner,:corner
; Just mark the square with the sprite as dirty
:corner tay
plx
jsr _MarkAsDirty
ldx tmp0
brl :next
; Mark the left column (x, y) and (x, y+1) as dirty
:column tay
plx
jsr _MarkAsDirty
iny
cpy #26
bcc *+5
ldy #0
lda tmp2
clc
adc #8
sta tmp2
jsr _MarkAsDirty
ldx tmp0
brl :next
; Mark the top row (x, y) and (x+1, y) as dirty
:row tay
plx
jsr _MarkAsDirty
inx
cpx #41
bcc *+5
ldx #0
lda tmp3
clc
adc #4
sta tmp3
jsr _MarkAsDirty
ldx tmp0
brl :next
; Mark all four squares as dirty
:square tay
lda 1,s
tax
jsr _MarkAsDirty
inx
cpx #41
bcc *+5
ldx #0
lda tmp3
clc
adc #4
sta tmp3
jsr _MarkAsDirty
iny
cpy #26
bcc *+5
ldy #0
lda tmp2
clc
adc #8
sta tmp2
jsr _MarkAsDirty
plx
lda tmp3
sec
sbc #4
sta tmp3
jsr _MarkAsDirty
ldx tmp0
brl :next
_MarkAsDirty
phx
phy
jsr _GetTileStoreOffset ; Get the tile store value
jsr _PushDirtyTile ; Enqueue for processing (Returns offset in Y-register)
lda TileStore+TS_SPRITE_FLAG,y ; If this tile has already been flagged on this frame, avoid recalculating the address
beq :early_out
lda #TILE_SPRITE_BIT ; Mark this tile as having a sprite, regardless of whether it was already enqueued
sta TileStore+TS_SPRITE_FLAG,y
jsr _SetSpriteAddr
:early_out
ply
plx
rts
; Set the TileStore+TS_SPRITE_ADDR for tile that a sprite is on.
;
; To calculate the sprite plane coordinate for this tile column. We really just have to compensate
; for the StartXMod164 mod 4 value, so the final value is (SPRITE_X + (StartXMod164 mod 4)) & 0xFFFC
; for the horizontal and (SPRITE_Y + (StartYMod208 mod 8)) & 0xFFF8
;
; The final address is (Y + NUM_BUFF_LINES) * 256 + X
;
; tmp2 = sprite Y coordinate
; tmp3 = sprite X coordinate
; Y = tile record index
_SetSpriteAddr
lda StartYMod208
and #$0007
clc
adc tmp2
and #$00F8
clc
adc #NUM_BUFF_LINES
xba
sta tmp4
lda StartXMod164
and #$0003
clc
adc tmp3
and #$00FC
clc
adc tmp4
sta TileStore+TS_SPRITE_ADDR,y
; 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.
_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 _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.
lda tmp5
beq :no_x_oflow
lda tmp3
clc
adc #4
sta tmp7
lda tmp4
inc
cmp #41
bcc *+5
lda #0
sta tmp8
_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
lda tmp1
clc
adc #8
sta tmp1
lda tmp2
inc
cmp #26
bcc *+5
lda #0
sta tmp2
_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 _PushDirtyTile
lda tmp5
beq :no_y_oflow
_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_y_oflow
ldx tmp0 ; Restore X register
rts
; _GetTileAt
@ -453,18 +499,18 @@ _AddSprite
:open lda #SPRITE_STATUS_DIRTY
sta _Sprites+SPRITE_STATUS,x ; Mark this sprite slot as occupied and that it needs to be drawn
pla
jsr _GetTileAddr
jsr _GetTileAddr ; This applies the TILE_ID_MASK
sta _Sprites+TILE_DATA_OFFSET,x
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 ; Add the horizontal position
tya ; Y coordinate
sta _Sprites+SPRITE_Y,x
pla ; X coordinate
sta _Sprites+SPRITE_X,x
jsr _GetSpriteVBuffAddr ; Preserves X-register
sta _Sprites+VBUFF_ADDR,x
pla ; Pop off the saved value
clc ; Mark that the sprite was successfully added
txa ; And return the sprite ID
rts
@ -472,15 +518,22 @@ _AddSprite
; 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
phx
clc
adc 1,s
plx
rtl
sta 1,s
pla
rts
; Move a sprite to a new location. If the tile ID of the sprite needs to be changed, then
; a full remove/add cycle needs to happen
@ -518,11 +571,8 @@ _UpdateSprite
tya ; Update the Y coordinate
sta _Sprites+SPRITE_Y,x
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 tmp0 ; Add the horizontal position
lda tmp0
jsr _GetSpriteVBuffAddr
sta _Sprites+VBUFF_ADDR,x
rts

View File

@ -113,6 +113,8 @@ _RenderTile2
lda TileStore+TS_TILE_ID,y ; build the finalized tile descriptor
ora TileStore+TS_SPRITE_FLAG,y
bpl :nosprite ; save a few cycles on average -- the sprite flag is $8000, so easy bpl/bmi test
tyx
stz TileStore+TS_SPRITE_FLAG,x ; clear the sprite flag
ldx TileStore+TS_SPRITE_ADDR,y
stx _SPR_X_REG

View File

@ -5,6 +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
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