iigs-game-engine/src/TileMap.s

386 lines
14 KiB
ArmAsm
Raw Normal View History

2021-08-05 13:20:38 +00:00
; Routines for handling tilemaps
;
; This module contains higher-level functions than the low-level tile rendering routines. The
; goal here is to take a rectangular tilemap data structure and efficiently render it into
; code buffer. Especially important is to only draw new tiles as they come into view.
2021-08-05 13:20:38 +00:00
;
; Also, we maintain a tilemap cache to track the current state of the tiles rendered into
; the code field so if, by chance, a tile that comes into view is the same as a tile that
; has already been drawn, then there is no reason to update it. This happen quite often
; in actual games since the primary background is often large empty areas, or runs
; of repeating tiles.
; Debug locations
LastTop ds 2
LastBottom ds 2
LastLeft ds 2
LastRight ds 2
; The ranges are [:Left, :Right] and [:Top, :Bottom], so :Right can be, at most, 40
; if we are drawing all 41 tiles (Index 0 through 40). The :Bottom value can be
; at most 25.
MAX_TILE_X equ 40
MAX_TILE_Y equ 25
; _UpdateBG0TileMap
;
; Fill in dirty tiles into the BG0 buffer.
;
; A = $FFFF re-render the entire playfield. Otherwise only render difference from the old
; coordinates.
2021-08-05 13:20:38 +00:00
_UpdateBG0TileMap
:Left equ tmp0
:Right equ tmp1
:Top equ tmp2
:Bottom equ tmp3
:Width equ tmp4 ; Used in DrawRectBG0
2021-08-05 13:20:38 +00:00
:Height equ tmp5
:MulA equ tmp6 ; Scratch space for multiplication
2021-08-05 13:20:38 +00:00
:MulB equ tmp7
:Offset equ tmp8 ; Address offset into the tilemap
2021-08-05 13:20:38 +00:00
:Span equ tmp9
:GlobalTileIdxX equ tmp10
:GlobalTileIdxY equ tmp11
:BlkX equ tmp12
:BlkY equ tmp13
lda StartY ; calculate the tile index of the current location
2021-08-05 13:20:38 +00:00
lsr
lsr
lsr
2021-08-05 13:20:38 +00:00
sta BG0TileOriginY
lda OldStartY
lsr
lsr
lsr
2021-08-05 13:20:38 +00:00
sta OldBG0TileOriginY
lda StartX
lsr
lsr
sta BG0TileOriginX
lda OldStartX
lsr
lsr
sta OldBG0TileOriginX
2021-08-05 13:20:38 +00:00
; Figure out the two rectangular regions that need to be updated. We check for changes in Y-direction
; first because it's a bit more efficient to redraw tiles in long horizontal strips, because we do not
; have to skip to different banks.
;
; +---------------------------+----------+ <-- Top
; | | |
; | | New |
; | | |
; | Old Area | (drawn) |
; | | (second) |
; | | |
; +---------------------------+==========|
; | |
; | New Area (drawn first) |
; | |
; +--------------------------------------+ <-- Bottom
; ^ ^
; | |
; +--- Left Right --+
stz :Left ; prepare to do the entire screen
lda ScreenTileWidth ; and then whack off the parts
sta :Right ; that are not needed
2021-08-05 13:20:38 +00:00
stz :Top ; since the ranges are inclusive, we are
lda ScreenTileHeight ; always going to be drawing width+1 tiles
sta :Bottom ; which takes care of edge tiles.
2021-08-05 13:20:38 +00:00
; If we are supposed to refresh the whole field, just do that and return
lda #DIRTY_BIT_BG0_REFRESH
bit DirtyBits
beq :NoRefresh
trb DirtyBits ; Clear the dirty bit
:FullScreen jmp :DrawRectBG0 ; Let the DrawRectBG0 RTS take care of the return for us
:NoRefresh
2021-08-05 13:20:38 +00:00
lda BG0TileOriginY
cmp OldBG0TileOriginY
beq :NoYUpdate ; if equal, don't change Y
2021-08-05 13:20:38 +00:00
sec
sbc OldBG0TileOriginY ; find the difference; D = Y_new - Y_old
bpl :DoBottom ; if we scrolled up, fill in the bottom row(s)
2021-08-05 13:20:38 +00:00
eor #$FFFF ; if we scrolled down, Y_new < Y_old and we need
cmp :Bottom ; to fill in the top row(s) from 0 to Y_new - Y_old - 1
bcs :FullScreen ; If the displacement was very large, just fill in the whole screen
sta :Bottom
2021-08-05 13:20:38 +00:00
bra :DoYUpdate
:DoBottom
eor #$FFFF ; same explanation as above, except we are filling in from
inc ; Bottom - (Y_new - Y_old) to Bottom
2021-08-05 13:20:38 +00:00
clc
adc ScreenTileHeight
bmi :FullScreen
2021-08-05 13:20:38 +00:00
sta :Top
:DoYUpdate
jsr :DrawRectBG0 ; Fill in the rectangle.
2021-08-05 13:20:38 +00:00
; We performed an update in the Y-direction, so now change the bounds so
; an update in the X-direction will not draw too many rows
;
; +---------------------------+----------+
; | | |
; | | New |
; | | |
; | Old Area | (drawn) |
; | | (second) |
; | | |
; +---------------------------+==========| <-- Top
; |//////////////////////////////////////|
; |// New Area (drawn first) ////////////|
; |//////////////////////////////////////|
; +--------------------------------------+ <-- Bottom
; ^ ^
; | |
; +--- Left Right --+
lda :Top
beq :drewTop
dec ; already did Y to HEIGHT, so only need to draw from
sta :Bottom ; 0 to (Y-1) for any horizontal updates
2021-08-05 13:20:38 +00:00
stz :Top
bra :NoYUpdate
:drewTop
lda :Bottom ; opposite, did 0 to Y
inc ; so do Y+1 to HEIGHT
2021-08-05 13:20:38 +00:00
sta :Top
lda ScreenTileHeight
sta :Bottom
; +---------------------------+----------+ <-- Top
; | | |
; | | New |
; | | |
; | Old Area | (drawn) |
; | | (second) |
; | | | <-- Bottom
; +---------------------------+==========|
; |//////////////////////////////////////|
; |// New Area (drawn first) ////////////|
; |//////////////////////////////////////|
; +--------------------------------------+
; ^ ^
; | |
; +--- Left Right --+
; The Top an Bottom are set the the correct values to draw in whatever potential range of tiles
; need to be draws if there was any horizontal displacement
:NoYUpdate
lda BG0TileOriginX ; Did the first column of the tile map change from before?
cmp OldBG0TileOriginX ; Did it change from before?
beq :NoXUpdate ; no, so we can ignore this
2021-08-05 13:20:38 +00:00
sec
sbc OldBG0TileOriginX ; find the difference
bpl :DoRightSide ; did we move in a pos or neg?
2021-08-05 13:20:38 +00:00
; Handle the two sides in an analagous way as the vertical code
eor #$FFFF
cmp :Right
bcs :FullScreen
2021-08-05 13:20:38 +00:00
sta :Right
bra :DoXUpdate
:DoRightSide
eor #$FFFF
inc
clc
adc ScreenTileWidth
bmi :FullScreen
2021-08-05 13:20:38 +00:00
sta :Left
:DoXUpdate
jsr :DrawRectBG0 ; Fill in the rectangle.
2021-08-05 13:20:38 +00:00
:NoXUpdate
rts
;:Debug
; lda :Top ; Debugging
; sta LastTop
; lda :Bottom
; sta LastBottom
; lda :Left
; sta LastLeft
; lda :Right
; sta LastRight
; rts
2021-08-05 13:20:38 +00:00
; This is a private subroutine that draws in tiles into the code fields using the
; data from the tilemap and the local :Top, :Left, :Bottom and :Right parameters.
:DrawRectBG0
lda :Bottom
sec
sbc :Top
inc
sta :Height ; Maximum value of 26 (top = 0, bottom = 25)
2021-08-05 13:20:38 +00:00
lda :Right
sec
sbc :Left
inc
sta :Width ; Maximum value of 41 (left = 0, right = 40)
2021-08-05 13:20:38 +00:00
; Compute the offset into the tile array of the top-left corner
lda :Left
clc
adc BG0TileOriginX
sta :GlobalTileIdxX
lda :Top
clc
adc BG0TileOriginY ; This is the global verical index
2021-08-05 13:20:38 +00:00
sta :GlobalTileIdxY
ldx TileMapWidth
jsr :MulAX
clc
adc :GlobalTileIdxX
asl ; Double for word sizes
sta :Offset ; Stash the pointer offset in Y
2021-08-05 13:20:38 +00:00
; Draw the tiles
2021-08-05 13:20:38 +00:00
lda TileMapWidth
sec
sbc :Width
asl ; This is the number of bytes to move the Offset to advance from the end of
sta :Span ; one line to the beginning of the next
2021-08-05 13:20:38 +00:00
; Now we need to figure out the code field tile coordinate of corner of
; play field. That is, becuase the screen is scrolling, the location of
; tile (0, 0) could be anywhere within the code field
lda StartYMod208 ; This is the code field line that is at the top of the screen
and #$FFF8 ; Clamp to the nearest block
2021-08-05 13:20:38 +00:00
lsr
lsr
lsr ; Could optimize because the Tile code shifts back....
2021-08-05 13:20:38 +00:00
clc
adc :Top
cmp #MAX_TILE_Y+1 ; Top can be less than or equal to 25
bcc *+5
sbc #MAX_TILE_Y+1
sta :BlkY ; This is the Y-block we start drawing from
2021-08-05 13:20:38 +00:00
lda StartXMod164 ; Dx the same thing for X, except only need to clamp by 4
2021-08-05 13:20:38 +00:00
and #$FFFC
lsr
lsr
clc
adc :Left
cmp #MAX_TILE_X+1 ; Left can be less than or equal to 40
bcc *+5
sbc #MAX_TILE_X+1
2021-08-05 13:20:38 +00:00
sta :BlkX
; Call the copy tile routine to blit the tile data into the playfield
;
; A = Tile ID (0 - 1023)
; X = Tile column (0 - 40)
; Y = Tile row (0 - 25)
pei :BlkX ; cache the starting X-block index to restore later
pei :Width ; cache the Width value to restore later
2021-08-05 13:20:38 +00:00
:yloop
:xloop
ldy :Offset ; Set up the arguments and call the tile blitter
2021-08-05 13:20:38 +00:00
lda [TileMapPtr],y
iny ; pre-increment the address. A bit faster than two "INC DP" instructions
2021-08-05 13:20:38 +00:00
iny
sty :Offset
ldx :BlkX
ldy :BlkY
jsr CopyTile
2021-08-05 13:20:38 +00:00
lda :BlkX
inc
cmp #MAX_TILE_X+1 ; If we go past the maximum block index, wrap around
bcc *+5
lda #0
sta :BlkX
dec :Width ; Decrement out count
2021-08-05 13:20:38 +00:00
bne :xloop
lda :Offset ; Move to the next line of the Tile Map
2021-08-05 13:20:38 +00:00
clc
adc :Span
sta :Offset
lda 3,s ; Reset the BlkX
2021-08-05 13:20:38 +00:00
sta :BlkX
lda 1,s ; Reset the width
2021-08-05 13:20:38 +00:00
sta :Width
lda :BlkY ; The y lookup has a double-length array, may not need the bounds check
inc
cmp #MAX_TILE_Y+1
bcc *+5
lda #0
sta :BlkY
dec :Height ; Have we done all of the rows?
2021-08-05 13:20:38 +00:00
bne :yloop
pla ; Pop off cached values
2021-08-05 13:20:38 +00:00
pla
rts
; Quick multiplication of the accumulator and x-register
; A = A * X
:MulAX
stx :MulA
cmp :MulA ; Put the smaller value in MulA (less shifts on average)
2021-08-05 13:20:38 +00:00
bcc :swap
sta :MulB
bra :entry
:swap stx :MulB
sta :MulA
:entry
lda #0
; Start shifting and adding. We actually do an extra
; shift if MulA is zero, but a zero value does not
; change the result and it allows us to eliminate a
; branch on the inner loop
:loop
lsr :MulA ; shift out the LSB
bcc :skip ; zero is no multiply
2021-08-05 13:20:38 +00:00
clc
adc :MulB
:skip
asl :MulB ; double the multplicand
2021-08-05 13:20:38 +00:00
ldx :MulA
bne :loop
rts