From 9cc5b2e3afe5c87defe60218a7973ee40d122476 Mon Sep 17 00:00:00 2001 From: Lucas Scharenbroich Date: Fri, 7 Jul 2023 15:57:46 -0500 Subject: [PATCH] Generalize to allow differrnt play field sizes --- demos/smb/Main.s | 159 ++++++++++++++++++++++++++++++++++++----- demos/smb/ppu.s | 130 +++++++++++++++++++++++---------- macros/GTE.Macs.s | 9 ++- src/Defs.s | 3 +- src/Render.s | 145 +++++++++++++++++++++++-------------- src/Tiles.s | 90 +++++++++++++++++++++++ src/Tool.s | 85 +++++++++++++++++++--- src/blitter/VertLite.s | 48 +++++++------ src/render/Render.s | 3 + 9 files changed, 532 insertions(+), 140 deletions(-) diff --git a/demos/smb/Main.s b/demos/smb/Main.s index 9a861df..064df94 100644 --- a/demos/smb/Main.s +++ b/demos/smb/Main.s @@ -18,7 +18,7 @@ DOWN_ARROW equ $0A ; Nametable queue NT_QUEUE_LEN equ $1000 NT_QUEUE_SIZE equ {2*NT_QUEUE_LEN} -NT_QUEUE_MOD equ {NT_QUEUE_SIZE-1} +NT_QUEUE_MOD equ {NT_QUEUE_SIZE-1} mx %00 @@ -38,8 +38,26 @@ CurrNTQueueEnd equ 40 BGToggle equ 44 LastEnable equ 46 ShowFPS equ 48 +HideStatusBar equ 50 +;ROMPlayerY equ 52 +YOrigin equ 54 -OldOneSec equ 100 +OldOneSec equ 100 +RenderFlags equ 102 +NesTop equ 104 ; Clip any sprite that has a NES OAM y-coordinate above this line +NesBottom equ 106 ; Clip any sprite that has a NES OAM y-coordinate below this line +ScreenBase equ 108 ; SHR address of the top-left edge of the play field +ScreenRows equ 110 ; Number of 8-line block we are showing on-screen +ScreenHeight equ 112 +ScreenTop equ 114 ; SCB line of the top of the play field + +MinYScroll equ 116 ; Smallest YOrigin value: 0 when showing stratus bar, 16 when hiding status bar +MaxYScroll equ 118 ; Largest YOrigin value for GTESetBG0Origin + +;VirtTop equ 104 ; The top virtual line that is the top of the active play field (excludes status bar) +;VirtBottom equ 106 ; The bottom virtual line that is bottom of the active play field +;MaxNesY equ 108 ; This is the largest NES y coordinate that will display in the active play field +;MinNesY equ 110 ; This is the smallest NES y coordinate that will display in the active play field Tmp0 equ 240 Tmp1 equ 242 @@ -66,6 +84,8 @@ FTblTmp equ 228 stz OldROMScrollEdge stz LastAreaType stz ShowFPS + stz HideStatusBar + stz YOrigin lda #1 sta BGToggle @@ -73,6 +93,39 @@ FTblTmp equ 228 lda #$0008 sta LastEnable + lda #16 +; lda #32 + sta NesTop + + lda #16 + sta MinYScroll + + lda #1 + sta HideStatusBar + + lda #128 + sta ScreenHeight + lsr + lsr + lsr + sta ScreenRows + + lda #200 + sec + sbc ScreenHeight + sta MaxYScroll + + lda NesTop + clc + adc ScreenHeight + sec + sbc #8 + inc + sta NesBottom + +; lda #0 +; sta ScreenTop + ; The next two direct pages will be used by GTE, so get another 2 pages beyond that for the ROM. We get ; 4K of DP/Stack space by default, so there is plenty to share @@ -143,12 +196,36 @@ FTblTmp equ 228 ; pea #184 pea #128 - pea #200 + pei ScreenHeight ; pea #80 ; pea #144 _GTESetScreenMode + pha ; Allocate space for x, y, width, height + pha + pha + pha + _GTEGetScreenInfo + pla ; Discard x + pla + sta ScreenTop + asl + asl + asl + asl + asl + sta ScreenBase + asl + asl + clc + adc ScreenBase + clc + adc #$2000+x_offset + sta ScreenBase + pla ; Discard width and height + pla + ldx #Area1Palette lda #TmpPalette jsr NESColorToIIgs @@ -391,6 +468,13 @@ EvtLoop brl EvtLoop :not_t + cmp #'x' ; break + bne :not_x + lda #1 + sta user_break + brl EvtLoop +:not_x + cmp #'q' beq Exit brl EvtLoop @@ -416,6 +500,7 @@ DPSave dw 0 LastAreaType dw 0 frameCount dw 0 show_vbl_cpu dw 0 +user_break dw 0 ; Toggle an APU control bit ToggleAPUChannel @@ -497,16 +582,42 @@ RenderFrame pha ; Get the player's Y coordinate and determine of we need to adjust the camera based on the physical play field size -; sep #$20 -; ldal ROMBase+$b5 ; Player_Y_HighPos -; xba -; ldal ROMBase+$ce ; Player_Y_Position -; rep #$20 -; -; sec -; sbc TopClip ; If we're hiding the + ldx ROMZeroPg + ldal $0000b5,x ; Player_Y_Page ; 0 = above screen, 1 = on screen, 2 = below + and #$00FF + beq :max_clamp + cmp #2 + beq :min_clamp - pea $0000 + ldal $0000ce,x ; Player_Y_Position + and #$00FF +; sta ROMPlayerY + +; The "full screen" size is 200 lines that cover NES rows 16 through 216. If the +; size of the playfield is less, then we adjust the origin a bit. +; +; Y_Origin = min(200 - ScreenHeight, max(0, ROMPlayerY - 16 - ScreenHeight/2)) + + sec + sbc NesTop + asl + sec + sbc ScreenHeight + bmi :max_neg + lsr + cmp MinYScroll + bcc :max_clamp + + cmp MaxYScroll + bcc :set_y +:min_clamp lda MaxYScroll + bra :set_y +:max_neg +:max_clamp + lda MinYScroll +:set_y + sta YOrigin + pha _GTESetBG0Origin lda ppumask @@ -518,11 +629,21 @@ RenderFrame _GTEEnableBackground :bghop - pea $FFFF ; NES mode +; pea $FFFF ; NES mode + lda HideStatusBar + beq :full_screen + + pea $FFFD _GTERender + bra :render_done + +:full_screen + pea $FFFF + _GTERender +:render_done ; Check the AreaType and see if the palette needs to be changed. We do this after the screen is blitted -; so the palette does not get changed too eary while old pixels are still on the screen. +; so the palette does not get changed too early while old pixels are still on the screen. ldal ROMBase+$074E and #$00FF @@ -845,7 +966,8 @@ DrainNTQueue ; issues because many frames can pass before Render gets control again. We need to expose a ; _SetTileImmediate function in the list of function callbacks.... - _GTESetTile + _GTESetTileImmediate +; _GTESetTile ; inc :Count brl :skip @@ -1106,7 +1228,9 @@ CopyStatus inx stx Tmp2 - _GTESetTile +; _GTESetTile + _GTESetTileImmediate + ply plx @@ -1193,7 +1317,8 @@ CopyNametable stx Tmp2 :x_hop - _GTESetTile +; _GTESetTile + _GTESetTileImmediate ply plx diff --git a/demos/smb/ppu.s b/demos/smb/ppu.s index 39c581f..ebcac41 100644 --- a/demos/smb/ppu.s +++ b/demos/smb/ppu.s @@ -110,6 +110,7 @@ PPUMASK_WRITE ENT ; $2002 - PPUSTATUS For "ldx ppustatus" PPUSTATUS_READ_X ENT + pha ; spacefor result php pha @@ -117,31 +118,29 @@ PPUSTATUS_READ_X ENT stal w_bit ; Reset the address latch used by PPUSCROLL and PPUADDR ldal ppustatus - tax + sta 3,s and #$7F ; Clear the VBL flag stal ppustatus pla ; Restore the accumulator (return value in X) plp - phx ; re-read x to set any relevant flags plx rtl PPUSTATUS_READ ENT + pha ; space for return value php lda #1 stal w_bit ; Reset the address latch used by PPUSCROLL and PPUADDR ldal ppustatus - pha + sta 2,s and #$7F ; Clear the VBL flag stal ppustatus - pla ; pop the return value plp - pha ; re-read accumulator to set any relevant flags pla rtl @@ -334,8 +333,8 @@ PPUDATA_WRITE ENT sta nt_queue,y :full -; lda #1 -; jsr setborder + lda #1 + jsr setborder rts :attrtbl @@ -421,6 +420,30 @@ PPUDATA_WRITE ENT dex jmp :enqueue +incborder + php + sep #$20 + ldal $E0C034 + inc + eorl $E0C034 + and #$0F + eorl $E0C034 + stal $E0C034 + plp + rts + +decborder + php + sep #$20 + ldal $E0C034 + dec + eorl $E0C034 + and #$0F + eorl $E0C034 + stal $E0C034 + plp + rts + setborder php sep #$20 @@ -588,25 +611,47 @@ PPUDMA_WRITE ENT plp rtl -y_offset_rows equ 2 -y_height_rows equ 25 -y_offset equ {y_offset_rows*8} -y_height equ {y_height_rows*8} -max_nes_y equ {y_height+y_offset-8} +;y_offset_rows equ 2 +;y_height_rows equ 25 +;y_offset equ {y_offset_rows*8} +;y_height equ {y_height_rows*8} +;max_nes_y equ {y_height+y_offset-8} x_offset equ 16 ; Scan the OAM memory and copy the values of the sprites that need to be drawn. There are two reasons to do this ; -; 1. Freeze the OAM memory at this instanct so that the NES ISR can keep running without changing values -; 2. We have to scan this list twice -- once to build up the shadow list and once to actually render the sprites +; This code has an optimization that it directly scans the NES RAM that would be DMA copied into the PPU +; OAM space. This is ok, because +; +; 1. The OAM DMA occurs in the NES ROM before running any game logic +; 2. This code is running after the prior ISR, so it is loically happening at the beginning of the next NMI +; +; When scanning the OAM values, sprites that are not visible for any number of reasons are skipped and the +; sprite's y-position is adjusted based on the GTE camera view. This allow all of the shadowBitmap and +; shadow lits work to assume an index value of zero is the top of the active play field. OAM_COPY ds 256 spriteCount ds 0 db 0 ; Pad in case we can to access using 16-bit instructions mx %00 scanOAMSprites - stz Tmp5 +:top_line equ Tmp5 +:bot_line equ Tmp6 + +; In order for the shadow bitmap to be zeroed based on the active playfield, we need to adjust the NES +; sprite y-coordinates by the designated top row of the NES graphics screen, and then add an additional +; adjustment for the position of the GTE rendering window within that vertical space + + lda NesTop + clc + adc YOrigin + sta :top_line + + lda NesBottom + clc + adc YOrigin + sta :bot_line sep #$30 @@ -616,15 +661,23 @@ scanOAMSprites :loop ; lda PPU_OAM,x ; Y-coordinate ldal ROMBase+$200,x - cmp #max_nes_y+1 ; Skip anything that is beyond this line + cmp :bot_line bcs :skip - cmp #y_offset + cmp :top_line bcc :skip + sbc :top_line + sta OAM_COPY,y ; Keep the adjusted coordinate + +; cmp #max_nes_y+1 ; Skip sprites that are +; bcs :skip +; cmp #y_offset +; bcc :skip ; lda PPU_OAM+1,x ; $FC is an empty tile, don't draw it ldal ROMBase+$201,x cmp #$FC beq :skip + sta OAM_COPY+1,y ; lda PPU_OAM+3,x ; If X-coordinate is off the edge skip it, too. ldal ROMBase+$203,x @@ -633,8 +686,8 @@ scanOAMSprites rep #$20 ; lda PPU_OAM,x - ldal ROMBase+$200,x - sta OAM_COPY,y +; ldal ROMBase+$200,x +; sta OAM_COPY,y ; lda PPU_OAM+2,x ldal ROMBase+$202,x sta OAM_COPY+2,y @@ -834,19 +887,18 @@ buildShadowBitmap rts ; Set the SCB values equal to the bitmap to visually debug - ldx #0 + ldx ScreenTop ldy #0 :vloop lda #8 sta Tmp6 - lda shadowBitmap+2,y + lda shadowBitmap,y :iloop asl pha lda #0 - bcc :zero - inc + rol :zero stal $E19D00,x pla @@ -855,7 +907,7 @@ buildShadowBitmap bne :iloop iny - cpy #25 + cpy ScreenRows bcc :vloop rep #$30 @@ -1016,7 +1068,7 @@ exposeShadowList :exit ldx :last ; Expose the final part - ldy #y_height + ldy ScreenHeight lda #0 jsl LngJmp rts @@ -1030,16 +1082,15 @@ shadowBitmapToList sep #$30 - ldx #y_offset_rows ; Start at the third row (y_offset = 16) walk the bitmap for 25 bytes (200 lines of height) - lda #0 - sta shadowListCount ; zero out the shadow list count + ldx #0 ; List is zero-based to the active play field + stz shadowListCount ; zero out the shadow list count ; This loop is called when we are not tracking a sprite range :zero_loop ldy shadowBitmap,x beq :zero_next - lda {mul8-y_offset_rows},x ; This is the scanline we're on (offset by the starting byte) + lda mul8,x ; This is the scanline we're on (offset by the starting byte) clc adc offset,y ; This is the first line defined by the bit pattern sta :top @@ -1047,7 +1098,7 @@ shadowBitmapToList :zero_next inx - cpx #y_height_rows+y_offset_rows ; +1 ; End at byte 27 + cpx ScreenRows bcc :zero_loop bra :exit ; ended while not tracking a sprite, so exit the function @@ -1063,7 +1114,8 @@ shadowBitmapToList and offsetMask,y sta shadowBitmap,x - lda {mul8-y_offset_rows},x +; lda {mul8-y_offset_rows},x + lda mul8,x clc adc invOffset,y @@ -1081,7 +1133,8 @@ shadowBitmapToList :one_next inx - cpx #y_height_rows+y_offset_rows+1 + cpx ScreenRows + bcc :one_loop ; If we end while tracking a sprite, add to the list as the last item @@ -1089,7 +1142,7 @@ shadowBitmapToList ldx shadowListCount lda :top sta shadowListTop,x - lda #y_height + lda ScreenHeight sta shadowListBot,x inx stx shadowListCount @@ -1130,6 +1183,8 @@ drawOAMSprites plb pha ; Save the phase indicator + pei 124 ; RenderFlags + tdc ; Keep a copy of the second page of GTE direct page space clc adc #$0100 @@ -1144,6 +1199,8 @@ drawOAMSprites stx FTblPtr+2 pla + sta RenderFlags + pla ; Check what phase we're in ; @@ -1160,10 +1217,7 @@ drawOAMSprites ; We need to "freeze" the OAM values, otherwise they can change between when we build the rendering pipeline sei - ldal nmiCount - pha jsr scanOAMSprites ; Filter out any sprites that don't need to be drawn - pla cli jsr buildShadowBitmap ; Run though and quickly create a bitmap of lines with sprites @@ -1202,7 +1256,7 @@ drawSprites oam_loop phx ; Save x - lda OAM_COPY,x ; Y-coordinate + lda OAM_COPY,x ; Y-coordinate (zero based to screen) ; inc ; Compensate for PPU delayed scanline rep #$30 @@ -1218,7 +1272,7 @@ oam_loop clc adc :tmp clc - adc #$2000-{y_offset*160}+x_offset + adc ScreenBase sta :tmp lda OAM_COPY+3,x diff --git a/macros/GTE.Macs.s b/macros/GTE.Macs.s index 2f67766..a2efda0 100644 --- a/macros/GTE.Macs.s +++ b/macros/GTE.Macs.s @@ -144,6 +144,12 @@ _GTEEnableSprites MAC _GTEEnableBackground MAC UserTool $3100+GTEToolNum <<< +_GTEDrawTileToScreen MAC + UserTool $3200+GTEToolNum + <<< +_GTESetTileImmediate MAC + UserTool $3300+GTEToolNum + <<< ; EngineMode definitions ; Script definition @@ -189,8 +195,9 @@ vblCallback equ $0004 extSpriteRenderer equ $0005 rawDrawTile equ $0006 extBG0TileUpdate equ $0007 -userTileCallback equ $0008 +userTileCallback equ $0008 ; Callback for rendering custom tiles into the code field liteBlitter equ $0009 +userTileDirectCallback equ $000A ; Callback for drawing custom tiles directly to the screen buffer ; CopyPicToBG1 flags COPY_PIC_NORMAL equ $0000 ; Copy into BG1 buffer in "normal mode" diff --git a/src/Defs.s b/src/Defs.s index fc7ac52..3ecf421 100644 --- a/src/Defs.s +++ b/src/Defs.s @@ -208,8 +208,9 @@ vblCallback equ $0004 ; User routine to be called by VBL int extSpriteRenderer equ $0005 rawDrawTile equ $0006 extBG0TileUpdate equ $0007 -userTileCallback equ $0008 +userTileCallback equ $0008 ; Callback for rendering custom tiles into the code field liteBlitter equ $0009 +userTileDirectCallback equ $000A ; Callback for drawing custom tiles directly to the screen buffer ; CopyPicToBG1 flags COPY_PIC_NORMAL equ $0000 ; Copy into BG1 buffer in "normal mode" treating the buffer as a 164x208 pixmap with stride of 256 diff --git a/src/Render.s b/src/Render.s index 32bfd3e..6df5462 100644 --- a/src/Render.s +++ b/src/Render.s @@ -160,13 +160,96 @@ ExtFuncBlock ; Special NES renderer that externalizes the sprite rendering in order to exceed the internal limit of 16 sprites _RenderNES -; jsr _ApplyBG0YPos -; jsr _ApplyBG0XPosPre + sta RenderFlags + bit #$0002 ; If this bit is off, don't render the top. If the top *is* rendered, assume full-screen + bne *+5 + jmp _RenderNES2 + + jsr _ApplyBG0YPosPreLite jsr _ApplyBG0YPosLite jsr _ApplyBG0XPosPre -; Callback to update the tilestore with any new tiles + jsr _RenderNESTileCallback + jsr _ApplyTiles ; This function actually draws the new tiles into the code field +; Render the status bar area, which is fixed + + stz tmp1 ; virt_line_x2 + lda #16*2 + sta tmp2 ; lines_left_x2 + lda #0 ; Xmod164 + jsr _ApplyBG0XPosAltLite + lda tmp4 ; :exit_offset + stal nesTopOffset + +; Next render the remaining lines, which should have their screen locations adjusted for the +; vertical offset + + lda #16*2 + clc + adc StartYMod208 + sta tmp1 ; virt_line_x2 + lda ScreenHeight + sec + sbc #16 + asl + sta tmp2 ; lines_left_x2 + lda StartXMod164 ; Xmod164 + jsr _ApplyBG0XPosAltLite + lda tmp4 + stal nesBottomOffset + + jsr _RenderNESSpritesCallback + + stz tmp1 ; :virt_line_x2 + lda #16*2 + sta tmp2 ; :lines_left_x2 + ldal nesTopOffset + sta tmp4 ; :exit_offset + jsr _RestoreBG0OpcodesAltLite + + lda #16*2 + sta tmp1 ; :virt_line_x2 + lda ScreenHeight + sec + sbc #16 + asl + sta tmp2 ; lines_left_x2 + ldal nesBottomOffset + sta tmp4 ; :exit_offset + jsr _RestoreBG0OpcodesAltLite + bra :final_step + +:no_status_restore + stz tmp1 + lda ScreenHeight + sta tmp2 + ldal nesBottomOffset + sta tmp4 ; :exit_offset + jsr _RestoreBG0OpcodesAltLite + +:final_step + jmp _RenderNESExit + +_RenderNES2 + jsr _ApplyBG0YPosPreLite + jsr _ApplyBG0YPosLite + jsr _ApplyBG0XPosPre + + jsr _RenderNESTileCallback + jsr _ApplyTiles ; This function actually draws the new tiles into the code field + + jsr _ApplyBG0XPosLite ; Do the full screen + jsr _RenderNESSpritesCallback + + lda StartYMod208 ; Restore the fields back to their original state + ldx ScreenHeight + jsr _RestoreBG0OpcodesLite + + jmp _RenderNESExit + +; Callback to update the tilestore with any new tiles +_RenderNESTileCallback lda ExtUpdateBG0Tiles ora ExtUpdateBG0Tiles+2 beq :no_tile @@ -176,31 +259,7 @@ _RenderNES lda ExtUpdateBG0Tiles+1 stal :patch0+2 :patch0 jsl $000000 -:no_tile - - jsr _ApplyTiles ; This function actually draws the new tiles into the code field - - stz tmp1 ; virt_line_x2 - lda #16*2 - sta tmp2 ; lines_left_x2 - lda #0 ; Xmod164 -; jsr _ApplyBG0XPosAlt - jsr _ApplyBG0XPosAltLite - lda tmp4 ; :exit_offset - stal nesTopOffset - - lda #16*2 - sta tmp1 ; virt_line_x2 - lda ScreenHeight - sec - sbc #16 - asl - sta tmp2 ; lines_left_x2 - lda StartXMod164 ; Xmod164 -; jsr _ApplyBG0XPosAlt - jsr _ApplyBG0XPosAltLite - lda tmp4 - stal nesBottomOffset +:no_tile rts ; This is a tricky part. The NES does not keep sprites sorted, so we need an alternative way to figure out ; which lines to shadow and which ones not to. Our compromise is to build a bitmap of lines that the sprite @@ -208,7 +267,7 @@ _RenderNES ; ; This is handled by the callback in two phases. We pass pointers to the internal function the callback needs ; access to. If there is no function defined, do nothing - +_RenderNESSpritesCallback lda GTEControlBits bit #CTRL_SPRITE_DISABLE bne :no_render @@ -243,32 +302,9 @@ _RenderNES ldx #^ExtFuncBlock ldy #ExtFuncBlock :patch2 jsl $000000 +:no_render rts -:no_render - stz tmp1 ; :virt_line_x2 - lda #16*2 - sta tmp2 ; :lines_left_x2 - ldal nesTopOffset - sta tmp4 ; :exit_offset -; jsr _RestoreBG0OpcodesAlt - jsr _RestoreBG0OpcodesAltLite - - lda #16*2 - sta tmp1 ; :virt_line_x2 - lda ScreenHeight - sec - sbc #16 - asl - sta tmp2 ; lines_left_x2 - ldal nesBottomOffset - sta tmp4 ; :exit_offset -; jsr _RestoreBG0OpcodesAlt - jsr _RestoreBG0OpcodesAltLite - -; lda StartYMod208 ; Restore the fields back to their original state -; ldx ScreenHeight -; jsr _RestoreBG0Opcodes - +_RenderNESExit lda StartY sta OldStartY lda StartX @@ -637,6 +673,7 @@ _RenderLite sta RenderFlags jsr _DoTimers ; Run any pending timer tasks + jsr _ApplyBG0YPosPreLite jsr _ApplyBG0YPosLite ; Set stack addresses for the virtual lines to the physical screen jsr _ApplyBG0XPosPre ; Lock in certain rendering variables (not lite/non-lite specific) diff --git a/src/Tiles.s b/src/Tiles.s index c7e80fc..665cbb2 100644 --- a/src/Tiles.s +++ b/src/Tiles.s @@ -352,6 +352,75 @@ _CalcTileProcIndex :no_flip_d lda #0 rts +; Set a tile value in the backing store and immediately render into the code field +; +; A = tile id +; X = tile column [0, 40] (41 columns) +; Y = tile row [0, 25] (26 rows) +; +; Registers are not preserved +_SetTileImmediate + sta newTileId + jsr _GetTileStoreOffset0 ; Get the address of the X,Y tile position + tax + + lda TileStore+TS_TILE_ID,x + cmp newTileId + bne :changed + rts + +:changed sta oldTileId + lda newTileId + sta TileStore+TS_TILE_ID,x ; Value is different, store it. + +; If the user bit is set, then skip most of the setup and just fill in the TileProcs with the user callback +; target + bit #TILE_USER_BIT + bne :set_user_tile + + jsr _GetTileAddr + sta TileStore+TS_TILE_ADDR,x ; Committed to drawing this tile, so get the address of the tile in the tiledata bank for later + +; Set the renderer procs for this tile. +; +; NOTE: Later on, optimize this to just take the Tile ID & TILE_CTRL_MASK and lookup the right proc +; table address from a lookup table.... +; +; 1. The dirty render proc is always set the same. +; 2. If BG1 and DYN_TILES are disabled, then the TS_BASE_TILE_DISP is selected from the Fast Renderers, otherwise +; it is selected from the full tile rendering functions. +; 3. The copy process is selected based on the flip bits +; +; When a tile overlaps the sprite, it is the responsibility of the Render function to compose the appropriate +; functionality. Sometimes it is simple, but in cases of the sprites overlapping Dynamic Tiles and other cases +; it can be more involved. + +; Calculate the base tile proc selector from the tile Id (need X-register set to tile store index) + + lda newTileId + jsr _SetNormalTileProcs + bra :render_tile + +:set_user_tile + and #$01FF + jsr _GetTileAddr ; The user tile callback needs access to this info, too, but + sta TileStore+TS_TILE_ADDR,x ; we just give the base tile address (hflip bit is ignored) + + lda #UserTileDispatch + stal K_TS_BASE_TILE_DISP,x + stal K_TS_SPRITE_TILE_DISP,x + stal K_TS_ONE_SPRITE,x + +:render_tile + phd ; save the current direct page + tdc + clc + adc #$100 ; move to the next page + tcd + jsr _RenderTile + pld + rts + ; Set a tile value in the tile backing store. Mark dirty if the value changes ; ; A = tile id @@ -447,6 +516,27 @@ _UTDPatch jsl UserHook1 ; Call the users code :done rts +; Provides a direct callback without using the TileStore information +; A = user data +; Y = screen address +; X = tile address +; +; Bank will be set to the tiledata bank, so lda $0000,x will load the first word of the tile's data +UserTileDirectDispatch + phd + pha + tdc + clc + adc #$100 + tcd + pla + pei DP2_TILEDATA_AND_TILESTORE_BANKS ; set the bank to the tiledata bank + plb +_UTD2Patch jsl UserHook1 ; Call the user's code + plb ; Restore the curent bank + pld + rts + ; Stub to have a valid address for initialization / reset UserHook1 rtl diff --git a/src/Tool.s b/src/Tool.s index d5c5121..de6f5a0 100644 --- a/src/Tool.s +++ b/src/Tool.s @@ -105,6 +105,9 @@ _CallTable adrl _TSEnableSprites-1 adrl _TSEnableBackground-1 + adrl _TSDrawTileToScreen-1 + adrl _TSSetTileImmediate-1 + _CTEnd _GTEAddSprite MAC UserTool $1000+GTEToolNum @@ -271,19 +274,36 @@ _TSReadControl _TSExit #0;#0 -; SetTile(xTile, yTile, tileId) -_TSSetTile -tileId equ FirstParam -yTile equ FirstParam+2 -xTile equ FirstParam+4 +; SetTileImmediate(xTile, yTile, tileId) +_TSSetTileImmediate +:tileId equ FirstParam +:yTile equ FirstParam+2 +:xTile equ FirstParam+4 _TSEntry - lda xTile,s ; Valid range [0, 40] (41 columns) + lda :xTile,s ; Valid range [0, 40] (41 columns) tax - lda yTile,s ; Valid range [0, 25] (26 rows) + lda :yTile,s ; Valid range [0, 25] (26 rows) tay - lda tileId,s + lda :tileId,s + jsr _SetTileImmediate + + _TSExit #0;#6 + +; SetTile(xTile, yTile, tileId) +_TSSetTile +:tileId equ FirstParam +:yTile equ FirstParam+2 +:xTile equ FirstParam+4 + + _TSEntry + + lda :xTile,s ; Valid range [0, 40] (41 columns) + tax + lda :yTile,s ; Valid range [0, 25] (26 rows) + tay + lda :tileId,s jsr _SetTile _TSExit #0;#6 @@ -312,6 +332,8 @@ _TSRender beq :nes cmp #$FFFE ; Experimental gte-lite mode beq :lite + cmp #$FFFD + beq :nes bit #RENDER_WITH_SHADOWING beq :no_shadowing @@ -1054,7 +1076,26 @@ _TSSetAddress stal _UTDPatch+2 brl :out -:next_6 +:next_6 cmp #userTileDirectCallback + bne :next_7 + lda :ptr,s + ora :ptr+2,s + beq :utd2_restore + + lda :ptr,s + stal _UTD2Patch+1 ; long addressing because we're patching code in the K bank + lda :ptr+1,s + stal _UTD2Patch+2 + brl :out + +:utd2_restore + lda #UserHook1 + stal _UTD2Patch+1 + lda #>UserHook1 + stal _UTD2Patch+2 + brl :out + +:next_7 :out _TSExit #0;#6 @@ -1110,6 +1151,32 @@ _TSEnableBackground :done _TSExit #0;#2 +; Draw a tile directly to the graphics screen. Provides an convenient way to immediately draw +; tile content on the the graphics buffer without having to go through a Render function. +; +; DrawTileToScreen(screenAddr, tileId, tileFlags) +_TSDrawTileToScreen +:tileId equ FirstParam+4 ; fetches the address of the internal tile data buffer +:screenAddr equ FirstParam+2 ; screen address on the SHR +:userData equ FirstParam ; used in place of :tileId for user callback function + + _TSEntry + + lda :tileId,s + bit #TILE_USER_BIT + bne :doUserTile + + bra :done +:doUserTile + jsr _GetTileAddr + tax + lda :screenAddr,s + tay + lda :userData,s + jsr UserTileDirectDispatch +:done + _TSExit #0;#6 + ; Insert the GTE code put Math.s diff --git a/src/blitter/VertLite.s b/src/blitter/VertLite.s index cb8701d..1a2bfe4 100644 --- a/src/blitter/VertLite.s +++ b/src/blitter/VertLite.s @@ -1,38 +1,39 @@ ; Subroutines that deal with the vertical scrolling and rendering. The primary function ; of these routines are to adjust tables and patch in new values into the code field ; when the virtual Y-position of the play field changes. - -_ApplyBG0YPosLite - -:rtbl_idx_x2 equ tmp0 -:virt_line_x2 equ tmp1 -:lines_left_x2 equ tmp2 -:draw_count_x2 equ tmp3 -:stk_save equ tmp4 -:line_count equ tmp5 - -; First task is to fill in the STK_ADDR values by copying them from the RTable array. We -; copy from RTable[i] into BlitField[StartY+i]. - - stz :rtbl_idx_x2 ; Start copying from the first entry in the table - +_ApplyBG0YPosPreLite lda StartY ; This is the base line of the virtual screen jsr Mod208 sta StartYMod208 + rts - asl - sta :virt_line_x2 ; Keep track of it +_ApplyBG0YPosLite -; copy a range of address from the table into the destination bank. If we restrict ourselves to -; rectangular playfields, this can be optimized to just subtracting a constant value. See the -; Templates::SetScreenAddrs subroutine. +:virt_line_x2 equ tmp1 +:lines_left_x2 equ tmp2 + +; First task is to fill in the STK_ADDR values by copying them from the RTable array. We +; copy from RTable[i] into BlitField[StartY+i]. lda ScreenHeight asl sta :lines_left_x2 + lda StartYMod208 + asl + sta :virt_line_x2 ; Keep track of it + + lda #0 + +_ApplyBG0YPosAltLite +:rtbl_idx_x2 equ tmp0 +:virt_line_x2 equ tmp1 +:lines_left_x2 equ tmp2 +:draw_count_x2 equ tmp3 + ; Check to see if we need to split the update into two parts, e.g. do we wrap around the end ; of the code field? + sta :rtbl_idx_x2 ldx :lines_left_x2 lda #208*2 @@ -46,6 +47,11 @@ _ApplyBG0YPosLite stz :virt_line_x2 ; virtual line is at the top (by construction) + lda :rtbl_idx_x2 + clc + adc :draw_count_x2 + sta :rtbl_idx_x2 + lda :lines_left_x2 sec sbc :draw_count_x2 ; this many left to draw. Fall through to finish up @@ -62,6 +68,8 @@ _ApplyBG0YPosLite tay iny ; Fill in the first byte (_ENTRY_1 = 0) + ldx :rtbl_idx_x2 ; Load the stack address from here + sep #$20 ; Set the data bank to the code field lda BTableHigh pha diff --git a/src/render/Render.s b/src/render/Render.s index 7ddc054..065d9af 100644 --- a/src/render/Render.s +++ b/src/render/Render.s @@ -1,3 +1,6 @@ +; External entrypoint to render a tile directly into the code field +RenderTile + ; If there are no sprites, then we copy the tile data into the code field as fast as possible. ; If there are sprites, then additional work is required _RenderTile