Initial shell of generalize sprite size support; just for marking the dirty tiles

This commit is contained in:
Lucas Scharenbroich 2021-11-11 17:06:38 -06:00
parent 13393965b3
commit 678c9a0563
8 changed files with 646 additions and 82 deletions

View File

@ -62,7 +62,7 @@ DOWN_ARROW equ $0A
; Add a sprite to the engine and save it's sprite ID
jsr UpdatePlayerLocal
lda #64 ; 8x8 sprite, tile ID = 64
lda #$1800+145 ; 16x16 sprite, tile ID = 64
ldx PlayerX
ldy PlayerY
jsl AddSprite
@ -322,7 +322,7 @@ UpdatePlayerPos
ldx PlayerX
lda PlayerY
clc
adc #8
adc #16
tay
jsr GetTileAt
cmp #EMPTY_TILE

View File

@ -119,6 +119,35 @@ min mac
lda ]1
mout <<<
; Increment a value mod some number.
incmod mac
inc
cmp ]1
bcc out
lda #0
out <<<
decmod mac
dec
bpl out
lda ]1
dec
out <<<
adcmod mac
adc ]1
cmp ]2
bcc out
sbc ]2
out <<<
sbcmod mac
sbc ]1
bpl out
clc
adc ]2
out <<<
asr16 mac
cmp #$8000
ror

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
@ -380,6 +380,7 @@ ReadControl ENT
put Memory.s
put Graphics.s
put Sprite.s
put Sprite2.s
put Render.s
put Timer.s
put Script.s

View File

@ -78,7 +78,9 @@ _Render
jsr _ApplyBG0XPosPre
jsr _ApplyBG1XPosPre
nop
jsr _RenderSprites ; Once the BG0 X and Y positions are committed, update sprite data
nop
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

@ -40,8 +40,8 @@ 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
; last time that the frame was redered, 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
stz forceSpriteFlag
lda StartX
@ -65,7 +65,7 @@ _RenderSprites
: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....
ora #SPRITE_STATUS_DIRTY ; If the dirty flag is set, do the things....
bne :render
:next inx
inx
@ -73,18 +73,17 @@ _RenderSprites
:out rts
; This is the complicated part; we need to draw the sprite into the sprite plane, but then
; calculate the code field tiles that this sprite potentially overlaps with and mark those
; tiles as dirty and store the appropriate sprite plane address that those tiles need to copy
; from.
; calculate the tiles that overlap with the sprite potentially and mark those as dirty _AND_
; store the appropriate sprite plane address from which those tiles need to copy.
:render
stx tmp0 ; stash the X register
txy ; switch to the Y register
; 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.
; those tiles as dirty. The largest number tiles that a sprite could possibly cover is 20
; (an unaligned 4x3 sprite), covering a 5x4 area of play field tiles.
;
; For now, we limit ourselved to 4 tiles until things are working....
; For now, we limit ourselves to 4 tiles until things are working....
lda _Sprites+TILE_STORE_ADDR_1,y
beq :erase_done
@ -98,14 +97,29 @@ _RenderSprites
lda _Sprites+TILE_STORE_ADDR_4,y
beq :erase_done
jsr _PushDirtyTile
lda _Sprites+TILE_STORE_ADDR_5,y
beq :erase_done
jsr _PushDirtyTile
lda _Sprites+TILE_STORE_ADDR_6,y
beq :erase_done
jsr _PushDirtyTile
lda _Sprites+TILE_STORE_ADDR_7,y
beq :erase_done
jsr _PushDirtyTile
lda _Sprites+TILE_STORE_ADDR_8,y
beq :erase_done
jsr _PushDirtyTile
lda _Sprites+TILE_STORE_ADDR_9,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
; Really, we should only be erasing and redrawing a sprite if its local coordinates 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
; jsr _EraseTileSprite ; erase from the old position
; Draw the sprite into the sprint plane buffer(s)
@ -113,26 +127,54 @@ _RenderSprites
lda _Sprites+TILE_DATA_OFFSET,y ; and the tile address of the tile
tay
jsr _DrawTileSprite ; draw the sprite into the sprite plane
tya
clc
adc #128
tay
txa
clc
adc #4
tax
jsr _DrawTileSprite
tya
clc
adc #128*31
tay
txa
clc
adc #{8*256}-4
tax
jsr _DrawTileSprite
tya
clc
adc #128
tay
txa
clc
adc #4
tax
jsr _DrawTileSprite
; 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
ldx tmp0 ; Restore the index into the sprite array
jsr _MarkDirtySprite8x8 ; Eventually will have routines for all sprite sizes
jsr _MarkDirtySprite ; Mark the tiles that this sprite overlaps as dirty
ldx tmp0 ; Restore the index again
bra :next
brl :next
; Marks a 8x8 square as dirty. The work here is mapping from local screen coordinates to the
; Marks an 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.
; The SpriteX is always the MAXIMUM value of the corner coordinates. We subtract (SpriteX + StartX) mod 4
; to find the coordinate in the sprite plane that matches up with the tile in the play field and
; then use that to calculate the VBUFF address from which to copy sprite data.
;
; StartX SpriteX z = * mod 4 (SprietX - z)
; StartX SpriteX z = * mod 4 (SpriteX - z)
; ----------------------------------------------
; 0 8 0 8
; 1 8 1 7
@ -150,10 +192,10 @@ _RenderSprites
; 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
stz _Sprites+TILE_STORE_ADDR_1,x ; Clear the Dirty Tile list 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
; values that we can use later.
lda _Sprites+SPRITE_Y,x ; This is a signed value
bpl :y_is_pos
@ -166,7 +208,7 @@ _MarkDirtySprite8x8
: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
; field. Figure out what tile(s) they are and what part of the sprite plane data/mask need to be
; accessed to overlay with the tile pixels
clc
@ -234,7 +276,7 @@ _MarkDirtySprite8x8
; 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
; Look at these values to determine, up front, exactly which bounding tiles will need to be put into the
; dirty tile queue.
;
; tmp5 tmp6
@ -264,6 +306,36 @@ _MarkDirtySprite8x8
; 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.
;
; The sprite plane address calculation is x + 256 * y and there are no wrap-around considerations,
; so we can take the calculated VBUFF address and just add a single, pre-calculate constant for each
; tile
;
; The tile store addresses are more involved, because we could wrap around in the X or Y direction
; at any step, so they need to be tracked separately. However, they can be decomposed so that we
; can update each independently. If the values are pre-multiplied by 2, then calculating the
; Tile Store for X and Y is just
;
; txa
; adc TileStoreYTable,y
;
; One other consideration is that the visibility tests for the sprite coverage vs the tile store
; coverage are different. We get into the main loop is *any* part of the sprite is potentially
; visible in the play field. However, for multi-tile sprites, some of the sub-tiles that
; comprise the sprite could be totally off-screen.
;
; To handle this, we pre-filter the tile list while calculating the sprite plane and tile store
; addresses to remove any tiles that are off-screen. This provides a natural break in the subroutine
; where the actually updating values in the TileStore and _Sprites tables and marking tiles as
; dirty involves walking a single list.
;
; A final note. Although this seems like a lot of code, rendering each tile requires, at a minimum,
; 16 LDA/STA pairs plus the overhead of the dirty tile list (~50 cycles), and possible much more.
; It's safe to assume that each tile no drawn saves around 500 cycles per frame.
;
; The worst-case for sprites is 16 sprites, all the maximum size of 4x3 and all unaligned, so
; 16 * 5 * 4 = 320 tiles total. There are, at most, 1066 tiles visible on a full-screen. This
; would effectively halve the framerate.
:mark1x1
sta _Sprites+TILE_STORE_ADDR_2,y ; Terminate the list after one item
@ -273,7 +345,7 @@ _MarkDirtySprite8x8
: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 :calc_col1 ; Calculate the values for the next column
jsr :top_left
sta _Sprites+TILE_STORE_ADDR_1,y
@ -285,7 +357,7 @@ _MarkDirtySprite8x8
: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 :calc_row1 ; Calculate the values for the next row
jsr :top_left
sta _Sprites+TILE_STORE_ADDR_1,y
@ -295,10 +367,11 @@ _MarkDirtySprite8x8
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
sta _Sprites+TILE_STORE_ADDR_3,y ; Terminate the list after four items
; jsr :calc_col1 ; Calculate the next row and column values
; jsr :calc_row1
jsr :top_left
sta _Sprites+TILE_STORE_ADDR_1,y
@ -316,34 +389,6 @@ _MarkDirtySprite8x8
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
sta tmp7
lda tmp4
inc
cmp #41
bcc *+5
lda #0
sta tmp8
rts
:calc_row1
lda tmp1
clc
adc #8
sta tmp9
lda tmp2
inc
cmp #26
bcc *+5
lda #0
sta tmp10
rts
:top_left
_TileStoreOffsetX tmp4;tmp2 ; Overwrites X
tax
@ -551,17 +596,14 @@ _EraseTileSprite
;
; Bit 9 : Horizontal flip.
; Bit 10 : Vertical flip.
; Bits 11 - 13 : Sprite Size Selector
; 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)
; Bit 14 : Low Sprite priority. Draws behind high priority tiles.
; Bit 15 : Reserved. Must be zero.
; Bits 11 - 12 : Sprite Size Selector
; 00 - 8x8 (1x1 tile)
; 01 - 8x16 (1x2 tiles)
; 10 - 16x8 (2x1 tiles)
; 11 - 16x16 (2x2 tiles)
; Bit 13 : Reserved. Must be zero.
; Bit 14 : Reserved. Must be zero.
; Bit 15 : Low Sprite priority. Draws behind high priority tiles.
;
; When a sprite has a size > 8x8, the horizontal tiles are taken from the next tile index and
; the vertical tiles are taken from tileId + 32. This is why tile sheets should be saved
@ -601,6 +643,7 @@ _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
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
@ -698,21 +741,28 @@ _UpdateSprite
NUM_BUFF_LINES equ 24
MAX_SPRITES equ 16
SPRITE_REC_SIZE equ 20
SPRITE_REC_SIZE equ 34
SPRITE_STATUS_EMPTY equ 0
SPRITE_STATUS_CLEAN equ 1
SPRITE_STATUS_DIRTY equ 2
SPRITE_STATUS equ 0
SPRITE_STATUS equ {MAX_SPRITES*0}
TILE_DATA_OFFSET equ {MAX_SPRITES*2}
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}
SPRITE_ID equ {MAX_SPRITES*6}
SPRITE_X equ {MAX_SPRITES*8}
SPRITE_Y equ {MAX_SPRITES*10}
OLD_VBUFF_ADDR equ {MAX_SPRITES*12}
TILE_STORE_ADDR_1 equ {MAX_SPRITES*14}
TILE_STORE_ADDR_2 equ {MAX_SPRITES*16}
TILE_STORE_ADDR_3 equ {MAX_SPRITES*18}
TILE_STORE_ADDR_4 equ {MAX_SPRITES*20}
TILE_STORE_ADDR_5 equ {MAX_SPRITES*22}
TILE_STORE_ADDR_6 equ {MAX_SPRITES*24}
TILE_STORE_ADDR_7 equ {MAX_SPRITES*26}
TILE_STORE_ADDR_8 equ {MAX_SPRITES*28}
TILE_STORE_ADDR_9 equ {MAX_SPRITES*30}
TILE_STORE_ADDR_10 equ {MAX_SPRITES*32}
_Sprites ds SPRITE_REC_SIZE*MAX_SPRITES

458
src/Sprite2.s Normal file
View File

@ -0,0 +1,458 @@
; Scratch space to lay out idealized _MakeDirtySprite
; On input, X register = Sprite Array Index
Left equ tmp1
Right equ tmp2
Top equ tmp3
Bottom equ tmp4
TileTop equ tmp5
RowTop equ tmp6
AreaIndex equ tmp7
TileLeft equ tmp8
ColLeft equ tmp9
SpriteBit equ tmp10 ; set the bit of the value that if the current sprite index
VBuffOrigin equ tmp11
mdsOut rts
_MarkDirtySprite
stz _Sprites+TILE_STORE_ADDR_1,x ; Clear the this sprite's dirty tile list in case of an early exit
lda _SpriteBits,x
sta SpriteBit
; Clip the sprite's extent to the screen so we can assume (mostly) position values from here on out. Note that
; the sprite width and height are _only_ used in the clip and afterward all calculation use the clip rect
;
; OPTIMIZATION NODE: These values can be calculated in AddSprite/UpdateSprite once and stored in the sprite
; record since the screen size doesn't change.
lda _Sprites+SPRITE_ID,x ; Get an index into the height/width tables based on the sprite bits
and #$1800
xba
lsr
lsr
tay
lda _Sprites+SPRITE_X,x
bpl :pos_x
lda #0
:pos_x cmp ScreenWidth
bcs mdsOut ; sprite is off-screen, exit early
sta Left
lda _Sprites+SPRITE_Y,x
bpl :pos_y
lda #0
:pos_y cmp ScreenHeight
bcs mdsOut ; sprite is off-screen, exit early
sta Top
lda _Sprites+SPRITE_X,x
clc
adc _SpriteWidthMinus1,y
bmi mdsOut ; another off-screen test
cmp ScreenWidth
bcc :ok_x
lda ScreenWidth
dec
:ok_x sta Right
lda _Sprites+SPRITE_Y,x
clc
adc _SpriteHeightMinus1,y
bmi mdsOut ; another off-screen test
cmp ScreenHeight
bcc :ok_y
lda ScreenHeight
dec
:ok_y sta Bottom
; At this point we know that we have to update the tile 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
; 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.
clc
lda Top
adc StartYMod208 ; Adjust for the scroll offset (could be a negative number!)
tay ; Save this value
and #$0007 ; Get (StartY + SpriteY) mod 8
eor #$FFFF
inc
clc
adc Top ; subtract from the Y position (possible to go negative here)
sta TileTop ; This position will line up with the tile that the sprite overlaps with
tya ; Get back the position of the sprite top in the code field
cmp #208 ; check if we went too far positive
bcc *+5
sbc #208
lsr
lsr
; lsr ; This is the row in the Tile Store for top-left corner of the sprite
and #$FFFE ; Store the pre-multiplied by 2 for indexing in the :mark_R_C routines
sta RowTop
lda Bottom ; Figure out how many tiles are needed to cover the sprite's area
sec
sbc TileTop
and #$0018 ; Clear out the lower bits and stash in bits 4 and 5
sta AreaIndex
; Repeat to get the same information for the columns
clc
lda Left
adc StartXMod164
tay
and #$0003
eor #$FFFF
inc
clc
adc Left
sta TileLeft
tya
cmp #164
bcc *+5
sbc #164
lsr
; lsr
and #$FFFE ; Same pre-multiply by 2 for later
sta ColLeft
; 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
clc
lda TileTop
adc #NUM_BUFF_LINES
xba
clc
adc TileLeft
sta VBuffOrigin ; Save once to use later (constant offsets)
; Calculate the number of columns and dispatch
txy ; Swap the sprite index into the Y register
lda Right
sec
sbc TileLeft
and #$000C
lsr ; bit 0 is always zero and width stored in bits 1 and 2
ora AreaIndex
tax
jmp (:mark,x)
:mark dw :mark1x1,:mark1x2,:mark1x3,mdsOut
dw :mark2x1,:mark2x2,:mark2x3,mdsOut
dw :mark3x1,:mark3x2,:mark3x3,mdsOut
dw mdsOut,mdsOut,mdsOut,mdsOut
; Dispatch to the calculated sizing
; Begin a list of subroutines to cover all of the valid sprite size compinations. This is all unrolled code,
; maily to be able to do an unrolled fill of the TILE_STORE_ADDR_X values. Thus, it's important that the clipping
; function does its job properly since it allows up to save a lot of time here.
;
; These functional are a trade off of being composable versus fast. Having to pay for multiple JSR/RTS invoations
; in the hot sprite path isn't great, but we're at a point of diminishing returns.
;
; There *might* be some speed gained by pushing a list of :mark_R_C addressed onto the stack in the clipping routing
; and dispatching that way, but probably not...
:mark1x1
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
lda #0
sta _Sprites+TILE_STORE_ADDR_2,y
rts
:mark1x2
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_2,y
lda #0
sta _Sprites+TILE_STORE_ADDR_3,y
rts
:mark1x3
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_0_2
sta _Sprites+TILE_STORE_ADDR_3,y
lda #0
sta _Sprites+TILE_STORE_ADDR_4,y
rts
:mark1x2
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_2,y
lda #0
sta _Sprites+TILE_STORE_ADDR_3,y
rts
:mark2x2
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_3,y
jsr :mark_1_1
sta _Sprites+TILE_STORE_ADDR_4,y
lda #0
sta _Sprites+TILE_STORE_ADDR_5,y
rts
:mark2x3
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_0_2
sta _Sprites+TILE_STORE_ADDR_3,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_4,y
jsr :mark_1_1
sta _Sprites+TILE_STORE_ADDR_5,y
jsr :mark_1_2
sta _Sprites+TILE_STORE_ADDR_6,y
lda #0
sta _Sprites+TILE_STORE_ADDR_7,y
rts
:mark3x1
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_2_0
sta _Sprites+TILE_STORE_ADDR_3,y
lda #0
sta _Sprites+TILE_STORE_ADDR_4,y
rts
:mark3x2
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_2_0
sta _Sprites+TILE_STORE_ADDR_3,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_4,y
jsr :mark_1_1
sta _Sprites+TILE_STORE_ADDR_5,y
jsr :mark_2_1
sta _Sprites+TILE_STORE_ADDR_6,y
lda #0
sta _Sprites+TILE_STORE_ADDR_7,y
rts
:mark3x3
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_2_0
sta _Sprites+TILE_STORE_ADDR_3,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_4,y
jsr :mark_1_1
sta _Sprites+TILE_STORE_ADDR_5,y
jsr :mark_2_1
sta _Sprites+TILE_STORE_ADDR_6,y
jsr :mark_0_2
sta _Sprites+TILE_STORE_ADDR_7,y
jsr :mark_1_2
sta _Sprites+TILE_STORE_ADDR_8,y
jsr :mark_2_2
sta _Sprites+TILE_STORE_ADDR_9,y
lda #0
sta _Sprites+TILE_STORE_ADDR_10,y
rts
; Begin List of subroutines to mark each tile offset
:mark_0_0
ldx RowTop
lda ColLeft
clc
adc TileStoreYTable,x ; Fixed offset to the next row
tax ; This is the tile store offset
lda VBuffOrigin
; adc #{0*4}+{0*256}
sta TileStore+TS_SPRITE_ADDR,x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,x
sta TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX ; Needs X = tile store offset; destroys A,X. Returns X in A
:mark_1_0
lda ColLeft
ldx RowTop
clc
adc TileStoreYTable+2,x
tax
lda VBuffOrigin
adc #{0*4}+{1*8*256}
sta TileStore+TS_SPRITE_ADDR,x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,x
sta TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_2_0
lda ColLeft
ldx RowTop
clc
adc TileStoreYTable+4,x
tax
lda VBuffOrigin
adc #{0*4}+{2*8*256}
sta TileStore+TS_SPRITE_ADDR,x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,x
sta TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_0_1
ldx ColLeft
lda NextCol+2,x
ldx RowTop
clc
adc TileStoreYTable,x
tax
lda VBuffOrigin
adc #{1*4}+{0*8*256}
sta TileStore+TS_SPRITE_ADDR,x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,x
sta TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_1_1
ldx ColLeft
lda NextCol+2,x
ldx RowTop
clc
adc TileStoreYTable+2,x
tax
lda VBuffOrigin
adc #{1*4}+{1*8*256}
sta TileStore+TS_SPRITE_ADDR,x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,x
sta TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_2_1
ldx ColLeft
lda NextCol+2,x
ldx RowTop
clc
adc TileStoreYTable+4,x
tax
lda VBuffOrigin
adc #{1*4}+{2*8*256}
sta TileStore+TS_SPRITE_ADDR,x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,x
sta TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_0_2
ldx ColLeft
lda NextCol+4,x
ldx RowTop
clc
adc TileStoreYTable,x
tax
lda VBuffOrigin
adc #{2*4}+{0*8*256}
sta TileStore+TS_SPRITE_ADDR,x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,x
sta TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_1_2
ldx ColLeft
lda NextCol+4,x
ldx RowTop
clc
adc TileStoreYTable+2,x
tax
lda VBuffOrigin
adc #{2*4}+{1*8*256}
sta TileStore+TS_SPRITE_ADDR,x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,x
sta TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_2_2
ldx ColLeft
lda NextCol+4,x
ldx RowTop
clc
adc TileStoreYTable+4,x
tax
lda VBuffOrigin
adc #{2*4}+{2*8*256}
sta TileStore+TS_SPRITE_ADDR,x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,x
sta TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
; End list of subroutines to mark dirty tiles
; Range-check and clamp the vertical part of the sprite. When this routine returns we will have valid
; values for the tile-top and row-top. Also, the accumulator will return the number of rows to render,
; a value of zero means that all of the sprite's rows are off-screen.
;
; This subroutine takes are of calculating the extra tile for unaligned accesses, too.
_SpriteHeight dw 8,8,16,16
_SpriteHeightMinus1 dw 7,7,15,15
_SpriteRows dw 1,1,2,2
_SpriteWidth dw 4,8,4,8
_SpriteWidthMinus1 dw 3,7,3,7
_SpriteCols dw 1,2,1,2
; Convert sprite index to a bit position
_SpriteBits dw $0001,$0002,$0004,$0008,$0010,$0020,$0040,$0080,$0100,$0200,$0400,$0800,$1000,$2000,$4000,$8000

View File

@ -224,12 +224,33 @@ ScreenAddr ENT
; Table of offsets into each row of a Tile Store table. We currently have two tables defined; one
; that is the backing store for the tiles rendered into the code field, and another that holds
; backlink information on the sprite entries that overlap various tiles.
]step equ 0
;
; This table is double-length to support accessing off the end modulo its legth
TileStoreYTable ENT
]step equ 0
lup 26
dw ]step
]step = ]step+{41*2}
--^
]step equ 0
lup 26
dw ]step
]step = ]step+{41*2}
--^
; Create a table to look up the "next" column with modulo wraparound. Basically a[i] = i
; and the table is double-length. Use contanct offsets to pick an amount to advance
NextCol
]step equ 0
lup 41
dw ]step
]step = ]step+2
--^
]step equ 0
lup 41
dw ]step
]step = ]step+2
--^
; This is a double-length table that holds the right-edge adresses of the playfield on the physical
; screen. At most, it needs to hold 200 addresses for a full height playfield. It is double-length

View File

@ -480,16 +480,16 @@ _CopyBG1Tile
;
; TileStore+TS_TILE_ID : Tile descriptor
; TileStore+TS_DIRTY : $FFFF is clean, otherwise stores a back-reference to the DirtyTiles array
; TileStore+TS_SPRITE_FLAG : Set to TILE_SPRITE_BIT is a sprite is present at this tile location
; TileStore+TS_SPRITE_FLAG : Set to TILE_SPRITE_BIT if a sprite is present at this tile location
; TileStore+TS_SPRITE_ADDR ; Address of the tile in the sprite plane
; TileStore+TS_TILE_ADDR : Address of the tile in the tile data buffer
; TIleStore+TS_CODE_ADDR_LOW : Low word of the address in the code field that receives the tile
; TileStore+TS_CODE_ADDR_LOW : Low word of the address in the code field that receives the tile
; TileStore+TS_CODE_ADDR_HIGH : High word of the address in the code field that receives the tile
; TileStore+TS_WORD_OFFSET : Logical number of word for this location
; TileStore+TS_BASE_ADDR : Copy of BTableAddrLow
TileStore ENT
ds TILE_STORE_SIZE*9
ds TILE_STORE_SIZE*10
; A list of dirty tiles that need to be updated in a given frame
DirtyTileCount ds 2
@ -674,6 +674,9 @@ _PushDirtyTileOld
; alternate version that is very slightly slower, but preserves the y-register
_PushDirtyTile
tax
; alternate entry point if the x-register is already set
_PushDirtyTileX
lda TileStore+TS_DIRTY,x
bpl :occupied2