Background rendering again

This commit is contained in:
Lucas Scharenbroich 2022-02-18 20:43:55 -06:00
parent 95572fbf49
commit bb83e7f7c5
8 changed files with 134 additions and 272 deletions

View File

@ -53,36 +53,35 @@ DOWN_ARROW equ $0A
SPRITE_ID equ {SPRITE_16X16+1}
OKTOROK equ {SPRITE_16X16+79}
lda #SPRITE_ID ; 16x16 sprite
ldx PlayerX
ldy PlayerY
jsl AddSprite
bcc :sprite_ok
brl Exit ; If we could not allocate a sprite, exit
:sprite_ok
sta PlayerID
brl Exit
; lda #SPRITE_ID ; 16x16 sprite
; ldx PlayerX
; ldy PlayerY
; jsl AddSprite
; bcc :sprite_ok
; brl Exit ; If we could not allocate a sprite, exit
;:sprite_ok
; sta PlayerID
; Add 4 octoroks
lda #OKTOROK
ldx #32
ldy #48
jsl AddSprite
; lda #OKTOROK
; ldx #32
; ldy #48
; jsl AddSprite
lda #OKTOROK
ldx #96
ldy #32
jsl AddSprite
; lda #OKTOROK
; ldx #96
; ldy #32
; jsl AddSprite
lda #OKTOROK
ldx #56
ldy #96
jsl AddSprite
; lda #OKTOROK
; ldx #56
; ldy #96
; jsl AddSprite
lda #OKTOROK
ldx #72
ldy #96
jsl AddSprite
; lda #OKTOROK
; ldx #72
; ldy #96
; jsl AddSprite
; Draw the initial screen
@ -90,6 +89,7 @@ OKTOROK equ {SPRITE_16X16+79}
tsb DirtyBits
jsl Render
; Set up a very specific test. First, we draw a sprite into the sprite plane, and then
; leave it alone. We are just testing the ability to merge sprite plane data into
; the play field tiles.
@ -122,7 +122,8 @@ EvtLoop
bne :not_q
brl Exit
:not_q
brl EvtLoop
cmp #'d'
bne :not_d
inc PlayerX

View File

@ -158,25 +158,6 @@ asr8 mac
ror
<<<
; Inline macros for fast calculation of some internal values
_TileStoreOffset mac
lda ]2
asl
tay
lda ]1
asl ; Assume in range, so asl puts a 0 bit into the carry
adc TileStoreYTable,y
<<<
_TileStoreOffsetX mac
lda ]2
asl
tax
lda ]1
asl ; Assume in range, so asl puts a 0 bit into the carry
adc TileStoreYTable,x
<<<
; Macro to define script steps
ScriptStep MAC
IF #=]5

View File

@ -57,7 +57,6 @@ GetTileAddr EXT
PushDirtyTile EXT ; A = address from GetTileStoreOffset, marks as dirty (will not mark the same tile more than once)
PopDirtyTile EXT ; No args, returns Y with tile store offset of the dirty tile
ApplyTiles EXT ; Drain the dirty tile queue and call RenderTile on each
RenderTile EXT ; Y = address from GetTileStoreOffset
GetTileStoreOffset EXT ; X = column, Y = row
TileStore EXT ; Tile store internal data structure

View File

@ -90,7 +90,7 @@ _Render
; The code fields are locked in now and ready to be rendered
jsr _ShadowOff
; jsr _ShadowOff
; 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
@ -102,27 +102,27 @@ _Render
; Turn shadowing back on
jsr _ShadowOn
; jsr _ShadowOn
; Now render all of the remaining lines in top-to-bottom (or bottom-to-top) order
lda ScreenY0 ; pass the address of the first line of the overlay
clc
adc #0
asl
tax
lda ScreenAddr,x
clc
adc ScreenX0
; 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 ; Blit the full virtual buffer to the screen
ldy ScreenHeight
jsr _BltRange
; ldx #0
; ldy ScreenHeight
; jsr _BltSCB
ldx #0
ldy ScreenHeight
jsr _BltSCB
lda StartY ; Restore the fields back to their original state
ldx ScreenHeight
@ -139,10 +139,10 @@ _Render
sta OldBG1StartX
stz DirtyBits
stz LastRender
stz LastRender ; Mark that a full render was just performed
rts
; This is a specialized redner function that only updated the dirty tiles *and* draws them
; This is a specialized render function that only updates the dirty tiles *and* draws them
; directly onto the SHR graphics buffer. The playfield is not used at all. In some way, this
; ignores almost all of the capabilities of GTE, but it does provide a convenient way to use
; the sprite subsystem + tile attributes for single-screen games which should be able to run
@ -207,7 +207,7 @@ _RenderDirtyTile
xba
tax
ldal DirtyTileSpriteProcs,x
sta :tiledisp+1
stal :tiledisp+1
bra :sprite
:nosprite
@ -216,7 +216,7 @@ _RenderDirtyTile
xba
tax
ldal DirtyTileProcs,x ; load and patch in the appropriate subroutine
sta :tiledisp+1
stal :tiledisp+1
:sprite
ldx TileStore+TS_TILE_ADDR,y ; load the address of this tile's data (pre-calculated)

View File

@ -1,49 +1,6 @@
; Functions for sprite handling. Mostly maintains the sprite list and provides
; utility functions to calculate sprite/tile intersections
;
; The sprite plane actually covers two banks so that more than 32K can be used as a virtual
; screen buffer. In order to be able to draw sprites offscreen, the virtual screen must be
; wider and taller than the physical graphics screen.
;
; NOTE: It may be posible to remove the sprite plane banks in the future and render directly from
; some small per-sprite graphic buffers. This would eliminate the need to erase/draw in
; the sprite planes and all drawing would go directly to the backing tiles. Need to
; figure out an efficient way to fall back when sprites are overlapping, though.
;
; All of the erasing must happen in an initial phase, because erasing a sprite could cause
; other sprites to be marked as "DAMAGED" which means they need to be drawn (similar to NEW state)
; What really has to happen in the various cases:
;
; When a sprite is added, it needs to
; * draw into the sprite buffer
; * add itself to the TS_SPRITE_FLAG bitfield on the tiles it occupies
; * mark the tiles it occupies as dirty
;
; When a sprite is updated (Tile ID or H/V flip flags), it needs to
; * erase itself from the sprite buffer
; * draw into the sprite buffer
; * mark the tiles it occupies as dirty
; * mark other sprites it intersects as DAMAGED
;
; When a sprite is moved, it needs to
; * erase itself from the sprite buffer at the old locations
; * remove itself from the TS_SPRITE_FLAG bitfields on the tiles it occupied
; * mark sprites that intersect as DAMAGED
; * draw into the sprite buffer at the new location
; * add itself to the TS_SPRITE_FLAG bitfield on the tiles it now occupies
; * mark the tiles it occupied as dirty
; * mark other sprites it intersects as DAMAGED
;
; When a sprite is removed, it needs to
; * erase itself from the sprite buffer at the old locations
; * remove itself from the TS_SPRITE_FLAG bitfields on the tiles it occupied
; * mark other sprites it intersects as DAMAGED
;
; The reason that things are broken into phases is that we have to handle all of the erasing first,
; set dirty tiles, identify DAMAGED sprites, and THEN perform the drawing. It is not possible to
; just do each sprite one at a time.
;
; Initialize the sprite data and mask banks (all data = $0000, all masks = $FFFF)
InitSprites
ldx #$FFFE
@ -92,6 +49,89 @@ VBUFF_SPRITE_START equ {8*VBUFF_TILE_ROW_BYTES}+4
jsr _CacheSpriteBanks
rts
; Add a new sprite to the rendering pipeline
;
; The tile id in the range 0 - 511. The top 7 bits are used as sprite control bits
;
; Bit 9 : Horizontal flip.
; Bit 10 : Vertical flip.
; 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
; 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
phb
phk
plb
jsr _AddSprite
plb
rtl
_AddSprite
phx ; Save the horizontal position
ldx _NextOpenSlot ; Get the next free sprite slot index
bpl :open ; A negative number means we are full
plx ; Early out
sec ; Signal that no sprite slot was available
rts
:open
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
lda #SPRITE_STATUS_OCCUPIED+SPRITE_STATUS_ADDED
sta _Sprites+SPRITE_STATUS,x
tya
sta _Sprites+SPRITE_Y,x ; Y coordinate
pla ; X coordinate
sta _Sprites+SPRITE_X,x
jsr _PrecalcAllSpriteInfo ; Cache sprite property values (simple stuff)
jsr _DrawSpriteSheet ; Render the sprite into internal space
; Mark the dirty bit to indicate that the active sprite list needs to be rebuilt in the next
; render call
lda #DIRTY_BIT_SPRITE_ARRAY
tsb DirtyBits
lda _SpriteBits,x ; Get the bit flag for this sprite slot
tsb SpriteMap ; Mark it in the sprite map bit field
txa ; And return the sprite ID
clc ; Mark that the sprite was successfully added
; We can only get to this point if there was an open slot, so we know we're not at the
; end of the list yet.
ldx _OpenListHead
inx
inx
stx _OpenListHead
ldy _OpenList,x ; If this is the end, then the sentinel value will
sty _NextOpenSlot ; get stored into _NextOpenSlot
rts
; Run through the list of tile store offsets that this sprite was last drawn into and mark
; those tiles as dirty. The largest number of tiles that a sprite could possibly cover is 20
; (an unaligned 4x3 sprite), covering a 5x4 area of play field tiles.
@ -538,91 +578,6 @@ SPRITE_PLANE_SPAN equ 52 ; 256
; adc tmp15
; rts
; Add a new sprite to the rendering pipeline
;
; The tile id in the range 0 - 511. The top 7 bits are used as sprite control bits
;
; Bit 9 : Horizontal flip.
; Bit 10 : Vertical flip.
; 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
; 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
phb
phk
plb
jsr _AddSprite
plb
rtl
_AddSprite
phx ; Save the horizontal position
ldx _NextOpenSlot ; Get the next free sprite slot index
bpl :open ; A negative number means we are full
plx ; Early out
sec ; Signal that no sprite slot was available
rts
:open
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
lda #SPRITE_STATUS_OCCUPIED+SPRITE_STATUS_ADDED
sta _Sprites+SPRITE_STATUS,x
tya
sta _Sprites+SPRITE_Y,x ; Y coordinate
pla ; X coordinate
sta _Sprites+SPRITE_X,x
; jsr _GetSpriteVBuffAddrTmp
; sta _Sprites+VBUFF_ADDR,x ; This is now pre-calculated since each sprite slot gets a fixed location
jsr _PrecalcAllSpriteInfo ; Cache sprite property values (simple stuff)
jsr _DrawSpriteSheet ; Render the sprite into internal space
; Mark the dirty bit to indicate that the active sprite list needs to be rebuilt in the next
; render call
lda #DIRTY_BIT_SPRITE_ARRAY
tsb DirtyBits
lda _SpriteBits,x ; Get the bit flag for this sprite slot
tsb SpriteMap ; Mark it in the sprite map bit field
txa ; And return the sprite ID
clc ; Mark that the sprite was successfully added
; We can only get to this point if there was an open slot, so we know we're not at the
; end of the list yet.
ldx _OpenListHead
inx
inx
stx _OpenListHead
ldy _OpenList,x ; If this is the end, then the sentinel value will
sty _NextOpenSlot ; get stored into _NextOpenSlot
rts
; Precalculate some cached values for a sprite. These are *only* to make other part of code,
; specifically the draw/erase routines more efficient.
;

View File

@ -318,21 +318,6 @@ _MarkDirtySprite
rts
; Begin List of subroutines to mark each tile offset
;
; If we had a double-sized 2D array to be able to look up the tile store address without
; adding rows and column, we could save ~6 cycles per tile
; If all that is needed is to record the Tile Store offset for the sprite and delay any
; actual calculations, then we just need to do
;
; lda TileStore2DArray,x
; sta _Sprites+TILE_STORE_ADDR_0,y
; lda TileStore2DArray+2,x
; sta _Sprites+TILE_STORE_ADDR_1,y
; lda TileStore2DArray+41,x
; sta _Sprites+TILE_STORE_ADDR_2,y
; ...
:mark_0_0
ldx RowTop
lda ColLeft

View File

@ -17,7 +17,7 @@
; The table values are pre-reversed so that loop can go in logical order 0, 2, 4, ...
; and the resulting offsets will map to the code instructions in right-to-left order.
;
; Remember, because the data is pushed on to the stask, the last instruction, which is
; Remember, because the data is pushed on to the stack, the last instruction, which is
; in the highest memory location, pushed data that apepars on the left edge of the screen.
PER_TILE_SIZE equ 3
]step equ 0
@ -238,18 +238,6 @@ TileStoreYTable ENT
]step = ]step+{41*2}
--^
;TileStore2DYTable
;]step equ 0
; lup 26
; dw ]step
;]step = ]step+{41*2*2}
; --^
;]step equ 0
; lup 26
; dw ]step
;]step = ]step+{41*2*2}
; --^
; Create a table to look up the "next" column with modulo wraparound. Basically a[i] = i
; and the table is double-length. Use constant offsets to pick an amount to advance
NextCol

View File

@ -103,14 +103,6 @@ _RenderTileBG1
; Store record contains all of the low-level information that's needed to call the renderer.
;
; Y = address of tile
RenderTile ENT
phb
phk
plb
jsr _RenderTile2
plb
rtl
_RenderTile2
pea >TileStore ; Need that addressing flexibility here. Callers responsible for restoring bank reg
plb
@ -118,9 +110,9 @@ _RenderTile2
lda TileStore+TS_TILE_ID,y ; build the finalized tile descriptor
ldx TileStore+TS_SPRITE_FLAG,y ; This is a bitfield of all the sprites that intersect this tile, only care if non-zero or not
beq :nosprite
; beq :nosprite
ora #TILE_SPRITE_BIT
; ora #TILE_SPRITE_BIT
; ldx TileStore+TS_SPRITE_ADDR,y ; TODO: collapse sprites
; stx _SPR_X_REG
@ -129,8 +121,8 @@ _RenderTile2
and #TILE_CTRL_MASK
xba
tax
lda TileProcs,x ; load and patch in the appropriate subroutine
sta :tiledisp+1
ldal TileProcs,x ; load and patch in the appropriate subroutine
stal :tiledisp+1
ldx TileStore+TS_TILE_ADDR,y ; load the address of this tile's data (pre-calculated)
@ -541,6 +533,7 @@ InitTiles
:col equ tmp0
:row equ tmp1
:vbuff equ tmp2
; Fill in the TileStoreYTable. This is just a table of offsets into the Tile Store for each row. There
; are 26 rows with a stride of 41
ldy #0
@ -554,36 +547,6 @@ InitTiles
cpy #26*2
bcc :yloop
; Fill in the TileStore2DLookup array. This is a full array lookup for the entire tile store space. Eventually
; we can remove TileStoreYTable and free up a bit of space.
lda #0
tay
tax
:xyloop
sta TileStoreYTable,y
sta TileStoreYTable+{2*41},y
sta TileStoreYTable+{4*41*26},y
sta TileStoreYTable+{4*41*26}+{2*41},y
inc ; Advance to the next offset value
inc
iny ; Advance to the next table location
iny
inx ; Increment the column counter
cpx #41 ; If we haven't filled an entire row, keep going
bcc :xyloop
ldx #0 ; reset the column counter
tya
clc
adc #2*26 ; skip over the repeated values in this row and to to the next row start
tay
cpy #4*41*26 ; Did we finish the last row, if not go back for more
bcc :xyloop
; Next, initialize the Tile Store itself
ldx #TILE_STORE_SIZE-2
@ -741,23 +704,13 @@ _PushDirtyTileX
bpl :occupied2
txa ; any non-negative value will work, this saves work below
stal TileStore+TS_DIRTY,x ; and is 1 cycle fater than loading a constanct value
stal TileStore+TS_DIRTY,x ; and is 1 cycle faster than loading a constant value
; txa
ldx DirtyTileCount ; 5
sta DirtyTiles,x ; 5
inx
inx
stx DirtyTileCount
; Same speed, but preserved the X register
; sta (DirtyTiles) ; 6
; lda DirtyTiles ; 4
; inc ; 2
; inc ; 2
; sta DirtyTiles ; 4
rts
:occupied2
txa ; Make sure TileStore offset is returned in the accumulator