iigs-game-engine/src/Sprite2.s

341 lines
12 KiB
ArmAsm

; Scratch space to lay out idealized _MakeDirtySprite
; On input, X register = Sprite Array Index
RowTop equ tmp6
AreaIndex equ tmp7
SpriteBit equ tmp8 ; set the bit of the value that if the current sprite index
; Table of pre-multiplied vbuff strides
vbuff_mul
dw 0*VBUFF_STRIDE_BYTES
dw 1*VBUFF_STRIDE_BYTES
dw 2*VBUFF_STRIDE_BYTES
dw 3*VBUFF_STRIDE_BYTES
dw 4*VBUFF_STRIDE_BYTES
dw 5*VBUFF_STRIDE_BYTES
dw 6*VBUFF_STRIDE_BYTES
dw 7*VBUFF_STRIDE_BYTES
; Marks a sprite 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 SpriteX is always the MAXIMUM value of the corner coordinates. We subtract (SpriteX + StartX) mod 4
; to find the coordinate in the sprite cache 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 (SpriteX - 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 use "mod 8" instead of "mod 4"
;
; When this subroutine is completed, the following values will be calculated
;
; _Sprites+TS_COVERAGE_SIZE : The number of horizontal and vertical playfield tiles covered by the sprite
; _Sprites+TS_LOOKUP_INDEX : TileStore index of the upper-left corner of the sprite
; _Sprites+TS_VBUFF_BASE : Address of the top-left corner of the sprite in the VBUFF sprite stamp memory
;
; The clipped sprite coordinates are used to calculate the tiles that are visible, but the actual
; sprite coordinates (including handling negative values) are used to calculate the VBUFF offset
; values.
mdsOut2
lda #6 ; Pick a value for a 0x0 tile sprite
sta _Sprites+TS_COVERAGE_SIZE,y ; zero the list of tile store addresses
rts
_CalcDirtySprite
lda _Sprites+IS_OFF_SCREEN,y ; Check if the sprite is visible in the playfield
bne mdsOut2
; Part 1: Calculate the visible tiles that the sprite covers. If the sprite is partially
; off-screen, then the visible tiles may be different than the set of tiles
; covered by the sprite. In particular, the upper-left corner tile which defines
; relative offset values will change.
;
; So, we do some calculations with the CLIPPED values and some with the actual
; sprite values. There is an optimization opportunity here to share calculations
; when the x or y position of the sprite is positive.
; Add the first visible row of the sprite to the Y-scroll offset to find the first line in the
; code field that needs to be drawn. The range of values is 0 to 199+207 = [0, 406]. This
; value is dividede by 8, so the range of lookup values is [0, 50], so 51 possible values.
clc
lda _Sprites+SPRITE_CLIP_TOP,y
adc StartYMod208 ; Adjust for the scroll offset
pha ; Cache
and #$FFF8 ; mask first to ensure LSR will clear the carry
lsr
lsr
tax
lda TileStoreLookupYTable,x
sta RowTop ; Even numbers from [0, 100] (51 elements)
; Get the position of the top edge within the tile and then add it to the sprite's height
; to calculate the number of tiles that are overlapped. We use the actual width and height
; values here so small sprites (like 4x4 bullets) only force an update to the actual tiles
; that are intersected, rather than assuming an 8x8 sprite always takes up that amount of
; space.
pla
and #$0007
adc _Sprites+SPRITE_CLIP_HEIGHT,y ; Nominal value between 0 and 16+7 = 23 = 10111
dec
and #$0018
sta AreaIndex
; Add the horizontal position to the horizontal offset to find the first column in the
; code field that needs to be drawn. The range of values is 0 to 159+163 = [0, 322].
; This value is divided by 4, so 81 possible values
clc
lda _Sprites+SPRITE_CLIP_LEFT,y
adc StartXMod164
pha
and #$FFFC
lsr ; Even numbers from [0, 160] (81 elements)
adc RowTop
sta _Sprites+TS_LOOKUP_INDEX,y ; This is the index into the TileStoreLookup table
; Calculate the final amount of visible tiles that need to be refreshed and use that to
; set the coverage size index.
pla
and #$0003
adc _Sprites+SPRITE_CLIP_WIDTH,y ; max width = 8 = 0x08
dec
and #$000C
lsr ; max value = 4 = 0x04
ora AreaIndex ; merge into the area index
sta _Sprites+TS_COVERAGE_SIZE,y ; Save this value as a key to the coverage size of the sprite
; Part 2: Redo some calculation with the actual (signed) sprite positions that take into
; account negative coordinates to set the VBuff offset values.
clc
lda _Sprites+SPRITE_Y,y
adc StartYMod208
bpl :y_ok
clc
adc #208 ; Wrap the actual coordinate around
:y_ok and #$FFF8 ; mask first to ensure LSR will clear the carry
lsr
lsr
tax ; Tile store lookup index
lda _Sprites+SPRITE_X,y
adc StartXMod164
bpl :x_ok
clc
adc #164
:x_ok and #$FFFC
lsr ; Even numbers from [0, 160] (81 elements)
sta tmp3
adc TileStoreLookupYTable,x
pha ; will be PLX later
clc ; Carry should still be clear here....
lda StartYMod208
adc _Sprites+SPRITE_Y,y
bpl :pos_y
clc
adc #208
:pos_y
and #$0007
asl ; Multiply by 48. Would be nice to use a
asl ; table lookup, but the values can be negative
asl ; so do the calculation
asl
sta tmp0
asl
clc
adc tmp0
sta tmp0
; Calculate the final address of the sprite data in the stamp buffer. We have to move earlier
; in the buffer based on the horizontal offset and move up for each vertical offset.
;
; For a negative value we need to adjust the vbuff by the number of off-screen tiles plus
; the alignment adjustment.
clc
lda StartXMod164
adc _Sprites+SPRITE_X,y
bpl :pos_x
clc
adc #164
:pos_x
and #$0003
clc
adc tmp0 ; add to the vertical offset
; Subtract this value from the SPRITE_DISP address
eor #$FFFF ; A = -X - 1
sec ; C = 1
adc _Sprites+SPRITE_DISP,y ; A = SPRITE_DISP + (-X - 1) + 1 = SPRITE_DISP - X
dec ; [!! INTERLOCK !!] pre-decrement to save clc in core blitter. See dobit macros in src/Tiles.s
sta _Sprites+TS_VBUFF_BASE,y
; Create an offset value for loading the calculated VBUFF addresses within the core renderer by
; subtracting the actual TileStore offset from the sprite's vbuff address array
;
; The X-register still has the TileStoreLookupYTable index, which we re-use to get a VBuff
; array selector for the vertical location
lda VBuffVertTableSelect,x ; A bunch of 0, 12 or 24 values
clc
ldx tmp3
adc VBuffHorzTableSelect,x ; A bunch of 0, 4 or 8 values
clc
adc #VBuffArray
plx
; ldx _Sprites+TS_LOOKUP_INDEX,y
sec
sbc TileStoreLookup,x
sta tmp1 ; Spill this value to direct page temp space
; Last task. Since we don't need to use the X-register to cache values; load the direct page 2
; offset for the SPRITE_VBUFF_PTR and save it
tmp_out
tya
ora #$100
tax
lda tmp1
sta SPRITE_VBUFF_PTR,x
mdsOut rts
_MarkDirtySpriteTiles
lda _SpriteBits,y
sta SpriteBit
clc
ldx _Sprites+TS_COVERAGE_SIZE,y
jmp (mdsmark,x)
mdsmark dw :mark1x1,:mark1x2,:mark1x3,mdsOut
dw :mark2x1,:mark2x2,:mark2x3,mdsOut
dw :mark3x1,:mark3x2,:mark3x3,mdsOut
dw mdsOut,mdsOut,mdsOut,mdsOut
; Pair of macros to make the unrolled loop more concise
;
; 1. Load the tile store address from a fixed offset
; 2. Set the sprite bit from the TS_SPRITE_FLAG location
; 3. Checks if the tile is dirty and marks it
; 4. If the tile was dirty, save the tile store address to be added to the DirtyTiles list later
; 5. Sets the VBUFF address for the current sprite slot
;
; The second macro is the same as the first, but the VBUFF calculation is moved up so that the value
; from the previous step can be reused and save a load every other step.
TSSetSprite mac
ldy TileStoreLookup+{]1},x
lda SpriteBit
ora TileStore+TS_SPRITE_FLAG,y
sta TileStore+TS_SPRITE_FLAG,y
lda TileStore+TS_DIRTY,y
bne next
inc
sta TileStore+TS_DIRTY,y
tya
ldy DirtyTileCount
sta DirtyTiles,y
iny
iny
sty DirtyTileCount
next
<<<
ROW equ TILE_STORE_WIDTH*2 ; This many bytes to the next row in TileStore coordinates
COL equ 2 ; This many bytes for each element
:mark1x1
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}
rts
:mark1x2
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+2
rts
:mark1x3
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+2
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+4
rts
:mark2x1
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+0
rts
:mark2x2
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+2
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+2
rts
:mark2x3
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+2
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+4
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+2
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+4
rts
:mark3x1
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 2*{TS_LOOKUP_SPAN*2}+0
rts
:mark3x2
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+2
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+2
TSSetSprite 2*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 2*{TS_LOOKUP_SPAN*2}+2
rts
:mark3x3
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+2
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+4
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+2
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+4
TSSetSprite 2*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 2*{TS_LOOKUP_SPAN*2}+2
TSSetSprite 2*{TS_LOOKUP_SPAN*2}+4
rts