mirror of
https://github.com/lscharen/iigs-game-engine.git
synced 2024-09-22 21:02:33 +00:00
586 lines
20 KiB
ArmAsm
586 lines
20 KiB
ArmAsm
; 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
|
|
|
|
|
|
; 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
|
|
|
|
; Initialize the Tile Store
|
|
|
|
ldx #TILE_STORE_SIZE-2
|
|
lda #25
|
|
sta :row
|
|
lda #40
|
|
sta :col
|
|
lda #$8000
|
|
sta :vbuff
|
|
|
|
:loop
|
|
|
|
; The first set of values in the Tile Store that are changed during each frame based on the actions
|
|
; that are happening
|
|
|
|
lda #0
|
|
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
|
|
|
|
; 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
|
|
; 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
|
|
|
|
: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
|
|
|
|
|
|
; 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
|
|
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
|
|
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
|
|
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
|
|
|
|
; 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
|
|
|
|
_SetTile
|
|
sta newTileId
|
|
jsr _GetTileStoreOffset0 ; Get the address of the X,Y tile position
|
|
tax
|
|
|
|
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.
|
|
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
|
|
|
|
; 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
|
|
beq :is_zero
|
|
lda #2
|
|
tsb procIdx
|
|
:is_zero
|
|
|
|
lda #TILE_VFLIP_BIT
|
|
bit newTileId
|
|
beq :no_vflip
|
|
lda #1
|
|
tsb procIdx
|
|
:no_vflip
|
|
|
|
; 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
|
|
and #TILE_VFLIP_BIT+TILE_HFLIP_BIT ; get the lookup value
|
|
xba
|
|
tay
|
|
; ldal DirtyTileProcs,x
|
|
; sta TileStore+TS_DIRTY_TILE_DISP,y
|
|
|
|
; ldal CopyTileProcs,x
|
|
; sta TileStore+TS_DIRTY_TILE_COPY,y
|
|
|
|
lda TileStore+TS_TILE_ID,x ; Get the non-sprite dispatch address
|
|
and #TILE_CTRL_MASK
|
|
xba
|
|
tay
|
|
; ldal TileProcs,y
|
|
; sta TileStore+TS_BASE_TILE_DISP,y
|
|
jmp _PushDirtyTileX ; on the next call to _ApplyTiles
|
|
|
|
; 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
|
|
|
|
; Multiple the proc index by 6 to get the correct table entry offset
|
|
|
|
asl
|
|
sta tblPtr
|
|
asl
|
|
adc tblPtr
|
|
sta tblPtr
|
|
|
|
; Add this offset to the base table address
|
|
|
|
tya
|
|
adc tblPtr
|
|
sta tblPtr
|
|
|
|
; Set the pointer to this bank
|
|
|
|
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
|
|
|
|
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 _TBCopyDataAFast,GenericOverZero,_OneSpriteFastOverA
|
|
FastOverNV dw _TBCopyDataVFast,GenericOverZero,_OneSpriteFastOverV
|
|
;FastOverNA dw _TBCopyDataAFast,GenericOverAFast,_OneSpriteFastOverA
|
|
;FastOverNV dw _TBCopyDataVFast,GenericOverVFast,_OneSpriteFastOverV
|
|
FastUnderZA dw _TBConstTile0,GenericUnderZero,GenericUnderZero
|
|
FastUnderZV dw _TBConstTile0,GenericUnderZero,GenericUnderZero
|
|
FastUnderNA dw _TBConstTile0,GenericOverZero,_OneSpriteFastOver0
|
|
FastUnderNV dw _TBConstTile0,GenericOverZero,_OneSpriteFastOver0
|
|
;FastUnderNA dw _TBCopyDataFast,GenericUnderAFast,_OneSpriteFastUnderA
|
|
;FastUnderNV dw _TBCopyDataVFast,GenericUnderVFast,_OneSpriteFastUnderV
|
|
|
|
DynProcs
|
|
DynOverZA
|
|
DynOverZV
|
|
DynOverNA
|
|
DynOverNV
|
|
DynUnderZA
|
|
DynUnderZV
|
|
DynUnderNA
|
|
DynUnderNV
|
|
|
|
; 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 ; 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
|