Small improvements to sprite prototype to fix dirty tiles getting out of sync

This commit is contained in:
Lucas Scharenbroich 2021-10-30 19:24:23 -05:00
parent 33280dc5c5
commit 2f73b9acf5
5 changed files with 327 additions and 151 deletions

View File

@ -45,11 +45,16 @@ DOWN_ARROW equ $0A
; Allocate room to load data
; jsr MovePlayerToOrigin ; Put the player at the beginning of the map
jsr InitOverlay ; Initialize the status bar
stz frameCount
ldal OneSecondCounter
sta oldOneSecondCounter
; Add a player sprite
lda #80
lda #0
sta PlayerX
sta PlayerXOld
lda #100
lda #14
sta PlayerY
sta PlayerYOld
lda #1
@ -68,25 +73,41 @@ EvtLoop
and #$007F ; Ignore the buttons for now
cmp #'q'
bne :1
bne :not_q
brl Exit
:1
:not_q
cmp #'x'
bne :not_x
lda #$0001
jsr UpdatePlayerPos
bra :4
:not_x
cmp #'y'
bne :not_y
lda #$0002
jsr UpdatePlayerPos
bra :4
:not_y
cmp #'r'
beq :3
cmp #'n'
beq :2
stz KeyState
bra EvtLoop
bra :4
:2
lda KeyState ; Wait for key up / key down
bne EvtLoop
bne :4
lda #1
sta KeyState
:3
lda #$0003
jsr UpdatePlayerPos
:4
; Draw the sprite in the sprite plane
ldx PlayerX
@ -104,12 +125,6 @@ EvtLoop
ldy PlayerY
jsr MakeDirtySprite8x8
; Add the tiles that the sprite was previously at as well.
ldx PlayerXOld
ldy PlayerYOld
jsr MakeDirtyTile8x8
; The dirty tile queue has been written to; apply it to the code field
jsl ApplyTiles
@ -118,6 +133,19 @@ EvtLoop
jsl Render
; Update the performance counters
inc frameCount
ldal OneSecondCounter
cmp oldOneSecondCounter
beq :noudt
sta oldOneSecondCounter
jsr UdtOverlay
stz frameCount
:noudt
; Erase the sprites that moved
ldx PlayerLastPos ; Delete the sprite because it moved
jsl EraseTileSprite
@ -125,6 +153,14 @@ EvtLoop
ldy PlayerYOld ; at the old position.
jsr ClearSpriteFlag8x8
; Add the tiles that the sprite was previously at as well.
ldx PlayerXOld
ldy PlayerYOld
jsr MakeDirtyTile8x8
; tax
; ldy PlayerY
; lda PlayerID
@ -156,46 +192,130 @@ PlayerXVel ds 2
PlayerYVel ds 2
KeyState ds 2
UpdatePlayerPos
oldOneSecondCounter ds 2
frameCount ds 2
PLAYER_X_MIN equ 65536-3
PLAYER_X_MAX equ 159
PLAYER_Y_MIN equ 65536-7
PLAYER_Y_MAX equ 199
; Need to use signed comparisons here
; @see http://6502.org/tutorials/compare_beyond.html
UpdatePlayerPosX
lda PlayerX ; Move the player sprite a bit
sta PlayerXOld
clc
adc PlayerXVel
sta PlayerX
cmp #160-4
bcc :x_ok_1
lda #$FFFF
sta PlayerXVel
:x_ok_1 cmp #0
bne :x_ok_2
lda #$0001
sta PlayerXVel
:x_ok_2
; Compate PlayerX with the X_MIN value. BMI if PlayerX < X_MIN, BPL is PlayerX >= X_MIN
lda PlayerY
cmp #PLAYER_X_MIN
beq :x_flip
cmp #PLAYER_X_MAX
bne :x_ok
:x_flip
lda PlayerXVel
eor #$FFFF
inc
sta PlayerXVel
:x_ok
rts
UpdatePlayerPosY
lda PlayerY
sta PlayerYOld
clc
adc PlayerYVel
sta PlayerY
cmp #200-8
bcc :y_ok_1
lda #$FFFF
cmp #PLAYER_Y_MIN
beq :y_flip
cmp #PLAYER_Y_MAX
bne :y_ok
:y_flip
lda PlayerYVel
eor #$FFFF
inc
sta PlayerYVel
:y_ok_1 cmp #0
bne :y_ok_2
lda #$0001
sta PlayerYVel
:y_ok_2
:y_ok
rts
UpdatePlayerPos
pha
bit #$0001
beq :skip_x
jsr UpdatePlayerPosX
:skip_x pla
bit #$0002
beq :skip_y
jsr UpdatePlayerPosY
:skip_y
rts
; Takes a signed playfield position (including off-screen coordinates) and a size and marks
; the tiles that are impacted by this shape. The main job of this subroutine is to ensure
; that all of the tile coordinate s are within the valid bounds [0 - 40], [0 - 25].
;
; X = signed integer
; Y = signed integer
; A = sprite size (0 - 7)
SpriteWidths dw 4,4,8,8,12,8,12,16
SpriteHeights dw 8,16,8,16,16,24,24,24
; 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)
MarkTilesOut
ply
plx
sec
rts
MarkTiles
phx
phy
and #$0007
asl
tax
; First, do a bound check against the whole sprite. It it's totally off-screen, do nothing because
; there are no physical tiles to mark.
lda 1,s ; load the Y coordinate
bpl :y_pos
eor #$FFFF ; for a negative coordinate, see if it's equal to or larger than the sprite height
inc
cmp SpriteHeights,x
bcs MarkTilesOut
bra :y_ok
:y_pos cmp ScreenHeight
bcc :y_ok
bra MarkTilesOut
:y_ok
rts
; X = coordinate
; Y = coordinate
MakeDirtySprite8x8
phx
phy
txa
txa ; need to do a signed shift...
lsr
lsr
tax
@ -397,10 +517,8 @@ MovePlayerToOrigin
qtRec adrl $0000
da $00
PUT ../shell/Overlay.s
PUT gen/App.TileMapBG0.s
PUT gen/App.TileSetAnim.s
Overlay ENT
rtl
ANGLEBNK ENT

View File

@ -119,6 +119,16 @@ min mac
lda ]1
mout <<<
asr16 mac
cmp #$8000
ror
<<<
asr8 mac
cmp #$80
ror
<<<
; Macro to define script steps
ScriptStep MAC
IF #=]5
@ -128,6 +138,76 @@ ScriptStep MAC
FIN
<<<
; A specialized CopyMaskedWord macro that draws a tile from a direct page workspace. Used
; to render fringe tiles and sprite tiles when BG1 is active. If there is no second background,
; then one should use the optimized functions which assumes a PEA opcode and only
; needs to copy data words
;
; ]1 : tiledata direct page address , the tilemask direct page address is tiledata + 32
; ]2 : code field offset
CopyMaskedWordD MAC
lda ]1+32 ; load the mask value
bne mixed ; a non-zero value may be mixed
; This is a solid word
lda #$00F4 ; PEA instruction
sta: ]2,y
ldal ]1 ; load the tile data
sta: ]2+1,y ; PEA operand
bra next
mixed cmp #$FFFF ; All 1's in the mask is fully transparent
beq transparent
; This is the slowest path because there is a *lot* of work to do. So much that it's
; worth it to change up the environment to optimize things a bit more.
;
; Need to fill in the first 8 bytes of the JMP handler with the following code sequence
;
; lda (00),y
; and #MASK
; ora #DATA
lda #$004C ; JMP instruction
sta: ]2,y
ldx _X_REG ; Get the addressing offset
ldal JTableOffset,x ; Get the address offset and add to the base address
adc _BASE_ADDR ; of the current code field line
adc #{]2&$F000} ; adjust for the current row offset
sta: ]2+1,y
tay ; This becomes the new address that we use to patch in
txa ; Get the offset and render a LDA (dp),y instruction
sep #$20
sta: $0001,y ; LDA (00),y operand
lda #$B1
sta: $0000,y ; LDA (00),y opcode
lda #$29
sta: $0002,y ; AND #$0000 opcode
lda #$09
sta: $0005,y ; ORA #$0000 opcode
rep #$20
lda ]1+32 ; insert the tile mask and data into the exception
sta: $0003,y ; handler.
lda ]1
sta: $0006,y
ldy _Y_REG ; restore original y-register value and move on
bra next
; This is a transparent word, so just show the second background layer
transparent
lda #$00B1 ; LDA (dp),y instruction
sta: ]2,y
lda _X_REG ; X is the logical tile offset (0, 2, 4, ... 82) left-to-right
ora #$4800 ; put a PHA after the offset
sta: ]2+1,y
next
eom
; Macros to use in the Masked Tile renderer
;
; ]1 : tiledata offset
@ -243,3 +323,56 @@ CopyMaskedDWord MAC
lda #$0290 ; BCC *+4
sta: $0006,x
eom
; Masked renderer for a dynamic tile with sprite data overlaid. What's interesting about this renderer is that the mask
; value is not used directly, but simply indicates if we can use a LDA 0,x / PHA sequence,
; a LDA (00),y / PHA, or a JMP to a blended render
;
; If a dynamic tile is animated, there is the possibility to create a special mask that marks
; words of the tile that a front / back / mixed across all frames.
;
; ]1 : tiledata offset
; ]2 : tilemask offset
; ]3 : code field offset
CopyMaskedDynSpriteWord MAC
; Need to fill in the first 12(!!) bytes of the JMP handler with the following code sequence
;
; lda (00),y
; and $80,x
; ora $00,x
; and #MASK
; ora #DATA
;
; If MASK == 0, then we can do a PEA. If MASK == $FFFF, then fall back to the simple Dynamic Masked
; code.
ldx _X_REG ; Get the addressing offset
ldal JTableOffset,x ; Get the address offset and add to the base address
adc _BASE_ADDR ; of the current code field line
adc #{]1&$F000} ; adjust for the current row offset
sta: ]1+1,y
tax ; This becomes the new address that we use to patch in
lda _X_REG ; Get the offset and render a LDA (dp),y instruction
sep #$20 ; Easier to do 8-bit operations
sta: $0001,x ; Set the LDA (00),y operand
lda #$B1
sta: $0000,x ; Set the LDA (00),y opcode
lda _T_PTR
sta: $0005,x ; Set ORA 00,x operand
ora #$80
sta: $0003,x ; Set AND 00,x operand
lda #$35
sta: $0002,x ; Set AND 00,x operand
lda #$15
sta: $0004,x ; Set ORA 00,x operand
rep #$30
lda #$0290 ; BCC *+4
sta: $0006,x
eom

View File

@ -8,7 +8,7 @@
use .\Defs.s
; Feature flags
NO_INTERRUPTS equ 1 ; turn off for crossrunner debugging
NO_INTERRUPTS equ 0 ; turn off for crossrunner debugging
NO_MUSIC equ 1 ; turn music + tool loading off
; External data provided by the main program segment

View File

@ -92,17 +92,13 @@ _Render
jsr _ShadowOff
; Shadowing is turned off. Render all of the scan lines that need a second pass. These
; are the lines that have a masked overlay, or a sprite. One optimization that can
; be done here is that the lines can be rendered in any order since it is not shown
; on-screen yet.
; Shadowing is turned off. Render all of the scan lines that need a second pass. One
; optimization that can be done here is that the lines can be rendered in any order
; since it is not shown on-screen yet.
; jsr _RenderPhaseA ; Draw all of the background lines
; jsr _RenderSprites ; Draw all of the sprites
; ldx #152 ; Blit the full virtual buffer to the screen
; ldy #160
; jsr _BltRange
ldx #0 ; Blit the full virtual buffer to the screen
ldy #8
jsr _BltRange
; Turn shadowing back on
@ -110,31 +106,17 @@ _Render
; Now render all of the remaining lines in top-to-bottom (or bottom-to-top) order
; jsr _RenderPhaseB ; Draw the mix of background lines overlays and PEI slams
lda ScreenY0 ; pass the address of the first line of the overlay
clc
adc #0
asl
tax
lda ScreenAddr,x
clc
adc ScreenX0
jsl Overlay
; ldx #0 ; Expose the top 8 rows
; ldy #8
; jsr _PEISlam
; ldx #0 ; Blit the full virtual buffer to the screen
; ldy #16
; jsr _BltRange
; ldx #0 ; Blit the full virtual buffer to the screen
; ldy #152
; jsr _BltRange
; lda ScreenY0 ; pass the address of the first line of the overlay
; clc
; adc #152
; asl
; tax
; lda ScreenAddr,x
; clc
; adc ScreenX0
; jsl Overlay
ldx #0 ; Blit the full virtual buffer to the screen
ldx #8 ; Blit the full virtual buffer to the screen
ldy ScreenHeight
jsr _BltRange
@ -154,83 +136,3 @@ _Render
stz DirtyBits
rts
MAX_SEGMENTS equ 128
PhaseACount ds 0
PhaseATop ds 2*MAX_SEGMENTS
PhaseABot ds 2*MAX_SEGMENTS
PhaseAOp ds 2*MAX_SEGMENTS
PhaseBCount ds 0
PhaseBTop ds 2*MAX_SEGMENTS
PhaseBBot ds 2*MAX_SEGMENTS
PhaseBOp ds 2*MAX_SEGMENTS
; Initialize the rendering tree to just render all of the code fields
_InitRenderTree
lda #1 ; Put the whole screen into Phase B
sta PhaseBCount
lda #0
sta PhaseBTop
lda ScreenHeight
sta PhaseBBot
lda #_BltRange
sta PhaseBOp
stz PhaseACount ; Phase A is initially empty
rts
; Solid overlays are called in Phase B, but do not require the screen
; to be drawn underneath, so this provides an opportunity to optimize
; the rendering pipeline
_AddSolidOverlay
rts
; A mixed overlay signals that the underlying scan line data must be
; redered first.
_AddMixedOverlay
rts
_RenderPhaseA
ldy #0
:loop
cpy PhaseACount
bcs :out
phy ; save the counter
lda PhaseAOp,y ; dispatch to the appropriate function
sta :op+1
ldx PhaseATop,y
lda PhaseABot,y
tay
:op jsr $0000
ply ; restore the counter
iny
iny
bra :loop
:out
rts
_RenderPhaseB
ldy #0
:loop
cpy PhaseBCount
bcs :out
phy ; save the counter
lda PhaseBOp,y ; dispatch to the appropriate function
sta :op+1
ldx PhaseBTop,y
lda PhaseBBot,y
tay
:op jsr $0000
ply ; restore the counter
iny
iny
bra :loop
:out
rts

View File

@ -399,7 +399,30 @@ _EraseTileSprite
; Add a new sprite to the rendering pipeline
;
; A = tileId
; The tile id ithe range 0 - 511. The top 7 bits are used as sprite control bits
;
; 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.
;
; 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
; with a width of 256 pixels.
;
; Single sprite are limited to 24 lines high because there are 28 lines of padding above and below the
; sprite plane buffers, so a sprite that is 32 lines high could overflow the drawing area.
;
; A = tileId + flags
; X = x position
; Y = y position
AddSprite ENT