diff --git a/demos/zelda/App.Main.s b/demos/zelda/App.Main.s index 5be311c..2829a8c 100644 --- a/demos/zelda/App.Main.s +++ b/demos/zelda/App.Main.s @@ -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 diff --git a/macros/CORE.MACS.S b/macros/CORE.MACS.S index 7aced11..bb29051 100644 --- a/macros/CORE.MACS.S +++ b/macros/CORE.MACS.S @@ -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 diff --git a/src/GTE.s b/src/GTE.s index f0749f8..803c929 100644 --- a/src/GTE.s +++ b/src/GTE.s @@ -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 diff --git a/src/Render.s b/src/Render.s index 1cbd25e..f21b8d8 100644 --- a/src/Render.s +++ b/src/Render.s @@ -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) diff --git a/src/Sprite.s b/src/Sprite.s index f8425f6..1fb351a 100644 --- a/src/Sprite.s +++ b/src/Sprite.s @@ -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. ; diff --git a/src/Sprite2.s b/src/Sprite2.s index 8965a8e..21f147d 100644 --- a/src/Sprite2.s +++ b/src/Sprite2.s @@ -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 diff --git a/src/blitter/Tables.s b/src/blitter/Tables.s index a4cd8f0..8493199 100644 --- a/src/blitter/Tables.s +++ b/src/blitter/Tables.s @@ -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 diff --git a/src/blitter/Tiles.s b/src/blitter/Tiles.s index fb491e4..2f35372 100644 --- a/src/blitter/Tiles.s +++ b/src/blitter/Tiles.s @@ -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