diff --git a/demos/sprites/App.Main.s b/demos/sprites/App.Main.s index 8057576..ba8d8ef 100644 --- a/demos/sprites/App.Main.s +++ b/demos/sprites/App.Main.s @@ -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 diff --git a/macros/CORE.MACS.S b/macros/CORE.MACS.S index f4b3a34..8fad9ee 100644 --- a/macros/CORE.MACS.S +++ b/macros/CORE.MACS.S @@ -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 diff --git a/src/Core.s b/src/Core.s index f9f1d36..35d582b 100644 --- a/src/Core.s +++ b/src/Core.s @@ -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 diff --git a/src/Render.s b/src/Render.s index 11eabe5..86d12d1 100644 --- a/src/Render.s +++ b/src/Render.s @@ -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 diff --git a/src/Sprite.s b/src/Sprite.s index 0298109..ae34db1 100644 --- a/src/Sprite.s +++ b/src/Sprite.s @@ -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 diff --git a/src/Sprite2.s b/src/Sprite2.s new file mode 100644 index 0000000..549471e --- /dev/null +++ b/src/Sprite2.s @@ -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 diff --git a/src/blitter/Tables.s b/src/blitter/Tables.s index acd94c1..bff2038 100644 --- a/src/blitter/Tables.s +++ b/src/blitter/Tables.s @@ -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 diff --git a/src/blitter/Tiles.s b/src/blitter/Tiles.s index 6adf57b..8af90ce 100644 --- a/src/blitter/Tiles.s +++ b/src/blitter/Tiles.s @@ -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