iigs-game-engine/src/Tiles.s

580 lines
20 KiB
ArmAsm
Raw Normal View History

; Basic tile functions
; Copy tileset data from a pointer in memory to the tiledata back
; X = high word
; A = low word
_LoadTileSet
sta tmp0
stx tmp1
ldy #0
tyx
:loop lda [tmp0],y
stal tiledata,x
dex
dex
dey
dey
bne :loop
rts
; Low-level function to take a tile descriptor and return the address in the tiledata
; bank. This is not too useful in the fast-path because the fast-path does more
; incremental calculations, but it is handy for other utility functions
;
; A = tile descriptor
;
; The address is the TileID * 128 + (HFLIP * 64)
_GetTileAddr
asl ; Multiply by 2
bit #2*TILE_HFLIP_BIT ; Check if the horizontal flip bit is set
beq :no_flip
inc ; Set the LSB
:no_flip asl ; x4
asl ; x8
asl ; x16
asl ; x32
asl ; x64
asl ; x128
rts
; Ignore the horizontal flip bit
_GetBaseTileAddr
asl ; Multiply by 2
asl ; x4
asl ; x8
asl ; x16
asl ; x32
asl ; x64
asl ; x128
rts
2022-04-29 17:38:04 +00:00
; Helper function to get the address offset into the tile cachce / tile backing store
; X = tile column [0, 40] (41 columns)
; Y = tile row [0, 25] (26 rows)
_GetTileStoreOffset
phx ; preserve the registers
phy
jsr _GetTileStoreOffset0
ply
plx
rts
_GetTileStoreOffset0
tya
asl
tay
txa
asl
clc
adc TileStoreYTable,y
rts
; Initialize the tile storage data structures. This takes care of populating the tile records with the
; appropriate constant values.
InitTiles
:col equ tmp0
:row equ tmp1
:vbuff equ tmp2
:base equ tmp3
2022-06-01 18:55:04 +00:00
; Initialize the Tile Store
ldx #TILE_STORE_SIZE-2
lda #25
sta :row
lda #40
sta :col
lda #$8000
sta :vbuff
:loop
2022-06-01 18:55:04 +00:00
; The first set of values in the Tile Store that are changed during each frame based on the actions
; that are happening
lda #0
2022-05-20 04:40:45 +00:00
sta TileStore+TS_TILE_ID,x ; clear the tile store with the special zero tile
sta TileStore+TS_TILE_ADDR,x
sta TileStore+TS_SPRITE_FLAG,x ; no sprites are set at the beginning
sta TileStore+TS_DIRTY,x ; none of the tiles are dirty
2022-06-01 18:55:04 +00:00
; Set the default tile rendering functions
lda EngineMode
bit #ENGINE_MODE_DYN_TILES+ENGINE_MODE_TWO_LAYER
beq :fast
bit #ENGINE_MODE_TWO_LAYER
beq :dyn
2022-06-01 18:55:04 +00:00
; ldal TileProcs
; sta TileStore+TS_BASE_TILE_DISP,x
bra :out
:fast
lda #0 ; Initialize with Tile 0
ldy #FastProcs
jsr _SetTileProcs
bra :out
:dyn lda #0 ; Initialize with Tile 0
ldy #DynOverZA
jsr _SetTileProcs
2022-06-01 18:55:04 +00:00
:out
; lda DirtyTileProcs ; Fill in with the first dispatch address
; stal TileStore+TS_DIRTY_TILE_DISP,x
;
; lda TileProcs ; Same for non-dirty, non-sprite base case
; stal TileStore+TS_BASE_TILE_DISP,x
2022-06-01 18:55:04 +00:00
; The next set of values are constants that are simply used as cached parameters to avoid needing to
; calculate any of these values during tile rendering
lda :row ; Set the long address of where this tile
asl ; exists in the code fields
tay
lda #>TileStore ; get middle 16 bits: "00 -->BBHH<-- LL"
and #$FF00 ; merge with code field bank
ora BRowTableHigh,y
2022-05-20 04:40:45 +00:00
sta TileStore+TS_CODE_ADDR_HIGH,x ; High word of the tile address (just the bank)
lda BRowTableLow,y
sta :base
; sta TileStore+TS_BASE_ADDR,x ; May not be needed later if we can figure out the right constant...
lda :col ; Set the offset values based on the column
asl ; of this tile
asl
2022-05-20 04:40:45 +00:00
sta TileStore+TS_WORD_OFFSET,x ; This is the offset from 0 to 82, used in LDA (dp),y instruction
tay
lda Col2CodeOffset+2,y
clc
adc :base
; adc TileStore+TS_BASE_ADDR,x
2022-05-20 04:40:45 +00:00
sta TileStore+TS_CODE_ADDR_LOW,x ; Low word of the tile address in the code field
dec :col
bpl :hop
dec :row
lda #40
sta :col
:hop
dex
dex
bpl :loop
rts
2022-04-29 17:38:04 +00:00
; Set a tile value in the tile backing store. Mark dirty if the value changes
;
; A = tile id
; X = tile column [0, 40] (41 columns)
; Y = tile row [0, 25] (26 rows)
;
; Registers are not preserved
oldTileId equ blttmp ; This location is used in _SetTileProcs, too
newTileId equ blttmp+2
procIdx equ blttmp+4
2022-04-29 17:38:04 +00:00
_SetTile
sta newTileId
2022-04-29 17:38:04 +00:00
jsr _GetTileStoreOffset0 ; Get the address of the X,Y tile position
tax
2022-04-29 17:38:04 +00:00
lda TileStore+TS_TILE_ID,x
cmp newTileId
bne :changed
rts
:changed sta oldTileId
lda newTileId
sta TileStore+TS_TILE_ID,x ; Value is different, store it.
2022-04-29 17:38:04 +00:00
jsr _GetTileAddr
sta TileStore+TS_TILE_ADDR,x ; Committed to drawing this tile, so get the address of the tile in the tiledata bank for later
2022-04-29 17:38:04 +00:00
; Set the standard renderer procs for this tile.
;
; 1. The dirty render proc is always set the same.
; 2. If BG1 and DYN_TILES are disabled, then the TS_BASE_TILE_DISP is selected from the Fast Renderers, otherwise
; it is selected from the full tile rendering functions.
; 3. The copy process is selected based on the flip bits
;
; When a tile overlaps the sprite, it is the responsibility of the Render function to compose the appropriate
; functionality. Sometimes it is simple, but in cases of the sprites overlapping Dynamic Tiles and other cases
; it can be more involved.
; Calculate the base tile proc selector from the tile Id
stz procIdx
lda #TILE_PRIORITY_BIT
bit newTileId
beq :low_priority
lda #4
sta procIdx
:low_priority
lda #TILE_ID_MASK
bit newTileId
bne :not_zero
lda #2
tsb procIdx
:not_zero
lda #TILE_VFLIP_BIT
beq :no_vflip
lda #1
tsb procIdx
:no_vflip
; Multiple by 6 to get the correct table entry index
asl procIdx
lda procIdx
asl
adc procIdx
sta procIdx
; Now integrate with the engine mode indicator
lda EngineMode
bit #ENGINE_MODE_DYN_TILES+ENGINE_MODE_TWO_LAYER
bne :not_fast
brl :setTileFast
:not_fast bit #ENGINE_MODE_TWO_LAYER
bne :not_dyn
brl :setTileDyn
:not_dyn
lda TileStore+TS_TILE_ID,x
2022-04-29 17:38:04 +00:00
and #TILE_VFLIP_BIT+TILE_HFLIP_BIT ; get the lookup value
xba
tay
; ldal DirtyTileProcs,x
; sta TileStore+TS_DIRTY_TILE_DISP,y
2022-04-29 17:38:04 +00:00
; ldal CopyTileProcs,x
; sta TileStore+TS_DIRTY_TILE_COPY,y
2022-04-29 17:38:04 +00:00
lda TileStore+TS_TILE_ID,x ; Get the non-sprite dispatch address
2022-04-29 17:38:04 +00:00
and #TILE_CTRL_MASK
xba
tay
; ldal TileProcs,y
; sta TileStore+TS_BASE_TILE_DISP,y
jmp _PushDirtyTileX ; on the next call to _ApplyTiles
2022-04-29 17:38:04 +00:00
; Specialized check for when the engine is in "Fast" mode. If is a simple decision tree based on whether
; the tile priority bit is set, and whether this is the special tile 0 or not.
:setTileFast
ldy #FastProcs
lda procIdx
jsr _SetTileProcs
jmp _PushDirtyTileX
; Specialized check for when the engine has enabled dynamic tiles. In this case we are no longer
; guaranteed that the opcodes in a tile are PEA instructions. If the old tile and the new tile
; are both Dynamic tiles or both Basic tiles, then we can use an optimized routine. Otherwise
; we must set the opcodes as well as the operands
:setTileDyn
brk $55
ldy #DynProcs
lda procIdx
jsr _SetTileProcs
jmp _PushDirtyTileX
; X = Tile Store offset
; Y = Engine Mode Base Table address
; A = Table proc index
;
; see TileProcTables in static/TileStore.s
tblPtr equ blttmp
_SetTileProcs
; Set a long pointer to this bank
sty tblPtr
clc
adc tblPtr
sta tblPtr
phk
phk
pla
and #$00FF
sta tblPtr+2
; Lookup the tile procedures
ldy #0
lda [tblPtr],y
stal K_TS_BASE_TILE_DISP,x
ldy #2
lda [tblPtr],y
stal K_TS_SPRITE_TILE_DISP,x
2022-04-29 17:38:04 +00:00
ldy #4
lda [tblPtr],y
stal K_TS_ONE_SPRITE,x
rts
; TileProcTables
;
; Tables of tuples used to populate the K_TS_* dispatch arrays for different combinations. This is
; easier to maintain than a bunch of conditional code. Each etry hold three addresses.
;
; First address: Draw a tile directly into the code buffer (no sprites)
; Second address: Draw a tile merged with sprite data from the direct page
; Third address: Specialize routine to draw a tile merged with one sprite
;
; There are unique tuples of routines for all of the different combinations of tile properties
; and engine modes. This is an extesive number of combinations, but it simplified the development
; and maintainence of the rendering subroutines. Also, the difference subroutines can be written
; in any way and can make use of their on subroutines to reduce code size.
;
; Properties:
;
; [MODE] ENGINE_MODE: Fast, Dyn, TwoLayer
; [Z | N] Is Tile 0? : Yes, No
; [A | V] Is VFLIP? : Yes, No
; [Over | Under] Priority? : Yes, No
;
; So eight tuples per engine mode; 24 tuples total. Table name convention
;
; <MODE><Over|Under><Z|N><A|V>
FastProcs
FastOverZA dw _TBConstTile0,GenericOverZero,_OneSpriteFastOver0
FastOverZV dw _TBConstTile0,GenericOverZero,_OneSpriteFastOver0
FastOverNA dw _TBCopyDataFast,GenericOverAFast,_OneSpriteFastOverA
FastOverNV dw _TBCopyDataVFast,GenericOverVFast,_OneSpriteFastOverV
FastUnderZA dw _TBConstTile0,GenericUnderZero,GenericUnderZero
FastUnderZV dw _TBConstTile0,GenericUnderZero,GenericUnderZero
FastUnderNA dw _TBCopyDataFast,GenericUnderAFast,_OneSpriteFastUnderA
FastUnderNV dw _TBCopyDataVFast,GenericUnderVFast,_OneSpriteFastUnderV
DynProcs
DynOverZA
DynOverZV
DynOverNA
DynOverNV
DynUnderZA
DynUnderZV
DynUnderNA
DynUnderNV
2022-04-29 17:38:04 +00:00
; SetBG0XPos
;
; Set the virtual horizontal position of the primary background layer. In addition to
; updating the direct page state locations, this routine needs to preserve the original
; value as well. This is a bit subtle, because if this routine is called multiple times
; with different values, we need to make sure the *original* value is preserved and not
; continuously overwrite it.
;
; We assume that there is a clean code field in this routine
_SetBG0XPos
cmp StartX
beq :out ; Easy, if nothing changed, then nothing changes
ldx StartX ; Load the old value (but don't save it yet)
sta StartX ; Save the new position
lda #DIRTY_BIT_BG0_X
tsb DirtyBits ; Check if the value is already dirty, if so exit
bne :out ; without overwriting the original value
stx OldStartX ; First change, so preserve the value
:out rts
; SetBG0YPos
;
; Set the virtual position of the primary background layer.
_SetBG0YPos
cmp StartY
beq :out ; Easy, if nothing changed, then nothing changes
ldx StartY ; Load the old value (but don't save it yet)
sta StartY ; Save the new position
lda #DIRTY_BIT_BG0_Y
tsb DirtyBits ; Check if the value is already dirty, if so exit
bne :out ; without overwriting the original value
stx OldStartY ; First change, so preserve the value
:out rts
; Macro helper for the bit test tree
; dobit bit_position,dest;next;exit
dobit mac
lsr
bcc next_bit
beq last_bit
tax
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
txa
jmp ]3
last_bit lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
jmp ]4
next_bit
<<<
; Specialization for the first sprite which can optimize its dispatch if its the only one
; dobit bit_position,dest;next;exit
dobit1 mac
lsr
bcc next_bit
beq last_bit
tax
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
txa
jmp ]3
last_bit lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
tyx
jmp (K_TS_ONE_SPRITE,x)
next_bit
<<<
; If we find a last bit (4th in this case) and will exit
stpbit mac
lsr
bcc next_bit
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
jmp ]3
next_bit
<<<
; Last bit test which *must* be set
endbit mac
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
jmp ]3
<<<
endbit1 mac
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
tyx
jmp (K_TS_ONE_SPRITE,x)
<<<
; OPTIMIZATION:
;
; bit #$00FF ; Optimization to skip the first 8 bits if they are all zeros
; bne norm_entry
; xba
; jmp skip_entry
;
; Placed at the entry point
; This is a complex, but fast subroutine that is called from the core tile rendering code. It
; Takes a bitmap of sprites in the Accumulator and then extracts the VBuff addresses for the
; target TileStore entry and places them in specific direct page locations.
;
; Inputs:
; A = sprite bitmap (assumed to be non-zero)
; Y = tile store index
; D = second work page
; B = vbuff array bank
; Output:
; X =
;
; ]1 address of single sprite process
; ]2 address of two sprite process
; ]3 address of three sprite process
; ]4 address of four sprite process
SpriteBitsToVBuffAddrs mac
dobit1 0;0;b_1_1
dobit1 1;0;b_2_1
dobit1 2;0;b_3_1
dobit1 3;0;b_4_1
dobit1 4;0;b_5_1
dobit1 5;0;b_6_1
dobit1 6;0;b_7_1
dobit1 7;0;b_8_1
dobit1 8;0;b_9_1
dobit1 9;0;b_10_1
dobit1 10;0;b_11_1
dobit1 11;0;b_12_1
dobit1 12;0;b_13_1
dobit1 13;0;b_14_1
dobit1 14;0;b_15_1
endbit1 15;0
b_1_1 dobit 1;1;b_2_2;]2
b_2_1 dobit 2;1;b_3_2;]2
b_3_1 dobit 3;1;b_4_2;]2
b_4_1 dobit 4;1;b_5_2;]2
b_5_1 dobit 5;1;b_6_2;]2
b_6_1 dobit 6;1;b_7_2;]2
b_7_1 dobit 7;1;b_8_2;]2
b_8_1 dobit 8;1;b_9_2;]2
b_9_1 dobit 9;1;b_10_2;]2
b_10_1 dobit 10;1;b_11_2;]2
b_11_1 dobit 11;1;b_12_2;]2
b_12_1 dobit 12;1;b_13_2;]2
b_13_1 dobit 13;1;b_14_2;]2
b_14_1 dobit 14;1;b_15_2;]2
b_15_1 endbit 15;1;]2
b_2_2 dobit 2;2;b_3_3;]3
b_3_2 dobit 3;2;b_4_3;]3
b_4_2 dobit 4;2;b_5_3;]3
b_5_2 dobit 5;2;b_6_3;]3
b_6_2 dobit 6;2;b_7_3;]3
b_7_2 dobit 7;2;b_8_3;]3
b_8_2 dobit 8;2;b_9_3;]3
b_9_2 dobit 9;2;b_10_3;]3
b_10_2 dobit 10;2;b_11_3;]3
b_11_2 dobit 11;2;b_12_3;]3
b_12_2 dobit 12;2;b_13_3;]3
b_13_2 dobit 13;2;b_14_3;]3
b_14_2 dobit 14;2;b_15_3;]3
b_15_2 endbit 15;2;]3
b_3_3 stpbit 3;3;]4
b_4_3 stpbit 4;3;]4
b_5_3 stpbit 5;3;]4
b_6_3 stpbit 6;3;]4
b_7_3 stpbit 7;3;]4
b_8_3 stpbit 8;3;]4
b_9_3 stpbit 9;3;]4
b_10_3 stpbit 10;3;]4
b_11_3 stpbit 11;3;]4
b_12_3 stpbit 12;3;]4
b_13_3 stpbit 13;3;]4
b_14_3 stpbit 14;3;]4
b_15_3 endbit 15;3;]4
<<<
; Store some tables in the K bank that will be used exclusively for jmp (abs,x) dispatch
K_TS_BASE_TILE_DISP ds TILE_STORE_SIZE ; draw the tile without a sprite
K_TS_COPY_TILE_DATA ds TILE_STORE_SIZE ; copy/merge the tile into temp storage
K_TS_SPRITE_TILE_DISP ds TILE_STORE_SIZE ; select the sprite routine for this tile
K_TS_ONE_SPRITE ds TILE_STORE_SIZE ; specialized sprite routine when only one sprite covers the tile
K_TS_APPLY_TILE_DATA ds TILE_STORE_SIZE ; move tile from temp storage into code field