; 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