Merge branch 'master' into toolbox-docs

This commit is contained in:
Lucas Scharenbroich 2022-06-27 19:48:28 -05:00
commit 7a2cba6bea
100 changed files with 23870 additions and 4490 deletions

3
.gitignore vendored
View File

@ -1,4 +1,7 @@
node_modules
emu
.vscode
*_Output.txt
src/GTETestApp
*.2mg
Tool160.SHK

View File

@ -211,3 +211,4 @@ GTE provides the following capabilities
* [A Guide to the Graphics of the Sega Mega Drive / Genesis](https://rasterscroll.com/mdgraphics/)
* [Jon Burton / Traveller's Tales / Coding Secrets](https://ttjontt.wixsite.com/gamehut/coding-secrets)
* [Lou's Pseudo 3d Page](http://www.extentofthejam.com/pseudo/)
* [A Great Old-Timey Game-Programming Hack](https://blog.moertel.com/posts/2013-12-14-great-old-timey-game-programming-hack.html)

2
_FileInformation.txt Normal file
View File

@ -0,0 +1,2 @@
GTETool.SHK=Type(E0),AuxType(8002),VersionCreate(00),MinVersion(87),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)
Tool160.SHK=Type(E0),AuxType(8002),VersionCreate(00),MinVersion(B8),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)

18
build-image.bat Normal file
View File

@ -0,0 +1,18 @@
echo off
REM Copy all of the assets into the ProDOS image for emulator testing
REM
REM Pass the path of the Cadius tool as the first argument (%1)
set CADIUS="%1"
set IMAGE=".\\emu\\Target.2mg"
set FOLDER="/GTEDEV/Toolbox"
REM Cadius does not overwrite files, so clear the root folder first
%CADIUS% DELETEFOLDER %IMAGE% %FOLDER%
%CADIUS% CREATEFOLDER %IMAGE% %FOLDER%
REM Now copy files and folders as needed
%CADIUS% ADDFILE %IMAGE% %FOLDER% .\src\GTETool
REM Copy in the image assets

View File

@ -3,10 +3,24 @@
"height": 4300,
"width": 2
},
"activeFile": "",
"activeFile": "world-map.tmx",
"expandedProjectPaths": [
"."
],
"file.lastUsedOpenFilter": "All Files (*)",
"fileStates": {
"world-map.tmx": {
"scale": 4,
"selectedLayer": 1,
"viewCenter": {
"x": 143.375,
"y": 120.75
}
},
"world-tiles.tsx": {
"scaleInDock": 4,
"scaleInEditor": 1
}
},
"last.imagePath": "C:/checkout/iigs-game-engine/demos/fatdog-rpg/assets",
"map.height": 128,
@ -15,9 +29,13 @@
"map.tileWidth": 8,
"map.width": 128,
"openFiles": [
"world-map.tmx",
"world-tiles.tsx"
],
"project": "fatdog-rpg.tiled-project",
"recentFiles": [
"world-tiles.tsx",
"world-map.tmx"
],
"tileset.lastUsedFormat": "tsx",
"tileset.tileSize": {

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.5" tiledversion="1.7.2" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="1">
<tileset firstgid="1" source="world-tiles.tsx"/>
<layer id="1" name="Base Layer" width="128" height="128" locked="1">
<layer id="1" name="Base Layer" width="128" height="128" visible="0" locked="1">
<data encoding="csv">
1,1,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

1
demos/fatdog/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
GTEShooter

View File

@ -0,0 +1 @@
GTEPacMan=Type(B3),AuxType(0000),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)

1
demos/shell/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
GTETestApp

View File

@ -9,11 +9,14 @@
use Tool222.Macs.s
use Util.Macs.s
use CORE.MACS.s
use ../../src/GTE.s
use GTE.Macs
use ../../src/Defs.s
mx %00
TSet EXT
; Feature flags
NO_INTERRUPTS equ 0 ; turn off for crossrunner debugging
NO_MUSIC equ 1 ; turn music + tool loading off
@ -22,40 +25,39 @@ NO_MUSIC equ 1 ; turn music + tool loadi
phk
plb
jsl EngineStartUp
sta MyUserId ; GS/OS passes the memory manager user ID for the application into the program
_MTStartUp ; GTE requires the miscellaneous toolset to be running
lda #^MyPalette
ldx #MyPalette
ldy #0
jsl SetPalette
jsr GTEStartUp ; Load and install the GTE User Tool
ldx #0
jsl SetScreenMode
; Load a tileset
pea #^TSet
pea #TSet
_GTELoadTileSet
pea $0000
pea #^MyPalette
pea #MyPalette
_GTESetPalette
pea $0000
_GTESetScreenMode
; Set up our level data
jsr BG0SetUp
jsr BG1SetUp
jsr TileAnimInit
jsr BG0SetUp
jsr BG1SetUp
jsr TileAnimInit
; Allocate room to load data
jsl AllocBank ; Alloc 64KB for Load/Unpack
sta BankLoad ; Store "Bank Pointer"
jsl AllocBank ; Alloc 64KB for Load/Unpack
sta BankLoad ; Store "Bank Pointer"
jsr MovePlayerToOrigin ; Put the player at the beginning of the map
jsr MovePlayerToOrigin ; Put the player at the beginning of the map
jsr InitOverlay ; Initialize the status bar
jsr InitOverlay ; Initialize the status bar
lda #DIRTY_BIT_BG0_REFRESH ; Redraw all of the tiles on the next Render
ora #DIRTY_BIT_BG1_REFRESH
tsb DirtyBits
stz frameCount
ldal OneSecondCounter
sta oldOneSecondCounter
lda #$FFFF
jsl Render
EvtLoop
jsl DoTimers
jsl Render
@ -439,6 +441,59 @@ closeRec dw 1 ; pCount
qtRec adrl $0000
da $00
; Load the GTE User Tool and install it
GTEStartUp
pea $0000
_LoaderStatus
pla
pea $0000
pea $0000
pea $0000
pea $0000
pea $0000 ; result space
lda MyUserId
pha
pea #^ToolPath
pea #ToolPath
pea $0001 ; do not load into special memory
_InitialLoad
bcc :ok1
brk $01
:ok1
ply
pla ; Address of the loaded tool
plx
ply
ply
pea $8000 ; User toolset
pea $00A0 ; Set the tool set number
phx
pha ; Address of function pointer table
_SetTSPtr
bcc :ok2
brk $02
:ok2
clc ; Give GTE a page of direct page memory
tdc
adc #$0100
pha
pea #ENGINE_MODE_DYN_TILES+ENGINE_MODE_TWO_LAYER ; Enable Dynamic Tiles and Two Layer
lda MyUserId ; Pass the userId for memory allocation
pha
_GTEStartUp
bcc :ok3
brk $03
:ok3
rts
PUT App.Msg.s
PUT Actions.s
PUT font.s

View File

@ -7,30 +7,9 @@
; Segment #1 -- Main execution block
ASM App.Main.s
DS 0 ; Number of bytes of 0's to add at the end of the Segment
KND #$1100 ; Type and Attributes ($11=Static+Bank Relative,$00=Code)
ALI None ; Boundary Alignment (None)
SNA Main
; Segment #2 -- Core GTE Code
ASM ..\..\src\Core.s
SNA Core
; Segment #3 -- 64KB Tile Memory
; Segment #2 -- Tileset
ASM gen\App.TileSet.s
DS 0
KND #$1001 ; Type and Attributes ($11=Static+Bank Relative,$01=Data)
; ALI BANK
SNA Tiles
; Segment #4 -- Rotation table data
ASM ..\..\src\RotData.s
DS 0
KND #$1001 ; Type and Attributes ($11=Static+Bank Relative,$01=Data)
ALI BANK
SNA RotData
SNA TSET

View File

@ -10,7 +10,27 @@
; There are two subroutines that need to be implemented -- one to update the overlay content and a
; second to actually render to the screen
; Initialize the overlay be drawin gin static content that will not change over time
STATE_REG equ $E0C068
_R0W0 mac ; Read Bank 0 / Write Bank 0
ldal STATE_REG
and #$FFCF
stal STATE_REG
<<<
_R0W1 mac ; Read Bank 0 / Write Bank 1
ldal STATE_REG
ora #$0010
stal STATE_REG
<<<
_R1W1 mac ; Read Bank 0 / Write Bank 1
ldal STATE_REG
ora #$0030
stal STATE_REG
<<<
; Initialize the overlay be drawing in static content that will not change over time
CHAR_TILE_BASE equ 193 ; set this to the real tile id that starts an ASCII run starting at '0' through 'Z'
@ -32,35 +52,57 @@ l_mask equ ovrly_mask
MASK_OFFSET equ {ovrly_mask-ovrly_buff}
TileDataPtr equ $FC
TileMaskPtr equ $F8
InitOverlay
pha
pha
_GTEGetTileDataAddr
pla
sta TileDataPtr
clc
adc #32
sta TileMaskPtr
pla
sta TileDataPtr+2
sta TileMaskPtr+2
lda #'F'
ldy #l_line+{CHAR_WIDTH*0}
ldx #l_line+{CHAR_WIDTH*0}
jsr _DrawChar
lda #'P'
ldy #l_line+{CHAR_WIDTH*1}
ldx #l_line+{CHAR_WIDTH*1}
jsr _DrawChar
lda #'S'
ldy #l_line+{CHAR_WIDTH*2}
ldx #l_line+{CHAR_WIDTH*2}
jsr _DrawChar
lda #':'
ldy #l_line+{CHAR_WIDTH*3}
ldx #l_line+{CHAR_WIDTH*3}
jsr _DrawChar
lda #'T'
ldy #r_line+{CHAR_WIDTH*0}
ldx #r_line+{CHAR_WIDTH*0}
jsr _DrawChar
lda #'I'
ldy #r_line+{CHAR_WIDTH*1}
ldx #r_line+{CHAR_WIDTH*1}
jsr _DrawChar
lda #'C'
ldy #r_line+{CHAR_WIDTH*2}
ldx #r_line+{CHAR_WIDTH*2}
jsr _DrawChar
lda #'K'
ldy #r_line+{CHAR_WIDTH*3}
ldx #r_line+{CHAR_WIDTH*3}
jsr _DrawChar
lda #':'
ldy #r_line+{CHAR_WIDTH*4}
ldx #r_line+{CHAR_WIDTH*4}
jsr _DrawChar
pea $0000
pea $0008
pea #^StatusBar
pea #StatusBar
_GTESetOverlay
rts
; Update the dynamic content of the overlay
@ -79,8 +121,8 @@ UdtOverlay
lda frameCount ; render the FPS value
xba
jsr _num2ascii
ldy #l_line+{CHAR_WIDTH*4}
jsr _DrawChar
ldx #l_line+{CHAR_WIDTH*4}
jsr _DrawChar
lda frameCount
lsr
@ -88,44 +130,52 @@ UdtOverlay
lsr
lsr
jsr _num2ascii
ldy #l_line+{CHAR_WIDTH*5}
ldx #l_line+{CHAR_WIDTH*5}
jsr _DrawChar
lda frameCount
jsr _num2ascii
ldy #l_line+{CHAR_WIDTH*6}
ldx #l_line+{CHAR_WIDTH*6}
jsr _DrawChar
ldal OneSecondCounter ; reder the number of remaining seconds
pha
_GTEGetSeconds
pla
sta oneSecondCounter ; render the number of remaining seconds
xba
jsr _num2ascii
ldy #r_line+{CHAR_WIDTH*5}
ldx #r_line+{CHAR_WIDTH*5}
jsr _DrawChar
ldal OneSecondCounter
lda oneSecondCounter
lsr
lsr
lsr
lsr
jsr _num2ascii
ldy #r_line+{CHAR_WIDTH*6}
ldx #r_line+{CHAR_WIDTH*6}
jsr _DrawChar
ldal OneSecondCounter
lda oneSecondCounter
jsr _num2ascii
ldy #r_line+{CHAR_WIDTH*7}
jsr _DrawChar
ldx #r_line+{CHAR_WIDTH*7}
jsr _DrawChar
rts
oneSecondCounter ds 2
; Draw the overlay
; A = address of the left edge of the screen
Overlay ENT
phb ; Called via JSL
StatusBar phb ; Called via JSL
phd ; save the direct page register
phk
plb
phd ; save the direct page register
ldx MyDirectPage ; Preserve the accumulator
phx
pld
sta l_addr ; save this value (will go into D-reg later)
clc
@ -144,12 +194,12 @@ Overlay ENT
sec
sbc m_addr ; calculate the number of words between the two ends
and #$FFFE
pha
lda #m_end
sec
sbc 1,s
eor #$FFFF
inc
clc
adc #m_end
sta m_patch+1
pla
sei
_R1W1
@ -192,7 +242,7 @@ l_ovrly_rtn
_R0W0
cli
:exit
o_exit
pld ; restore the direct page and bank and return
plb
rtl
@ -201,7 +251,6 @@ l_addr ds 2
m_addr ds 2
r_addr ds 2
r_ovrly
]idx equ 0
lup R_CHAR_COUNT
@ -247,10 +296,7 @@ m_end
;
; A = Tile ID
; Y = overlay address location
tiledata EXT
_DCOut rts
_DrawChar
cmp #'0'
bcc _DCOut
@ -261,19 +307,122 @@ _DrawChar
sbc #'0'
clc
adc #CHAR_TILE_BASE
jsl GetTileAddr
tax
jsr _GetTileAddr
tay
lda [TileMaskPtr],y
sta: {0*OVRLY_SPAN}+MASK_OFFSET,x
lda [TileDataPtr],y
sta: {0*OVRLY_SPAN},x
iny
iny
lda [TileMaskPtr],y
sta: {0*OVRLY_SPAN}+MASK_OFFSET+2,x
lda [TileDataPtr],y
sta: {0*OVRLY_SPAN}+2,x
iny
iny
lda [TileMaskPtr],y
sta: {1*OVRLY_SPAN}+MASK_OFFSET,x
lda [TileDataPtr],y
sta: {1*OVRLY_SPAN},x
iny
iny
lda [TileMaskPtr],y
sta: {1*OVRLY_SPAN}+MASK_OFFSET+2,x
lda [TileDataPtr],y
sta: {1*OVRLY_SPAN}+2,x
iny
iny
lda [TileMaskPtr],y
sta: {2*OVRLY_SPAN}+MASK_OFFSET,x
lda [TileDataPtr],y
sta: {2*OVRLY_SPAN},x
iny
iny
lda [TileMaskPtr],y
sta: {2*OVRLY_SPAN}+MASK_OFFSET+2,x
lda [TileDataPtr],y
sta: {2*OVRLY_SPAN}+2,x
iny
iny
lda [TileMaskPtr],y
sta: {3*OVRLY_SPAN}+MASK_OFFSET,x
lda [TileDataPtr],y
sta: {3*OVRLY_SPAN},x
iny
iny
lda [TileMaskPtr],y
sta: {3*OVRLY_SPAN}+MASK_OFFSET+2,x
lda [TileDataPtr],y
sta: {3*OVRLY_SPAN}+2,x
iny
iny
lda [TileMaskPtr],y
sta: {4*OVRLY_SPAN}+MASK_OFFSET,x
lda [TileDataPtr],y
sta: {4*OVRLY_SPAN},x
iny
iny
lda [TileMaskPtr],y
sta: {4*OVRLY_SPAN}+MASK_OFFSET+2,x
lda [TileDataPtr],y
sta: {4*OVRLY_SPAN}+2,x
iny
iny
lda [TileMaskPtr],y
sta: {5*OVRLY_SPAN}+MASK_OFFSET,x
lda [TileDataPtr],y
sta: {5*OVRLY_SPAN},x
iny
iny
lda [TileMaskPtr],y
sta: {5*OVRLY_SPAN}+MASK_OFFSET+2,x
lda [TileDataPtr],y
sta: {5*OVRLY_SPAN}+2,x
iny
iny
lda [TileMaskPtr],y
sta: {6*OVRLY_SPAN}+MASK_OFFSET,x
lda [TileDataPtr],y
sta: {6*OVRLY_SPAN},x
iny
iny
lda [TileMaskPtr],y
sta: {6*OVRLY_SPAN}+MASK_OFFSET+2,x
lda [TileDataPtr],y
sta: {6*OVRLY_SPAN}+2,x
iny
iny
lda [TileMaskPtr],y
sta: {7*OVRLY_SPAN}+MASK_OFFSET,x
lda [TileDataPtr],y
sta: {7*OVRLY_SPAN},x
iny
iny
lda [TileMaskPtr],y
sta: {7*OVRLY_SPAN}+MASK_OFFSET+2,x
lda [TileDataPtr],y
sta: {7*OVRLY_SPAN}+2,x
]idx equ 0
lup 8
ldal tiledata+32+{]idx*4},x
sta: {]idx*OVRLY_SPAN}+MASK_OFFSET,y
ldal tiledata+{]idx*4},x
sta: {]idx*OVRLY_SPAN},y
ldal tiledata+32+{]idx*4}+2,x
sta: {]idx*OVRLY_SPAN}+MASK_OFFSET+2,y
ldal tiledata+{]idx*4}+2,x
sta: {]idx*OVRLY_SPAN}+2,y
]idx equ ]idx+1
--^
rts
_GetTileAddr
asl ; Multiply by 2
bit #2*TILE_HFLIP_BIT ; Check if the horizontal flip bit is set
beq :no_flip
inc ; Set the LSB
:no_flip asl ; x4
asl ; x8
asl ; x16
asl ; x32
asl ; x64
asl ; x128
rts

View File

@ -3,7 +3,7 @@
"height": 4300,
"width": 2
},
"activeFile": "tiled/world_1-1.tmx",
"activeFile": "C:/checkout/iigs-game-engine/demos/zelda/assets/overworld.tmx",
"expandedProjectPaths": [
"tiled"
],
@ -26,46 +26,145 @@
"scaleInDock": 2,
"scaleInEditor": 4
},
"C:/checkout/iigs-game-engine/demos/sprites/assets/tiled/Overworld.tsx": {
"scaleInDock": 2,
"scaleInEditor": 4
},
"C:/checkout/iigs-game-engine/demos/sprites/assets/tiled/world_1-1.tmx": {
"scale": 2,
"selectedLayer": 0,
"viewCenter": {
"x": 412,
"y": 309
}
},
"C:/checkout/iigs-game-engine/demos/zelda/assets/Zelda.tsx": {
"scaleInDock": 4,
"scaleInEditor": 1
},
"C:/checkout/iigs-game-engine/demos/zelda/assets/overworld.tmx": {
"scale": 2,
"selectedLayer": 0,
"viewCenter": {
"x": 222.75,
"y": 192.5
}
},
"C:/checkout/tiled/examples/desert.tmx": {
"scale": 0.5462499999999999,
"selectedLayer": 0,
"viewCenter": {
"x": 716.704805491991,
"y": 793.5926773455379
}
},
"C:/checkout/tiled/examples/desert.tsx": {
"scaleInDock": 1
},
"C:/checkout/tiled/examples/hexagonal-mini.tmx": {
"scale": 3.0067010309278346,
"selectedLayer": 0,
"viewCenter": {
"x": 159.14452254414542,
"y": 116.90553745928342
}
},
"C:/checkout/tiled/examples/hexagonal-mini.tmx#hex mini": {
"scaleInDock": 1
},
"C:/checkout/tiled/examples/isometric_grass_and_water.tmx": {
"scale": 0.4643125,
"selectedLayer": 0,
"viewCenter": {
"x": 1039.1708170682462,
"y": 573.9668865257775
}
},
"C:/checkout/tiled/examples/isometric_grass_and_water.tmx#isometric_grass_and_water": {
"scaleInDock": 1
},
"C:/checkout/tiled/examples/perspective_walls.tmx": {
"scale": 0.8369268292682926,
"selectedLayer": 0,
"viewCenter": {
"x": 497.65401876784983,
"y": 479.7313050067028
}
},
"C:/checkout/tiled/examples/perspective_walls.tsx": {
"scaleInDock": 1
},
"C:/checkout/tiled/examples/rpg/beach_tileset.tsx": {
"scaleInDock": 1
},
"C:/checkout/tiled/examples/rpg/island.tmx": {
"scale": 1.0267780172413792,
"selectedLayer": 0,
"viewCenter": {
"x": 464.0730440258173,
"y": 376.42021304507534
}
},
"C:/checkout/tiled/examples/sewers.tmx": {
"scale": 0.7148749999999999,
"selectedLayer": 0,
"viewCenter": {
"x": 600.8043364224515,
"y": 600.8043364224516
}
},
"C:/checkout/tiled/examples/sewers.tmx#sewer_tileset": {
"scaleInDock": 1
},
"tiled/Overworld.tsx": {
"scaleInDock": 2,
"scaleInEditor": 1
"scaleInEditor": 8
},
"tiled/world_1-1.tmx": {
"scale": 2,
"selectedLayer": 1,
"viewCenter": {
"x": 210.5,
"y": 107.75
"x": 212.75,
"y": 211.25
}
}
},
"last.exportedFilePath": "C:/checkout/iigs-game-engine/assets/tiled",
"last.imagePath": "C:/checkout/iigs-game-engine/assets/tilesets",
"map.height": 30,
"frame.defaultDuration": 256,
"last.exportedFilePath": "C:/checkout/iigs-game-engine/demos/zelda/assets",
"last.imagePath": "C:/checkout/iigs-game-engine/demos/zelda/assets",
"map.height": 44,
"map.lastUsedExportFilter": "JSON map files (*.json)",
"map.lastUsedFormat": "tmx",
"map.renderOrder": null,
"map.tileHeight": 8,
"map.tileWidth": 8,
"map.width": 256,
"map.width": 64,
"openFiles": [
"tiled/world_1-1.tmx",
"tiled/Overworld.tsx"
"C:/checkout/iigs-game-engine/demos/zelda/assets/overworld.tmx",
"C:/checkout/iigs-game-engine/demos/zelda/assets/Zelda.tsx"
],
"project": "assets.tiled-project",
"property.type": "int",
"property.type": "bool",
"recentFiles": [
"tiled/Overworld.tsx",
"C:/checkout/iigs-game-engine/demos/zelda/assets/Zelda.tsx",
"C:/checkout/iigs-game-engine/demos/zelda/assets/overworld.tmx",
"C:/checkout/iigs-game-engine/demos/sprites/assets/tiled/Overworld.tsx",
"C:/checkout/iigs-game-engine/demos/sprites/assets/tiled/world_1-1.tmx",
"tiled/world_1-1.tmx",
"C:/Users/lscharen/SNES - Final Fantasy 6 - South Figaro Exterior.tsx",
"C:/Users/lscharen/Overworld.tmx",
"C:/Users/lscharen/EUm-lfFWkAEb5fJ.tsx"
"tiled/Overworld.tsx",
"C:/checkout/tiled/examples/desert.tmx",
"C:/checkout/tiled/examples/hexagonal-mini.tmx",
"C:/checkout/tiled/examples/isometric_grass_and_water.tmx",
"C:/checkout/tiled/examples/perspective_walls.tmx",
"C:/checkout/tiled/examples/sewers.tmx",
"C:/checkout/tiled/examples/rpg/island.tmx"
],
"resizeMap.removeObjects": true,
"tileset.lastUsedFormat": "tsx",
"tileset.tileSize": {
"height": 8,
"width": 8
},
"tileset.transparentColor": "#6b8cff",
"tileset.useTransparentColor": false
"tileset.transparentColor": "#ff00ff",
"tileset.useTransparentColor": true
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 243 B

View File

@ -2,7 +2,7 @@
; Palette:
; $0000,$0777,$0F31,$0E51,$00A0,$02E3,$0BF1,$0FA4,$0FD7,$0EE6,$0F59,$068F,$01CE,$09B9,$0EDA,$0EEE
; Converting to BG0 format...
tiledata ENT
TSet ENT
; Reserved space (tile 0 is special...
ds 128

1
demos/smb/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
GTETestApp

View File

@ -0,0 +1 @@
GTETestApp=Type(B3),AuxType(0000),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)

28
demos/smb/package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "super-mario-bros-nes-demo",
"version": "1.0.0",
"description": "Wrapping the SMB 6502 ROM in GTE",
"main": "index.js",
"config": {
"merlin32": "C:\\Programs\\IIgsXDev\\bin\\Merlin32-1.1.10.exe",
"cadius": "C:\\Programs\\IIgsXDev\\bin\\Cadius.exe",
"gsport": "C:\\Programs\\gsport\\gsport_0.31\\GSPort.exe",
"macros": "C:\\Programs\\BrutalDeluxe\\Merlin32\\Library",
"crossrunner": "C:\\Programs\\Crossrunner\\Crossrunner.exe"
},
"scripts": {
"build": "%npm_package_config_merlin32% -V %npm_package_config_macros% App.s"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lscharen/iigs-game-engine.git"
},
"author": "Lucas Scharenbroich",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/lscharen/iigs-game-engine/issues"
},
"homepage": "https://github.com/lscharen/iigs-game-engine#readme",
"devDependencies": {
}
}

View File

@ -1,293 +1,353 @@
; Test driver to exercise graphics routines.
REL
DSK MAINSEG
REL
DSK MAINSEG
use Locator.Macs.s
use Misc.Macs.s
use EDS.GSOS.MACS.s
use Tool222.Macs.s
use Util.Macs.s
use CORE.MACS.s
use ../../src/GTE.s
use ../../src/Defs.s
use Locator.Macs
use Load.Macs
use Mem.Macs
use Misc.Macs
use Tool222.Macs.s
use Util.Macs
use EDS.GSOS.Macs
use GTE.Macs
mx %00
; use ../../src/Defs.s
; Feature flags
NO_INTERRUPTS equ 0 ; turn off for crossrunner debugging
NO_MUSIC equ 1 ; turn music + tool loading off
mx %00
TSet EXT ; tileset buffer
; Keycodes
LEFT_ARROW equ $08
RIGHT_ARROW equ $15
UP_ARROW equ $0B
DOWN_ARROW equ $0A
LEFT_ARROW equ $08
RIGHT_ARROW equ $15
UP_ARROW equ $0B
DOWN_ARROW equ $0A
; Typical init
phk
plb
; Direct page space
appTmp0 equ 0
BankLoad equ 2
StartX equ 4
StartY equ 6
TileMapWidth equ 8
TileMapHeight equ 10
ScreenWidth equ 12
ScreenHeight equ 14
jsl EngineStartUp
phk
plb
lda #^MyPalette ; Fill Palette #0 with our colors
ldx #MyPalette
ldy #0
jsl SetPalette
sta MyUserId ; GS/OS passes the memory manager user ID for the application into the program
tdc
sta MyDirectPage ; Keep a copy for the overlay callback
ldx #5 ; Mode 0 is full-screen, mode 5 is 256x160
ldx #320
ldy #200
jsl SetScreenMode
_MTStartUp ; GTE requires the miscellaneous toolset to be running
jsr GTEStartUp ; Load and install the GTE User Tool
; Initialize local variables
stz appTmp0
stz BankLoad
stz StartX
stz StartY
; Initialize the graphics screen to a 256x160 playfield
pea #320
pea #200
_GTESetScreenMode
; Load a tileset
pea #^TSet
pea #TSet
_GTELoadTileSet
pea $0000
pea #^MyPalette
pea #MyPalette
_GTESetPalette
; Set up our level data
jsr BG0SetUp
jsr TileAnimInit
jsr SetLimits
jsr BG0SetUp
jsr TileAnimInit
jsr SetLimits
jsr InitOverlay ; Initialize the status bar
stz frameCount
ldal OneSecondCounter
sta oldOneSecondCounter
jsr UdtOverlay
jsr InitOverlay ; Initialize the status bar
stz frameCount
pha
_GTEGetSeconds
pla
sta oldOneSecondCounter
jsr UdtOverlay
; Allocate a buffer for loading files
jsl AllocBank ; Alloc 64KB for Load/Unpack
sta BankLoad ; Store "Bank Pointer"
jsl AllocBank ; Alloc 64KB for Load/Unpack
sta BankLoad ; Store "Bank Pointer"
; Load in the 256 color background into BG1 buffer
brl :nobackground
; brl :nobackground
DoLoadBG1
lda BankLoad
ldx #BG1DataFile
jsr LoadFile
lda BankLoad
ldx #BG1DataFile
jsr LoadFile
ldx BankLoad
lda #0
ldy BG1DataBank
jsl CopyPicToBG1
lda BankLoad
pha
pea $0000
_GTECopyPicToBG1
; Copy the palettes into place
stz tmp0
stz appTmp0
:ploop
lda tmp0
tay
asl
asl
asl
asl
asl
clc
adc #$7E00
tax
lda appTmp0
pha ; Palette number
ldy BankLoad
phy ; High word pointer to palette
lda BankLoad
jsl SetPalette
asl
asl
asl
asl
asl
clc
adc #$7E00
pha ; Low word pointer to palette
_GTESetPalette
inc tmp0
lda tmp0
cmp #16
bcc :ploop
inc appTmp0
lda appTmp0
cmp #16
bcc :ploop
; Bind the SCBs
lda BankLoad
ora #$8000 ; set high bit to bind to BG1 Y-position
ldx #$7D00
jsl SetSCBArray
lda BankLoad
ora #$8000 ; set high bit to bind to BG1 Y-position
pha
pea $7D00
_GTEBindSCBArray
:nobackground
; Initialize the sprite's global position (this is tracked outside of the tile engine)
lda #16
sta PlayerGlobalX
lda MaxGlobalY
sec
lda #40 ; 32 for tiles, 8 for sprite
sta PlayerGlobalY
lda #16
sta PlayerGlobalX
lda MaxGlobalY
sec
lda #40 ; 32 for tiles, 8 for sprite
sta PlayerGlobalY
stz PlayerXVel
stz PlayerYVel
stz PlayerXVel
stz PlayerYVel
; Add a sprite to the engine and save it's sprite ID
SPRITE_ID equ {SPRITE_16X16+145}
MUSHROOM_ID equ {SPRITE_16X16+255}
; Create the sprites
HERO_ID equ {SPRITE_16X16+145}
HERO_VBUFF equ VBUFF_SPRITE_START+0*VBUFF_SPRITE_STEP
HERO_SLOT equ 1
MUSHROOM_ID equ {SPRITE_16X16+255}
MUSHROOM_VBUFF equ VBUFF_SPRITE_START+1*VBUFF_SPRITE_STEP
MUSHROOM_SLOT equ 0
lda #MUSHROOM_ID ; 16x16 sprite, tile ID = 145
ldx #80
ldy #152
jsl AddSprite
pea HERO_ID ; sprint id
pea HERO_VBUFF ; vbuff address
_GTECreateSpriteStamp
jsr UpdatePlayerLocal
lda #SPRITE_ID ; 16x16 sprite, tile ID = 145
ldx PlayerX
ldy PlayerY
jsl AddSprite
bcc :sprite_ok
brl Exit ; If we could not allocate a sprite, exit
:sprite_ok
sta PlayerID
pea MUSHROOM_ID ; sprint id
pea MUSHROOM_VBUFF ; vbuff address
_GTECreateSpriteStamp
; Draw the initial screen
pea MUSHROOM_ID ; Put the mushroom in Slot 0
pea #80 ; at x=80, y=152
pea #152
pea MUSHROOM_SLOT
_GTEAddSprite
lda #DIRTY_BIT_BG0_REFRESH ; Redraw all of the tiles on the next Render
tsb DirtyBits
jsl Render
pea MUSHROOM_SLOT
pea $0000 ; with these flags (h/v flip)
pea MUSHROOM_VBUFF ; and use this stamp
_GTEUpdateSprite
jsr UpdatePlayerLocal
pea HERO_ID
lda PlayerX
pha
lda PlayerY
pha
pea HERO_SLOT ; Put the player in slot 1
_GTEAddSprite
pea HERO_SLOT
pea $0000
pea HERO_VBUFF ; and use this stamp
_GTEUpdateSprite
; 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.
EvtLoop
jsl ReadControl
pha
_GTEReadControl
; Check the buttons first
pha
lda 1,s
bit #$0100
beq :no_jump
lda PlayerStanding
beq :no_jump
lda #$FFF8
sta PlayerYVel
bit #$0100
beq :no_jump
lda PlayerStanding
beq :no_jump
lda #$FFF8
sta PlayerYVel
:no_jump
; Enable/disable v-sync
lda 1,s
bit #$0400
beq :no_key_down
and #$007F
cmp #'v'
bne :not_v
lda #$0001
eor vsync
sta vsync
lda 1,s
bit #$0400
beq :no_key_down
and #$007F
cmp #'v'
bne :not_v
lda #$0001
eor vsync
sta vsync
:not_v
cmp #'f'
bne :not_f
lda SpriteToggle
eor #SPRITE_HIDE
sta SpriteToggle
bne :not_f
stz SpriteCount
cmp #'f'
bne :not_f
lda SpriteToggle
eor #SPRITE_HIDE
sta SpriteToggle
bne :not_f
stz SpriteCount
:not_f
:no_key_down
pla
and #$007F ; Ignore the buttons for now
cmp #'q'
bne :not_q
brl Exit
pla
and #$007F ; Ignore the buttons for now
cmp #'q'
bne :not_q
brl Exit
:not_q
cmp #'d'
bne :not_d
lda StartX
cmp MaxBG0X
bcs :do_render
inc
jsl SetBG0XPos
bra :do_render
cmp #'d'
bne :not_d
lda StartX
cmp MaxBG0X
bcc *+5
brl :do_render
inc StartX
pei StartX
pei StartY
_GTESetBG0Origin
brl :do_render
:not_d
cmp #'a'
bne :not_a
lda StartX
beq :do_render
dec
jsl SetBG0XPos
bra :do_render
cmp #'a'
bne :not_a
lda StartX
bne *+5
brl :do_render
dec StartX
pei StartX
pei StartY
_GTESetBG0Origin
brl :do_render
:not_a
cmp #'s'
bne :not_s
lda StartY
cmp MaxBG0Y
bcs :do_render
inc
jsl SetBG0YPos
bra :do_render
cmp #'s'
bne :not_s
lda StartY
cmp MaxBG0Y
bcs :do_render
inc StartY
pei StartX
pei StartY
_GTESetBG0Origin
bra :do_render
:not_s
cmp #'w'
bne :not_w
lda StartY
beq :do_render
dec
jsl SetBG0YPos
bra :do_render
cmp #'w'
bne :not_w
lda StartY
beq :do_render
dec StartY
pei StartX
pei StartY
_GTESetBG0Origin
bra :do_render
:not_w
; Do j,l to move the character left/right
cmp #'j'
bne :not_j
lda PlayerXVel
bpl :pos_xvel
cmp #$FFFA
bcc :not_j
:pos_xvel dec
dec
sta PlayerXVel
bra :do_render
cmp #'j'
bne :not_j
lda PlayerXVel
bpl :pos_xvel
cmp #$FFFA
bcc :not_j
:pos_xvel dec
dec
sta PlayerXVel
bra :do_render
:not_j
cmp #'l'
bne :not_l
lda PlayerXVel
bmi :neg_xvel
cmp #6
bcs :not_l
:neg_xvel inc
inc
sta PlayerXVel
bra :do_render
cmp #'l'
bne :not_l
lda PlayerXVel
bmi :neg_xvel
cmp #6
bcs :not_l
:neg_xvel inc
inc
sta PlayerXVel
bra :do_render
:not_l
; Update the camera position
:do_render
jsr UpdatePlayerPos ; Moves in global cordinates
jsr UpdateCameraPos ; Moves the screen
jsr UpdatePlayerLocal ; Gets local sprite coordinates
jsr UpdatePlayerPos ; Moves in global cordinates
jsr UpdateCameraPos ; Moves the screen
jsr UpdatePlayerLocal ; Gets local sprite coordinates
lda PlayerID
ldx PlayerX
ldy PlayerY
jsl MoveSprite ; Move the sprite to this local position
pea HERO_SLOT
lda PlayerX
pha
lda PlayerY
pha
_GTEMoveSprite ; Move the sprite to this local position
; Update the timers
jsl DoTimers
; jsl DoTimers
; Let's see what it looks like!
lda vsync
beq :no_vsync
:vsyncloop jsl GetVerticalCounter ; 8-bit value
cmp ScreenY0
bcc :vsyncloop
sec
sbc ScreenY0
cmp #8
bcs :vsyncloop
lda #1
jsl SetBorderColor
:no_vsync
jsl Render
; lda vsync
; beq :no_vsync
;:vsyncloop jsl GetVerticalCounter ; 8-bit value
; cmp ScreenY0
; bcc :vsyncloop
; sec
; sbc ScreenY0
; cmp #8
; bcs :vsyncloop
; lda #1
; jsl SetBorderColor
;:no_vsync
_GTERender
lda vsync
beq :no_vsync2
lda #0
jsl SetBorderColor
:no_vsync2
; lda vsync
; beq :no_vsync2
; lda #0
; jsl SetBorderColor
;:no_vsync2
; Update the performance counters
inc frameCount
ldal OneSecondCounter
pha
_GTEGetSeconds
pla
cmp oldOneSecondCounter
beq :noudt
sta oldOneSecondCounter
@ -298,19 +358,29 @@ EvtLoop
; Exit code
Exit
jsl EngineShutDown
_GTEShutDown
_QuitGS qtRec
bcs Fatal
Fatal brk $00
Hold
_GTERender
:busy
pha
_GTEReadControl
pla
and #$00FF
cmp #'q'
bne :busy
jmp Exit
BG1DataFile strl '1/sunset.c1'
; Color palette
MyPalette dw $068F,$0EDA,$0000,$0000,$0BF1,$00A0,$0EEE,$0456,$0FA4,$0F59,$0E30,$01CE,$02E3,$0870,$0F93,$0FD7
; B&W Palette
;MyPalette dw $0000,$0EDA,$0000,$0E51,$0BF1,$00A0,$0EEE,$0456,$0FA4,$0F59,$0E30,$01CE,$02E3,$0870,$0F93,$0FFF
; MyPalette dw $0000,$0EDA,$0000,$0E51,$0BF1,$00A0,$0EEE,$0456,$0FA4,$0F59,$0E30,$01CE,$02E3,$0870,$0F93,$0FFF
PlayerGlobalX ds 2
PlayerGlobalY ds 2
@ -331,6 +401,8 @@ MaxBG0Y ds 2
oldOneSecondCounter ds 2
frameCount ds 2
MyUserId ds 2
MyDirectPage ds 2
PLAYER_X_MIN equ 0
PLAYER_X_MAX equ 160-4
@ -339,22 +411,31 @@ PLAYER_Y_MAX equ 200-8
EMPTY_TILE equ 33 ; the tile that makes up the background
AdjustLocalX
clc
adc StartXMod164
cmp #164
bcc *+5
sbc #164
rts
AdjustLocalY
clc
adc StartYMod208
cmp #208
bcc *+5
sbc #208
rts
SetLimits
pha ; Allocate space for width (in tiles), height (in tiles), pointer
pha
pha
pha
_GTEGetBG0TileMapInfo
pla
sta TileMapWidth
pla
sta TileMapHeight
pla
pla ; discard the pointer
pha ; Allocate space for x, y, width, height
pha
pha
pha
_GTEGetScreenInfo
pla
pla ; Discard screen corner
pla
sta ScreenWidth
pla
sta ScreenHeight
lda TileMapWidth
asl
asl
@ -373,38 +454,44 @@ SetLimits
sta MaxBG0Y
rts
; Set the scroll position based on the global cooridinate of the player
; Set the scroll position based on the global coordinates of the player
; Try to center the player on the screen
UpdateCameraPos
lda ScreenWidth
lsr
sta tmp0
sta appTmp0
lda PlayerGlobalX
sec
sbc tmp0
sbc appTmp0
bpl :x_pos
lda #0
:x_pos cmp MaxBG0X
bcc :x_ok
lda MaxBG0X
:x_ok jsl SetBG0XPos
:x_ok sta StartX
lda ScreenHeight
lsr
sta tmp0
sta appTmp0
lda PlayerGlobalY
sec
sbc tmp0
sbc appTmp0
bpl :y_pos
lda #0
:y_pos cmp MaxBG0Y
bcc :y_ok
lda MaxBG0Y
:y_ok jsl SetBG0YPos
:y_ok sta StartY
pei StartX
pei StartY
_GTESetBG0Origin
pea $0000
lda StartY
lsr
jsl SetBG1YPos
pha
_GTESetBG1Origin
rts
; Convert the global coordinates to adjusted local coordinated (compensating for wrap-around)
@ -412,13 +499,11 @@ UpdatePlayerLocal
lda PlayerGlobalX
sec
sbc StartX
; jsr AdjustLocalX
sta PlayerX
lda PlayerGlobalY
sec
sbc StartY
; jsr AdjustLocalY
sta PlayerY
rts
@ -431,13 +516,16 @@ UpdatePlayerPos
; Check if the player is standing on the ground at their current local position
ldx PlayerX
pha ; space for result
lda PlayerX
pha
lda PlayerY
clc
adc #16
tay
jsr GetTileAt
and #$1FF
pha
_GTEGetTileAt
pla
and #TILE_ID_MASK
cmp #EMPTY_TILE
beq :no_ground_check
@ -501,7 +589,7 @@ UpdatePlayerPos
txa
ora LastHFlip
ora #SPRITE_ID
ora #HERO_ID
sta SpriteFrame
lda SpriteCount
@ -514,7 +602,7 @@ UpdatePlayerPos
lda PlayerXVel
beq :frame
jsl GetVBLTicks
jsr _GetVBLTicks
and #$0003
inc
and #$0003
@ -527,65 +615,21 @@ UpdatePlayerPos
tax
lda PlayerID
jsl UpdateSprite ; Change the tile ID and / or flags
; jsl UpdateSprite ; Change the tile ID and / or flags
; pea HERO_SLOT
; pei Flips ; with these flags (h/v flip)
; pea VBUFF_SPRITE_START ; and use this stamp
; _GTEUpdateSprite
rts
ToolPath str '1/Tool160'
LastHFlip dw 0
SpriteFrame ds 2
SpriteCount dw 0
SpriteToggle dw 0
; X = coordinate
; Y = coordinate
GetTileAt
txa
bmi :out
clc
adc StartXMod164
cmp #164
bcc *+5
sbc #164
lsr
lsr
tax
tya
bmi :out
clc
adc StartYMod208
cmp #208
bcc *+5
sbc #208
lsr
lsr
lsr
tay
jsl GetTileStoreOffset
tax
ldal TileStore+TS_TILE_ID,x
rts
:out
lda #EMPTY_TILE
rts
; Position the screen with the botom-left corner of the tilemap visible
MovePlayerToOrigin
lda #0 ; Set the player's position
jsl SetBG0XPos
lda TileMapHeight
asl
asl
asl
sec
sbc ScreenHeight
jsl SetBG0YPos
rts
openRec dw 2 ; pCount
ds 2 ; refNum
@ -657,8 +701,91 @@ msgLine2 str 'Press a key :'
msgLine3 str ' -> Return to Try Again'
msgLine4 str ' -> Esc to Quit'
PUT ../shell/Overlay.s
PUT gen/App.TileMapBG0.s
PUT gen/App.TileSetAnim.s
ANGLEBNK ENT
; Load the GTE User Tool and install it
GTEStartUp
pea $0000
_LoaderStatus
pla
pea $0000
pea $0000
pea $0000
pea $0000
pea $0000 ; result space
lda MyUserId
pha
pea #^ToolPath
pea #ToolPath
pea $0001 ; do not load into special memory
_InitialLoad
bcc :ok1
brk $01
:ok1
ply
pla ; Address of the loaded tool
plx
ply
ply
pea $8000 ; User toolset
pea $00A0 ; Set the tool set number
phx
pha ; Address of function pointer table
_SetTSPtr
bcc :ok2
brk $02
:ok2
clc ; Give GTE a page of direct page memory
tdc
adc #$0100
pha
pea #ENGINE_MODE_DYN_TILES+ENGINE_MODE_TWO_LAYER ; Enable Dynamic Tiles and Two Layer
lda MyUserId ; Pass the userId for memory allocation
pha
_GTEStartUp
bcc :ok3
brk $03
:ok3
rts
_Deref MAC
phb ; save caller's data bank register
pha ; push high word of handle on stack
plb ; sets B to the bank byte of the pointer
lda |$0002,x ; load the high word of the master pointer
pha ; and save it on the stack
lda |$0000,x ; load the low word of the master pointer
tax ; and return it in X
pla ; restore the high word in A
plb ; pull the handle's high word high byte off the
; stack
plb ; restore the caller's data bank register
<<<
AllocBank PushLong #0
PushLong #$10000
PushWord MyUserId
PushWord #%11000000_00011100
PushLong #0
_NewHandle
plx ; base address of the new handle
pla ; high address 00XX of the new handle (bank)
_Deref
rts
_GetVBLTicks
PushLong #0
_GetTick
pla
plx
rts
PUT ../shell/Overlay.s
PUT gen/App.TileMapBG0.s
PUT gen/App.TileSetAnim.s

View File

@ -7,31 +7,9 @@
; Segment #1 -- Main execution block
ASM App.Main.s
DS 0 ; Number of bytes of 0's to add at the end of the Segment
KND #$1100 ; Type and Attributes ($11=Static+Bank Relative,$00=Code)
ALI None ; Boundary Alignment (None)
SNA Main
; Segment #2 -- Core GTE Code
ASM ..\..\src\Core.s
SNA Core
; Segment #3 -- 64KB Tile Memory
; Segment #2 -- Tileset
ASM gen\App.TileSet.s
DS 0
KND #$1001 ; Type and Attributes ($11=Static+Bank Relative,$01=Data)
SNA Tiles
; Segment #4 -- 64KB Sprite Plane Data
ASM SprData.s
KND #$1001 ; Type and Attributes ($11=Static+Bank Relative,$01=Data)
SNA SPRDATA
; Segment #5 -- 64KB Sprite Mask Data
ASM SprMask.s
KND #$1001 ; Type and Attributes ($11=Static+Bank Relative,$01=Data)
SNA SPRMASK
SNA TSET

View File

@ -1 +1,2 @@
GTEZelda=Type(B3),AuxType(0000),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)
GTETestSprites=Type(B3),AuxType(0000),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)

View File

@ -14,24 +14,24 @@
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,185,186,186,187,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,153,154,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,155,156,157,158,33,33,33,33,33,33,33,33,33,33,33,33,153,154,153,154,153,154,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,153,154,153,154,153,154,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,59,60,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,185,186,186,187,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,185,186,186,186,186,186,186,187,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,185,186,186,186,186,186,186,187,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,55,56,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,155,156,157,158,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,155,156,157,156,157,156,157,158,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,155,156,157,156,157,156,157,158,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,55,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,137,138,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,155,156,157,158,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,155,156,157,156,157,156,157,158,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,155,156,157,156,157,156,157,158,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,55,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,169,170,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,159,160,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,33,33,33,33,33,33,188,188,188,188,188,188,159,160,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,159,160,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,188,188,188,188,188,188,33,33,33,33,33,33,33,33,188,188,159,160,159,160,188,188,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,191,192,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,33,33,33,33,33,33,26,26,26,26,26,26,191,192,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,191,192,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,26,26,26,26,26,26,33,33,33,33,33,33,33,33,26,26,191,192,191,192,26,26,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,7,8,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,7,8,7,8,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,31,32,31,32,31,32,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,5,6,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,26,26,26,26,26,26,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,7,8,7,8,7,8,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,26,26,26,26,26,26,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,137,138,137,138,137,138,137,138,137,138,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,5,6,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,26,26,26,26,26,26,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,169,170,169,170,169,170,169,170,169,170,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,7,8,7,8,7,8,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,26,26,26,26,26,26,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,159,160,33,33,33,33,33,33,188,188,159,160,188,188,159,160,188,188,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,11,12,13,14,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,11,12,13,14,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,188,188,159,160,188,188,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,188,188,33,33,33,33,33,33,33,33,33,33,188,188,188,188,33,33,33,33,33,33,33,33,159,160,33,33,33,33,159,160,33,33,33,33,159,160,33,33,33,33,33,33,33,33,33,33,188,188,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,188,188,188,188,33,33,33,33,33,33,33,33,33,33,33,33,5,6,33,33,33,33,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,33,33,33,33,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,188,188,188,188,159,160,188,188,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,5,6,5,6,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,26,64,26,26,64,26,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,191,192,33,33,33,33,33,33,26,26,191,192,26,26,191,192,26,26,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,15,16,17,18,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,15,16,17,18,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,26,26,191,192,26,26,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,26,26,33,33,33,33,33,33,33,33,33,33,26,26,26,26,33,33,33,33,33,33,33,33,191,192,33,33,33,33,191,192,33,33,33,33,191,192,33,33,33,33,33,33,33,33,33,33,26,26,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,26,26,26,26,33,33,33,33,33,33,33,33,33,33,33,33,7,8,33,33,33,33,7,8,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,33,33,33,33,7,8,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,26,26,26,26,191,192,26,26,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,7,8,7,8,7,8,7,8,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,33,33,26,64,26,26,64,26,33,33,33,33,
33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,11,12,13,14,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,33,33,33,33,5,6,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,33,33,33,33,5,6,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,5,6,5,6,5,6,5,6,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,9,10,33,33,33,33,33,33,31,32,24,25,24,25,24,25,31,32,33,33,
33,33,33,33,49,50,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,15,16,17,18,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,49,50,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,49,50,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,33,33,33,33,7,8,7,8,33,33,33,33,33,33,33,33,49,50,33,33,33,33,33,33,7,8,7,8,7,8,33,33,33,33,7,8,7,8,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,7,8,7,8,7,8,7,8,7,8,33,33,33,33,33,33,33,33,49,50,33,33,33,33,33,33,9,10,33,33,33,33,33,33,26,26,26,26,57,58,26,26,26,26,33,33,
33,33,33,48,21,54,51,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,11,12,13,14,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,48,21,54,51,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,48,21,54,51,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,33,33,33,33,5,6,5,6,5,6,33,33,33,33,33,48,21,54,51,33,33,33,5,6,5,6,5,6,5,6,33,33,33,33,5,6,5,6,5,6,33,33,33,33,33,33,33,33,33,33,11,12,13,14,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,11,12,13,14,33,33,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,33,33,33,33,33,33,33,48,21,54,51,33,33,33,33,33,9,10,33,33,33,33,33,33,26,26,26,26,64,64,26,26,26,26,33,33,
33,33,48,21,21,21,21,51,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,49,2147483697,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,15,16,17,18,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,48,21,21,21,21,51,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,49,50,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,48,21,21,21,21,51,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,49,50,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,7,8,33,33,33,33,7,8,7,8,7,8,33,33,33,33,48,21,21,21,21,51,33,33,7,8,7,8,7,8,7,8,33,33,33,33,7,8,7,8,7,8,33,33,33,33,33,33,49,50,33,33,15,16,17,18,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,15,16,17,18,33,33,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,33,33,33,33,33,33,48,21,21,21,21,51,33,33,33,33,9,10,33,33,33,33,33,33,26,26,26,26,64,64,26,26,26,26,33,33,
33,48,21,54,21,21,54,21,51,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,34,35,34,35,34,35,33,33,33,48,21,54,2147483696,33,33,87,86,87,86,87,86,87,86,33,34,35,33,33,87,86,87,86,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,34,35,34,35,33,33,33,33,19,20,21,22,33,48,21,54,21,21,54,21,51,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,34,35,34,35,34,35,33,33,33,48,21,54,51,33,33,33,33,33,33,33,33,33,33,33,34,35,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,34,35,34,35,33,33,33,33,33,33,33,33,33,48,21,54,21,21,54,21,51,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,34,35,34,35,34,35,33,33,33,48,21,54,51,33,33,33,33,33,33,33,33,33,33,33,34,35,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,5,6,34,35,34,35,5,6,5,6,5,6,5,6,33,48,21,54,21,21,54,21,5,6,5,6,5,6,5,6,5,6,33,33,33,33,5,6,5,6,5,6,5,6,33,33,33,48,21,54,51,33,19,20,21,22,33,33,33,33,0,33,34,35,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,33,33,33,33,33,48,21,54,21,21,54,21,51,33,33,33,5,6,33,33,33,33,33,33,26,26,26,26,64,64,26,26,26,26,33,33,
48,21,21,21,21,21,21,21,21,51,33,33,33,33,33,33,33,33,33,33,33,33,33,36,37,37,37,37,37,37,38,33,48,21,21,21,21,2147483696,33,119,118,119,118,119,118,119,118,36,37,37,38,33,119,118,119,118,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,36,37,37,37,37,38,33,33,33,19,20,21,22,48,21,21,21,21,21,21,21,21,51,33,33,33,33,33,33,33,33,19,20,21,22,33,36,37,37,37,37,37,37,38,33,48,21,21,21,21,51,33,33,33,33,33,33,33,33,33,36,37,37,38,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,36,37,37,37,37,38,33,33,33,33,33,33,33,48,21,21,21,21,21,21,21,21,51,33,33,33,33,33,33,33,33,33,33,33,33,33,36,37,37,37,37,37,37,38,33,48,21,21,21,21,51,33,33,33,33,33,33,33,33,33,36,37,37,38,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,7,8,7,8,37,37,37,37,7,8,7,8,7,8,7,8,48,21,21,21,21,21,21,21,7,8,7,8,7,8,7,8,7,8,33,33,33,33,7,8,7,8,7,8,7,8,38,33,48,21,21,21,21,51,19,20,21,22,33,33,33,33,0,36,37,37,38,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,33,33,33,33,48,21,21,21,21,21,21,21,21,51,33,33,7,8,33,33,33,33,33,33,26,26,26,26,64,64,26,26,26,26,38,33,
33,48,21,54,21,21,54,21,51,33,137,138,33,33,33,33,33,33,33,33,33,33,33,33,34,35,34,35,34,35,33,33,33,48,21,54,2147483696,33,33,87,86,87,86,87,86,87,86,33,34,35,33,33,87,86,87,86,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,33,34,35,34,35,33,33,33,33,19,20,21,22,33,48,21,54,21,21,54,21,51,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,34,35,34,35,34,35,33,33,33,48,21,54,51,33,33,33,33,33,33,33,33,33,33,33,34,35,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,34,35,34,35,33,33,33,33,33,33,33,33,33,48,21,54,21,21,54,21,51,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,34,35,34,35,34,35,33,33,33,48,21,54,51,33,33,33,33,33,33,33,33,33,33,33,34,35,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,5,6,5,6,5,6,5,6,34,35,34,35,5,6,5,6,5,6,5,6,33,48,21,54,21,21,54,21,5,6,5,6,5,6,5,6,5,6,33,33,33,33,5,6,5,6,5,6,5,6,33,33,33,48,21,54,51,33,19,20,21,22,33,33,33,33,0,33,34,35,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,33,33,33,33,33,48,21,54,21,21,54,21,51,33,33,33,5,6,33,33,33,33,33,33,26,26,26,26,64,64,26,26,26,26,33,33,
48,21,21,21,21,21,21,21,21,51,169,170,33,33,33,33,33,33,33,33,33,33,33,36,37,37,37,37,37,37,38,33,48,21,21,21,21,2147483696,33,119,118,119,118,119,118,119,118,36,37,37,38,33,119,118,119,118,19,20,21,22,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,33,33,33,36,37,37,37,37,38,33,33,33,19,20,21,22,48,21,21,21,21,21,21,21,21,51,33,33,33,33,33,33,33,33,19,20,21,22,33,36,37,37,37,37,37,37,38,33,48,21,21,21,21,51,33,33,33,33,33,33,33,33,33,36,37,37,38,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,36,37,37,37,37,38,33,33,33,33,33,33,33,48,21,21,21,21,21,21,21,21,51,33,33,33,33,33,33,33,33,33,33,33,33,33,36,37,37,37,37,37,37,38,33,48,21,21,21,21,51,33,33,33,33,33,33,33,33,33,36,37,37,38,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,7,8,7,8,7,8,7,8,37,37,37,37,7,8,7,8,7,8,7,8,48,21,21,21,21,21,21,21,7,8,7,8,7,8,7,8,7,8,33,33,33,33,7,8,7,8,7,8,7,8,38,33,48,21,21,21,21,51,19,20,21,22,33,33,33,33,0,36,37,37,38,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,33,19,20,21,22,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,33,33,33,33,48,21,21,21,21,21,21,21,21,51,33,33,7,8,33,33,33,33,33,33,26,26,26,26,64,64,26,26,26,26,38,33,
1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,33,33,33,33,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,33,33,33,33,33,33,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,33,33,33,33,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,
3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,33,33,33,33,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,33,33,33,33,33,33,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,33,33,33,33,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,3,4,
1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,33,33,33,33,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,33,33,33,33,33,33,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,33,33,33,33,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,

View File

@ -14,6 +14,7 @@ REM Cadius does not overwrite files, so clear the root folder first
REM Now copy files and folders as needed
%CADIUS% ADDFILE %IMAGE% %FOLDER% .\GTETestSprites
%CADIUS% ADDFILE %IMAGE% %FOLDER% ..\..\src\Tool160
REM Copy in the image assets
%CADIUS% ADDFILE %IMAGE% %FOLDER% .\assets\mario1.c1

View File

@ -5,14 +5,19 @@
BG0SetUp
lda #416
sta TileMapWidth
lda #30
sta TileMapHeight
lda #App_TileMapBG0
sta TileMapPtr
lda #^App_TileMapBG0
sta TileMapPtr+2
pea #416
pea #30
pea #^App_TileMapBG0
pea #App_TileMapBG0
_GTESetBG0TileMapInfo
; lda #416
; sta TileMapWidth
; lda #30
; sta TileMapHeight
; lda #App_TileMapBG0
; sta TileMapPtr
; lda #^App_TileMapBG0
; sta TileMapPtr+2
rts
App_TileMapBG0

View File

@ -2,7 +2,7 @@
; Palette:
; $068F,$0EDA,$0000,$0F0F,$0BF1,$00A0,$0EEE,$0456,$0FA4,$0F59,$0E30,$01CE,$02E3,$0870,$0F93,$0FD7
; Converting to BG0 format...
tiledata ENT
TSet ENT
; Reserved space (tile 0 is special...
ds 128

View File

@ -1,52 +1,40 @@
TileAnimInit ENT
TileAnimInit
pea #137
pea #0
_GTECopyTileToDynamic
pea #138
pea #1
_GTECopyTileToDynamic
pea #169
pea #2
_GTECopyTileToDynamic
pea #170
pea #3
_GTECopyTileToDynamic
ldx #137
ldy #0
jsl CopyTileToDyn
ldx #138
ldy #1
jsl CopyTileToDyn
ldx #169
ldy #2
jsl CopyTileToDyn
ldx #170
ldy #3
jsl CopyTileToDyn
lda #TileAnim_136
ldx #^TileAnim_136
ldy #15
jsl StartScript
lda #TileAnim_137
ldx #^TileAnim_137
ldy #15
jsl StartScript
lda #TileAnim_168
ldx #^TileAnim_168
ldy #15
jsl StartScript
lda #TileAnim_169
ldx #^TileAnim_169
ldy #15
jsl StartScript
pea #15
pea #^TileAnim
pea #TileAnim
_GTEStartScript
rts
TileAnim_136
dw $8006,137,0,0
dw $8006,139,0,0
dw $8006,141,0,0
dw $cd06,143,0,0
TileAnim_137
dw $8006,138,1,0
dw $8006,140,1,0
dw $8006,142,1,0
dw $cd06,144,1,0
TileAnim_168
dw $8006,169,2,0
dw $8006,171,2,0
dw $8006,173,2,0
dw $cd06,175,2,0
TileAnim_169
TileAnim
dw $0006,137,0,0
dw $0006,138,1,0
dw $0006,169,2,0
dw $8006,170,3,0
dw $0006,139,0,0
dw $0006,140,1,0
dw $0006,171,2,0
dw $8006,172,3,0
dw $0006,141,0,0
dw $0006,142,1,0
dw $0006,173,2,0
dw $8006,174,3,0
dw $cd06,176,3,0
dw $0006,143,0,0
dw $0006,144,1,0
dw $0006,175,2,0
dw $cc46,176,3,0 ; STOP; JUMP(-15) -15 = $31 (6 bit) = %110001 = 1100 0100 = C4

View File

@ -15,11 +15,13 @@
"scripts": {
"test": "npm run build && build-image.bat %npm_package_config_cadius% && %npm_package_config_gsport%",
"debug": "%npm_package_config_crossrunner% GTETestSprites -Source GTETestSprites_S02_MAINSEG_Output.txt -Debug -CompatibilityLayer",
"build": "%npm_package_config_merlin32% -V %npm_package_config_macros% App.s",
"build:sys16": "%npm_package_config_merlin32% -V %npm_package_config_macros% App.s",
"build:map": "node %npm_package_config_tiled2iigs% ./assets/tiled/world_1-1.json --output-dir ./gen",
"build:map:masked": "node %npm_package_config_tiled2iigs% ./assets/tiled/world_1-1.json --force-masked --empty-tile 33 --no-gen-tiles --output-dir ./gen",
"build:tiles": "node %npm_package_config_png2iigs% ../shell/assets/tilesets/smb-256-128-4bpp.png --max-tiles 360 --as-tile-data --transparent-color FF00FF --background-color 6B8CFF > ./gen/App.TileSet.s",
"build:tiles:1bpp": "node %npm_package_config_png2iigs% ../shell/assets/tilesets/smb-256-128-1bpp.png --max-tiles 360 --as-tile-data --transparent-color FF00FF --palette 010102,00000,000000,000000,000000,000000,000000,000000,000000,000000,000000,000000,000000,000000,000000,FDFEFE > ./gen/App.TileSet.s"
"build:tiles:1bpp": "node %npm_package_config_png2iigs% ../shell/assets/tilesets/smb-256-128-1bpp.png --max-tiles 360 --as-tile-data --transparent-color FF00FF --palette 010102,00000,000000,000000,000000,000000,000000,000000,000000,000000,000000,000000,000000,000000,000000,FDFEFE > ./gen/App.TileSet.s",
"build": "npm run build:tool && npm run build:sys16",
"build:tool": "%npm_package_config_merlin32% -V %npm_package_config_macros% ../../src/Master.s"
},
"repository": {
"type": "git",

1
demos/tool/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
GTEToolDemo

654
demos/tool/App.Main.s Normal file
View File

@ -0,0 +1,654 @@
REL
DSK MAINSEG
use Locator.Macs
use Load.Macs
use Mem.Macs
use Misc.Macs
use Util.Macs
use EDS.GSOS.Macs
use GTE.Macs
mx %00
TSZelda EXT ; tileset buffer
MAX_SPRITES equ 16
ScreenX equ 0
ScreenY equ 2
Tmp0 equ 4
Tmp1 equ 6
KeyState equ 8
Selected equ 10
Flips equ 12
DTile equ 14
Tmp2 equ 16
; Typical init
phk
plb
sta MyUserId ; GS/OS passes the memory manager user ID for the application into the program
_MTStartUp ; GTE requires the miscellaneous toolset to be running
jsr GTEStartUp ; Load and install the GTE User Tool
; Initialize the graphics screen to a 256x160 playfield
pea #320
pea #200
_GTESetScreenMode
; Load a tileset
pea #^TSZelda
pea #TSZelda
_GTELoadTileSet
; Set the palette
ldx #11*2
:ploop
lda palette,x
stal $E19E00,x
dex
dex
bpl :ploop
bra sprt
palette dw $0000,$08C1,$0C41,$0F93,$0777,$0FDA,$00A0,$0000,$0D20,$0FFF,$023E
sprt
; Create stamps for the sprites we are going to use
HERO_SPRITE equ SPRITE_16X16+1
pea HERO_SPRITE ; sprint id
pea VBUFF_SPRITE_START ; vbuff address
_GTECreateSpriteStamp
; Create sprites
stz Tmp0
stz Tmp1
ldx Tmp0
:sloop
pea HERO_SPRITE ; sprite id
lda PlayerX,x
pha
lda PlayerY,x
pha
pei Tmp1
_GTEAddSprite
pei Tmp1 ; update the sprite in this slot
pea $0000 ; with these flags (h/v flip)
pea VBUFF_SPRITE_START ; and use this stamp
_GTEUpdateSprite
inc Tmp1
ldx Tmp0
inx
inx
stx Tmp0
cpx #MAX_SPRITES*2
bcc :sloop
; Manually fill in the 41x26 tiles of the TileStore with a test pattern of trees
; lda #TILE_DYN_BIT+TILE_PRIORITY_BIT+0 ; fill the screen the the dynamic tile slot 0
lda #TILE_DYN_BIT+0 ; fill the screen the the dynamic tile slot 0
jsr _fillTileStore
; brl :no_trees
ldx #0
ldy #0
jsr _drawTree
ldx #3
ldy #0
jsr _drawTreeH
ldx #0
ldy #3
jsr _drawTreeV
ldx #3
ldy #3
jsr _drawTreeHV
ldx #9
ldy #0
jsr _drawTree
ldx #9
ldy #3
jsr _drawTree
ldx #12
ldy #0
jsr _drawTree
ldx #12
ldy #3
jsr _drawTree
ldx #6
ldy #0
jsr _drawTreeFront
ldx #6
ldy #3
jsr _drawTreeFront
ldx #6
ldy #6
jsr _drawTreeFront
ldx #3
ldy #6
jsr _drawTreeFront
ldx #0
ldy #6
jsr _drawTreeFront
:no_trees
; Set up the dynamic tile
lda #65
sta DTile
pei DTile
pea $0000
_GTECopyTileToDynamic ; Copy DTile into the first dynamic tile slot
; Initialize the frame counter
stz FrameCount
; Set the screen coordinates
lda #128
sta ScreenX
lda #128
sta ScreenY
stz Selected
stz Flips
; Very simple actions
:evt_loop
pha ; space for result, with pattern
_GTEReadControl
pla
and #$00FF
cmp #'q'
bne :2
brl :exit
:2
; cmp KeyState
; beq :evt_loop
; sta KeyState
; cmp #0
; beq :evt_loop
; cmp #' '
; bne :evt_loop ; only advance one frame at a time
; brl :next
:3
cmp #'1'
bcc :3a
cmp #'9'
bcs :3a
sec
sbc #'1'
asl
sta Selected
brl :next
:3a
cmp #'r'
bne :3b
lda Flips
clc
adc #SPRITE_HFLIP
and #SPRITE_VFLIP+SPRITE_HFLIP
sta Flips
pei Selected ; update the sprite in this slot
pei Flips ; with these flags (h/v flip)
pea VBUFF_SPRITE_START ; and use this stamp
_GTEUpdateSprite
:3b
cmp #'x'
bne :3d
ldx Selected
lda PlayerX,x
clc
adc PlayerU,x
sta PlayerX,x
lda PlayerY,x
clc
adc PlayerV,x
sta PlayerY,x
brl :next
:3d
cmp #'z'
bne :3e
ldx Selected
lda PlayerX,x
sec
sbc PlayerU,x
sta PlayerX,x
lda PlayerY,x
sec
sbc PlayerV,x
sta PlayerY,x
brl :next
:3e
cmp #'s'
bne :4
ldx Selected
inc PlayerY,x
brl :next
:4
cmp #'w'
bne :5
ldx Selected
dec PlayerY,x
brl :next
:5
cmp #'d'
bne :6
ldx Selected
inc PlayerX,x
brl :next
:6
cmp #'a'
bne :7
ldx Selected
dec PlayerX,x
brl :next
:7
cmp #$15 ; left = $08, right = $15, up = $0B, down = $0A
bne :8
inc ScreenX
bra :next
:8 cmp #$08
bne :9
dec ScreenX
brl :next
:9 cmp #$0B
bne :10
inc ScreenY
brl :next
:10 cmp #$0A
bne :11
dec ScreenY
brl :next
:11 cmp #'y'
bne :12
lda DTile
inc
and #$007F
sta DTile
pha
pea $0000
_GTECopyTileToDynamic
brl :next
:12 cmp #'f'
bne :13
pea $0000
_GTEFillTileStore
brl :next
:13 cmp #'m'
bne :next
_GTERefresh
:next
; inc ScreenX
pei ScreenX
pei ScreenY
_GTESetBG0Origin
; brl no_animate
stz Tmp0
stz Tmp1
ldx Tmp0
loopX
lda PlayerX,x
clc
adc PlayerU,x
sta PlayerX,x
bpl is_posx
cmp #-15
bcs do_y
lda PlayerU,x
eor #$FFFF
inc
sta PlayerU,x
bra do_y
is_posx cmp #128
bcc do_y
lda PlayerU,x
eor #$FFFF
inc
sta PlayerU,x
do_y
lda PlayerY,x
clc
adc PlayerV,x
sta PlayerY,x
bpl is_posy
cmp #-15
bcs do_z
lda PlayerV,x
eor #$FFFF
inc
sta PlayerV,x
bra do_z
is_posy cmp #160
bcc do_z
lda PlayerV,x
eor #$FFFF
inc
sta PlayerV,x
do_z
inc Tmp1
ldx Tmp0
inx
inx
stx Tmp0
cpx #MAX_SPRITES*2
bcc loopX
no_animate
stz Tmp0
stz Tmp1
ldx Tmp0
loopY
pei Tmp1
lda PlayerX,x
pha
lda PlayerY,x
pha
_GTEMoveSprite
inc Tmp1
ldx Tmp0
inx
inx
stx Tmp0
cpx #MAX_SPRITES*2
bcc loopY
_GTERender
inc FrameCount
; Debug stuff
pha
_GTEGetSeconds
pla
cmp LastSecond
beq :no_fps
sta LastSecond
lda FrameCount
ldx #0
ldy #$FFFF
jsr DrawWord
stz FrameCount
:no_fps
; tdc
; ldx #160*32
; jsr DrawWord
brl :evt_loop
; Shut down everything
:exit
_GTEShutDown
_QuitGS qtRec
qtRec adrl $0000
da $00
; Array of sprite positions and velocities
PlayerX dw 8,14,29,34,45,67,81,83,92,101,39,22,7,74,111,9
PlayerY dw 72,24,13,56,35,72,23,8,93,123,134,87,143,14,46,65
PlayerU dw 1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4
PlayerV dw 1,1,1,1,2,2,2,4,3,3,3,3,4,4,4,4
; Load the GTE User Tool and install it
GTEStartUp
pea $0000
_LoaderStatus
pla
pea $0000
pea $0000
pea $0000
pea $0000
pea $0000 ; result space
lda MyUserId
pha
pea #^ToolPath
pea #ToolPath
pea $0001 ; do not load into special memory
_InitialLoad
bcc :ok1
brk $01
:ok1
ply
pla ; Address of the loaded tool
plx
ply
ply
pea $8000 ; User toolset
pea $00A0 ; Set the tool set number
phx
pha ; Address of function pointer table
_SetTSPtr
bcc :ok2
brk $02
:ok2
clc ; Give GTE a page of direct page memory
tdc
adc #$0100
pha
pea #ENGINE_MODE_DYN_TILES+ENGINE_MODE_TWO_LAYER ; Enable Dynamic Tiles and Two Layer
lda MyUserId ; Pass the userId for memory allocation
pha
_GTEStartUp
bcc :ok3
brk $03
:ok3
rts
_fillTileStore
sta Tmp2
stz Tmp0
:oloop
stz Tmp1
:iloop
pei Tmp1
pei Tmp0
pei Tmp2
_GTESetTile
lda Tmp2
eor #TILE_PRIORITY_BIT
sta Tmp2
lda Tmp1
inc
sta Tmp1
cmp #41
bcc :iloop
lda Tmp0
inc
sta Tmp0
cmp #26
bcc :oloop
rts
; Tile 65 Tile 66
; Tile 97 Tile 98
_drawTreeFront
phx
phy
pea #65+TILE_PRIORITY_BIT
inx
phx
phy
pea #66+TILE_PRIORITY_BIT
iny
phx
phy
pea #98+TILE_PRIORITY_BIT
dex
phx
phy
pea #97+TILE_PRIORITY_BIT
_GTESetTile
_GTESetTile
_GTESetTile
_GTESetTile
rts
_drawTree
phx
phy
pea #65
inx
phx
phy
pea #66
iny
phx
phy
pea #98
dex
phx
phy
pea #97
_GTESetTile
_GTESetTile
_GTESetTile
_GTESetTile
rts
_drawTreeH
phx
phy
pea #66+TILE_HFLIP_BIT
inx
phx
phy
pea #65+TILE_HFLIP_BIT
iny
phx
phy
pea #97+TILE_HFLIP_BIT
dex
phx
phy
pea #98+TILE_HFLIP_BIT
_GTESetTile
_GTESetTile
_GTESetTile
_GTESetTile
rts
_drawTreeV
phx
phy
pea #97+TILE_VFLIP_BIT
inx
phx
phy
pea #98+TILE_VFLIP_BIT
iny
phx
phy
pea #66+TILE_VFLIP_BIT
dex
phx
phy
pea #65+TILE_VFLIP_BIT
_GTESetTile
_GTESetTile
_GTESetTile
_GTESetTile
rts
_drawTreeHV
phx
phy
pea #98+TILE_VFLIP_BIT+TILE_HFLIP_BIT
inx
phx
phy
pea #97+TILE_VFLIP_BIT+TILE_HFLIP_BIT
iny
phx
phy
pea #65+TILE_VFLIP_BIT+TILE_HFLIP_BIT
dex
phx
phy
pea #66+TILE_VFLIP_BIT+TILE_HFLIP_BIT
_GTESetTile
_GTESetTile
_GTESetTile
_GTESetTile
rts
MyUserId ds 2
ToolPath str '1/Tool160'
FrameCount ds 2
LastSecond dw 0
PUT App.Msg.s
PUT font.s

105
demos/tool/App.Msg.s Normal file
View File

@ -0,0 +1,105 @@
HexToChar dfb '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
; Convert a byte (Acc) into a string and store at (Y)
ByteToString and #$00FF
sep #$20
pha
lsr
lsr
lsr
lsr
and #$0F
tax
ldal HexToChar,x
sta: $0000,y
pla
and #$0F
tax
ldal HexToChar,x
sta: $0001,y
rep #$20
rts
; Convert a word (Acc) into a hexadecimal string and store at (Y)
WordToString pha
bra Addr2ToString
; Pass in Acc = High, X = low
Addr3ToString phx
jsr ByteToString
iny
iny
lda 1,s
Addr2ToString xba
jsr ByteToString
iny
iny
pla
jsr ByteToString
rts
; A=Value
; X=Screen offset
DrawWord phx ; Save register value
phy
ldy #WordBuff+1
jsr WordToString
ply
plx
lda #WordBuff
jsr DrawString
rts
WordBuff str '0000'
Addr3Buff str '000000' ; str adds leading length byte

15
demos/tool/App.s Normal file
View File

@ -0,0 +1,15 @@
; IIgs Sprite Testbed
TYP $B3 ; S16 file
DSK GTEToolDemo
XPL
; Segment #1 -- Main execution block
ASM App.Main.s
SNA Main
; Segment #2 -- Tileset
ASM Zelda.TileSet.s
SNA TSET

13688
demos/tool/Zelda.TileSet.s Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
GTEToolDemo=Type(B3),AuxType(0000),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)

View File

@ -0,0 +1,19 @@
echo off
REM Copy all of the assets into the ProDOS image for emulator testing
REM
REM Pass the path of the Cadius tool as the first argument (%1)
set CADIUS="%1"
set IMAGE="..\\..\\emu\\Target.2mg"
set FOLDER="/GTEDEV/Tool"
REM Cadius does not overwrite files, so clear the root folder first
%CADIUS% DELETEFOLDER %IMAGE% %FOLDER%
%CADIUS% CREATEFOLDER %IMAGE% %FOLDER%
REM Now copy files and folders as needed
%CADIUS% ADDFILE %IMAGE% %FOLDER% .\GTEToolDemo
%CADIUS% ADDFILE %IMAGE% %FOLDER% ..\..\src\Tool160
REM Copy in the image assets

668
demos/tool/font.s Normal file
View File

@ -0,0 +1,668 @@
****************************************
* FONT ENGINE (v3?) *
* *
* Dagen Brock <dagenbrock@gmail.com> *
* 2013-07-20 *
****************************************
* A= ptr to string preceded by length *
* X= screen location *
****************************************
; each char:
; draw char at loc
; update loc
; see if length hit - no? back to draw char
rel
mx %00
]F_Length ds 2 ;length of string (only one byte currently used)
]F_CharIdx ds 2 ;index of current character
]F_CurrentPos ds 2 ;current top left char position
]F_StrPtr equ $01 ;pointer to string (including length byte) / DP
]F_StrClr equ $03
DrawString
pha ; local variable space
pha
tsc
phd
tcd
; sta ]F_StrPtr ; (done in pha init above) store at dp 0 ($00) for indirect loads
stx ]F_CurrentPos
sty ]F_StrClr
stz ]F_CharIdx
lda (]F_StrPtr)
and #$00ff ;strip off first char (len is only one byte)
sta ]F_Length ;get our length byte
NextChar lda ]F_CharIdx
cmp ]F_Length
bne :notDone
ldy ]F_StrClr ;restore the color pattern
pld
pla
pla
rts ;DONE! Return to caller
:notDone inc ]F_CharIdx
ldy ]F_CharIdx
lda (]F_StrPtr),y ;get next char!
and #$00FF ;mask high byte
sec
sbc #' ' ;our table starts with space ' '
asl ;*2
tay
ldx ]F_CurrentPos
jsr :drawChar
inc ]F_CurrentPos ;compare to addition time (?)
inc ]F_CurrentPos
inc ]F_CurrentPos
inc ]F_CurrentPos ;update screen pos (2 words=8 pixels)
bra NextChar
;x = TopLeft screen pos
;y = char table offset
:drawChar lda FontTable,y ;get real address of char data
sec
sbc #FontData ;pivot offset - now a is offset of fontdata
tay ;so we'll index with that
lda FontData,y
and ]F_StrClr
stal $E12000,x
lda FontData+2,y
and ]F_StrClr
stal $E12000+2,x
lda FontData+4,y
and ]F_StrClr
stal $E12000+160,x
lda FontData+6,y
and ]F_StrClr
stal $E12000+160+2,x
lda FontData+8,y
and ]F_StrClr
stal {$E12000+160*2},x
lda FontData+10,y
and ]F_StrClr
stal {$E12000+160*2+2},x
lda FontData+12,y
and ]F_StrClr
stal {$E12000+160*3},x
lda FontData+14,y
and ]F_StrClr
stal {$E12000+160*3+2},x
lda FontData+16,y
and ]F_StrClr
stal {$E12000+160*4},x
lda FontData+18,y
and ]F_StrClr
stal {$E12000+160*4+2},x
lda FontData+20,y
and ]F_StrClr
stal {$E12000+160*5},x
lda FontData+22,y
and ]F_StrClr
stal {$E12000+160*5+2},x
rts
FontTable dw s_Space
dw s_Exclaim
dw s_Quote
dw s_Number
dw s_Dollar
dw s_Percent
dw s_Amper
dw s_Single
dw s_OpenParen
dw s_CloseParen
dw s_Asterix
dw s_Plus
dw s_Comma
dw s_Minus
dw s_Period
dw s_Slash
dw s_N0
dw s_N1
dw s_N2
dw s_N3
dw s_N4
dw s_N5
dw s_N6
dw s_N7
dw s_N8
dw s_N9
dw s_Colon
dw s_Semi
dw s_LAngle
dw s_Equal
dw s_RAngle
dw s_Question
dw s_At
dw s_A
dw s_B
dw s_C
dw s_D
dw s_E
dw s_F
dw s_G
dw s_H
dw s_I
dw s_J
dw s_K
dw s_L
dw s_M
dw s_N
dw s_O
dw s_P
dw s_Q
dw s_R
dw s_S
dw s_T
dw s_U
dw s_V
dw s_W
dw s_X
dw s_Y
dw s_Z
dw s_LBracket
dw s_BackSlash
dw s_RBracket
dw s_Carot
dw s_UnderLine
FontData = *
s_Space hex 00000000
hex 00000000
hex 00000000
hex 00000000
hex 00000000
hex 00000000
s_Exclaim hex 000FF000
hex 000FF000
hex 000FF000
hex 000FF000
hex 00000000
hex 000FF000
s_Quote hex 0FF00FF0
hex 00F000F0
hex 00000000
hex 00000000
hex 00000000
hex 00000000
s_Number hex 00000000
hex 00F00F00
hex 0FFFFFF0
hex 00F00F00
hex 0FFFFFF0
hex 00F00F00
s_Dollar hex 000F0F00
hex 00FFFFF0
hex 0F0F0F00
hex 00FFFF00
hex 000F0FF0
hex 0FFFFF00
s_Percent hex 0FF000F0
hex 00000F00
hex 0000F000
hex 000F0000
hex 00F00000
hex 0F000FF0
s_Amper hex 000FF000
hex 00F00F00
hex 0F00F000
hex 00F000F0
hex 0F0FFF00
hex 00F0F000
s_Single hex 000FF000
hex 0000F000
hex 00000000
hex 00000000
hex 00000000
hex 00000000
s_OpenParen hex 000FF000
hex 00FF0000
hex 0FF00000
hex 0FF00000
hex 00FF0000
hex 000FF000
s_CloseParen hex 000FF000
hex 0000FF00
hex 00000FF0
hex 00000FF0
hex 0000FF00
hex 000FF000
s_Asterix hex 00000000
hex 00F0F0F0
hex 000FFF00
hex 00FFFFF0
hex 000FFF00
hex 00F0F0F0
s_Plus hex 000F0000
hex 000F0000
hex 0FFFFF00
hex 000F0000
hex 000F0000
hex 00000000
s_Comma hex 00000000
hex 00000000
hex 00000000
hex 00000000
hex 0000FF00
hex 0000F000
s_Minus hex 00000000
hex 00000000
hex 0FFFFF00
hex 00000000
hex 00000000
hex 00000000
s_Period hex 00000000
hex 00000000
hex 00000000
hex 00000000
hex 0000FF00
hex 0000FF00
s_Slash hex 000000F0
hex 00000F00
hex 0000F000
hex 000F0000
hex 00F00000
hex 0F000000
s_N0 hex 00FFFF00
hex 0F000FF0
hex 0F00F0F0
hex 0F0F00F0
hex 0FF000F0
hex 00FFFF00
s_N1 hex 000F0000
hex 00FF0000
hex 000F0000
hex 000F0000
hex 000F0000
hex 00FFF000
s_N2 hex 00FFFF00
hex 0F0000F0
hex 00000F00
hex 000FF000
hex 00F00000
hex 0FFFFFF0
s_N3 hex 00FFFF00
hex 000000F0
hex 000FFF00
hex 000000F0
hex 000000F0
hex 00FFFF00
s_N4 hex 0000FF00
hex 000F0F00
hex 00F00F00
hex 0FFFFFF0
hex 00000F00
hex 00000F00
s_N5 hex 0FFFFFF0
hex 0F000000
hex 0FFFFF00
hex 000000F0
hex 0F0000F0
hex 00FFFF00
s_N6 hex 000FFF00
hex 00F00000
hex 0F000000
hex 0FFFFF00
hex 0F0000F0
hex 00FFFFF0
s_N7 hex 0FFFFFF0
hex 000000F0
hex 00000F00
hex 0000F000
hex 000F0000
hex 000F0000
s_N8 hex 00FFFF00
hex 0F0000F0
hex 00FFFF00
hex 0F0000F0
hex 0F0000F0
hex 00FFFF00
s_N9 hex 00FFFF00
hex 0F0000F0
hex 00FFFF00
hex 0000F000
hex 000F0000
hex 00F00000
s_Colon hex 000FF000
hex 000FF000
hex 00000000
hex 000FF000
hex 000FF000
hex 00000000
s_Semi hex 00000000
hex 000FF000
hex 000FF000
hex 00000000
hex 000FF000
hex 000F0000
s_LAngle hex 0000F000
hex 000F0000
hex 00F00000
hex 000F0000
hex 0000F000
hex 00000000
s_Equal hex 00000000
hex 00000000
hex 0FFFFF00
hex 00000000
hex 0FFFFF00
hex 00000000
s_RAngle hex 0000F000
hex 00000F00
hex 000000F0
hex 00000F00
hex 0000F000
hex 00000000
s_Question hex 00FFF000
hex 0F000F00
hex 00000F00
hex 000FF000
hex 00000000
hex 000FF000
s_At hex 00FFFF00
hex 0F0000F0
hex 0F00F0F0
hex 0FFFF0F0
hex 000000F0
hex 0FFFFF00
s_A hex 000FF000
hex 00F00F00
hex 0F0000F0
hex 0FFFFFF0
hex 0F0000F0
hex 0F0000F0
s_B hex 0FFFFF00
hex 0F0000F0
hex 0FFFFF00
hex 0F0000F0
hex 0F0000F0
hex 0FFFFF00
s_C hex 00FFFFF0
hex 0F000000
hex 0F000000
hex 0F000000
hex 0F000000
hex 00FFFFF0
s_D hex 0FFFFF00
hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 0FFFFF00
s_E hex 0FFFFFF0
hex 0F000000
hex 0FFFF000
hex 0F000000
hex 0F000000
hex 0FFFFFF0
s_F hex 0FFFFFF0
hex 0F000000
hex 0FFFF000
hex 0F000000
hex 0F000000
hex 0F000000
s_G hex 00FFFFF0
hex 0F000000
hex 0F000000
hex 0F00FFF0
hex 0F0000F0
hex 00FFFF00
s_H hex 0F0000F0
hex 0F0000F0
hex 0FFFFFF0
hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
s_I hex 0FFFFF00
hex 000F0000
hex 000F0000
hex 000F0000
hex 000F0000
hex 0FFFFF00
s_J hex 000000F0
hex 000000F0
hex 000000F0
hex 0F0000F0
hex 0F0000F0
hex 00FFFF00
s_K hex 0F000F00
hex 0F00F000
hex 0FFF0000
hex 0F00F000
hex 0F000F00
hex 0F000F00
s_L hex 0F000000
hex 0F000000
hex 0F000000
hex 0F000000
hex 0F000000
hex 0FFFFFF0
s_M hex 0F0000F0
hex 0FF00FF0
hex 0F0FF0F0
hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
s_N hex 0F0000F0
hex 0FF000F0
hex 0F0F00F0
hex 0F00F0F0
hex 0F000FF0
hex 0F0000F0
s_O hex 00FFFF00
hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 00FFFF00
s_P hex 0FFFFF00
hex 0F0000F0
hex 0FFFFF00
hex 0F000000
hex 0F000000
hex 0F000000
s_Q hex 00FFFF00
hex 0F0000F0
hex 0F0000F0
hex 0F00F0F0
hex 0F000FF0
hex 00FFFFF0
s_R hex 0FFFFF00
hex 0F0000F0
hex 0FFFFF00
hex 0F000F00
hex 0F0000F0
hex 0F0000F0
s_S hex 00FFFFF0
hex 0F000000
hex 00FFFF00
hex 000000F0
hex 000000F0
hex 0FFFFF00
s_T hex 0FFFFF00
hex 000F0000
hex 000F0000
hex 000F0000
hex 000F0000
hex 000F0000
s_U hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 00FFFF00
s_V hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 00F00F00
hex 000FF000
s_W hex 0F0000F0
hex 0F0000F0
hex 0F0000F0
hex 0F0FF0F0
hex 0FF00FF0
hex 0F0000F0
s_X hex 0F0000F0
hex 00F00F00
hex 000FF000
hex 000FF000
hex 00F00F00
hex 0F0000F0
s_Y hex F00000F0
hex 0F000F00
hex 00F0F000
hex 000F0000
hex 000F0000
hex 000F0000
s_Z hex 0FFFFFF0
hex 00000F00
hex 0000F000
hex 000F0000
hex 00F00000
hex 0FFFFFF0
s_LBracket hex 000FFF00
hex 000F0000
hex 000F0000
hex 000F0000
hex 000F0000
hex 000FFF00
s_BackSlash hex 0F000000
hex 00F00000
hex 000F0000
hex 0000F000
hex 00000F00
hex 000000F0
s_RBracket hex 00FFF000
hex 0000F000
hex 0000F000
hex 0000F000
hex 0000F000
hex 00FFF000
s_Carot hex 0000F000
hex 000F0F00
hex 00F000F0
hex 00000000
hex 00000000
hex 00000000
s_UnderLine hex 00000000
hex 00000000
hex 00000000
hex 00000000
hex 00000000
hex FFFFFFF0
s_Template hex 00000000
hex 00000000
hex 00000000
hex 00000000
hex 00000000
hex 00000000

32
demos/tool/package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "gte-toolbox-demo",
"version": "1.0.0",
"description": "A testbed for developing a toolbox wrapper around the GTE library",
"main": "index.js",
"config": {
"merlin32": "C:\\Programs\\IIgsXDev\\bin\\Merlin32-1.1.10.exe",
"cadius": "C:\\Programs\\IIgsXDev\\bin\\Cadius.exe",
"gsport": "C:\\Programs\\gsport\\gsport_0.31\\GSPort.exe",
"macros": "../../macros",
"crossrunner": "C:\\Programs\\Crossrunner\\Crossrunner.exe"
},
"scripts": {
"test": "npm run build && build-image.bat %npm_package_config_cadius% && %npm_package_config_gsport%",
"debug": "%npm_package_config_crossrunner% GTEToolDemo -Source MAINSEG_Output.txt -Debug -CompatibilityLayer -Map App.s",
"build": "npm run build:tool && npm run build:sys16",
"build:sys16": "%npm_package_config_merlin32% -V %npm_package_config_macros% App.s",
"build:tool": "%npm_package_config_merlin32% -V %npm_package_config_macros% ../../src/Master.s"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lscharen/iigs-game-engine.git"
},
"author": "Lucas Scharenbroich",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/lscharen/iigs-game-engine/issues"
},
"homepage": "https://github.com/lscharen/iigs-game-engine#readme",
"devDependencies": {
}
}

1
demos/zelda/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
GTEZelda

View File

@ -69,9 +69,9 @@
"dev": true
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"nan": {

View File

@ -325,3 +325,57 @@ transparent
sta: ]3+1,y
next
eom
; Large code blocks that can be used in sprite blitters
; ]1: line number
OneSpriteToCodeField mac
lda blttmp+{]1*4}
andl spritemask+{]1*SPRITE_PLANE_SPAN},x
oral spritedata+{]1*SPRITE_PLANE_SPAN},x
sta: $0004+{]1*$1000},y
lda blttmp+{]1*4}+2
andl spritemask+{]1*SPRITE_PLANE_SPAN}+2,x
oral spritedata+{]1*SPRITE_PLANE_SPAN}+2,x
sta: $0001+{]1*$1000},y
eom
TwoSpritesToCodeField mac
ldy #{]1*SPRITE_PLANE_SPAN}
lda blttmp+{]1*4}
andl [spritemask_1],y
oral [spritedata_1],y
andl [spritemask_0],y
oral [spritedata_0],y
sta: $0004+{]1*$1000},x
ldy #{]1*SPRITE_PLANE_SPAN}+2
lda blttmp+{]1*4}+2
andl [spritemask_1],y
oral [spritedata_1],y
andl [spritemask_0],y
oral [spritedata_0],y
sta: $0001+{]1*$1000},x
eom
ThreeSpritesToCodeField mac
ldy #{]1*SPRITE_PLANE_SPAN}
lda blttmp+{]1*4}
andl [spritemask_2],y
oral [spritedata_2],y
andl [spritemask_1],y
oral [spritedata_1],y
andl [spritemask_0],y
oral [spritedata_0],y
sta: $0004+{]1*$1000},x
ldy #{]1*SPRITE_PLANE_SPAN}+2
lda blttmp+{]1*4}+2
andl [spritemask_2],y
oral [spritedata_2],y
andl [spritemask_1],y
oral [spritedata_1],y
andl [spritemask_0],y
oral [spritedata_0],y
sta: $0001+{]1*$1000},x
eom

160
macros/GTE.Macs.s Normal file
View File

@ -0,0 +1,160 @@
* Generic Tile Engine Macros
* by Lucas Scharenbroich
GTEToolNum equ $A0
_GTEBootInit MAC
UserTool $0100+GTEToolNum
<<<
_GTEStartUp MAC
UserTool $0200+GTEToolNum
<<<
_GTEShutDown MAC
UserTool $0300+GTEToolNum
<<<
_GTEVersion MAC
UserTool $0400+GTEToolNum
<<<
_GTEReset MAC
UserTool $0500+GTEToolNum
<<<
_GTEStatus MAC
UserTool $0600+GTEToolNum
<<<
_GTEReadControl MAC
UserTool $0900+GTEToolNum
<<<
_GTESetScreenMode MAC
UserTool $0A00+GTEToolNum
<<<
_GTESetTile MAC
UserTool $0B00+GTEToolNum
<<<
_GTESetBG0Origin MAC
UserTool $0C00+GTEToolNum
<<<
_GTERender MAC
UserTool $0D00+GTEToolNum
<<<
_GTELoadTileSet MAC
UserTool $0E00+GTEToolNum
<<<
_GTECreateSpriteStamp MAC
UserTool $0F00+GTEToolNum
<<<
_GTEAddSprite MAC
UserTool $1000+GTEToolNum
<<<
_GTEMoveSprite MAC
UserTool $1100+GTEToolNum
<<<
_GTEUpdateSprite MAC
UserTool $1200+GTEToolNum
<<<
_GTERemoveSprite MAC
UserTool $1300+GTEToolNum
<<<
_GTEGetSeconds MAC
UserTool $1400+GTEToolNum
<<<
_GTECopyTileToDynamic MAC
UserTool $1500+GTEToolNum
<<<
_GTESetPalette MAC
UserTool $1600+GTEToolNum
<<<
_GTECopyPicToBG1 MAC
UserTool $1700+GTEToolNum
<<<
_GTEBindSCBArray MAC
UserTool $1800+GTEToolNum
<<<
_GTEGetBG0TileMapInfo MAC
UserTool $1900+GTEToolNum
<<<
_GTEGetScreenInfo MAC
UserTool $1A00+GTEToolNum
<<<
_GTESetBG1Origin MAC
UserTool $1B00+GTEToolNum
<<<
_GTEGetTileAt MAC
UserTool $1C00+GTEToolNum
<<<
_GTESetBG0TileMapInfo MAC
UserTool $1D00+GTEToolNum
<<<
_GTESetBG1TileMapInfo MAC
UserTool $1E00+GTEToolNum
<<<
_GTEAddTimer MAC
UserTool $1F00+GTEToolNum
<<<
_GTERemoveTimer MAC
UserTool $2000+GTEToolNum
<<<
_GTEStartScript MAC
UserTool $2100+GTEToolNum
<<<
_GTESetOverlay MAC
UserTool $2200+GTEToolNum
<<<
_GTEClearOverlay MAC
UserTool $2300+GTEToolNum
<<<
_GTEGetTileDataAddr MAC
UserTool $2400+GTEToolNum
<<<
_GTEFillTileStore MAC
UserTool $2500+GTEToolNum
<<<
_GTERefresh MAC
UserTool $2600+GTEToolNum
<<<
; EngineMode definitions
; Script definition
YIELD equ $8000
JUMP equ $4000
SET_PALETTE_ENTRY equ $0002
SWAP_PALETTE_ENTRY equ $0004
SET_DYN_TILE equ $0006
CALLBACK equ $0010
; ReadControl return value bits
PAD_BUTTON_B equ $01
PAD_BUTTON_A equ $02
PAD_KEY_DOWN equ $04
ENGINE_MODE_TWO_LAYER equ $0001
ENGINE_MODE_DYN_TILES equ $0002
ENGINE_MODE_BNK0_BUFF equ $0004
; Tile constants
; TILE_RESERVED_BIT equ $8000
TILE_PRIORITY_BIT equ $4000 ; Put tile on top of sprite
TILE_FRINGE_BIT equ $2000 ; Unused
TILE_SOLID_BIT equ $1000 ; Hint bit used in TWO_LAYER_MODE to optimize rendering
TILE_DYN_BIT equ $0800 ; Is this a Dynamic Tile?
TILE_VFLIP_BIT equ $0400
TILE_HFLIP_BIT equ $0200
TILE_ID_MASK equ $01FF
TILE_CTRL_MASK equ $FE00
; Sprite constants
SPRITE_HIDE equ $2000
SPRITE_16X16 equ $1800
SPRITE_16X8 equ $1000
SPRITE_8X16 equ $0800
SPRITE_8X8 equ $0000
SPRITE_VFLIP equ $0400
SPRITE_HFLIP equ $0200
; Stamp storage parameters
VBUFF_STRIDE_BYTES equ {12*4} ; Each line has 4 slots of 16 pixels + 8 buffer pixels
VBUFF_TILE_ROW_BYTES equ {8*VBUFF_STRIDE_BYTES} ; Each row is comprised of 8 lines
VBUFF_TILE_COL_BYTES equ 4
VBUFF_SPRITE_STEP equ {VBUFF_TILE_ROW_BYTES*3} ; Allocate space for 16 rows + 8 rows of buffer
VBUFF_SPRITE_START equ {VBUFF_TILE_ROW_BYTES+4} ; Start at an offset so $0000 can be used as an empty value
VBUFF_SLOT_COUNT equ 48 ; Have space for this many stamps

32
package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "generic-tile-engine",
"version": "1.0.0",
"description": "A tile-base game engine for the Apple IIgs",
"main": "index.js",
"config": {
"merlin32": "C:\\Programs\\IIgsXDev\\bin\\Merlin32-1.1.10.exe",
"cadius": "C:\\Programs\\IIgsXDev\\bin\\Cadius.exe",
"gsport": "C:\\Programs\\gsport\\gsport_0.31\\GSPort.exe",
"macros": "./macros",
"crossrunner": "C:\\Programs\\Crossrunner\\Crossrunner.exe"
},
"scripts": {
"archive": "%npm_package_config_cadius% EXTRACTFILE ./emu/Target.2mg /GTEDev/Tool160.SHK .",
"test": "npm run build && build-image.bat %npm_package_config_cadius% && %npm_package_config_gsport%",
"build": "%npm_package_config_merlin32% -V %npm_package_config_macros% ./src/Master.s",
"build:debug": "%npm_package_config_merlin32% -V %npm_package_config_macros% ./src/Debug.s",
"debug": "%npm_package_config_crossrunner% ./src/Debug160 -Source ./src/Debug160_S02__Output.txt -Debug -CompatibilityLayer"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lscharen/iigs-game-engine.git"
},
"author": "Lucas Scharenbroich",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/lscharen/iigs-game-engine/issues"
},
"homepage": "https://github.com/lscharen/iigs-game-engine#readme",
"devDependencies": {
}
}

View File

@ -8,94 +8,6 @@
use .\Defs.s
; Feature flags
NO_INTERRUPTS equ 1 ; turn off for crossrunner debugging
NO_MUSIC equ 1 ; turn music + tool loading off
; External data space provided by the main program segment
tiledata EXT
TileStore EXT
; Sprite plane data and mask banks are provided as an exteral segment
;
; The sprite data holds a set of pre-rendered sprites that are optimized to support the rendering pipeline. There
; are four copies of each sprite, along with the cooresponding mask laid out into 4x4 tile regions where the
; empty row and column is shared between adjacent blocks.
;
; Logically, the memory is laid out as 4 columns of sprites and 4 rows.
;
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | | | | | | | | | | | | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | 0 | 0 | | 1 | 1 | | 2 | 2 | | 3 | 3 | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | 0 | 0 | | 1 | 1 | | 2 | 2 | | 3 | 3 | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | | | | | | | | | | | | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | 4 | 4 | | 5 | 5 | | 6 | 6 | | 7 | 7 | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | 4 | 4 | | 5 | 5 | | 6 | 6 | | 7 | 7 | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | | | | | | | | | | | | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
;
; For each sprite, when it needs to be copied into an on-screen tile, it could exist at any offset compared to its
; natural alignment. By having a buffer around the sprite data, an address pointer can be set to a different origin
; and a simple 8x8 block copy can cut out the appropriate bit of the sprite. For example, here is a zoomed-in look
; at a sprite with an offset, O, at (-2,-3). As shown, by selecting an appropriate origin, just the top corner
; of the sprite data will be copied.
;
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | || | || | | | || | | | |
; +---+-- O----------------+ --+---++---+---+---+---++---+---+---+---+..
; | | | | | || | | | || | | | |
; +---+-- | | --+---++---+---+---+---++---+---+---+---+..
; | | | | | || | | | || | | | |
; +---+-- | | --+---++---+---+---+---++---+---+---+---+..
; | | | | | || | | | || | | | |
; +===+== | ++===+== | ==+===++===+===+===+===++===+===+===+===+..
; | | | || | S | S | S || S | S | S | || | | | |
; +---+-- +----------------+ --+---++---+---+---+---++---+---+---+---+..
; | | || S | S S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +===+===+===+===++===+===+===+===++===+===+===+===++===+===+===+===+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || | S | S | S || S | S | S | || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; . . . . . . . . . . . . . . . . .
;
; Each sprite will take up, effectively 9 tiles of storage space per
; instance (plus edges) and there are 4 instances for the H/V bits
; and 4 more for the masks. This results in a need for 43,264 bytes
; for all 16 sprites.
spritedata EXT
spritemask EXT
; If there are overlays, they are provided as an external
Overlay EXT
; Core engine functionality. The idea is that that source file can be PUT into
; a main source file and all of the functionality will be available.
;
; There are some constancts that must be externally defined that can affect how
; the GTE runtime works
;
; NO_MUSIC : Set to non-zero to avoid using any source
; NO_INTERRUPTS : Set to non-zero to avoid installing custom interrupt handlers
mx %00
; High-Level StartUp and ShutDown functions
EngineStartUp ENT
phb
phk
@ -103,41 +15,23 @@ EngineStartUp ENT
jsr ToolStartUp ; Start up the toolbox tools we rely on
jsr _CoreStartUp
jsr SoundStartUp ; Start up any sound/music tools
plb
rtl
_CoreStartUp
jsr SoundStartUp ; Start up any sound/music tools
jsr IntStartUp ; Enable certain iterrupts
jsr InitMemory ; Allocate and initialize memory for the engine
jsr EngineReset ; All of the resources are allocated, put the engine in a known state
jsr InitGraphics ; Initialize all of the graphics-related data
nop
jsr InitSprites ; Initialize the sprite subsystem
jsr InitTiles ; Initialize the tile subsystem
jsr InitTimers ; Initialize the timer subsystem
rts
EngineShutDown ENT
phb
phk
plb
jsr SoundShutDown
jsr _CoreShutDown
jsr ToolShutDown
plb
rtl
_CoreShutDown
jsr IntShutDown
jsr SoundShutDown
rts
ToolStartUp
_TLStartUp ; normal tool initialization
pha
@ -152,7 +46,6 @@ ToolStartUp
rts
MasterId ds 2
UserId ds 2
; Fatal error handler invoked by the _Err macro
PgmDeath tax
@ -168,9 +61,6 @@ PgmDeath0 pha
ContDeath ldx #$1503
jsl $E10000
ToolShutDown
rts
; Use Tool222 (NinjaTrackerPlus) for music playback
SoundStartUp
lda #NO_MUSIC
@ -194,299 +84,10 @@ SoundShutDown
:no_music
rts
; Install interrupt handlers. We use the VBL interrupt to keep animations
; moving at a consistent rate, regarless of the rendered frame rate. The
; one-second timer is generally just used for counters and as a handy
; frames-per-second trigger.
IntStartUp
lda #NO_INTERRUPTS
bne :no_interrupts
PushLong #0
pea $0015 ; Get the existing 1-second interrupt handler and save
_GetVector
PullLong OldOneSecVec
pea $0015 ; Set the new handler and enable interrupts
PushLong #OneSecHandler
_SetVector
pea $0006
_IntSource
PushLong #VBLTASK ; Also register a Heart Beat Task
_SetHeartBeat
:no_interrupts
rts
IntShutDown
lda #NO_INTERRUPTS
bne :no_interrupts
pea $0007 ; disable 1-second interrupts
_IntSource
PushLong #VBLTASK ; Remove our heartbeat task
_DelHeartBeat
pea $0015
PushLong OldOneSecVec ; Reset the interrupt vector
_SetVector
:no_interrupts
rts
; Interrupt handlers. We install a heartbeat (1/60th second and a 1-second timer)
OneSecHandler mx %11
phb
pha
phk
plb
rep #$20
inc OneSecondCounter
sep #$20
ldal $E0C032
and #%10111111 ;clear IRQ source
stal $E0C032
pla
plb
clc
rtl
mx %00
OneSecondCounter ENT
dw 0
OldOneSecVec ds 4
VBLTASK hex 00000000
dw 0
hex 5AA5
; Reset the engine to a known state
; Blitter initialization
EngineReset
stz ScreenHeight
stz ScreenWidth
stz ScreenY0
stz ScreenY1
stz ScreenX0
stz ScreenX1
stz ScreenTileHeight
stz ScreenTileWidth
stz StartX
stz OldStartX
stz StartXMod164
stz StartY
stz OldStartY
stz StartYMod208
stz EngineMode
stz DirtyBits
stz LastRender
stz LastPatchOffset
stz BG1StartX
stz BG1StartXMod164
stz BG1StartY
stz BG1StartYMod208
stz BG1OffsetIndex
stz BG0TileOriginX
stz BG0TileOriginY
stz OldBG0TileOriginX
stz OldBG0TileOriginY
stz BG1TileOriginX
stz BG1TileOriginY
stz OldBG1TileOriginX
stz OldBG1TileOriginY
stz TileMapWidth
stz TileMapHeight
stz TileMapPtr
stz TileMapPtr+2
stz FringeMapPtr
stz FringeMapPtr+2
stz BG1TileMapWidth
stz BG1TileMapHeight
stz BG1TileMapPtr
stz BG1TileMapPtr+2
stz SCBArrayPtr
stz SCBArrayPtr+2
stz SpriteBanks
stz SpriteMap
stz ActiveSpriteCount
stz OneSecondCounter
lda #13
sta tmp15
stz tmp14
:loop
ldx #BlitBuff
lda #^BlitBuff
ldy tmp14
jsr BuildBank
lda tmp14
clc
adc #4
sta tmp14
dec tmp15
bne :loop
rts
; Allow the user to dynamically select one of the pre-configured screen sizes, or pass
; in a specific width and height. The screen is automatically centered. If this is
; not desired, then SetScreenRect should be used directly
;
; 0. Full Screen : 40 x 25 320 x 200 (32,000 bytes (100.0%))
; 1. Sword of Sodan : 34 x 24 272 x 192 (26,112 bytes ( 81.6%))
; 2. ~NES : 32 x 25 256 x 200 (25,600 bytes ( 80.0%))
; 3. Task Force : 32 x 22 256 x 176 (22,528 bytes ( 70.4%))
; 4. Defender of the World : 35 x 20 280 x 160 (22,400 bytes ( 70.0%))
; 5. Rastan : 32 x 20 256 x 160 (20,480 bytes ( 64.0%))
; 6. Game Boy Advanced : 30 x 20 240 x 160 (19,200 bytes ( 60.0%))
; 7. Ancient Land of Y's : 36 x 16 288 x 128 (18,432 bytes ( 57.6%))
; 8. Game Boy Color : 20 x 18 160 x 144 (11,520 bytes ( 36.0%))
; 9. Agony (Amiga) : 36 x 24 288 x 192 (27,648 bytes ( 86.4%))
; 10. Atari Lynx : 20 x 13 160 x 102 (8,160 bytes ( 25.5%))
;
; X = mode number OR width in pixels (must be multiple of 2)
; Y = height in pixels (if X > 8)
ScreenModeWidth dw 320,272,256,256,280,256,240,288,160,288,160,320
ScreenModeHeight dw 200,192,200,176,160,160,160,128,144,192,102,1
SetScreenMode ENT
phb
phk
plb
jsr _SetScreenMode
plb
rtl
_SetScreenMode
cpx #11
bcs :direct ; if x > 10, then assume X and Y are the dimensions
txa
asl
tax
ldy ScreenModeHeight,x
lda ScreenModeWidth,x
tax
:direct cpy #201
bcs :exit
cpx #321
bcs :exit
txa
lsr
pha ; Save X (width / 2) and Y (height)
phy
lda #160 ; Center the screen
sec
sbc 3,s
lsr
xba
pha ; Save half the origin coordinate
lda #200
sec
sbc 3,s ; This is now Y because of the PHA above
lsr
ora 1,s
plx ; Throw-away to pop the stack
ply
plx
jsr SetScreenRect
jmp FillScreen ; tail return
:exit
rts
WaitForKey sep #$20
stal KBD_STROBE_REG ; clear the strobe
:WFK ldal KBD_REG
bpl :WFK
rep #$20
and #$007F
rts
ClearKbdStrobe sep #$20
stal KBD_STROBE_REG
rep #$20
rts
; Read the keyboard and paddle controls and return in a game-controller-like format
LastKey db 0
ReadControl ENT
jsr _ReadControl
rtl
_ReadControl
pea $0000 ; low byte = key code, high byte = %------AB
sep #$20
ldal OPTION_KEY_REG ; 'B' button
and #$80
beq :BNotDown
lda #PAD_BUTTON_B
ora 2,s
sta 2,s
:BNotDown
ldal COMMAND_KEY_REG
and #$80
beq :ANotDown
lda #PAD_BUTTON_A
ora 2,s
sta 2,s
:ANotDown
ldal KBD_STROBE_REG ; read the keyboard
bit #$80
beq :KbdNotDwn ; check the key-down status
and #$7f
ora 1,s
sta 1,s
cmpl LastKey
beq :KbdDown
stal LastKey
lda #PAD_KEY_DOWN ; set the keydown flag
ora 2,s
sta 2,s
bra :KbdDown
:KbdNotDwn
lda #0
stal LastKey
:KbdDown
rep #$20
pla
ToolShutDown
rts
put CoreImpl.s
put blitter/Template.s
put Memory.s

353
src/CoreImpl.s Normal file
View File

@ -0,0 +1,353 @@
; Feature flags
NO_INTERRUPTS equ 0 ; turn off for crossrunner debugging
NO_MUSIC equ 1 ; turn music + tool loading off
; External data space provided by the main program segment
tiledata EXT
TileStore EXT
; Sprite plane data and mask banks are provided as an external segment
;
; The sprite data holds a set of pre-rendered sprites that are optimized to support the rendering pipeline. There
; are four copies of each sprite, along with the cooresponding mask laid out into 4x4 tile regions where the
; empty row and column is shared between adjacent blocks.
;
; Logically, the memory is laid out as 4 columns of sprites and 4 rows.
;
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | | | | | | | | | | | | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | 0 | 0 | | 1 | 1 | | 2 | 2 | | 3 | 3 | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | 0 | 0 | | 1 | 1 | | 2 | 2 | | 3 | 3 | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | | | | | | | | | | | | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | 4 | 4 | | 5 | 5 | | 6 | 6 | | 7 | 7 | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | 4 | 4 | | 5 | 5 | | 6 | 6 | | 7 | 7 | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
; | | | | | | | | | | | | | ...
; +---+---+---+---+---+---+---+---+---+---+---+---+-...
;
; For each sprite, when it needs to be copied into an on-screen tile, it could exist at any offset compared to its
; natural alignment. By having a buffer around the sprite data, an address pointer can be set to a different origin
; and a simple 8x8 block copy can cut out the appropriate bit of the sprite. For example, here is a zoomed-in look
; at a sprite with an offset, O, at (-2,-3). As shown, by selecting an appropriate origin, just the top corner
; of the sprite data will be copied.
;
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | || | || | | | || | | | |
; +---+-- O----------------+ --+---++---+---+---+---++---+---+---+---+..
; | | | | | || | | | || | | | |
; +---+-- | | --+---++---+---+---+---++---+---+---+---+..
; | | | | | || | | | || | | | |
; +---+-- | | --+---++---+---+---+---++---+---+---+---+..
; | | | | | || | | | || | | | |
; +===+== | ++===+== | ==+===++===+===+===+===++===+===+===+===+..
; | | | || | S | S | S || S | S | S | || | | | |
; +---+-- +----------------+ --+---++---+---+---+---++---+---+---+---+..
; | | || S | S S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +===+===+===+===++===+===+===+===++===+===+===+===++===+===+===+===+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || S | S | S | S || S | S | S | S || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; | | | | || | S | S | S || S | S | S | || | | | |
; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+..
; . . . . . . . . . . . . . . . . .
;
; Each sprite will take up, effectively 9 tiles of storage space per
; instance (plus edges) and there are 4 instances for the H/V bits
; and 4 more for the masks. This results in a need for 43,264 bytes
; for all 16 sprites.
spritedata EXT
spritemask EXT
; Core engine functionality. The idea is that that source file can be PUT into
; a main source file and all of the functionality will be available.
;
; There are some constancts that must be externally defined that can affect how
; the GTE runtime works
;
; NO_MUSIC : Set to non-zero to avoid using any source
; NO_INTERRUPTS : Set to non-zero to avoid installing custom interrupt handlers
mx %00
; Assumes the direct page is set and EngineMode and UserId has been initialized
_CoreStartUp
jsr IntStartUp ; Enable certain interrupts
jsr InitMemory ; Allocate and initialize memory for the engine
jsr EngineReset ; All of the resources are allocated, put the engine in a known state
jsr InitGraphics ; Initialize all of the graphics-related data
jsr InitSprites ; Initialize the sprite subsystem
jsr InitTiles ; Initialize the tile subsystem
jsr InitTimers ; Initialize the timer subsystem
rts
_CoreShutDown
jsr IntShutDown
rts
; Install interrupt handlers. We use the VBL interrupt to keep animations
; moving at a consistent rate, regarless of the rendered frame rate. The
; one-second timer is generally just used for counters and as a handy
; frames-per-second trigger.
IntStartUp
lda #NO_INTERRUPTS
bne :no_interrupts
PushLong #0
pea $0015 ; Get the existing 1-second interrupt handler and save
_GetVector
PullLong OldOneSecVec
pea $0015 ; Set the new handler and enable interrupts
PushLong #OneSecHandler
_SetVector
pea $0006
_IntSource
PushLong #VBLTASK ; Also register a Heart Beat Task
_SetHeartBeat
:no_interrupts
rts
IntShutDown
lda #NO_INTERRUPTS
bne :no_interrupts
pea $0007 ; disable 1-second interrupts
_IntSource
PushLong #VBLTASK ; Remove our heartbeat task
_DelHeartBeat
pea $0015
PushLong OldOneSecVec ; Reset the interrupt vector
_SetVector
:no_interrupts
rts
; Interrupt handlers. We install a heartbeat (1/60th second and a 1-second timer)
OneSecHandler mx %11
phb
pha
jsr _SetDataBank
rep #$20
inc OneSecondCounter
sep #$20
ldal $E0C032
and #%10111111 ;clear IRQ source
stal $E0C032
pla
plb
clc
rtl
mx %00
; This is OK, it's referenced by a long address
VBLTASK hex 00000000
dw 0
hex 5AA5
; Reset the engine to a known state
; Blitter initialization
EngineReset
stz ScreenHeight
stz ScreenWidth
stz ScreenY0
stz ScreenY1
stz ScreenX0
stz ScreenX1
stz ScreenTileHeight
stz ScreenTileWidth
stz StartX
stz OldStartX
stz StartXMod164
stz StartY
stz OldStartY
stz StartYMod208
; stz EngineMode
stz DirtyBits
stz LastRender
stz LastPatchOffset
stz BG1StartX
stz BG1StartXMod164
stz BG1StartY
stz BG1StartYMod208
stz BG1OffsetIndex
stz BG0TileOriginX
stz BG0TileOriginY
stz OldBG0TileOriginX
stz OldBG0TileOriginY
stz BG1TileOriginX
stz BG1TileOriginY
stz OldBG1TileOriginX
stz OldBG1TileOriginY
stz TileMapWidth
stz TileMapHeight
stz TileMapPtr
stz TileMapPtr+2
stz FringeMapPtr
stz FringeMapPtr+2
stz BG1TileMapWidth
stz BG1TileMapHeight
stz BG1TileMapPtr
stz BG1TileMapPtr+2
stz SCBArrayPtr
stz SCBArrayPtr+2
stz SpriteBanks
stz SpriteMap
stz ActiveSpriteCount
stz OneSecondCounter
lda #13
sta tmp15
stz tmp14
:loop
ldx #BlitBuff
lda #^BlitBuff
ldy tmp14
jsr BuildBank
lda tmp14
clc
adc #4
sta tmp14
dec tmp15
bne :loop
rts
WaitForKey sep #$20
stal KBD_STROBE_REG ; clear the strobe
:WFK ldal KBD_REG
bpl :WFK
rep #$20
and #$007F
rts
ClearKbdStrobe sep #$20
stal KBD_STROBE_REG
rep #$20
rts
; Read the keyboard and paddle controls and return in a game-controller-like format
_ReadControl pea $0000 ; low byte = key code, high byte = %------AB
sep #$20
ldal OPTION_KEY_REG ; 'B' button
and #$80
beq :BNotDown
lda #PAD_BUTTON_B
ora 2,s
sta 2,s
:BNotDown
ldal COMMAND_KEY_REG
and #$80
beq :ANotDown
lda #PAD_BUTTON_A
ora 2,s
sta 2,s
:ANotDown
ldal KBD_STROBE_REG ; read the keyboard
bit #$80
beq :KbdNotDwn ; check the key-down status
and #$7f
ora 1,s
sta 1,s
cmp LastKey
beq :KbdDown
sta LastKey
lda #PAD_KEY_DOWN ; set the keydown flag
ora 2,s
sta 2,s
bra :KbdDown
:KbdNotDwn
stz LastKey
:KbdDown
rep #$20
pla
rts
; Helper function to take a local pixel coordinate [0, ScreenWidth-1],[0, ScreenHeight-1] and return the
; row and column in the tile store that is corresponds to. This takes into consideration the StartX and
; StartY offsets.
;
; This is more specialized than the code in the _MarkDirtySprite routine below since it does not deal with
; negative or off-screen values.
_OriginToTileStore
lda StartYMod208
lsr
lsr
and #$FFFE ; Store the pre-multiplied by 2 for indexing
tay
lda StartXMod164
lsr
and #$FFFE ; Same pre-multiply by 2 for later
tax
rts
; X = local x-coordinate (0, playfield width)
; Y = local y-coordinate (0, playfield height)
_LocalToTileStore
clc
tya
adc StartYMod208 ; Adjust for the scroll offset
cmp #208 ; check if we went too far positive
bcc *+5
sbc #208
lsr
lsr
and #$FFFE ; Store the pre-multiplied by 2 for indexing
tay
clc
txa
adc StartXMod164
cmp #164
bcc *+5
sbc #164
lsr
and #$FFFE ; Same pre-multiply by 2 for later
tax
rts

View File

@ -34,6 +34,8 @@ StartX equ 16 ; Which code buffer byte is the left ed
StartY equ 18 ; Which code buffer line is the top of the screen. Range = 0 to 207
EngineMode equ 20 ; Defined the mode/capabilities that are enabled
; bit 0: 0 = Single Background, 1 = Parallax
; bit 1: 0 = No Dynamic Tiles, 1 = Allocate Bank 00 space for dynamic tiles
; bit 2: 0 = No static buffer, 1 = Allocation Bank 00 space for a static screen buffer
DirtyBits equ 22 ; Identify values that have changed between frames
BG1DataBank equ 24 ; Data bank that holds BG1 layer data
@ -80,18 +82,25 @@ BG1TileMapPtr equ 86
SCBArrayPtr equ 90 ; Used for palette binding
SpriteBanks equ 94 ; Bank bytes for the sprite data and sprite mask
LastRender equ 96 ; Record which reder function was last executed
LastRender equ 96 ; Record which render function was last executed
; gap
SpriteMap equ 100 ; Bitmap of open sprite slots.
ActiveSpriteCount equ 102
BankLoad equ 104
TileStoreBankAndBank01 equ 106
TileStoreBankAndTileDataBank equ 108
Next equ 110
TileStoreBankDoubled equ 110
UserId equ 112 ; Memory manager user Id to use
ToolNum equ 114 ; Tool number assigned to us
LastKey equ 116
LastTick equ 118
ForceSpriteFlag equ 120
SpriteRemovedFlag equ 122 ; Indicate if any sprites were removed this frame
activeSpriteList equ 128 ; 32 bytes for the active sprite list (can persist across frames)
AppSpace equ 160 ; 16 bytes of space reserved for application use
tiletmp equ 178 ; 16 bytes of temp storage for the tile renderers
; tiletmp equ 178 ; 16 bytes of temp storage for the tile renderers
blttmp equ 192 ; 32 bytes of local cache/scratch space for blitter
tmp8 equ 224 ; another 16 bytes of temporary space to be used as scratch
@ -112,6 +121,50 @@ tmp5 equ 250
tmp6 equ 252
tmp7 equ 254
; Defines for the second direct page (used in the tile blitters)
sprite_ptr0 equ 0 ; Each tile can render up to 4 sprite blocks. The sprite
sprite_ptr1 equ 4 ; data and mask values live in different banks, but have a
sprite_ptr2 equ 8 ; parallel structure. The high word of each point is set to
sprite_ptr3 equ 12 ; the mask bank. With the Bank register set, both data and mask
; ; can be accessed through the same pointer, e.g. lda (sprite_ptr0)
; ; and [sprite_ptr0]
tmp_sprite_data equ 16 ; 32 byte temporary buffer to build up sprite data values
tmp_sprite_mask equ 48 ; 32 byte temporary buffer to build up sprite mask values
tmp_tile_data equ 80 ; 32 byte temporary buffer to build up tile data values
tmp_tile_mask equ 112 ; 32 byte temporary buffer to build up tile mask values
; Temporary direct page locations used by some of the complex tile renderers
_X_REG equ 144
_Y_REG equ 146
_T_PTR equ 148 ; Copy of the tile address pointer
_OP_CACHE2 equ 148 ; CAche of second opcode
_BASE_ADDR equ 150 ; Copy of BTableLow for this tile
_SPR_X_REG equ 152 ; Cache address of sprite plane source for a tile
_JTBL_CACHE equ 154 ; Cache the offset to the exception handler for a column
_OP_CACHE equ 156 ; Cache of a relevant operand / oeprator
_TILE_ID equ 158 ; Copy of the tile descriptor
; Define free space the the application to use
; FREE_SPACE_DP2 equ 160
DP2_DIRTY_TILE_COUNT equ 160 ; Local copy of dirty tile count to avoid banking
DP2_DIRTY_TILE_CALLBACK equ 162
; Some pre-defined bank values
DP2_TILEDATA_AND_TILESTORE_BANKS equ 164
DP2_SPRITEDATA_AND_TILESTORE_BANKS equ 166
DP2_TILEDATA_AND_SPRITEDATA_BANKS equ 168
SPRITE_VBUFF_PTR equ 224 ; 32 bytes of adjusted pointers to VBuffArray addresses
; End direct page values
; EngineMode definitions
ENGINE_MODE_TWO_LAYER equ $0001
ENGINE_MODE_DYN_TILES equ $0002
ENGINE_MODE_BNK0_BUFF equ $0004
; DirtyBits definitions
DIRTY_BIT_BG0_X equ $0001
DIRTY_BIT_BG0_Y equ $0002
DIRTY_BIT_BG1_X equ $0004
@ -135,14 +188,16 @@ PAD_BUTTON_A equ $02
PAD_KEY_DOWN equ $04
; Tile constants
TILE_ID_MASK equ $01FF
TILE_SPRITE_BIT equ $8000 ; Set if this tile intersects an active sprite
; TILE_RESERVED_BIT equ $8000
TILE_PRIORITY_BIT equ $4000 ; Put tile on top of sprite
TILE_FRINGE_BIT equ $2000
TILE_MASK_BIT equ $1000
TILE_DYN_BIT equ $0800
TILE_FRINGE_BIT equ $2000 ; Unused
TILE_SOLID_BIT equ $1000 ; Hint bit used in TWO_LAYER_MODE to optimize rendering
TILE_DYN_BIT equ $0800 ; Is this a Dynamic Tile?
TILE_VFLIP_BIT equ $0400
TILE_HFLIP_BIT equ $0200
TILE_ID_MASK equ $01FF
TILE_CTRL_MASK equ $FE00
; TILE_PROC_MASK equ $F800 ; Select tile proc for rendering
; Sprite constants
SPRITE_HIDE equ $2000
@ -153,17 +208,53 @@ SPRITE_8X8 equ $0000
SPRITE_VFLIP equ $0400
SPRITE_HFLIP equ $0200
MAX_TILES equ {26*41} ; Number of tiles in the code field (41 columns * 26 rows)
TILE_STORE_SIZE equ {MAX_TILES*2} ; The tile store contains a tile descriptor in each slot
; Stamp storage parameters
VBUFF_STRIDE_BYTES equ {12*4} ; Each line has 4 slots of 16 pixels + 8 buffer pixels
VBUFF_TILE_ROW_BYTES equ {8*VBUFF_STRIDE_BYTES} ; Each row is comprised of 8 lines
VBUFF_TILE_COL_BYTES equ 4
VBUFF_SPRITE_STEP equ {VBUFF_TILE_ROW_BYTES*3} ; Allocate space for 16 rows + 8 rows of buffer
VBUFF_SPRITE_START equ {VBUFF_TILE_ROW_BYTES+4} ; Start at an offset so $0000 can be used as an empty value
VBUFF_SLOT_COUNT equ 48 ; Have space for this many stamps
TS_TILE_ID equ TILE_STORE_SIZE*0 ; tile descriptor for this location
TS_DIRTY equ TILE_STORE_SIZE*1 ; Flag. Used to prevent a tile from being queued multiple times per frame
TS_SPRITE_FLAG equ TILE_STORE_SIZE*2 ; Bitfield of all sprites that intersect this tile. 0 if no sprites.
TS_TILE_ADDR equ TILE_STORE_SIZE*3 ; cached value, the address of the tiledata for this tile
TS_CODE_ADDR_LOW equ TILE_STORE_SIZE*4 ; const value, address of this tile in the code fields
TS_CODE_ADDR_HIGH equ TILE_STORE_SIZE*5 ; const value
TS_WORD_OFFSET equ TILE_STORE_SIZE*6 ; const value, word offset value for this tile if LDA (dp),y instructions re used
TS_BASE_ADDR equ TILE_STORE_SIZE*7 ; const value, because there are two rows of tiles per bank, this is set to $0000 ot $8000.
TS_SCREEN_ADDR equ TILE_STORE_SIZE*8 ; cached value of on-screen location of tile. Used for DirtyRender.
TS_VBUFF_ARRAY_ADDR equ TILE_STORE_SIZE*9 ; const value to an aligned 32-byte array starting at $8000 in TileStore bank
TS_TILE_DISP equ TILE_STORE_SIZE*10 ; derived from TS_TILE_ID to optimize tile dispatch in the Render function
; This is 13 blocks wide
SPRITE_PLANE_SPAN equ VBUFF_STRIDE_BYTES
; External references to data bank
TileStore EXT
DirtyTileCount EXT
DirtyTiles EXT
_Sprites EXT
TileStore EXT
TileStoreLookupYTable EXT
TileStoreLookup EXT
Col2CodeOffset EXT
JTableOffset EXT
CodeFieldEvenBRA EXT
CodeFieldOddBRA EXT
ScreenAddr EXT
TileStoreYTable EXT
NextCol EXT
RTable EXT
BlitBuff EXT
BTableHigh EXT
BTableLow EXT
BRowTableHigh EXT
BRowTableLow EXT
BG1YTable EXT
BG1YOffsetTable EXT
OldOneSecVec EXT
OneSecondCounter EXT
Timers EXT
DefaultPalette EXT
ScreenModeWidth EXT
ScreenModeHeight EXT
_SpriteBits EXT
_SpriteBitsNot EXT
VBuffArray EXT
_stamp_step EXT
VBuffVertTableSelect EXT
VBuffHorzTableSelect EXT
Overlays EXT
; Tool error codes
NO_TIMERS_AVAILABLE equ 10

View File

@ -20,12 +20,10 @@ SetBG1XPos EXT
SetBG1YPos EXT
CopyBG0Tile EXT
CopyBG1Tile EXT
CopyTileToDyn EXT
Render EXT
; SCB/Palette binding (high bit of array point indicates whether to bind to BG0 Y position (0)
; or BG1 Y position (1).
SetSCBArray EXT
; SetSCBArray EXT
BltSCB EXT
; Rotation
@ -73,21 +71,3 @@ AllocBank EXT
ScreenAddr EXT
OneSecondCounter EXT
BlitBuff EXT
;; Helper function to load the GTE User Toolset
;GTEInstall
; php
; ~InitialLoad userId;localToolPath;#0
; pea $8000 ; User tool
; pea $00A5 ; Tool 165
; PushLong toolPtr
; _SetTSPtr
; plp
; rtl
; Look for the tool set in the System Tools folder and then next to the application
;sysToolPath strl '*:System:Tools:ToolGTE'
;localToolPath strl '9:ToolGTE'
;toolPtr adrl 0

View File

@ -11,18 +11,81 @@ InitGraphics
lda #0
jsr _SetPalette
jsr _InitBG0 ; Initialize the background layers
jsr _InitBG1
jsr _InitBG0 ; Initialize the background layer
lda EngineMode
bit #ENGINE_MODE_TWO_LAYER
beq :no_bg1
jsr _InitBG1
lda #0
jsr _ClearBG1Buffer
:no_bg1
rts
DefaultPalette dw $0000,$007F,$0090,$0FF0
dw $000F,$0080,$0f70,$0FFF
dw $0fa9,$0ff0,$00e0,$04DF
dw $0d00,$078f,$0ccc,$0FFF
; Allow the user to dynamically select one of the pre-configured screen sizes, or pass
; in a specific width and height. The screen is automatically centered. If this is
; not desired, then SetScreenRect should be used directly
;
; 0. Full Screen : 40 x 25 320 x 200 (32,000 bytes (100.0%))
; 1. Sword of Sodan : 34 x 24 272 x 192 (26,112 bytes ( 81.6%))
; 2. ~NES : 32 x 25 256 x 200 (25,600 bytes ( 80.0%))
; 3. Task Force : 32 x 22 256 x 176 (22,528 bytes ( 70.4%))
; 4. Defender of the World : 35 x 20 280 x 160 (22,400 bytes ( 70.0%))
; 5. Rastan : 32 x 20 256 x 160 (20,480 bytes ( 64.0%))
; 6. Game Boy Advanced : 30 x 20 240 x 160 (19,200 bytes ( 60.0%))
; 7. Ancient Land of Y's : 36 x 16 288 x 128 (18,432 bytes ( 57.6%))
; 8. Game Boy Color : 20 x 18 160 x 144 (11,520 bytes ( 36.0%))
; 9. Agony (Amiga) : 36 x 24 288 x 192 (27,648 bytes ( 86.4%))
; 10. Atari Lynx : 20 x 13 160 x 102 (8,160 bytes ( 25.5%))
;
; X = mode number OR width in pixels (must be multiple of 2)
; Y = height in pixels (if X > 8)
_SetScreenMode
cpx #11
bcs :direct ; if x > 10, then assume X and Y are the dimensions
txa
asl
tax
ldy ScreenModeHeight,x
lda ScreenModeWidth,x
tax
:direct cpy #201
bcs :exit
cpx #321
bcs :exit
txa
lsr
pha ; Save X (width / 2) and Y (height)
phy
lda #160 ; Center the screen
sec
sbc 3,s
lsr
xba
pha ; Save half the origin coordinate
lda #200
sec
sbc 3,s ; This is now Y because of the PHA above
lsr
ora 1,s
plx ; Throw-away to pop the stack
ply
plx
jsr SetScreenRect
jmp FillScreen ; tail return
:exit
rts
; Return the current border color ($0 - $F) in the accumulator
_GetBorderColor lda #0000
@ -33,10 +96,6 @@ _GetBorderColor lda #0000
rts
; Set the border color to the accumulator value.
SetBorderColor ENT
jsr _SetBorderColor
rtl
_SetBorderColor sep #$20 ; ACC = $X_Y, REG = $W_Z
eorl BORDER_REG ; ACC = $(X^Y)_(Y^Z)
and #$0F ; ACC = $0_(Y^Z)
@ -55,17 +114,6 @@ _ClearToColor
rts
; Set a palette values
; A = high word of palette data pointer, X = low word of palette data pointer, Y = palette number
SetPalette ENT
phb ; save old data bank
pha ; push 16-bit value
plb ; pop 8-bit bank register
tya
jsr _SetPalette
plb ; pop the other half of the 16-bit push off
plb ; restore the original data bank
rtl
; A = palette number, X = palette address
_SetPalette
and #$000F ; palette values are 0 - 15 and each palette is 32 bytes
@ -148,9 +196,139 @@ _WaitForVBL
rep #$20
rts
; Set the physical location of the virtual screen on the physical screen. The
; screen size must by a multiple of 8
;
; A = XXYY where XX is the left edge [0, 159] and YY is the top edge [0, 199]
; X = width (in bytes)
; Y = height (in lines)
;
; This subroutine stores the screen positions in the direct page space and fills
; in the double-length ScreenAddrR table that holds the address of the right edge
; of the playfield. This table is used to set addresses in the code banks when the
; virtual origin is changed.
;
; We are not concerned about the raw performance of this function because it should
; usually only be executed once during app initialization. It doesn't get called
; with any significant frequency.
SetScreenRect sty ScreenHeight ; Save the screen height and width
stx ScreenWidth
tax ; Temp save of the accumulator
and #$00FF
sta ScreenY0
clc
adc ScreenHeight
sta ScreenY1
txa ; Restore the accumulator
xba
and #$00FF
sta ScreenX0
clc
adc ScreenWidth
sta ScreenX1
lda ScreenHeight ; Divide the height in scanlines by 8 to get the number tiles
lsr
lsr
lsr
sta ScreenTileHeight
lda ScreenWidth ; Divide width in bytes by 4 to get the number of tiles
lsr
lsr
sta ScreenTileWidth
lda ScreenY0 ; Calculate the address of the first byte
asl ; of the right side of the playfield
tax
lda ScreenAddr,x ; This is the address for the edge of the physical screen
clc
adc ScreenX1
dec
pha ; Save for second loop
ldx #0
ldy ScreenHeight
:loop clc
sta RTable,x
adc #160
inx
inx
dey
bne :loop
ldy ScreenHeight
pla ; Reset the address and continue filling in the
:loop2 clc
sta RTable,x
adc #160
inx
inx
dey
bne :loop2
; Calculate the screen locations for each tile corner
lda ScreenY0 ; Calculate the address of the first byte
asl ; of the right side of the playfield
tax
lda ScreenAddr,x ; This is the address for the left edge of the physical screen
clc
adc ScreenX0
ldx #0
ldy #0
:tsloop
sta TileStore+TS_SCREEN_ADDR,x
clc
adc #4 ; Go to the next tile
iny
cpy #41 ; If we've done 41 columns, move to the next line
bcc :nohop
ldy #0
clc
adc #{8*160}-{4*41}
:nohop
inx
inx
cpx #TILE_STORE_SIZE-2
bcc :tsloop
rts
; Clear the SHR screen and then infill the defined field
FillScreen lda #0
jsr _ClearToColor
ldy ScreenY0
:yloop
tya
asl a
tax
lda ScreenAddr,x
clc
adc ScreenX0
tax
phy
lda ScreenWidth
lsr
tay
lda #$FFFF
:xloop stal $E10000,x ; X is the absolute address
inx
inx
dey
bne :xloop
ply
iny
cpy ScreenY1
bcc :yloop
rts

38
src/Master.s Normal file
View File

@ -0,0 +1,38 @@
; IIgs Generic Tile Engine User Toolset
TYP $BA ; Tool set file
DSK Tool160
XPL
; Main toolbox interface and code
ASM Tool.s
SNA Main
; 64KB Tile Memory
ASM static\TileData.s
KND #$1001 ; Type and Attributes ($10=Static,$01=Data)
ALI BANK
SNA TDATA
; 64KB Sprite Plane Data
ASM static\SprData.s
KND #$1001 ; Type and Attributes ($11=Static+Bank Relative,$01=Data)
ALI BANK
SNA SDATA
; 64KB Sprite Mask Data
ASM static\SprMask.s
KND #$1001 ; Type and Attributes ($11=Static+Bank Relative,$01=Data)
ALI BANK
SNA SMASK
; 64KB Tile Store
ASM static\TileStore.s
KND #$1001 ; Type and Attributes ($11=Static+Bank Relative,$01=Data)
ALI BANK
SNA TSTORE

88
src/Math.s Normal file
View File

@ -0,0 +1,88 @@
; Math-y functions
mx %00
; Special subroutine to divide the accumulator by 164 and return remainder in the Accumulator
;
; 164 = $A4 = 1010_0100
Mod164 cmp #%1010010000000000
bcc *+5
sbc #%1010010000000000
cmp #%0101001000000000
bcc *+5
sbc #%0101001000000000
cmp #%0010100100000000
bcc *+5
sbc #%0010100100000000
cmp #%0001010010000000
bcc *+5
sbc #%0001010010000000
cmp #%0000101001000000
bcc *+5
sbc #%0000101001000000
cmp #%0000010100100000
bcc *+5
sbc #%0000010100100000
cmp #%0000001010010000
bcc *+5
sbc #%0000001010010000
cmp #%0000000101001000
bcc *+5
sbc #%0000000101001000
cmp #%0000000010100100
bcc *+5
sbc #%0000000010100100
rts
; Special subroutine to divide the accumulator by 208 and return remainder in the Accumulator
;
; 208 = $D0 = 1101_0000
;
; There are probably faster hacks to divide a 16-bit unsigned value by 208
; https://www.drdobbs.com/parallel/optimizing-integer-division-by-a-constan/184408499
; https://embeddedgurus.com/stack-overflow/2009/06/division-of-integers-by-constants/
Mod208 cmp #%1101000000000000
bcc *+5
sbc #%1101000000000000
cmp #%0110100000000000
bcc *+5
sbc #%0110100000000000
cmp #%0011010000000000
bcc *+5
sbc #%0011010000000000
cmp #%0001101000000000
bcc *+5
sbc #%0001101000000000
cmp #%0000110100000000
bcc *+5
sbc #%0000110100000000
cmp #%0000011010000000
bcc *+5
sbc #%0000011010000000
cmp #%0000001101000000
bcc *+5
sbc #%0000001101000000
cmp #%0000000110100000
bcc *+5
sbc #%0000000110100000
cmp #%0000000011010000
bcc *+5
sbc #%0000000011010000
rts

View File

@ -16,7 +16,11 @@
mx %00
InitMemory PushLong #0 ; space for result
InitMemory lda EngineMode
bit #ENGINE_MODE_BNK0_BUFF
beq :no_bnk0_buff
PushLong #0 ; space for result
PushLong #$008000 ; size (32k)
PushWord UserId
PushWord #%11000000_00010111 ; Fixed location
@ -24,9 +28,10 @@ InitMemory PushLong #0 ; space for result
_NewHandle ; returns LONG Handle on stack
plx ; base address of the new handle
pla ; high address 00XX of the new handle (bank)
_Deref
stx Buff00
sta Buff00+2
; _Deref
; stx Buff00
; sta Buff00+2
:no_bnk0_buff
PushLong #0 ; space for result
PushLong #$008000 ; size (32k)
@ -36,12 +41,20 @@ InitMemory PushLong #0 ; space for result
_NewHandle ; returns LONG Handle on stack
plx ; base address of the new handle
pla ; high address 00XX of the new handle (bank)
_Deref
stx Buff01
sta Buff01+2
; _Deref
; stx Buff01
; sta Buff01+2
PushLong #0 ; space for result
PushLong #$000A00 ; size (10 pages)
pea #0000 ; size (2 or 10 pages)
lda EngineMode
bit #ENGINE_MODE_DYN_TILES
beq :no_dyn_tiles
pea #$0A00 ; 10 pages if dynamic tiles are enabled
bra :dyn_done
:no_dyn_tiles pea #$0200 ; 2 pages if dynamic tiles are disabled
:dyn_done
PushWord UserId
PushWord #%11000000_00010101 ; Page-aligned, fixed bank
PushLong #$000000
@ -52,11 +65,15 @@ InitMemory PushLong #0 ; space for result
stx BlitterDP
; Allocate banks of memory for BG1
lda EngineMode
bit #ENGINE_MODE_TWO_LAYER
beq :no_bg1
jsr AllocOneBank2
sta BG1DataBank
jsr AllocOneBank2
sta BG1AltBank
:no_bg1
; Allocate the 13 banks of memory we need and store in double-length array
]step equ 0
@ -80,6 +97,7 @@ InitMemory PushLong #0 ; space for result
sta BTableHigh+]step+{208*2},x ; 16 lines per bank
]step equ ]step+2
--^
lda BlitBuff,y
sta BTableLow,x
sta BTableLow+{208*2},x
@ -93,10 +111,12 @@ InitMemory PushLong #0 ; space for result
--^
txa
clc
adc #16*2 ; move to the next chunk of BTableHigh and BTableLow
tax
tya
clc
adc #4 ; move to the next bank address
tay
cmp #4*13
@ -104,6 +124,7 @@ InitMemory PushLong #0 ; space for result
brl :bloop
:exit1
ldx #0
ldy #0
:bloop2
@ -135,9 +156,6 @@ InitMemory PushLong #0 ; space for result
:exit
rts
Buff00 ds 4
Buff01 ds 4
; Bank allocator (for one full, fixed bank of memory. Can be immediately deferenced)
AllocOneBank PushLong #0
@ -149,19 +167,11 @@ AllocOneBank PushLong #0
plx ; base address of the new handle
pla ; high address 00XX of the new handle (bank)
xba ; swap accumulator bytes to XX00
sta :bank+2 ; store as bank for next op (overwrite $XX00)
stal :bank+2 ; store as bank for next op (overwrite $XX00)
:bank ldal $000001,X ; recover the bank address in A=XX/00
rts
; Variation that returns the pointer in the X/A registers (X = low, A = high)
AllocBank ENT
phb
phk
plb
jsr AllocOneBank2
plb
rtl
AllocOneBank2 PushLong #0
PushLong #$10000
PushWord UserId
@ -172,5 +182,3 @@ AllocOneBank2 PushLong #0
pla ; high address 00XX of the new handle (bank)
_Deref
rts

File diff suppressed because it is too large Load Diff

View File

@ -33,18 +33,13 @@
;
; A pointer to the current command instruction is stored in the first 4 bytes of the
; timer's user data section.
StartScript ENT
phb
phk
plb
phx ; Save the script array address
_StartScript phx ; Save the script array address
pha
lda #_DoScriptSeq ; Try to create a timer for this script
ldx #^_DoScriptSeq
clc
jsl AddTimer
jsr _AddTimer
bcs :err ; No timer slots available :(
tax ; Initialize the UserData with the command pointer
@ -52,15 +47,12 @@ StartScript ENT
sta Timers+8,x
pla
sta Timers+10,x
plb
rtl
rts
:err
pla ; Pop the values and return with the carry flag set
pla
plb
rtl
rts
; This routine executes script command until it encounters one with the STOP bit set. In some
; sense, the stop bit acts like a "yield" in high-level languages.
@ -69,14 +61,6 @@ ARG1 equ 2
ARG2 equ 4
ARG3 equ 6
DoScriptSeq ENT
phb
phk
plb
jsl _DoScriptSeq ; Yes, this is a special JSL, because _DoScriptSeq is a time callback
plb
rtl
_DoScriptSeq
phx ; save the timer index; will need to update user data at the end
phb ; save the current data bank
@ -99,6 +83,7 @@ _dss_loop phx ; Save the command address
txy ; Cache in the y-register
lda: 0,x ; Load the command word
pha ; Stash it
and #$001E ; Only have 16 built-in commands. Use the _UserCallback
@ -116,18 +101,20 @@ _dss_cmd_rtn
; to the next entry.
bit #JUMP ; Just do a fall through and set the jump offset to
bne :move_addr ; a hard-coded value of 1 if the jump bit is not set
:retry lda #$0100
:move_addr and #$0F00 ; mask out the number of commands to move
:retry lda #$0040
:move_addr and #$0FC0 ; mask out the number of commands to move
beq :retry ; Don't allow zeros; will cause infinite loop. Just advance by one.
xba ; put it in the low byte
cmp #$0008 ; Sign-extend the 4-bit value
cmp #$0800 ; Sign-extend the 6-bit value
bcc *+5
ora #$FFF0
ora #$F000
asl ; multiply by 8
asl
asl
cmp #$8000 ; make it a multiple of 8 (preserve sign)
ror
cmp #$8000
ror
cmp #$8000
ror
clc
adc 3,s ; add it to the saved command address
sta 3,s
@ -175,14 +162,14 @@ _SetDTile
ldx: ARG1,y
lda: ARG2,y
tay
jsl CopyTileToDyn
jsr CopyTileToDyn
brl _dss_cmd_rtn
_UserCallback
lda: ARG1,y
sta :dispatch+1
stal :dispatch+1
lda: ARG1+1,y
sta :dispatch+2
stal :dispatch+2
lda: ARG3,y
:dispatch jsl $000000
brl _dss_cmd_rtn

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +1,20 @@
; Scratch space to lay out idealized _MakeDirtySprite
; On input, X register = Sprite Array Index
;Left equ tmp1
;Right equ tmp2
;Top equ tmp3
;Bottom equ tmp4
Origin equ tmp4
TileTop equ tmp5
RowTop equ tmp6
AreaIndex equ tmp7
SpriteBit equ tmp8 ; set the bit of the value that if the current sprite index
TileLeft equ tmp8
ColLeft equ tmp9
SpriteBit equ tmp10 ; set the bit of the value that if the current sprite index
VBuffOrigin equ tmp11
; Helper function to take a local pixel coordinate [0, ScreenWidth-1],[0, ScreenHeight-1] and return the
; row and column in the tile store that is corresponds to. This takes into consideration the StartX and
; StartY offsets.
;
; This is more specialized than the code in the _MarkDirtySprite routine below since it does not deal with
; negative or off-screen values.
_OriginToTileStore
lda StartYMod208
lsr
lsr
and #$FFFE ; Store the pre-multiplied by 2 for indexing
tay
lda StartXMod164
lsr
and #$FFFE ; Same pre-multiply by 2 for later
tax
rts
; X = local x-coordinate (0, playfield width)
; Y = local y-coordinate (0, playfield height)
_LocalToTileStore
clc
tya
adc StartYMod208 ; Adjust for the scroll offset
cmp #208 ; check if we went too far positive
bcc *+5
sbc #208
lsr
lsr
and #$FFFE ; Store the pre-multiplied by 2 for indexing
tay
clc
txa
adc StartXMod164
cmp #164
bcc *+5
sbc #164
lsr
and #$FFFE ; Same pre-multiply by 2 for later
tax
rts
; 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
@ -80,454 +37,307 @@ _LocalToTileStore
; 8 10 2 8
; ...
;
; For the Y-coordinate, we just use "mod 8" instead of "mod 4"
mdsOut rts
_MarkDirtySprite
lda #0
sta _Sprites+TILE_STORE_ADDR_1,y ; Clear this sprite's dirty tile list in case of an early exit
lda _SpriteBits,y ; Cache its bit flag to mark in the tile slots
sta SpriteBit
; 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 mdsOut
bne mdsOut2
; At this point we know that we have to update the tiles that overlap the sprite's rectangle defined
; by (Top, Left), (Bottom, Right). First, calculate the row and column in the TileStore that
; encloses the top-left on-screen corner of the sprite
; 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
tax ; cache
cmp #208 ; check if we went too far positive
bcc *+5
sbc #208
pha ; Cache
and #$FFF8 ; mask first to ensure LSR will clear the carry
lsr
lsr ; This is the row in the Tile Store for top-left corner of the sprite
and #$FFFE ; Store the value pre-multiplied by 2 for indexing in the :mark_R_C routines
sta RowTop
lsr
tax
lda TileStoreLookupYTable,x
sta RowTop ; Even numbers from [0, 100] (51 elements)
; Next, calculate how many tiles are covered by the sprite. This uses the table at the top of this function, but
; the idea is that for every increment of StartX or StartY, that can shift the sprite into the next tile, up to
; a maximum of mod 4 / mod 8. So the effective width of a sprite is (((StartX + Clip_Left) mod 4) + Clip_Width) / 4
; 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.
txa
pla
and #$0007
sta tmp0 ; save to adjust sprite origin
lda _Sprites+SPRITE_CLIP_HEIGHT,y ; Nominal value between 0 and 16+7 = 23 = 10111
adc _Sprites+SPRITE_CLIP_HEIGHT,y ; Nominal value between 0 and 16+7 = 23 = 10111
dec
clc
adc tmp0
and #$0018
sta AreaIndex
; Repeat to get the same information for the columns
; 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
tax
cmp #164
bcc *+5
sbc #164
lsr
and #$FFFE ; Same pre-multiply by 2 for later
sta ColLeft
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
txa
; Calculate the final amount of visible tiles that need to be refreshed and use that to
; set the coverage size index.
pla
and #$0003
sta tmp1 ; save to adjust sprite origin
lda _Sprites+SPRITE_CLIP_WIDTH,y ; max width = 8 = 0x08
adc _Sprites+SPRITE_CLIP_WIDTH,y ; max width = 8 = 0x08
dec
clc
adc tmp1
and #$000C
lsr ; max value = 4 = 0x04
and #$0006
ora AreaIndex
sta AreaIndex
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
; Calculate the modified origin address for the sprite. We need to look at the sprite flip bits
; to determine which of the four sprite stamps is the correct one to use. Then, offset that origin
; based on the (x, y) and (startx, starty) positions.
lda _Sprites+SPRITE_DISP,y ; Each stamp is 12 bytes
and #$0006
tax
lda :stamp_step,x
; Part 2: Redo some calculation with the actual (signed) sprite positions that take into
; account negative coordinates to set the VBuff offset values.
clc
adc _Sprites+VBUFF_ADDR,y
sec
sbc tmp1 ; Subtract the horizontal within-tile displacement
asl tmp0
ldx tmp0
sec
sbc :vbuff_mul,x
sta VBuffOrigin
lda #^TileStore
sta tmp1
lda _Sprites+SPRITE_Y,y
adc StartYMod208
bpl :y_ok
clc
adc #208 ; Wrap the actual coordinat around
:y_ok and #$FFF8 ; mask first to ensure LSR will clear the carry
lsr
lsr
tax ; Tile store lookup index
; Dispatch to cover the tiles
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
ldx AreaIndex
jmp (:mark,x)
:mark dw :mark1x1,:mark1x2,:mark1x3,mdsOut
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
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
; NOTE: The VBuffArray table is set up so that each sprite's vbuff address is stored in a
; parallel structure to the Tile Store. This allows up to use the same TileStoreLookup
; offset to index into the array of 16 sprite VBUFF addresses that are bound to a given tile
_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
:stamp_step dw 0,12,24,36
:vbuff_mul dw 0,52,104,156,208,260,312,364
; Dispatch to the calculated sizing
; 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
; Begin a list of subroutines to cover all of the valid sprite size combinations. This is all unrolled code,
; mainly to be able to do an unrolled fill of the TILE_STORE_ADDR_X values. Thus, it's important that the clipping
; function does its job properly since it allows us to save a lot of time here.
;
; These functions are a trade off of being composable versus fast. Having to pay for multiple JSR/RTS invocations
; in the hot sprite path isn't great, but we're at a point of diminishing returns.
;
; There *might* be some speed gained by pushing a list of :mark_R_C addressed onto the stack in the clipping routing
; and dispatching that way, but probably not...
:mark1x1
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
lda #0
sta _Sprites+TILE_STORE_ADDR_2,y
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}
rts
; NOTE: If we rework the _PushDirtyTile to use the Y register instead of the X register, we can
; optimize all of these :mark routines as
;
; :mark1x1
; jsr :mark_0_0
; sty _Sprites+TILE_STORE_ADDR_1,x
; stz _Sprites+TILE_STORE_ADDR_2,y
; rts
:mark1x2
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_2,y
lda #0
sta _Sprites+TILE_STORE_ADDR_3,y
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+2
rts
:mark1x3
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_0_2
sta _Sprites+TILE_STORE_ADDR_3,y
lda #0
sta _Sprites+TILE_STORE_ADDR_4,y
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
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_2,y
lda #0
sta _Sprites+TILE_STORE_ADDR_3,y
ldx _Sprites+TS_LOOKUP_INDEX,y
TSSetSprite 0*{TS_LOOKUP_SPAN*2}+0
TSSetSprite 1*{TS_LOOKUP_SPAN*2}+0
rts
:mark2x2
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_3,y
jsr :mark_1_1
sta _Sprites+TILE_STORE_ADDR_4,y
lda #0
sta _Sprites+TILE_STORE_ADDR_5,y
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
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_0_2
sta _Sprites+TILE_STORE_ADDR_3,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_4,y
jsr :mark_1_1
sta _Sprites+TILE_STORE_ADDR_5,y
jsr :mark_1_2
sta _Sprites+TILE_STORE_ADDR_6,y
lda #0
sta _Sprites+TILE_STORE_ADDR_7,y
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
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_2_0
sta _Sprites+TILE_STORE_ADDR_3,y
lda #0
sta _Sprites+TILE_STORE_ADDR_4,y
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
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_2_0
sta _Sprites+TILE_STORE_ADDR_3,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_4,y
jsr :mark_1_1
sta _Sprites+TILE_STORE_ADDR_5,y
jsr :mark_2_1
sta _Sprites+TILE_STORE_ADDR_6,y
lda #0
sta _Sprites+TILE_STORE_ADDR_7,y
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
jsr :mark_0_0
sta _Sprites+TILE_STORE_ADDR_1,y
jsr :mark_1_0
sta _Sprites+TILE_STORE_ADDR_2,y
jsr :mark_2_0
sta _Sprites+TILE_STORE_ADDR_3,y
jsr :mark_0_1
sta _Sprites+TILE_STORE_ADDR_4,y
jsr :mark_1_1
sta _Sprites+TILE_STORE_ADDR_5,y
jsr :mark_2_1
sta _Sprites+TILE_STORE_ADDR_6,y
jsr :mark_0_2
sta _Sprites+TILE_STORE_ADDR_7,y
jsr :mark_1_2
sta _Sprites+TILE_STORE_ADDR_8,y
jsr :mark_2_2
sta _Sprites+TILE_STORE_ADDR_9,y
lda #0
sta _Sprites+TILE_STORE_ADDR_10,y
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
; Begin List of subroutines to mark each tile offset
:mark_0_0
ldx RowTop
lda ColLeft
clc
adc TileStoreYTable,x ; Fixed offset to the next row
tax
ldal TileStore+TS_VBUFF_ARRAY_ADDR,x
sta tmp0
lda VBuffOrigin
sta [tmp0],y
; lda VBuffOrigin ; This is an interesting case. The mapping between the tile store
; adc #{0*4}+{0*256} ; and the sprite buffers changes as the StartX, StartY values change
; stal TileStore+TS_SPRITE_ADDR,x ; but don't depend on any sprite information. However, by setting the
; value only for the tiles that get added to the dirty tile list, we
; can avoid recalculating over 1,000 values whenever the screen scrolls
; (which is common) and just limit it to the number of tiles covered by
; the sprites. If the screen is not scrolling and the sprites are not
; moving and they are being dirtied, then we may do more work, but the
; odds are in our favor to just take care of it here.
; lda TileStore+TS_SPRITE_FLAG,x
lda SpriteBit
oral TileStore+TS_SPRITE_FLAG,x
stal TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX ; Needs X = tile store offset; destroys A,X. Returns X in A
:mark_1_0
lda ColLeft
ldx RowTop
clc
adc TileStoreYTable+2,x
tax
ldal TileStore+TS_VBUFF_ARRAY_ADDR,x
sta tmp0
lda VBuffOrigin
adc #{0*4}+{1*8*SPRITE_PLANE_SPAN}
sta [tmp0],y
lda SpriteBit
oral TileStore+TS_SPRITE_FLAG,x
stal TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_2_0
lda ColLeft
ldx RowTop
clc
adc TileStoreYTable+4,x
tax
ldal TileStore+TS_VBUFF_ARRAY_ADDR,x
sta tmp0
lda VBuffOrigin
adc #{0*4}+{2*8*SPRITE_PLANE_SPAN}
sta [tmp0],y
lda SpriteBit
oral TileStore+TS_SPRITE_FLAG,x
stal TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_0_1
ldx ColLeft
lda NextCol+2,x
ldx RowTop
clc
adc TileStoreYTable,x
tax
ldal TileStore+TS_VBUFF_ARRAY_ADDR,x
sta tmp0
lda VBuffOrigin
adc #{1*4}+{0*8*SPRITE_PLANE_SPAN}
sta [tmp0],y
lda SpriteBit
oral TileStore+TS_SPRITE_FLAG,x
stal TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_1_1
ldx ColLeft
lda NextCol+2,x
ldx RowTop
clc
adc TileStoreYTable+2,x
tax
ldal TileStore+TS_VBUFF_ARRAY_ADDR,x
sta tmp0
lda VBuffOrigin
adc #{1*4}+{1*8*SPRITE_PLANE_SPAN}
sta [tmp0],y
lda SpriteBit
oral TileStore+TS_SPRITE_FLAG,x
stal TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_2_1
ldx ColLeft
lda NextCol+2,x
ldx RowTop
clc
adc TileStoreYTable+4,x
tax
ldal TileStore+TS_VBUFF_ARRAY_ADDR,x
sta tmp0
lda VBuffOrigin
adc #{1*4}+{2*8*SPRITE_PLANE_SPAN}
sta [tmp0],y
lda SpriteBit
oral TileStore+TS_SPRITE_FLAG,x
stal TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_0_2
ldx ColLeft
lda NextCol+4,x
ldx RowTop
clc
adc TileStoreYTable,x
tax
ldal TileStore+TS_VBUFF_ARRAY_ADDR,x
sta tmp0
lda VBuffOrigin
adc #{2*4}+{0*8*SPRITE_PLANE_SPAN}
sta [tmp0],y
lda SpriteBit
oral TileStore+TS_SPRITE_FLAG,x
stal TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_1_2
ldx ColLeft
lda NextCol+4,x
ldx RowTop
clc
adc TileStoreYTable+2,x
tax
ldal TileStore+TS_VBUFF_ARRAY_ADDR,x
sta tmp0
lda VBuffOrigin
adc #{2*4}+{1*8*SPRITE_PLANE_SPAN}
sta [tmp0],y
lda SpriteBit
oral TileStore+TS_SPRITE_FLAG,x
stal TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
:mark_2_2
ldx ColLeft
lda NextCol+4,x
ldx RowTop
clc
adc TileStoreYTable+4,x
tax
ldal TileStore+TS_VBUFF_ARRAY_ADDR,x
sta tmp0
lda VBuffOrigin
adc #{2*4}+{2*8*SPRITE_PLANE_SPAN}
sta [tmp0],y
lda SpriteBit
oral TileStore+TS_SPRITE_FLAG,x
stal TileStore+TS_SPRITE_FLAG,x
jmp _PushDirtyTileX
; End list of subroutines to mark dirty tiles
; Range-check and clamp the vertical part of the sprite. When this routine returns we will have valid
; values for the tile-top and row-top. Also, the accumulator will return the number of rows to render,
; a value of zero means that all of the sprite's rows are off-screen.
;
; This subroutine takes are of calculating the extra tile for unaligned accesses, too.
;_SpriteHeight dw 8,8,16,16
;_SpriteHeightMinus1 dw 7,7,15,15
;_SpriteRows dw 1,1,2,2
;_SpriteWidth dw 4,8,4,8
;_SpriteWidthMinus1 dw 3,7,3,7
;_SpriteCols dw 1,2,1,2
; Convert sprite index to a bit position
_SpriteBits dw $0001,$0002,$0004,$0008,$0010,$0020,$0040,$0080,$0100,$0200,$0400,$0800,$1000,$2000,$4000,$8000
_SpriteBitsNot dw $FFFE,$FFFD,$FFFB,$FFF7,$FFEF,$FFDF,$FFBF,$FF7F,$FEFF,$FDFF,$FBFF,$F7FF,$EFFF,$DFFF,$BFFF,$7FFF

View File

@ -1,28 +1,27 @@
; Function to render a sprite from a sprite definition into the internal data buffers
; Alternate entry point that takes arguments in registers instead of using a _Sprite
; record
;
; X = sprite index
_DrawSpriteSheet
; Y = VBUFF address
; X = Tile Data address
; A = Sprite Flags
DISP_VFLIP equ $0004 ; hard code these because they are internal values
DISP_HFLIP equ $0002
DISP_MASK equ $0018 ; Isolate the size bits
DISP_MASK equ $0018 ; Preserve the size bits
phx
lda _Sprites+VBUFF_ADDR,x
sta tmp1
lda _Sprites+TILE_DATA_OFFSET,x
sta tmp2
lda _Sprites+SPRITE_DISP,x
_DrawSpriteStamp
sty tmp1
stx tmp2
xba
and #DISP_MASK ; dispatch to all of the different orientations
sta tmp3
; Set bank
phb
pea #^tiledata ; Set the bank to the tile data
plb
; X = sprite ID
; Y = Tile Data
; A = VBUFF address
ldx tmp3
ldy tmp2
lda tmp1
@ -34,7 +33,7 @@ DISP_MASK equ $0018 ; Isolate the size bits
ldy tmp2
lda tmp1
clc
adc #4*3
adc #3*4
jsr _DrawSprite
lda tmp3
@ -43,7 +42,7 @@ DISP_MASK equ $0018 ; Isolate the size bits
ldy tmp2
lda tmp1
clc
adc #4*6
adc #6*4
jsr _DrawSprite
lda tmp3
@ -52,19 +51,16 @@ DISP_MASK equ $0018 ; Isolate the size bits
ldy tmp2
lda tmp1
clc
adc #4*9
adc #9*4
jsr _DrawSprite
; Restore bank
plb ; pop extra byte
plb
plx
rts
;
; X = _Sprites array offset
_DrawSprite
; ldx _Sprites+SPRITE_DISP,y ; use bits 9, 10, 11, 12 and 13 to dispatch
jmp (draw_sprite,x)
draw_sprite dw draw_8x8,draw_8x8h,draw_8x8v,draw_8x8hv
@ -166,6 +162,9 @@ draw_16x8hv
ply
jmp _DrawTile8x8V
; X = sprite ID
; Y = Tile Data
; A = VBUFF address
draw_16x16
clc
tax
@ -262,7 +261,7 @@ draw_16x16hv
tax
tya
pha
adc #128+{128*32} ; Bottom-right source to top-left
adc #{128*{32+1}}+64 ; Bottom-right source to top-left
tay
jsr _DrawTile8x8V
@ -270,7 +269,7 @@ draw_16x16hv
adc #4
tax
lda 1,s
adc #{128*32}
adc #{128*32}+64
tay
jsr _DrawTile8x8V
@ -278,14 +277,16 @@ draw_16x16hv
adc #{8*SPRITE_PLANE_SPAN}-4
tax
lda 1,s
adc #128
adc #128+64
tay
jsr _DrawTile8x8V
txa
adc #4
tax
ply
pla
adc #64
tay
jmp _DrawTile8x8V

226
src/SpriteV1.s Normal file
View File

@ -0,0 +1,226 @@
; Old code the was in Version 1, but is not needed. May be adapted for Verions 2.
; Y = _Sprites array offset
_EraseSpriteY
lda _Sprites+OLD_VBUFF_ADDR,y
beq :noerase
ldx _Sprites+SPRITE_DISP,y ; get the dispatch index for this sprite (32 values)
jmp (:do_erase,x)
:noerase rts
:do_erase dw _EraseTileSprite8x8,_EraseTileSprite8x8,_EraseTileSprite8x8,_EraseTileSprite8x8
dw _EraseTileSprite8x16,_EraseTileSprite8x16,_EraseTileSprite8x16,_EraseTileSprite8x16
dw _EraseTileSprite16x8,_EraseTileSprite16x8,_EraseTileSprite16x8,_EraseTileSprite16x8
dw _EraseTileSprite16x16,_EraseTileSprite16x16,_EraseTileSprite16x16,_EraseTileSprite16x16
dw _EraseTileSprite8x8,_EraseTileSprite8x8,_EraseTileSprite8x8,_EraseTileSprite8x8
dw _EraseTileSprite8x16,_EraseTileSprite8x16,_EraseTileSprite8x16,_EraseTileSprite8x16
dw _EraseTileSprite16x8,_EraseTileSprite16x8,_EraseTileSprite16x8,_EraseTileSprite16x8
dw _EraseTileSprite16x16,_EraseTileSprite16x16,_EraseTileSprite16x16,_EraseTileSprite16x16
; A = bank address
_EraseTileSprite8x8
tax
phb ; Save the bank to switch to the sprite plane
pei SpriteBanks
plb ; pop the data bank (low byte)
]line equ 0
lup 8
stz: {]line*SPRITE_PLANE_SPAN}+0,x
stz: {]line*SPRITE_PLANE_SPAN}+2,x
]line equ ]line+1
--^
plb ; pop the mask bank (high byte)
lda #$FFFF
]line equ 0
lup 8
sta: {]line*SPRITE_PLANE_SPAN}+0,x
sta: {]line*SPRITE_PLANE_SPAN}+2,x
]line equ ]line+1
--^
plb
rts
_EraseTileSprite8x16
tax
phb ; Save the bank to switch to the sprite plane
pei SpriteBanks
plb ; pop the data bank (low byte)
]line equ 0
lup 16
stz: {]line*SPRITE_PLANE_SPAN}+0,x
stz: {]line*SPRITE_PLANE_SPAN}+2,x
]line equ ]line+1
--^
plb ; pop the mask bank (high byte)
lda #$FFFF
]line equ 0
lup 16
sta: {]line*SPRITE_PLANE_SPAN}+0,x
sta: {]line*SPRITE_PLANE_SPAN}+2,x
]line equ ]line+1
--^
plb
rts
_EraseTileSprite16x8
tax
phb ; Save the bank to switch to the sprite plane
pei SpriteBanks
plb ; pop the data bank (low byte)
]line equ 0
lup 8
stz: {]line*SPRITE_PLANE_SPAN}+0,x
stz: {]line*SPRITE_PLANE_SPAN}+2,x
stz: {]line*SPRITE_PLANE_SPAN}+4,x
stz: {]line*SPRITE_PLANE_SPAN}+6,x
]line equ ]line+1
--^
plb ; pop the mask bank (high byte)
lda #$FFFF
]line equ 0
lup 8
sta: {]line*SPRITE_PLANE_SPAN}+0,x
sta: {]line*SPRITE_PLANE_SPAN}+2,x
sta: {]line*SPRITE_PLANE_SPAN}+4,x
sta: {]line*SPRITE_PLANE_SPAN}+6,x
]line equ ]line+1
--^
plb
rts
_EraseTileSprite16x16
tax
phb ; Save the bank to switch to the sprite plane
pei SpriteBanks
plb ; pop the data bank (low byte)
]line equ 0
lup 16
stz: {]line*SPRITE_PLANE_SPAN}+0,x
stz: {]line*SPRITE_PLANE_SPAN}+2,x
stz: {]line*SPRITE_PLANE_SPAN}+4,x
stz: {]line*SPRITE_PLANE_SPAN}+6,x
]line equ ]line+1
--^
plb ; pop the mask bank (high byte)
lda #$FFFF
]line equ 0
lup 16
sta: {]line*SPRITE_PLANE_SPAN}+0,x
sta: {]line*SPRITE_PLANE_SPAN}+2,x
sta: {]line*SPRITE_PLANE_SPAN}+4,x
sta: {]line*SPRITE_PLANE_SPAN}+6,x
]line equ ]line+1
--^
plb
rts
; First, if there is only one sprite, then we can skip any overhead and do a single lda/and/ora/sta to put the
; sprite data on the screen.
;
; Second, if there are 4 or less, then we "stack" the sprite data using an unrolled loop that allows each
; sprite to just be a single and/ora pair and the final result is not written to any intermediate memory buffer.
;
; Third, if there are 5 or more sprites, then we assume that the sprites are "dense" and that there will be a
; non-trivial amount of overdraw. In this case we do a series of optimized copies of the sprite data *and*
; masks into a direct page buffer in *reverse order*. Once a mask value becomes zero, then nothing else can
; show through and that value can be skipped. Once all of the mask values are zero, then the render is terminated
; and the data buffer copied to the final destination.
;
; Note that these rendering algorithms impose a priority ordering on the sprites where lower sprite IDs are drawn
; underneath higher sprite IDs.
RenderActiveSpriteTiles
cmp #0 ; Is there only one active sprite? If so optimise
bne :many
ldx vbuff ; load the address to the (adjusted) sprite tile
lda TileStore+TS_SCREEN_ADDR,y
tay
lda tiledata+0,y
andl spritemask,x
oral spritedata,x
sta 00,s
lda tiledata+2,y
andl spritemask+2,x
oral spritedata+2,x
sta 02,s
...
tsc
adc #320
tcs
...
lda tiledata+{line*4},y
andl spritemask+{line*SPAN},x
oral spritedata+{line*SPAN},x
sta 160,s
lda tiledata+{line*4}+2,y
andl spritemask+{line*SPAN}+2,x
oral spritedata+{line*SPAN}+2,x
sta 162,s
rts
:many
lda TileStore+TS_SCREEN_ADDR,y
tcs
lda TileStore+TS_TILE_ADDR,y
tay
ldx count
jmp (:arr,x)
lda tiledata+0,y
ldx vbuff
andl spritemask,x
oral spritedata,x
ldx vbuff+2
andl spritemask,x
oral spritedata,x
ldx vbuff+4
andl spritemask,x
oral spritedata,x
...
sta 00,s
ldx count
jmp (:arr,x)
lda tiledata+0,y
ldx vbuff
andl spritemask,x
oral spritedata,x
ldx vbuff+2
andl spritemask,x
oral spritedata,x
ldx vbuff+4
andl spritemask,x
oral spritedata,x
...
sta 02,s
sta 160,s
sta 162,s
tsc
adc #320

View File

@ -10,12 +10,6 @@
; in actual games since the primary background is often large empty areas, or runs
; of repeating tiles.
; Debug locations
LastTop ds 2
LastBottom ds 2
LastLeft ds 2
LastRight ds 2
; The ranges are [:Left, :Right] and [:Top, :Bottom], so :Right can be, at most, 40
; if we are drawing all 41 tiles (Index 0 through 40). The :Bottom value can be
; at most 25.
@ -222,17 +216,6 @@ _UpdateBG0TileMap
:NoXUpdate
rts
;:Debug
; lda :Top ; Debugging
; sta LastTop
; lda :Bottom
; sta LastBottom
; lda :Left
; sta LastLeft
; lda :Right
; sta LastRight
; rts
; This is a private subroutine that draws in tiles into the code fields using the
; data from the tilemap and the local :Top, :Left, :Bottom and :Right parameters.
:DrawRectBG0
@ -639,7 +622,7 @@ _DrawRectBG1
ldx :BlkX
ldy :BlkY
jsr _CopyBG1Tile
; jsr _CopyBG1Tile
lda :BlkX
inc
@ -676,4 +659,3 @@ _DrawRectBG1
pla
rts

628
src/Tiles.s Normal file
View File

@ -0,0 +1,628 @@
; Basic tile functions
; Copy tileset data from a pointer in memory to the tiledata back
; X = high word
; A = low word
_LoadTileSet
sta tmp0
stx tmp1
ldy #0
tyx
:loop lda [tmp0],y
stal tiledata,x
dex
dex
dey
dey
bne :loop
rts
; Low-level function to take a tile descriptor and return the address in the tiledata
; bank. This is not too useful in the fast-path because the fast-path does more
; incremental calculations, but it is handy for other utility functions
;
; A = tile descriptor
;
; The address is the TileID * 128 + (HFLIP * 64)
_GetTileAddr
asl ; Multiply by 2
bit #2*TILE_HFLIP_BIT ; Check if the horizontal flip bit is set
beq :no_flip
inc ; Set the LSB
:no_flip asl ; x4
asl ; x8
asl ; x16
asl ; x32
asl ; x64
asl ; x128
rts
; Ignore the horizontal flip bit
_GetBaseTileAddr
asl ; Multiply by 2
asl ; x4
asl ; x8
asl ; x16
asl ; x32
asl ; x64
asl ; x128
rts
; Helper function to get the address offset into the tile cachce / tile backing store
; X = tile column [0, 40] (41 columns)
; Y = tile row [0, 25] (26 rows)
_GetTileStoreOffset
phx ; preserve the registers
phy
jsr _GetTileStoreOffset0
ply
plx
rts
_GetTileStoreOffset0
tya
asl
tay
txa
asl
clc
adc TileStoreYTable,y
rts
; Initialize the tile storage data structures. This takes care of populating the tile records with the
; appropriate constant values.
InitTiles
:col equ tmp0
:row equ tmp1
:vbuff equ tmp2
:base equ tmp3
; Initialize the Tile Store
ldx #TILE_STORE_SIZE-2
lda #25
sta :row
lda #40
sta :col
lda #$8000
sta :vbuff
:loop
; The first set of values in the Tile Store that are changed during each frame based on the actions
; that are happening
lda #0
sta TileStore+TS_TILE_ID,x ; clear the tile store with the special zero tile
sta TileStore+TS_TILE_ADDR,x
sta TileStore+TS_SPRITE_FLAG,x ; no sprites are set at the beginning
sta TileStore+TS_DIRTY,x ; none of the tiles are dirty
; Set the default tile rendering functions
lda EngineMode
bit #ENGINE_MODE_DYN_TILES+ENGINE_MODE_TWO_LAYER
beq :fast
bit #ENGINE_MODE_TWO_LAYER
beq :dyn
; ldal TileProcs
; sta TileStore+TS_BASE_TILE_DISP,x
bra :out
:fast
lda #0 ; Initialize with Tile 0
ldy #FastProcs
jsr _SetTileProcs
bra :out
:dyn lda #0 ; Initialize with Tile 0
ldy #FastProcs
jsr _SetTileProcs
:out
; lda DirtyTileProcs ; Fill in with the first dispatch address
; stal TileStore+TS_DIRTY_TILE_DISP,x
;
; lda TileProcs ; Same for non-dirty, non-sprite base case
; stal TileStore+TS_BASE_TILE_DISP,x
; The next set of values are constants that are simply used as cached parameters to avoid needing to
; calculate any of these values during tile rendering
lda :row ; Set the long address of where this tile
asl ; exists in the code fields
tay
lda #>TileStore ; get middle 16 bits: "00 -->BBHH<-- LL"
and #$FF00 ; merge with code field bank
ora BRowTableHigh,y
sta TileStore+TS_CODE_ADDR_HIGH,x ; High word of the tile address (just the bank)
lda BRowTableLow,y
sta :base
; sta TileStore+TS_BASE_ADDR,x ; May not be needed later if we can figure out the right constant...
lda :col ; Set the offset values based on the column
asl ; of this tile
asl
sta TileStore+TS_WORD_OFFSET,x ; This is the offset from 0 to 82, used in LDA (dp),y instruction
tay
lda Col2CodeOffset+2,y
clc
adc :base
; adc TileStore+TS_BASE_ADDR,x
sta TileStore+TS_CODE_ADDR_LOW,x ; Low word of the tile address in the code field
lda JTableOffset,y
clc
adc :base
sta TileStore+TS_JMP_ADDR,x ; Address of the snippet handler for this tile
dec :col
bpl :hop
dec :row
lda #40
sta :col
:hop
dex
dex
bpl :loop
rts
; Set a tile value in the tile backing store. Mark dirty if the value changes
;
; A = tile id
; X = tile column [0, 40] (41 columns)
; Y = tile row [0, 25] (26 rows)
;
; Registers are not preserved
oldTileId equ blttmp ; This location is used in _SetTileProcs, too
newTileId equ blttmp+2
procIdx equ blttmp+4
_SetTile
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.
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
stz procIdx
lda #TILE_PRIORITY_BIT
bit newTileId
beq :low_priority
lda #4
sta procIdx
:low_priority
lda #TILE_ID_MASK
bit newTileId
beq :is_zero
lda #2
tsb procIdx
:is_zero
lda #TILE_VFLIP_BIT
bit newTileId
beq :no_vflip
lda #1
tsb procIdx
:no_vflip
; Now integrate with the engine mode indicator
lda EngineMode
bit #ENGINE_MODE_DYN_TILES+ENGINE_MODE_TWO_LAYER
beq :setTileFast
bit #ENGINE_MODE_TWO_LAYER
bne :not_dyn
brl :setTileDyn
:not_dyn
lda #TILE_DYN_BIT
bit newTileId
beq :pickTwoLyrProc
ldy #TwoLyrDynProcs
brl :pickDynProc
:pickTwoLyrProc ldy #TwoLyrProcs
lda procIdx
jsr _SetTileProcs
jmp _PushDirtyTileX
; Specialized check for when the engine is in "Fast" mode. If is a simple decision tree based on whether
; the tile priority bit is set, and whether this is the special tile 0 or not.
:setTileFast
ldy #FastProcs
lda procIdx
jsr _SetTileProcs
jmp _PushDirtyTileX
; Specialized check for when the engine has enabled dynamic tiles. In this case we are no longer
; guaranteed that the opcodes in a tile are PEA instructions.
:setTileDyn
lda #TILE_DYN_BIT
bit newTileId
beq :pickSlowProc ; If the Dynamic bit is not set, select a tile proc that sets opcodes
ldy #DynProcs ; use this table
:pickDynProc
lda newTileId ; Otherwise chose one of the two dynamic tuples
and #TILE_PRIORITY_BIT
beq *+5 ; If the Priority bit is not set, pick the first entry
lda #1 ; If the Priority bit is set, pick the other one
jsr _SetTileProcs
jmp _PushDirtyTileX
:pickSlowProc ldy #SlowProcs
lda procIdx
jsr _SetTileProcs
jmp _PushDirtyTileX
; X = Tile Store offset
; Y = Engine Mode Base Table address
; A = Table proc index
;
; see TileProcTables in static/TileStore.s
tblPtr equ blttmp
_SetTileProcs
; Multiple the proc index by 6 to get the correct table entry offset
asl
sta tblPtr
asl
adc tblPtr
sta tblPtr
; Add this offset to the base table address
tya
adc tblPtr
sta tblPtr
; Set the pointer to this bank
phk
phk
pla
and #$00FF
sta tblPtr+2
; Lookup the tile procedures
ldy #0
lda [tblPtr],y
stal K_TS_BASE_TILE_DISP,x
ldy #2
lda [tblPtr],y
stal K_TS_SPRITE_TILE_DISP,x
ldy #4
lda [tblPtr],y
stal K_TS_ONE_SPRITE,x
rts
; TileProcTables
;
; Tables of tuples used to populate the K_TS_* dispatch arrays for different combinations. This is
; easier to maintain than a bunch of conditional code. Each etry hold three addresses.
;
; First address: Draw a tile directly into the code buffer (no sprites)
; Second address: Draw a tile merged with sprite data from the direct page
; Third address: Specialize routine to draw a tile merged with one sprite
;
; There are unique tuples of routines for all of the different combinations of tile properties
; and engine modes. This is an extesive number of combinations, but it simplified the development
; and maintainence of the rendering subroutines. Also, the difference subroutines can be written
; in any way and can make use of their on subroutines to reduce code size.
;
; Properties:
;
; [MODE] ENGINE_MODE: Fast, Dyn, TwoLayer
; [Z | N] Is Tile 0? : Yes, No
; [A | V] Is VFLIP? : Yes, No
; [Over | Under] Priority? : Yes, No
;
; So eight tuples per engine mode; 24 tuples total. Table name convention
;
; <MODE><Over|Under><Z|N><A|V>
FastProcs
FastOverZA dw ConstTile0Fast,SpriteOver0Fast,OneSpriteFastOver0
FastOverZV dw ConstTile0Fast,SpriteOver0Fast,OneSpriteFastOver0
FastOverNA dw CopyTileAFast,SpriteOverAFast,OneSpriteFastOverA
FastOverNV dw CopyTileVFast,SpriteOverVFast,OneSpriteFastOverV
FastUnderZA dw ConstTile0Fast,SpriteUnder0Fast,SpriteUnder0Fast
FastUnderZV dw ConstTile0Fast,SpriteUnder0Fast,SpriteUnder0Fast
FastUnderNA dw CopyTileAFast,SpriteUnderAFast,OneSpriteFastUnderA
FastUnderNV dw CopyTileVFast,SpriteUnderVFast,OneSpriteFastUnderV
; "Slow" procs. These are duplicates of the "Fast" functions, but also
; set the PEA opcode in all cases.
SlowProcs
SlowOverZA dw ConstTile0Slow,SpriteOver0Slow,OneSpriteSlowOver0
SlowOverZV dw ConstTile0Slow,SpriteOver0Slow,OneSpriteSlowOver0
SlowOverNA dw CopyTileASlow,SpriteOverASlow,OneSpriteSlowOverA
SlowOverNV dw CopyTileVSlow,SpriteOverVSlow,OneSpriteSlowOverV
SlowUnderZA dw ConstTile0Slow,SpriteUnder0Slow,SpriteUnder0Slow
SlowUnderZV dw ConstTile0Slow,SpriteUnder0Slow,SpriteUnder0Slow
SlowUnderNA dw CopyTileASlow,SpriteUnderASlow,OneSpriteSlowUnderA
SlowUnderNV dw CopyTileVSlow,SpriteUnderVSlow,OneSpriteSlowUnderV
; "Dynamic" procs. These are the specialized routines for a dynamic tiles
; that does not need to worry about a second background. Because dynamic
; tiles don't support horizontal or vertical flipping, there are only two
; sets of procedures: one for Over and one for Under.
DynProcs
DynOver dw CopyDynamicTile,DynamicOver,OneSpriteDynamicOver
DynUnder dw CopyDynamicTile,DynamicUnder,OneSpriteDynamicUnder
; "Two Layer" procs. These are the most complex procs. Generally,
; all of these methods are implemented by building up the data
; and mask into the direct page space and then calling a common
; function to create the complex code fragments in the code field.
; There is not a lot of opportuinity to optimize these routines.
;
; To improve the performance when two-layer rendering is enabled,
; the TILE_SOLID_BIT hint bit can be set to indicate that a tile
; has no transparency. This allows one of the faster routines
; to be selected from the other Proc tables
TwoLyrProcs
TwoLyrOverZA dw Tile0TwoLyr,SpriteOver0TwoLyr,OneSpriteOver0TwoLyr
TwoLyrOverZV dw Tile0TwoLyr,SpriteOver0TwoLyr,OneSpriteOver0TwoLyr
TwoLyrOverNA dw CopyTileATwoLyr,SpriteOverATwoLyr,OneSpriteTwoLyrOverA
TwoLyrOverNV dw CopyTileVTwoLyr,SpriteOverVTwoLyr,OneSpriteTwoLyrOverV
TwoLyrUnderZA dw Tile0TwoLyr,SpriteOver0TwoLyr,OneSpriteOver0TwoLyr ; if sprites are over or under the transparent tile, same rendering code
TwoLyrUnderZV dw Tile0TwoLyr,SpriteOver0TwoLyr,OneSpriteOver0TwoLyr
TwoLyrUnderNA dw CopyTileATwoLyr,SpriteUnderATwoLyr,OneSpriteTwoLyrUnderA
TwoLyrUnderNV dw CopyTileVTwoLyr,SpriteUnderVTwoLyr,OneSpriteTwoLyrUnderV
; "Dynamic" procs that can handle the second background.
TwoLyrDynProcs
TwoLyrDynOver dw CopyDynamicTileTwoLyr,DynamicOverTwoLyr,OneSpriteDynamicOverTwoLyr
TwoLyrDynUnder dw CopyDynamicTileTwoLyr,DynamicUnderTwoLyr,OneSpriteDynamicUnderTwoLyr
; SetBG0XPos
;
; Set the virtual horizontal position of the primary background layer. In addition to
; updating the direct page state locations, this routine needs to preserve the original
; value as well. This is a bit subtle, because if this routine is called multiple times
; with different values, we need to make sure the *original* value is preserved and not
; continuously overwrite it.
;
; We assume that there is a clean code field in this routine
_SetBG0XPos
cmp StartX
beq :out ; Easy, if nothing changed, then nothing changes
ldx StartX ; Load the old value (but don't save it yet)
sta StartX ; Save the new position
lda #DIRTY_BIT_BG0_X
tsb DirtyBits ; Check if the value is already dirty, if so exit
bne :out ; without overwriting the original value
stx OldStartX ; First change, so preserve the value
:out rts
; SetBG0YPos
;
; Set the virtual position of the primary background layer.
_SetBG0YPos
cmp StartY
beq :out ; Easy, if nothing changed, then nothing changes
ldx StartY ; Load the old value (but don't save it yet)
sta StartY ; Save the new position
lda #DIRTY_BIT_BG0_Y
tsb DirtyBits ; Check if the value is already dirty, if so exit
bne :out ; without overwriting the original value
stx OldStartY ; First change, so preserve the value
:out rts
; Macro helper for the bit test tree
; dobit bit_position,dest;next;exit
dobit mac
lsr
bcc next_bit
beq last_bit
tax
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
txa
jmp ]3
last_bit lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
jmp ]4
next_bit
<<<
; Specialization for the first sprite which can optimize its dispatch if its the only one
; dobit bit_position,dest;next;exit
dobit1 mac
lsr
bcc next_bit
beq last_bit
tax
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
txa
jmp ]3
last_bit lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
tyx
jmp (K_TS_ONE_SPRITE,x)
next_bit
<<<
; If we find a last bit (4th in this case) and will exit
stpbit mac
lsr
bcc next_bit
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
jmp ]3
next_bit
<<<
; Last bit test which *must* be set
endbit mac
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
jmp ]3
<<<
endbit1 mac
lda (SPRITE_VBUFF_PTR+{]1*2}),y
clc ; pre-adjust these later
adc _Sprites+TS_VBUFF_BASE+{]1*2}
sta sprite_ptr0+{]2*4}
tyx
jmp (K_TS_ONE_SPRITE,x)
<<<
; OPTIMIZATION:
;
; bit #$00FF ; Skip the first 8 bits if they are all zeros
; bne norm_entry
; xba
; jmp skip_entry
;
; Placed at the entry point
; This is a complex, but fast subroutine that is called from the core tile rendering code. It
; Takes a bitmap of sprites in the Accumulator and then extracts the VBuff addresses for the
; target TileStore entry and places them in specific direct page locations.
;
; Inputs:
; A = sprite bitmap (assumed to be non-zero)
; Y = tile store index
; D = second work page
; B = vbuff array bank
; Output:
; X =
;
; ]1 address of single sprite process
; ]2 address of two sprite process
; ]3 address of three sprite process
; ]4 address of four sprite process
SpriteBitsToVBuffAddrs mac
dobit1 0;0;b_1_1
dobit1 1;0;b_2_1
dobit1 2;0;b_3_1
dobit1 3;0;b_4_1
dobit1 4;0;b_5_1
dobit1 5;0;b_6_1
dobit1 6;0;b_7_1
dobit1 7;0;b_8_1
dobit1 8;0;b_9_1
dobit1 9;0;b_10_1
dobit1 10;0;b_11_1
dobit1 11;0;b_12_1
dobit1 12;0;b_13_1
dobit1 13;0;b_14_1
dobit1 14;0;b_15_1
endbit1 15;0
b_1_1 dobit 1;1;b_2_2;]2
b_2_1 dobit 2;1;b_3_2;]2
b_3_1 dobit 3;1;b_4_2;]2
b_4_1 dobit 4;1;b_5_2;]2
b_5_1 dobit 5;1;b_6_2;]2
b_6_1 dobit 6;1;b_7_2;]2
b_7_1 dobit 7;1;b_8_2;]2
b_8_1 dobit 8;1;b_9_2;]2
b_9_1 dobit 9;1;b_10_2;]2
b_10_1 dobit 10;1;b_11_2;]2
b_11_1 dobit 11;1;b_12_2;]2
b_12_1 dobit 12;1;b_13_2;]2
b_13_1 dobit 13;1;b_14_2;]2
b_14_1 dobit 14;1;b_15_2;]2
b_15_1 endbit 15;1;]2
b_2_2 dobit 2;2;b_3_3;]3
b_3_2 dobit 3;2;b_4_3;]3
b_4_2 dobit 4;2;b_5_3;]3
b_5_2 dobit 5;2;b_6_3;]3
b_6_2 dobit 6;2;b_7_3;]3
b_7_2 dobit 7;2;b_8_3;]3
b_8_2 dobit 8;2;b_9_3;]3
b_9_2 dobit 9;2;b_10_3;]3
b_10_2 dobit 10;2;b_11_3;]3
b_11_2 dobit 11;2;b_12_3;]3
b_12_2 dobit 12;2;b_13_3;]3
b_13_2 dobit 13;2;b_14_3;]3
b_14_2 dobit 14;2;b_15_3;]3
b_15_2 endbit 15;2;]3
b_3_3 stpbit 3;3;]4
b_4_3 stpbit 4;3;]4
b_5_3 stpbit 5;3;]4
b_6_3 stpbit 6;3;]4
b_7_3 stpbit 7;3;]4
b_8_3 stpbit 8;3;]4
b_9_3 stpbit 9;3;]4
b_10_3 stpbit 10;3;]4
b_11_3 stpbit 11;3;]4
b_12_3 stpbit 12;3;]4
b_13_3 stpbit 13;3;]4
b_14_3 stpbit 14;3;]4
b_15_3 endbit 15;3;]4
<<<
; Store some tables in the K bank that will be used exclusively for jmp (abs,x) dispatch
K_TS_BASE_TILE_DISP ds TILE_STORE_SIZE ; draw the tile without a sprite
K_TS_COPY_TILE_DATA ds TILE_STORE_SIZE ; copy/merge the tile into temp storage
K_TS_SPRITE_TILE_DISP ds TILE_STORE_SIZE ; select the sprite routine for this tile
K_TS_ONE_SPRITE ds TILE_STORE_SIZE ; specialized sprite routine when only one sprite covers the tile
K_TS_APPLY_TILE_DATA ds TILE_STORE_SIZE ; move tile from temp storage into code field

View File

@ -1,37 +1,5 @@
; Timer implementation
;
; The engire provides four timer slot that can be used by one-shot or
; recurring timers. Each timer is given an initial tick count, a
; reset tick count (0 = one-shot), and an action to perform.
;
; The timers handle overflow, so if a recurring timer has a tick count of 3
; and 7 VBL ticks have passed, then the timer will be fired twice and
; a tick count of 2 will be set.
;
; As such, the timers are appropriate to drive physical and other game
; behaviors at a frame-independent rate.
;
; A collection of 4 timers that are triggered when their countdown
; goes below zero. Each timer takes up 16 bytes
;
; A timer can fire multiple times during a singular evaluation. For example, if the
; timer delay is set to 1 and 3 VBL ticks happen, then the timer delta is -2, will fire,
; have the delay added and get -1, fire again, increment to zero, first again and then
; finally reset to 1.
;
; +0 counter decremented by the number of ticks since last run
; +2 reset copied into counter when triggered. 0 turns off the timer.
; +4 addr long address of timer routine
; +8 user 8 bytes of user data space for timer state
MAX_TIMERS equ 4
TIMER_REC_SIZE equ 16
mx %00
lastTick ds 2
Timers ds TIMER_REC_SIZE*MAX_TIMERS
GetVBLTicks ENT
jsr _GetVBLTicks
rtl
_GetVBLTicks
PushLong #0
_GetTick
@ -42,7 +10,7 @@ _GetVBLTicks
; Initialize the timers
InitTimers
jsr _GetVBLTicks
sta lastTick
sta LastTick
lda #0
ldx #{TIMER_REC_SIZE*MAX_TIMERS}-2
@ -63,17 +31,12 @@ InitTimers
; Return
; C = 0 if success, 1 if no timer slots are available
; A = timer slot ID if C = 0
AddTimer ENT
phb
_AddTimer
php ; Save the input parameters
phx
pha
phy
phk
plb
ldx #0
:loop lda Timers,x ; If the counter is zero, timer is free
beq :freeslot
@ -105,28 +68,23 @@ AddTimer ENT
lda Timers+0,x ; if not a one-shot, put the counter
sta Timers+2,x ; value into the reset field
:oneshot plb
txa ; return the slot ID and a success status
:oneshot txa ; return the slot ID and a success status
clc
rtl
rts
:notimers ply
pla
plx
plp
plb
sec ; Return an error status
lda #0
rtl
rts
; Small function to remove a timer
;
; A = Timer ID
RemoveTimer ENT
phb
phk
plb
_RemoveTimer
cmp #{TIMER_REC_SIZE*{MAX_TIMERS-1}}+1
bcs :exit
@ -137,38 +95,51 @@ RemoveTimer ENT
stz Timers+6,x
:exit
plb
rtl
rts
; Execute the timer functions
DoTimers ENT
phb
phk
plb
;DoTimers ENT
; phb
; jsr _SetDataBank
;
; jsr _GetVBLTicks
;
; cmp LastTick ; Throttle to 60 fps
; beq :exit
; tax ; Calculate the increment
; sec
; sbc LastTick
; stx LastTick
jsr _GetVBLTicks
cmp lastTick ; Throttle to 60 fps
beq :exit
tax ; Calculate the increment
sec
sbc lastTick
stx lastTick
; We don't want times to fire excessively. If the timer has nt been evaluated for over
; We don't want times to fire excessively. If the timer hasn't been evaluated for over
; one second, then just skip processing and wait for the next call.
cmp #60
bcs :exit
; cmp #60
; bcs :exit
jsr _DoTimers
; jsr _DoTimers
:exit plb
rtl
;:exit plb
; rtl
; Countdown the timers
;
; A = number of elapsed ticks
_DoTimers
jsr _GetVBLTicks
cmp LastTick ; Throttle to 60 fps
beq :exit
tax ; Calculate the increment
sec
sbc LastTick
stx LastTick
; We don't want times to fire excessively. If the timer hasn't been evaluated for over
; one second, then just skip processing and wait for the next call.
cmp #60
bcc :do_timer
:exit rts
:do_timer
pha
ldx #0
:loop
@ -186,9 +157,9 @@ _DoTimers
phx ; Save our index
lda Timers+4,x ; execute the timer callback
sta :dispatch+1
stal :dispatch+1
lda Timers+5,x
sta :dispatch+2
stal :dispatch+2
:dispatch jsl $000000
plx

View File

@ -3,10 +3,47 @@
; Ref: Toolbox Reference, Volume 2, Appendix A
; Ref: IIgs Tech Note #73
use Mem.Macs.s
use Misc.Macs.s
use Util.Macs
use Locator.Macs
use Core.MACS.s
use Defs.s
use static/TileStoreDefs.s
ToStrip equ $E10184
; Define some macros to help streamline the entry and exit from the toolbox calls
_TSEntry mac
phd
phb
tcd
jsr _SetDataBank
<<<
_TSExit mac
plb
pld
ldx ]1 ; Error code
ldy ]2 ; Number of stack bytes to remove
jml ToStrip
<<<
_TSExit1 mac
plb
pld
; ldx ]1 ; Error code already in X
ldy ]1 ; Number of stack bytes to remove
jml ToStrip
<<<
FirstParam equ 10 ; When using the _TSEntry macro, the first parameter is at 10,s
mx %00
_CallTable
dw {_CTEnd-_CallTable}/4,0
adrl {_CTEnd-_CallTable}/4
adrl _TSBootInit-1
adrl _TSStartUp-1
adrl _TSShutDown-1
@ -16,8 +53,63 @@ _CallTable
adrl _TSReserved-1
adrl _TSReserved-1
adrl _TSSetScreenMode
adrl _TSReadControl-1
adrl _TSSetScreenMode-1
adrl _TSSetTile-1
adrl _TSSetBG0Origin-1
adrl _TSRender-1
adrl _TSLoadTileSet-1
adrl _TSCreateSpriteStamp-1
adrl _TSAddSprite-1
adrl _TSMoveSprite-1
adrl _TSUpdateSprite-1
adrl _TSRemoveSprite-1
adrl _TSGetSeconds-1
adrl _TSCopyTileToDynamic-1
adrl _TSSetPalette-1
adrl _TSCopyPicToBG1-1
adrl _TSBindSCBArray-1
adrl _TSGetBG0TileMapInfo-1
adrl _TSGetScreenInfo-1
adrl _TSSetBG1Origin-1
adrl _TSGetTileAt-1
adrl _TSSetBG0TileMapInfo-1
adrl _TSSetBG1TileMapInfo-1
adrl _TSAddTimer-1
adrl _TSRemoveTimer-1
adrl _TSStartScript-1
adrl _TSSetOverlay-1
adrl _TSClearOverlay-1
adrl _TSGetTileDataAddr-1
adrl _TSFillTileStore-1
adrl _TSRefresh-1
_CTEnd
_GTEAddSprite MAC
UserTool $1000+GTEToolNum
<<<
_GTEMoveSprite MAC
UserTool $1100+GTEToolNum
<<<
_GTEUpdateSprite MAC
UserTool $1200+GTEToolNum
<<<
_GTERemoveSprite MAC
UserTool $1300+GTEToolNum
<<<
; Helper function to set the data back to the toolset default
_SetDataBank sep #$20
lda #^TileStore
pha
plb
rep #$20
rts
; Do nothing when the tool set is installed
_TSBootInit
@ -25,40 +117,66 @@ _TSBootInit
clc
rtl
; Call the regular GTE startup function after setting the Work Area Point (WAP). The caller much provide
; one page of Bank 0 memory for the tool set's private use
; Call the regular GTE startup function after setting the Work Area Pointer (WAP). The caller must provide
; one page of Bank 0 memory for the tool set's private use and a userId to use for allocating memory
;
; X =
; X = tool set number in low byte and function number in high byte
;
; StartUp(dPageAddr, capFlags, userId)
_TSStartUp
:zpToUse equ 7
pea #$8000
userId = 7
capFlags = userId+2
zpToUse = userId+4
lda zpToUse,s ; Get the direct page address
phd ; Save the current direct page
tcd ; Set to our working direct page space
txa
and #$00FF
pha
and #$00FF ; Get just the tool number
sta ToolNum
pea $0000
lda :zpToUse+6,s
pha
lda userId+2,s ; Get the userId for memory allocations
sta UserId
lda capFlags+2,s ; Get the engine capability bits
sta EngineMode
phb
jsr _SetDataBank
jsr _CoreStartUp ; Initialize the library
plb
; SetWAP(userOrSystem, tsNum, waptPtr)
pea #$8000 ; $8000 = user tool set
pei ToolNum ; Push the tool number from the direct page
pea $0000 ; High word of WAP is zero (bank 0)
phd ; Low word of WAP is the direct page
_SetWAP
jsr _CoreStartUp
pld ; Restore the caller's direct page
ldx #0 ; No error
ldy #6 ; Remove the 6 input bytes
jml ToStrip
; ShutDown()
_TSShutDown
cmp #0 ; Acc is low word of the WAP (direct page)
beq :inactive
phd
pha
pld ; Set the direct page for the toolset
tcd ; Set the direct page for the toolset
phx ; Preserve the X register
jsr _CoreShutDown ; Shut down GTE
pla
phb
jsr _SetDataBank
jsr _CoreShutDown ; Shut down the library
plb
pea $8000
and #$00FF
pha
pei ToolNum
pea $0000 ; Set WAP to null
pea $0000
_SetWAP
@ -71,7 +189,7 @@ _TSShutDown
rtl
_TSVersion
lda #$0100 ; Version 1
lda #$0100 ; Version 1
sta 7,s
lda #0
@ -88,7 +206,7 @@ _TSStatus
sta 1,s
tya
ora 1,s
sta 1,s ; 0 if WAP is null, non-zero if WAP is set
sta 1,s ; 0 if WAP is null, non-zero if WAP is set
lda #0
clc
@ -101,31 +219,523 @@ _TSReserved
sec
rtl
; SetScreenMode(width, height)
_TSSetScreenMode
phd ; Preserve the direct page
pha
pld
height equ FirstParam
width equ FirstParam+2
lda 9,s
_TSEntry
lda height,s
tay
lda 9,s
lda width,s
tax
jsr _SetScreenMode
pld
ldx #0 ; No error
ldy #4 ; Remove the 4 input bytes
jml ToStrip
_TSExit #0;#4
; ReadControl()
_TSReadControl
phd ; Preserve the direct page
pha
pld
:output equ FirstParam
_TSEntry
jsr _ReadControl
sta 9,s
sta :output,s
pld
ldx #0 ; No error
ldy #0 ; Remove zero input bytes
jml ToStrip
_TSExit #0;#0
; 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
; SetBG0Origin(x, y)
_TSSetBG0Origin
yPos equ FirstParam
xPos equ FirstParam+2
_TSEntry
lda xPos,s
jsr _SetBG0XPos
lda yPos,s
jsr _SetBG0YPos
_TSExit #0;#4
; Render()
_TSRender
_TSEntry
jsr _Render
_TSExit #0;#0
; LoadTileSet(Pointer)
_TSLoadTileSet
TSPtr equ FirstParam
_TSEntry
lda TSPtr+2,s
tax
lda TSPtr,s
jsr _LoadTileSet
_TSExit #0;#4
; CreateSpriteStamp(spriteId: Word, vbuffAddr: Word)
_TSCreateSpriteStamp
:vbuff equ FirstParam
:spriteId equ FirstParam+2
_TSEntry
lda :vbuff,s
tay
lda :spriteId,s
jsr _CreateSpriteStamp
_TSExit #0;#4
_TSAddSprite
:spriteSlot equ FirstParam+0
:spriteY equ FirstParam+2
:spriteX equ FirstParam+4
:spriteId equ FirstParam+6
_TSEntry
lda :spriteY,s
and #$00FF
xba
sta :spriteY,s
lda :spriteX,s
and #$00FF
ora :spriteY,s
tay
lda :spriteSlot,s
tax
lda :spriteId,s
jsr _AddSprite
_TSExit #0;#8
_TSMoveSprite
:spriteY equ FirstParam+0
:spriteX equ FirstParam+2
:spriteSlot equ FirstParam+4
_TSEntry
lda :spriteX,s
tax
lda :spriteY,s
tay
lda :spriteSlot,s
jsr _MoveSprite
_TSExit #0;#6
_TSUpdateSprite
:vbuff equ FirstParam+0
:spriteFlags equ FirstParam+2
:spriteSlot equ FirstParam+4
_TSEntry
lda :spriteFlags,s
tax
lda :vbuff,s
tay
lda :spriteSlot,s
jsr _UpdateSprite
_TSExit #0;#6
_TSRemoveSprite
:spriteSlot equ FirstParam+0
_TSEntry
lda :spriteSlot,s
jsr _RemoveSprite
_TSExit #0;#2
_TSGetSeconds
:output equ FirstParam
_TSEntry
ldal OneSecondCounter
sta :output,s
_TSExit #0;#0
_TSCopyTileToDynamic
:dynId equ FirstParam+0
:tileId equ FirstParam+2
_TSEntry
lda EngineMode
bit #ENGINE_MODE_DYN_TILES
beq :notEnabled
lda :tileId,s
tax
lda :dynId,s
tay
jsr CopyTileToDyn
:notEnabled
_TSExit #0;#4
; SetPalette(palNum, Pointer)
_TSSetPalette
:ptr equ FirstParam+0
:palNum equ FirstParam+4
_TSEntry
phb
lda :ptr+3,s ; add one extra byte for the phb
xba
pha
plb
plb
lda :ptr+1,s
tax
lda :palNum+1,s
jsr _SetPalette
plb
_TSExit #0;#6
_TSCopyPicToBG1
:ptr equ FirstParam+0
_TSEntry
lda BG1DataBank
tay
lda :ptr+2,s
tax
lda :ptr,s
jsr _CopyPicToBG1
_TSExit #0;#4
_TSBindSCBArray
:ptr equ FirstParam+0
_TSEntry
lda :ptr,s
tax
lda :ptr+2,s
jsr _BindSCBArray
_TSExit #0;#4
_TSGetBG0TileMapInfo
:ptr equ FirstParam+4
:height equ FirstParam+2
:width equ FirstParam+0
_TSEntry
lda TileMapWidth
sta :width,s
lda TileMapHeight
sta :height,s
lda TileMapPtr
sta :ptr,s
lda TileMapPtr+2
sta :ptr+2,s
_TSExit #0;#0
_TSGetScreenInfo
:height equ FirstParam+6
:width equ FirstParam+4
:y equ FirstParam+2
:x equ FirstParam+0
_TSEntry
lda ScreenX0
sta :x,s
lda ScreenY0
sta :y,s
lda ScreenWidth
sta :width,s
lda ScreenHeight
sta :height,s
_TSExit #0;#0
; SetBG1Origin(x, y)
_TSSetBG1Origin
:y equ FirstParam
:x equ FirstParam+2
_TSEntry
lda :x,s
jsr _SetBG1XPos
lda :y,s
jsr _SetBG1YPos
_TSExit #0;#4
; GetTileAt(x, y)
_TSGetTileAt
:y equ FirstParam
:x equ FirstParam+2
:output equ FirstParam+4
_TSEntry
; Convert the x, y coordinated to tile store block coordinates
lda :x,s
tax
lda :y,s
tay
jsr _GetTileAt
bcc :ok
lda #0
bra :out
; Load the tile at that tile store location
:ok
jsr _GetTileStoreOffset0 ; Get the address of the X,Y tile position
tax
lda TileStore+TS_TILE_ID,x
:out
sta :output,s
_TSExit #0;#4
; SetBG0TileMapInfo(width, height, ptr)
_TSSetBG0TileMapInfo
:ptr equ FirstParam+0
:height equ FirstParam+4
:width equ FirstParam+6
_TSEntry
lda :width,s
sta TileMapWidth
lda :height,s
sta TileMapHeight
lda :ptr,s
sta TileMapPtr
lda :ptr+2,s
sta TileMapPtr+2
lda #DIRTY_BIT_BG0_REFRESH ; force a refresh of the BG0 on the next Render
tsb DirtyBits
_TSExit #0;#8
; SetBG1TileMapInfo(width, height, ptr)
_TSSetBG1TileMapInfo
:ptr equ FirstParam+0
:height equ FirstParam+4
:width equ FirstParam+6
_TSEntry
lda :width,s
sta BG1TileMapWidth
lda :height,s
sta BG1TileMapHeight
lda :ptr,s
sta BG1TileMapPtr
lda :ptr+2,s
sta TileMapPtr+2
_TSExit #0;#8
; AddTimer(numTicks, callback, flags)
_TSAddTimer
:flags equ FirstParam+0
:callback equ FirstParam+2
:numTicks equ FirstParam+6
:output equ FirstParam+8
_TSEntry
lda :callback+2,s
tax
lda :callback,s
tay
lda :flags,s
lsr ; put low bit into carry
lda :numTicks,s
jsr _AddTimer
sta :output,s
ldx #0
bcc :no_err
ldx #NO_TIMERS_AVAILABLE
:no_err
_TSExit1 #8
; RemoveTimer(timerId)
_TSRemoveTimer
:timerId equ FirstParam+0
_TSEntry
lda :timerId,s
jsr _RemoveTimer
_TSExit #0;#2
; StartScript(timerId)
_TSStartScript
:scriptAddr equ FirstParam+0
:numTicks equ FirstParam+4
_TSEntry
lda :numTicks,s
tay
lda :scriptAddr+2,s
tax
lda :scriptAddr,s
jsr _StartScript
_TSExit #0;#6
; SetOverlay(top, bottom, proc)
_TSSetOverlay
:proc equ FirstParam+0
:bottom equ FirstParam+4
:top equ FirstParam+6
_TSEntry
lda #1
sta Overlays
lda :top,s
sta Overlays+2
lda :bottom,s
sta Overlays+4
lda :proc,s
sta Overlays+6
lda :proc+2,s
sta Overlays+8
_TSExit #0;#8
; ClearOverlay()
_TSClearOverlay
_TSEntry
lda #0
sta Overlays
_TSExit #0;#0
; GetTileDataAddr()
_TSGetTileDataAddr
:output equ FirstParam+0
_TSEntry
lda #tiledata
sta :output,s
lda #^tiledata
sta :output+2,s
_TSExit #0;#0
; FillTileStore(tileId)
_TSFillTileStore
:tileId equ FirstParam+0
_TSEntry
stz tmp0
:oloop
stz tmp1
:iloop
ldx tmp1
ldy tmp0
lda :tileId,s
jsr _SetTile
lda tmp1
inc
sta tmp1
cmp #TILE_STORE_WIDTH
bcc :iloop
lda tmp0
inc
sta tmp0
cmp #TILE_STORE_HEIGHT
bcc :oloop
_TSExit #0;#2
; _TSRefresh()
_TSRefresh
_TSEntry
ldx #TILE_STORE_SIZE-2
:loop jsr _PushDirtyTileX
dex
dex
bpl :loop
_TSExit #0;#0
; Insert the GTE code
put Math.s
put CoreImpl.s
put Memory.s
put Timer.s
put Script.s
put TileMap.s
put Graphics.s
put Tiles.s
put Sprite.s
put Sprite2.s
put SpriteRender.s
put Render.s
put render/Render.s
put render/Fast.s
put render/Slow.s
put render/Dynamic.s
put render/TwoLayer.s
put render/Sprite1.s
put render/Sprite2.s
put tiles/DirtyTileQueue.s
put blitter/SCB.s
put blitter/Horz.s
put blitter/Vert.s
put blitter/BG0.s
put blitter/BG1.s
put blitter/Template.s
put blitter/TemplateUtils.s
put blitter/Blitter.s
put blitter/TileProcs.s
put blitter/Tiles00000.s
; put blitter/Tiles.s

1
src/_FileInformation.txt Normal file
View File

@ -0,0 +1 @@
Tool160=Type(BA),AuxType(0000),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)

View File

@ -1,4 +1,4 @@
; Support routinges for the primary background
; Support routines for the primary background
_InitBG0
lda #DIRTY_BIT_BG0_X+DIRTY_BIT_BG0_Y
tsb DirtyBits
@ -8,14 +8,6 @@ _InitBG0
;
; A=low word of picture address
; X=high word of pixture address
CopyBinToField ENT
phb
phk
plb
jsr _CopyBinToField
plb
rtl
_CopyBinToField
:srcptr equ tmp0
:line_cnt equ tmp2
@ -222,14 +214,6 @@ _CopyBinToField
; X=high workd of pixture address
;
; Picture must be within one bank
CopyPicToField ENT
phb
phk
plb
jsr _CopyPicToField
plb
rtl
_CopyPicToField
:srcptr equ tmp0
:line_cnt equ tmp2

View File

@ -4,20 +4,11 @@ _InitBG1
jsr _ApplyBG1XPos
rts
; Copy a binary image data file into BG1. Assumes the file is the correct size (328 x 208)
;
; A=low word of picture address
; X=high word of pixture address
; Y=high word of BG1 bank
CopyBinToBG1 ENT
phb
phk
plb
jsr _CopyBinToBG1
plb
rtl
_CopyBinToBG1
:src_width equ tmp6
:src_height equ tmp7
@ -40,14 +31,6 @@ _CopyBinToBG1
; A=low word of picture address
; X=high word of pixture address
; Y=high word of BG1 bank
CopyPicToBG1 ENT
phb
phk
plb
jsr _CopyPicToBG1
plb
rtl
_CopyPicToBG1
:src_width equ tmp6
:src_height equ tmp7
@ -193,59 +176,6 @@ _ApplyBG1XPos
pld
rts
ANGLEBNK ext
ApplyBG1XPosAngle ENT
phb
phk
plb
jsr _ApplyBG1XPosAngle
plb
rtl
_ApplyBG1XPosAngle
; phy
; lda BG1StartX
; jsr Mod164
; sta BG1StartXMod164
; lda #162
; sec
; sbc StartXMod164
; bpl *+6
; clc
; adc #164
; clc
; adc BG1StartXMod164
; cmp #164
; bcc *+5
; sbc #164
; clc
; adc 1,s
; tay ; cache the value
; pla ; pop the value
phd ; save the direct page because we are going to switch to the
lda BlitterDP ; blitter direct page space and fill in the addresses
tcd
lda #^ANGLEBNK
sta $fe
sty $fc ; Store in the new direct page
ldy #162
tyx
:loop
lda [$fc],y
sta 00,x ; store the value
dey
dey
dex
dex
bpl :loop
pld
rts
_ClearBG1Buffer
phb
pha
@ -266,88 +196,6 @@ _ClearBG1Buffer
plb
rts
ApplyBG1YPosAngle ENT
phb
phk
plb
jsr _ApplyBG1YPosAngle
plb
rtl
_ApplyBG1YPosAngle
:virt_line equ tmp0
:lines_left equ tmp1
:draw_count equ tmp2
:ytbl_idx equ tmp3
:angle_tbl equ tmp4
sty :angle_tbl
lda BG1StartY
jsr Mod208
sta BG1StartYMod208
sta :ytbl_idx ; Start copying from the first entry in the table
lda StartYMod208 ; This is the base line of the virtual screen
sta :virt_line ; Keep track of it
lda ScreenHeight
sta :lines_left
:loop
lda :virt_line
asl
tax
ldal BTableLow,x ; Get the address of the first code field line
tay
sep #$20
ldal BTableHigh,x
pha ; push the bank on the stack
plb
rep #$20
lda :virt_line
and #$000F
eor #$FFFF
inc
clc
adc #16
min :lines_left
sta :draw_count ; Do this many lines
asl
tax
lda :ytbl_idx ; Read from this location (duplicate every 4 lines)
lsr
lsr
asl
clc
adc :angle_tbl
sec
sbc #ANGLEBNK
jsr CopyAngleYTableToBG1Addr ; or CopyBG1YTableToBG1Addr2
lda :virt_line ; advance to the virtual line after the segment we just
clc ; filled in
adc :draw_count
sta :virt_line
lda :ytbl_idx ; advance the index into the YTable
adc :draw_count
sta :ytbl_idx
lda :lines_left ; subtract the number of lines we just completed
sec
sbc :draw_count
sta :lines_left
jne :loop
phk
plb
rts
; Everytime either BG1 or BG0 Y-position changes, we have to update the Y-register
; value in all of the code fields (within the visible screen)
@ -357,6 +205,8 @@ _ApplyBG1YPos
:draw_count equ tmp2
:ytbl_idx equ tmp3
phb ; Save the bank
lda BG1StartY
jsr Mod208
sta BG1StartYMod208
@ -413,7 +263,6 @@ _ApplyBG1YPos
jne :loop
phk
plb
rts
@ -494,97 +343,6 @@ CopyBG1YTableToBG1Addr
sta: BG1_ADDR+$0000,y
:none rts
; Unrolled copy routine to move y_angle entries into BG1_ADDR position with an additional
; shift. This has to be split into two
;
; A = index into the array (x2)
; Y = starting line * $1000
; X = number of lines (x2)
CopyAngleYTableToBG1Addr
phx
phb
phk ; restore access to this bank
plb
jsr SaveBG1AngleValues
plb
plx ; x is used directly in this routine
jsr ApplyBG1OffsetValues
rts
SaveBG1AngleValues
jmp (:tbl,x)
:tbl da :none
da :do01,:do02,:do03,:do04
da :do05,:do06,:do07,:do08
da :do09,:do10,:do11,:do12
da :do13,:do14,:do15,:do16
:do15 tax
bra :x15
:do14 tax
bra :x14
:do13 tax
bra :x13
:do12 tax
bra :x12
:do11 tax
bra :x11
:do10 tax
bra :x10
:do09 tax
bra :x09
:do08 tax
bra :x08
:do16 tax
ldal ANGLEBNK+06,x
sta BG1YCache+30
:x15 ldal ANGLEBNK+06,x
sta BG1YCache+28
:x14 ldal ANGLEBNK+06,x
sta BG1YCache+26
:x13 ldal ANGLEBNK+06,x
sta BG1YCache+24
:x12 ldal ANGLEBNK+04,x
sta BG1YCache+22
:x11 ldal ANGLEBNK+04,x
sta BG1YCache+20
:x10 ldal ANGLEBNK+04,x
sta BG1YCache+18
:x09 ldal ANGLEBNK+04,x
sta BG1YCache+16
:x08 ldal ANGLEBNK+02,x
sta BG1YCache+14
:x07 ldal ANGLEBNK+02,x
sta BG1YCache+12
:x06 ldal ANGLEBNK+02,x
sta BG1YCache+10
:x05 ldal ANGLEBNK+02,x
sta BG1YCache+08
:x04 ldal ANGLEBNK+00,x
sta BG1YCache+06
:x03 ldal ANGLEBNK+00,x
sta BG1YCache+04
:x02 ldal ANGLEBNK+00,x
sta BG1YCache+02
:x01 ldal ANGLEBNK+00,x
sta BG1YCache+00
:none rts
:do07 tax
bra :x07
:do06 tax
bra :x06
:do05 tax
bra :x05
:do04 tax
bra :x04
:do03 tax
bra :x03
:do02 tax
bra :x02
:do01 tax
bra :x01
; Unrolled copy routine to move BG1YTable entries into BG1_ADDR position with an additional
; shift. This has to be split into two
;
@ -596,16 +354,14 @@ CopyBG1YTableToBG1Addr2
phx
phb
phk ; restore access to this bank
plb
jsr _SetDataBank ; restore access to this bank
ldy BG1OffsetIndex ; Get the offset and save the values
jsr SaveBG1OffsetValues
plb
plx ; x is used directly in this routine
ply
jsr ApplyBG1OffsetValues
rts
jmp ApplyBG1OffsetValues
SaveBG1OffsetValues
jmp (:tbl,x)
@ -738,7 +494,3 @@ ApplyBG1OffsetValues
:none rts
BG1YCache ds 32

View File

@ -18,7 +18,7 @@ _BltRange
dey
tya ; Get the address of the line that we want to return from
adc StartY ; and create a pointer to it
adc StartYMod208 ; and create a pointer to it
asl
tay
lda BTableLow,y
@ -27,16 +27,16 @@ _BltRange
sta :exit_ptr+2
txa ; get the first line (0 - 199)
adc StartY ; add in the virtual offset (0, 207) -- max value of 406
adc StartYMod208 ; add in the virtual offset (0, 207) -- max value of 406
asl
tax ; this is the offset into the blitter table
sep #$20 ; 8-bit Acc
lda BTableHigh,x ; patch in the bank
sta blt_entry+3
stal blt_entry+3
lda BTableLow+1,x ; patch in the page
sta blt_entry+2
stal blt_entry+2
; The way we patch the exit code is subtle, but very fast. The CODE_EXIT offset points to
; an JMP/JML instruction that transitions to the next line after all of the code has been
@ -51,6 +51,11 @@ _BltRange
lda #FULL_RETURN ; this is the offset of the return code
sta [:exit_ptr],y ; patch out the low byte of the JMP/JML
; lda StartYMod208
; cmp #63
; bne *+4
; brk $40
; Now we need to set up the Bank, Stack Pointer and Direct Page registers for calling into
; the code field
@ -59,7 +64,7 @@ _BltRange
; beq :primary
; lda BG1AltBank
; bra :alt
:primary lda BG1DataBank
:primary lda BG1DataBank ; This is $00 if the TWO_LAYER bit of EngineMode is not set
:alt
pha
plb

View File

@ -3,33 +3,6 @@
; when the virtual X-position of the play field changes.
; SetBG0XPos
;
; Set the virtual horizontal position of the primary background layer. In addition to
; updating the direct page state locations, this routine needs to preserve the original
; value as well. This is a bit subtle, because if this routine is called multiple times
; with different values, we need to make sure the *original* value is preserved and not
; continuously overwrite it.
;
; We assume that there is a clean code field in this routine
SetBG0XPos ENT
jsr _SetBG0XPos
rtl
_SetBG0XPos
cmp StartX
beq :out ; Easy, if nothing changed, then nothing changes
ldx StartX ; Load the old value (but don't save it yet)
sta StartX ; Save the new position
lda #DIRTY_BIT_BG0_X
tsb DirtyBits ; Check if the value is already dirty, if so exit
bne :out ; without overwriting the original value
stx OldStartX ; First change, so preserve the value
:out rts
; Simple function that restores the saved opcode that are stashed in _applyBG0Xpos. It is
; very important that opcodes are restored before new ones are inserted, because there is
; only one, fixed storage location and old values will be overwritten if operations are not
@ -48,6 +21,8 @@ _RestoreBG0Opcodes
:draw_count_x2 equ tmp3
:exit_offset equ tmp4
phb ; Save data bank
asl
sta :virt_line_x2 ; Keep track of it
@ -57,7 +32,6 @@ _RestoreBG0Opcodes
lda LastPatchOffset ; If zero, there are no saved opcodes
sta :exit_offset
beq :loop
:loop
ldx :virt_line_x2
@ -102,7 +76,6 @@ _RestoreBG0Opcodes
stz LastPatchOffset ; Clear the value once completed
:out
phk
plb
rts
@ -306,6 +279,7 @@ _ApplyBG0XPos
; 2. Writes the BRA instruction to exit the code field
; 3. Writes the JMP entry point to enter the code field
phb ; Save the existing bank
:loop
lda :virt_line
asl ; This will clear the carry bit
@ -380,7 +354,7 @@ _ApplyBG0XPos
ldx :draw_count_x2
ldy :base_address ; Y-register is preserved, this can be removed
pei :exit_address
jmp :SaveHighOperand ; Only used once, so "inline" it
jmp :SaveHighOperand ; Only used once, so "inline" it
:save_high_op_rtn
:not_odd
@ -401,7 +375,6 @@ _ApplyBG0XPos
jne :loop
phk
plb
rts

View File

@ -15,13 +15,13 @@
_PEISlam
lda ScreenWidth
dec
sta :screen_width_1 ; save the width-1 outside of the direct page
stal :screen_width_1 ; save the width-1 outside of the direct page
lda #:pei_end ; patch the PEI entry address
and #$FFFE ; should always be even, but....
sec
sbc ScreenWidth
sta :inner+1
stal :inner+1
phx
tya
@ -43,7 +43,7 @@ _PEISlam
tcd ; screen address to the direct page register
tsc
sta :stk_save ; save the stack pointer to restore later
stal :stk_save ; save the stack pointer to restore later
clc ; clear before the loop -- nothing in the loop affect the carry bit
brl :outer ; hop into the entry point.
@ -57,7 +57,7 @@ _PEISlam
tdc ; Move to the next line
adc #160
tcd
adc :screen_width_1
adcl :screen_width_1
tcs
dey ; decrement the total counter, if zero then we're done
@ -79,7 +79,7 @@ _PEISlam
:restore
tsx ; save the current stack
_R0W0 ; restore the execution environment and
lda :stk_save ; give a few cycles to catch some interrupts
ldal :stk_save ; give a few cycles to catch some interrupts
tcs
cli ; fall through here -- saves a BRA instruction
@ -92,7 +92,7 @@ _PEISlam
:exit
_R0W0
lda :stk_save
ldal :stk_save
tcs
cli

220
src/blitter/Rotation.s Normal file
View File

@ -0,0 +1,220 @@
; Support rotating the BG1 graphics by leveraging the fact that a rotation function can be decomposed
; into an addition of two function parametertized by the angle of rotation: pixel = *(f(x, a) + f(y, a))
;
; The pre-build a number of rotation tables and then populate the direct page values and Y-register values
; for each line of the blitter, such that a single lda (00),y instruction fetched the appropriate data
;
; This is about as fast of a rotation as we can do.
;
; When possible, off-screen locations are calculate to produce an address of $FFFE, so that the last two bytes
; of the BG1 data buffer provides the "fill value".
ANGLEBNK ext
_ApplyBG1XPosAngle
; phy
; lda BG1StartX
; jsr Mod164
; sta BG1StartXMod164
; lda #162
; sec
; sbc StartXMod164
; bpl *+6
; clc
; adc #164
; clc
; adc BG1StartXMod164
; cmp #164
; bcc *+5
; sbc #164
; clc
; adc 1,s
; tay ; cache the value
; pla ; pop the value
phd ; save the direct page because we are going to switch to the
lda BlitterDP ; blitter direct page space and fill in the addresses
tcd
lda #^ANGLEBNK
sta $fe
sty $fc ; Store in the new direct page
ldy #162
tyx
:loop
lda [$fc],y
sta 00,x ; store the value
dey
dey
dex
dex
bpl :loop
pld
rts
_ApplyBG1YPosAngle
:virt_line equ tmp0
:lines_left equ tmp1
:draw_count equ tmp2
:ytbl_idx equ tmp3
:angle_tbl equ tmp4
sty :angle_tbl
lda BG1StartY
jsr Mod208
sta BG1StartYMod208
sta :ytbl_idx ; Start copying from the first entry in the table
lda StartYMod208 ; This is the base line of the virtual screen
sta :virt_line ; Keep track of it
lda ScreenHeight
sta :lines_left
phb
:loop
lda :virt_line
asl
tax
ldal BTableLow,x ; Get the address of the first code field line
tay
sep #$20
ldal BTableHigh,x
pha ; push the bank on the stack
plb
rep #$20
lda :virt_line
and #$000F
eor #$FFFF
inc
clc
adc #16
min :lines_left
sta :draw_count ; Do this many lines
asl
tax
lda :ytbl_idx ; Read from this location (duplicate every 4 lines)
lsr
lsr
asl
clc
adc :angle_tbl
sec
sbc #ANGLEBNK
jsr CopyAngleYTableToBG1Addr ; or CopyBG1YTableToBG1Addr2
lda :virt_line ; advance to the virtual line after the segment we just
clc ; filled in
adc :draw_count
sta :virt_line
lda :ytbl_idx ; advance the index into the YTable
adc :draw_count
sta :ytbl_idx
lda :lines_left ; subtract the number of lines we just completed
sec
sbc :draw_count
sta :lines_left
jne :loop
plb
rts
; Unrolled copy routine to move y_angle entries into BG1_ADDR position with an additional
; shift. This has to be split into two
;
; A = index into the array (x2)
; Y = starting line * $1000
; X = number of lines (x2)
CopyAngleYTableToBG1Addr
phx
phb
jsr _SetDataBank ; restore access to this bank
jsr SaveBG1AngleValues
plb
plx ; x is used directly in this routine
jmp ApplyBG1OffsetValues
SaveBG1AngleValues
jmp (:tbl,x)
:tbl da :none
da :do01,:do02,:do03,:do04
da :do05,:do06,:do07,:do08
da :do09,:do10,:do11,:do12
da :do13,:do14,:do15,:do16
:do15 tax
bra :x15
:do14 tax
bra :x14
:do13 tax
bra :x13
:do12 tax
bra :x12
:do11 tax
bra :x11
:do10 tax
bra :x10
:do09 tax
bra :x09
:do08 tax
bra :x08
:do16 tax
ldal ANGLEBNK+06,x
sta BG1YCache+30
:x15 ldal ANGLEBNK+06,x
sta BG1YCache+28
:x14 ldal ANGLEBNK+06,x
sta BG1YCache+26
:x13 ldal ANGLEBNK+06,x
sta BG1YCache+24
:x12 ldal ANGLEBNK+04,x
sta BG1YCache+22
:x11 ldal ANGLEBNK+04,x
sta BG1YCache+20
:x10 ldal ANGLEBNK+04,x
sta BG1YCache+18
:x09 ldal ANGLEBNK+04,x
sta BG1YCache+16
:x08 ldal ANGLEBNK+02,x
sta BG1YCache+14
:x07 ldal ANGLEBNK+02,x
sta BG1YCache+12
:x06 ldal ANGLEBNK+02,x
sta BG1YCache+10
:x05 ldal ANGLEBNK+02,x
sta BG1YCache+08
:x04 ldal ANGLEBNK+00,x
sta BG1YCache+06
:x03 ldal ANGLEBNK+00,x
sta BG1YCache+04
:x02 ldal ANGLEBNK+00,x
sta BG1YCache+02
:x01 ldal ANGLEBNK+00,x
sta BG1YCache+00
:none rts
:do07 tax
bra :x07
:do06 tax
bra :x06
:do05 tax
bra :x05
:do04 tax
bra :x04
:do03 tax
bra :x03
:do02 tax
bra :x02
:do01 tax
bra :x01

View File

@ -10,14 +10,6 @@
;
; This could be made faster by forcing a SCB array to be copied into PEAs ahead of time, but this
; is a bit more flexible
BltSCB ENT
phb
phk
plb
jsr _BltSCB
plb
rtl
_BltSCBOut
rts
_BltSCB
@ -36,10 +28,10 @@ _BltSCB
lda SCBArrayPtr+2
bpl :bind_to_bg0
lda BG1StartY
lda BG1StartYMod208
bra :bind_to_bg1
:bind_to_bg0
lda StartY
lda StartYMod208
:bind_to_bg1
clc
adc SCBArrayPtr
@ -52,7 +44,7 @@ _BltSCB
inc
clc
adc #:scb_end
sta :entry+1
stal :entry+1
lda ScreenY1 ; Get the SCB address to put into the stack register
dec
@ -82,13 +74,7 @@ _BltSCB
plb ; restore the bank
rts
; Quick helper to set the pointer (X = low word, A = high work)
SetSCBArray ENT
jsr _SetSCBArray
rtl
_SetSCBArray
_BindSCBArray
stx SCBArrayPtr
sta SCBArrayPtr+2
rts

View File

@ -42,7 +42,7 @@ PagePatches da {long_0-base+2}
]index equ 0
lup 82 ; All the snippet addresses. The two JMP
da {snippets-base+{]index*32}+31} ; instructino are at the end of each of
da {snippets-base+{]index*32}+31} ; instructions are at the end of each of
da {snippets-base+{]index*32}+28} ; the 32-byte buffers
]index equ ]index+1
--^
@ -58,538 +58,6 @@ BankPatches da {long_0-base+3}
da {long_6-base+3}
BankPatchNum equ *-BankPatches
; Set the physical location of the virtual screen on the physical screen. The
; screen size must by a multiple of 8
;
; A = XXYY where XX is the left edge [0, 159] and YY is the top edge [0, 199]
; X = width (in bytes)
; Y = height (in lines)
;
; This subroutine stores the screen positions in the direct page space and fills
; in the double-length ScreenAddrR table that holds the address of the right edge
; of the playfield. This table is used to set addresses in the code banks when the
; virtual origin is changed.
;
; We are not concerned about the raw performance of this function because it should
; usually only be executed once during app initialization. It doesn't get called
; with any significant frequency.
SetScreenRect sty ScreenHeight ; Save the screen height and width
stx ScreenWidth
tax ; Temp save of the accumulator
and #$00FF
sta ScreenY0
clc
adc ScreenHeight
sta ScreenY1
txa ; Restore the accumulator
xba
and #$00FF
sta ScreenX0
clc
adc ScreenWidth
sta ScreenX1
lda ScreenHeight ; Divide the height in scanlines by 8 to get the number tiles
lsr
lsr
lsr
sta ScreenTileHeight
lda ScreenWidth ; Divide width in bytes by 4 to get the number of tiles
lsr
lsr
sta ScreenTileWidth
lda ScreenY0 ; Calculate the address of the first byte
asl ; of the right side of the playfield
tax
lda ScreenAddr,x ; This is the address for the left edge of the physical screen
clc
adc ScreenX1
dec
pha ; Save for second loop
ldx #0
ldy ScreenHeight
jsr :loop
pla ; Reset the address and continue filling in the
ldy ScreenHeight ; second half of the table
:loop clc
sta RTable,x
adc #160
inx
inx
dey
bne :loop
; Calculate the screen locations for each tile corner
lda ScreenY0 ; Calculate the address of the first byte
asl ; of the right side of the playfield
tax
lda ScreenAddr,x ; This is the address for the left edge of the physical screen
clc
adc ScreenX0
ldx #0
ldy #0
:tsloop
stal TileStore+TS_SCREEN_ADDR,x
clc
adc #4 ; Go to the next tile
iny
cpy #41 ; If we've done 41 columns, move to the next line
bcc :nohop
ldy #0
clc
adc #{8*160}-{4*41}
:nohop
inx
inx
cpx #TILE_STORE_SIZE-2
bcc :tsloop
; Return
rts
; Generalized routine that calculates the on-screen address of the tiles and takes the
; StartX and StartY values into consideration. This routine really exists to support
; the dirty tile rendering mode and the tiles *must* be aligned with the playfield.
; That is, StartX % 4 == 0 and StartY % 8 == 0. If these conditions are not met, then
; screen will not render correctly.
_RecalcTileScreenAddrs
NextColPtr equ tmp0
RowAddrPtr equ tmp1
OnScreenAddr equ tmp2
Counter equ tmp3
jsr _OriginToTileStore ; Get the (col,row) of the tile in the upper-left corner of the playfield
; Manually add the offsets to the NextCol and TileStoreYTable array address and put in a direct page
; location so we can free up the registers.
clc
txa
adc #NextCol
sta NextColPtr
tya
adc #TileStoreYTable
sta RowAddrPtr
; Calculate the on-screen address of the upper-left corner of the playfiled
lda ScreenY0 ; Calculate the address of the first byte
asl ; of the right side of the playfield
tax
lda ScreenAddr,x ; This is the address for the left edge of the physical screen
clc
adc ScreenX0
sta OnScreenAddr
; Now, loop through the tile store
lda #MAX_TILES
sta Counter
ldy #0
:tsloop
lda (NextColPtr),y ; Need to recalculate each time since the wrap-around could
clc ; happen anywhere
adc (RowAddrPtr) ;
tax ; NOTE: Try to rework to use new TileStore2DLookup array
lda OnScreenAddr
stal TileStore+TS_SCREEN_ADDR,x
clc
adc #4 ; Go to the next tile
iny
iny
cpy #2*41 ; If we've done 41 columns, move to the next line
bcc :nohop
inc RowAddrPtr ; Advance the row address (with wrap-around)
inc RowAddrPtr
ldy #0 ; Reset the column counter
clc
adc #{8*160}-{4*41}
:nohop
sta OnScreenAddr ; Save the updated on-screen address
dec Counter
bne :tsloop
rts
; Clear the SHR screen and then infill the defined field
FillScreen lda #0
jsr _ClearToColor
ldy ScreenY0
:yloop
tya
asl a
tax
lda ScreenAddr,x
clc
adc ScreenX0
tax
phy
lda ScreenWidth
lsr
tay
lda #$FFFF
:xloop stal $E10000,x ; X is the absolute address
inx
inx
dey
bne :xloop
ply
iny
cpy ScreenY1
bcc :yloop
rts
; Special subroutine to divide the accumulator by 164 and return remainder in the Accumulator
;
; 164 = $A4 = 1010_0100
Mod164 cmp #%1010010000000000
bcc *+5
sbc #%1010010000000000
cmp #%0101001000000000
bcc *+5
sbc #%0101001000000000
cmp #%0010100100000000
bcc *+5
sbc #%0010100100000000
cmp #%0001010010000000
bcc *+5
sbc #%0001010010000000
cmp #%0000101001000000
bcc *+5
sbc #%0000101001000000
cmp #%0000010100100000
bcc *+5
sbc #%0000010100100000
cmp #%0000001010010000
bcc *+5
sbc #%0000001010010000
cmp #%0000000101001000
bcc *+5
sbc #%0000000101001000
cmp #%0000000010100100
bcc *+5
sbc #%0000000010100100
rts
; Special subroutine to divide the accumulator by 208 and return remainder in the Accumulator
;
; 208 = $D0 = 1101_0000
;
; There are probably faster hacks to divide a 16-bit unsigned value by 208
; https://www.drdobbs.com/parallel/optimizing-integer-division-by-a-constan/184408499
; https://embeddedgurus.com/stack-overflow/2009/06/division-of-integers-by-constants/
Mod208 cmp #%1101000000000000
bcc *+5
sbc #%1101000000000000
cmp #%0110100000000000
bcc *+5
sbc #%0110100000000000
cmp #%0011010000000000
bcc *+5
sbc #%0011010000000000
cmp #%0001101000000000
bcc *+5
sbc #%0001101000000000
cmp #%0000110100000000
bcc *+5
sbc #%0000110100000000
cmp #%0000011010000000
bcc *+5
sbc #%0000011010000000
cmp #%0000001101000000
bcc *+5
sbc #%0000001101000000
cmp #%0000000110100000
bcc *+5
sbc #%0000000110100000
cmp #%0000000011010000
bcc *+5
sbc #%0000000011010000
rts
; Patch an 8-bit or 16-bit valueS into the bank. These are a set up unrolled loops to
; quickly patch in a constanct value, or a value from an array into a given set of
; templates.
;
; Because we have structured everything as parallel code blocks, most updates to the blitter
; reduce to storing a constant value and have an amortized cost of just a single store.
;
; The utility of these routines is that they also handle setting just a range of lines
; within a single bank.
;
; X = number of lines * 2, 0 to 32
; Y = starting line * $1000
; A = value
;
; Set M to 0 or 1
SetConst ; Need a blank line here, otherwise the :tbl local variable resolveds backwards
jmp (:tbl,x)
:tbl da :bottom-00,:bottom-03,:bottom-06,:bottom-09
da :bottom-12,:bottom-15,:bottom-18,:bottom-21
da :bottom-24,:bottom-27,:bottom-30,:bottom-33
da :bottom-36,:bottom-39,:bottom-42,:bottom-45
da :bottom-48
:top sta $F000,y
sta $E000,y
sta $D000,y
sta $C000,y
sta $B000,y
sta $A000,y
sta $9000,y
sta $8000,y
sta $7000,y
sta $6000,y
sta $5000,y
sta $4000,y
sta $3000,y
sta $2000,y
sta $1000,y
sta: $0000,y
:bottom rts
; SetDPAddrs
;
; A = absolute address (largest)
; Y = offset
;
; Initializes a bank of direct page offsets
SetDPAddrs
lda #$0800
sta $F000,y
lda #$0700
sta $E000,y
lda #$0600
sta $D000,y
lda #$0500
sta $C000,y
lda #$0400
sta $B000,y
lda #$0300
sta $A000,y
lda #$0200
sta $9000,y
lda #$0100
sta: $8000,y
lda #$0800
sta $7000,y
lda #$0700
sta $6000,y
lda #$0600
sta $5000,y
lda #$0500
sta $4000,y
lda #$0400
sta $3000,y
lda #$0300
sta $2000,y
lda #$0200
sta $1000,y
lda #$0100
sta: $0000,y
rts
; SetAbsAddrs
;
; A = absolute address (largest)
; Y = offset
; X = number of lines
;
; Stores a value and decrements by $1000 for each line
SetAbsAddrs sec
jmp (:tbl,x)
:tbl da :bottom-00,:bottom-03,:bottom-09,:bottom-15
da :bottom-21,:bottom-27,:bottom-33,:bottom-39
da :bottom-45,:bottom-51,:bottom-57,:bottom-63
da :bottom-69,:bottom-75,:bottom-81,:bottom-87
da :bottom-93
:top sta $F000,y
sbc #$1000
sta $E000,y
sbc #$1000
sta $D000,y
sbc #$1000
sta $C000,y
sbc #$1000
sta $B000,y
sbc #$1000
sta $A000,y
sbc #$1000
sta $9000,y
sbc #$1000
sta $8000,y
sbc #$1000
sta $7000,y
sbc #$1000
sta $6000,y
sbc #$1000
sta $5000,y
sbc #$1000
sta $4000,y
sbc #$1000
sta $3000,y
sbc #$1000
sta $2000,y
sbc #$1000
sta $1000,y
sbc #$1000
sta: $0000,y
:bottom rts
; Fill up a full bank with blitter templates. Currently we can fit 16 lines per bank, so need
; a total of 13 banks to hold the 208 lines for full-screen support
;
; A = high word of bank table
; Y = index * 4 of the bank to initialize
BuildBank
:bankArray equ tmp0
:target equ tmp2
:nextBank equ tmp4
stx :bankArray
sta :bankArray+2
stz :target
iny
iny
lda [:bankArray],y
sta :target+2
iny ; move to the next item
iny
iny ; middle byte
cpy #4*13 ; if greater than the array length, wrap back to zero
bcc :ok
ldy #1
:ok lda [:bankArray],y ; Get the middle and high bytes of the address
sta :nextBank
:next
jsr :BuildLine2
lda :target
clc
adc #$1000
sta :target
bcc :next
phb
pei :target+1
plb
plb
; Change the patched value to one of DP_ENTRY, TWO_LYR_ENTRY or ONE_LYR_ENTRY based on the capabilities
; that the engine needs.
lda #$F000+{DP_ENTRY} ; Set the address from each line to the next
ldy #CODE_EXIT+1
ldx #15*2
jsr SetAbsAddrs
ldy #DP_ADDR
jsr SetDPAddrs
ldy #$F000+CODE_EXIT ; Patch the last line with a JML to go to the next bank
lda #{$005C+{DP_ENTRY}*256}
sta [:target],y
ldy #$F000+CODE_EXIT+2
lda :nextBank
sta [:target],y
ldy #$8000+CODE_EXIT ; Patch one line per bank to enable interrupts
lda #{$004C+{ENABLE_INT}*256}
sta [:target],y
plb
rts
; This is the relocation subroutine, it is responsible for copying the template to a
; memory location and patching up the necessary instructions.
;
; X = low word of address (must be a multiple of $1000)
; A = high word of address (bank)
:BuildLine
stx :target
sta :target+2
:BuildLine2
lda #CODE_LEN ; round up to an even number of bytes
inc
and #$FFFE
beq :nocopy
dec
dec
tay
:loop lda base,y
sta [:target],y
dey
dey
bpl :loop
:nocopy lda #0 ; copy is complete, now patch up the addresses
sep #$20
ldx #0
lda :target+2 ; patch in the bank for the absolute long addressing mode
:dobank ldy BankPatches,x
sta [:target],y
inx
inx
cpx #BankPatchNum
bcc :dobank
ldx #0
:dopage ldy PagePatches,x ; patch the page addresses by adding the page offset to each
lda [:target],y
clc
adc :target+1
sta [:target],y
inx
inx
cpx #PagePatchNum
bcc :dopage
:out
rep #$20
rts
; Start of the template code. This code is replicated 16 times per bank and spans
; 13 banks for a total of 208 lines, which is what is required to render 26 tiles
; to cover the full screen vertical scrolling.

336
src/blitter/TemplateUtils.s Normal file
View File

@ -0,0 +1,336 @@
; Untility function related to patching and manipulating the blitter template code
mx %00
; Generalized routine that calculates the on-screen address of the tiles and takes the
; StartX and StartY values into consideration. This routine really exists to support
; the dirty tile rendering mode and the tiles *must* be aligned with the playfield.
; That is, StartX % 4 == 0 and StartY % 8 == 0. If these conditions are not met, then
; screen will not render correctly.
_RecalcTileScreenAddrs
NextColPtr equ tmp0
RowAddrPtr equ tmp1
OnScreenAddr equ tmp2
Counter equ tmp3
jsr _OriginToTileStore ; Get the (col,row) of the tile in the upper-left corner of the playfield
; Manually add the offsets to the NextCol and TileStoreYTable array address and put in a direct page
; location so we can free up the registers.
clc
txa
adc #NextCol
sta NextColPtr
tya
adc #TileStoreYTable
sta RowAddrPtr
; Calculate the on-screen address of the upper-left corner of the playfiled
lda ScreenY0 ; Calculate the address of the first byte
asl ; of the right side of the playfield
tax
lda ScreenAddr,x ; This is the address for the left edge of the physical screen
clc
adc ScreenX0
sta OnScreenAddr
; Now, loop through the tile store
lda #MAX_TILES
sta Counter
ldy #0
:tsloop
lda (NextColPtr),y ; Need to recalculate each time since the wrap-around could
clc ; happen anywhere
adc (RowAddrPtr) ;
tax ; NOTE: Try to rework to use new TileStore2DLookup array
lda OnScreenAddr
sta TileStore+TS_SCREEN_ADDR,x
clc
adc #4 ; Go to the next tile
iny
iny
cpy #2*41 ; If we've done 41 columns, move to the next line
bcc :nohop
inc RowAddrPtr ; Advance the row address (with wrap-around)
inc RowAddrPtr
ldy #0 ; Reset the column counter
clc
adc #{8*160}-{4*41}
:nohop
sta OnScreenAddr ; Save the updated on-screen address
dec Counter
bne :tsloop
rts
; Patch an 8-bit or 16-bit valueS into the bank. These are a set up unrolled loops to
; quickly patch in a constant value, or a value from an array into a given set of
; templates.
;
; Because we have structured everything as parallel code blocks, most updates to the blitter
; reduce to storing a constant value and have an amortized cost of just a single store.
;
; The utility of these routines is that they also handle setting just a range of lines
; within a single bank.
;
; X = number of lines * 2, 0 to 32
; Y = starting line * $1000
; A = value
;
; Set M to 0 or 1
SetConst ; Need a blank line here, otherwise the :tbl local variable resolveds backwards
jmp (:tbl,x)
:tbl da :bottom-00,:bottom-03,:bottom-06,:bottom-09
da :bottom-12,:bottom-15,:bottom-18,:bottom-21
da :bottom-24,:bottom-27,:bottom-30,:bottom-33
da :bottom-36,:bottom-39,:bottom-42,:bottom-45
da :bottom-48
:top sta $F000,y
sta $E000,y
sta $D000,y
sta $C000,y
sta $B000,y
sta $A000,y
sta $9000,y
sta $8000,y
sta $7000,y
sta $6000,y
sta $5000,y
sta $4000,y
sta $3000,y
sta $2000,y
sta $1000,y
sta: $0000,y
:bottom rts
; SetDPAddrs
;
; A = absolute address (largest)
; Y = offset
;
; Initializes a bank of direct page offsets
SetDPAddrs
lda #$0800
sta $F000,y
lda #$0700
sta $E000,y
lda #$0600
sta $D000,y
lda #$0500
sta $C000,y
lda #$0400
sta $B000,y
lda #$0300
sta $A000,y
lda #$0200
sta $9000,y
lda #$0100
sta: $8000,y
lda #$0800
sta $7000,y
lda #$0700
sta $6000,y
lda #$0600
sta $5000,y
lda #$0500
sta $4000,y
lda #$0400
sta $3000,y
lda #$0300
sta $2000,y
lda #$0200
sta $1000,y
lda #$0100
sta: $0000,y
rts
; SetAbsAddrs
;
; A = absolute address (largest)
; Y = offset
; X = number of lines
;
; Stores a value and decrements by $1000 for each line
SetAbsAddrs sec
jmp (:tbl,x)
:tbl da :bottom-00,:bottom-03,:bottom-09,:bottom-15
da :bottom-21,:bottom-27,:bottom-33,:bottom-39
da :bottom-45,:bottom-51,:bottom-57,:bottom-63
da :bottom-69,:bottom-75,:bottom-81,:bottom-87
da :bottom-93
:top sta $F000,y
sbc #$1000
sta $E000,y
sbc #$1000
sta $D000,y
sbc #$1000
sta $C000,y
sbc #$1000
sta $B000,y
sbc #$1000
sta $A000,y
sbc #$1000
sta $9000,y
sbc #$1000
sta $8000,y
sbc #$1000
sta $7000,y
sbc #$1000
sta $6000,y
sbc #$1000
sta $5000,y
sbc #$1000
sta $4000,y
sbc #$1000
sta $3000,y
sbc #$1000
sta $2000,y
sbc #$1000
sta $1000,y
sbc #$1000
sta: $0000,y
:bottom rts
; Fill up a full bank with blitter templates. Currently we can fit 16 lines per bank, so need
; a total of 13 banks to hold the 208 lines for full-screen support
;
; A = high word of bank table
; Y = index * 4 of the bank to initialize
BuildBank
:bankArray equ tmp0
:target equ tmp2
:nextBank equ tmp4
:entryOffset equ tmp6
stx :bankArray
sta :bankArray+2
stz :target
iny
iny
lda [:bankArray],y
sta :target+2
iny ; move to the next item
iny
iny ; middle byte
cpy #4*13 ; if greater than the array length, wrap back to zero
bcc :ok
ldy #1
:ok lda [:bankArray],y ; Get the middle and high bytes of the address
sta :nextBank
:next
jsr :BuildLine2
lda :target
clc
adc #$1000
sta :target
bcc :next
phb
pei :target+1
plb
plb
; Change the patched value to one of DP_ENTRY, TWO_LYR_ENTRY or ONE_LYR_ENTRY based on the capabilities
; that the engine needs.
lda #DP_ENTRY
sta :entryOffset
lda EngineMode
bne :not_simple
lda #ONE_LYR_ENTRY
sta :entryOffset
:not_simple
lda #$F000 ; Set the address from each line to the next
ora :entryOffset
ldy #CODE_EXIT+1
ldx #15*2
jsr SetAbsAddrs
ldy #DP_ADDR
jsr SetDPAddrs
ldy #$F000+CODE_EXIT ; Patch the last line with a JML to go to the next bank
lda :entryOffset
xba
ora #$005C
sta [:target],y
ldy #$F000+CODE_EXIT+2
lda :nextBank
sta [:target],y
ldy #$8000+CODE_EXIT ; Patch one line per bank to enable interrupts
lda #{$004C+{ENABLE_INT}*256}
sta [:target],y
plb
rts
; This is the relocation subroutine, it is responsible for copying the template to a
; memory location and patching up the necessary instructions.
;
; X = low word of address (must be a multiple of $1000)
; A = high word of address (bank)
:BuildLine
stx :target
sta :target+2
:BuildLine2
phb ; save bank and reset to the code bank because
phk ; the template is part of this bank
plb
lda #CODE_LEN ; round up to an even number of bytes
inc
and #$FFFE
beq :nocopy
dec
dec
tay
:loop lda base,y
sta [:target],y
dey
dey
bpl :loop
:nocopy lda #0 ; copy is complete, now patch up the addresses
sep #$20
ldx #0
lda :target+2 ; patch in the bank for the absolute long addressing mode
:dobank ldy BankPatches,x
sta [:target],y
inx
inx
cpx #BankPatchNum
bcc :dobank
ldx #0
:dopage ldy PagePatches,x ; patch the page addresses by adding the page offset to each
lda [:target],y
clc
adc :target+1
sta [:target],y
inx
inx
cpx #PagePatchNum
bcc :dopage
rep #$20
plb
rts

121
src/blitter/TileProcs.s Normal file
View File

@ -0,0 +1,121 @@
; A simple helper function that fills in all of the opcodes of a tile with the PEA opcode. This is
; a separate functino because we can often just update the tile data if we know the opcodes are already
; set. When we have to fill the opcodes, this function is used
_TBFillPEAOpcode
sep #$20
lda #$F4
]line equ 0
lup 8
sta: $0000+{]line*$1000},y
sta: $0003+{]line*$1000},y
]line equ ]line+1
--^
rep #$20
rts
; Copy tile data into the direct page compositing buffer. The main reason to do this in full passes is
; because we can avoid needing to use both the X and Y registers during the compositing process and
; reserve Y to hold the code field address.
;
; Also, we can get away with not setting the bank register, this is a wash in terms of speed, but results
; in simpler, more composable subroutines
_TBCopyTileDataAndMaskToCBuff
jsr _TBCopyTileDataToCBuff
jmp _TBCopyTileMaskToCBuff
_TBCopyTileDataAndMaskToCBuffV
jsr _TBCopyTileDataToCBuffV
jmp _TBCopyTileMaskToCBuffV
_TBCopyTileDataToCBuff
]line equ 0
lup 8
ldal tiledata+{]line*4},x
sta blttmp+{]line*4}
ldal tiledata+{]line*4}+2,x
sta blttmp+{]line*4}+2
]line equ ]line+1
--^
rts
_TBCopyTileDataToCBuffV
]src equ 7
]dest equ 0
lup 8
ldal tiledata+{]src*4},x
sta blttmp+{]dest*4}
ldal tiledata+{]src*4}+2,x
sta blttmp+{]dest*4}+2
]src equ ]src-1
]dest equ ]dest+1
--^
rts
; Copy tile mask data into the direct page compositing buffer.
_TBCopyTileMaskToCBuff
]line equ 0
lup 8
ldal tiledata+{]line*4}+32,x
sta blttmp+{]line*4}+32
ldal tiledata+{]line*4}+32+2,x
sta blttmp+{]line*4}+32+2
]line equ ]line+1
--^
rts
_TBCopyTileMaskToCBuffV
]src equ 7
]dest equ 0
lup 8
ldal tiledata+{]src*4}+32,x
sta blttmp+{]dest*4}+32
ldal tiledata+{]src*4}+32+2,x
sta blttmp+{]dest*4}+32+2
]src equ ]src-1
]dest equ ]dest+1
--^
rts
; Tile 0 specializations
; _TBConstTile
;
; A specialized routine that fills in a tile with a single constant value. It's intended to be used to
; fill in solid colors, so there are no specialized horizontal or verical flipped variants
_TBConstTileX
lda #0
sta: $0001,y
sta: $0004,y
sta $1001,y
sta $1004,y
sta $2001,y
sta $2004,y
sta $3001,y
sta $3004,y
sta $4001,y
sta $4004,y
sta $5001,y
sta $5004,y
sta $6001,y
sta $6004,y
sta $7001,y
sta $7004,y
plb
rts
_TBConstTileSlow0
tax
jsr _TBFillPEAOpcode
jmp _TBConstTileX
_TBConstTileDataToDP2
]line equ 0
lup 8
stz tmp_tile_data+{]line*4}
stz tmp_tile_data+{]line*4}+2
]line equ ]line+1
--^
rts

View File

@ -37,132 +37,45 @@
;
; It is simply too slow to try to horizontally reverse the pixel data on the fly. This still allows
; for up to 512 tiles to be stored in a single bank, which should be sufficient.
TILE_CTRL_MASK equ $FE00
TILE_PROC_MASK equ $F800 ; Select tile proc for rendering
; Temporary direct page locatinos used by some of the complex tile renderers
_X_REG equ tiletmp
_Y_REG equ tiletmp+2
_T_PTR equ tiletmp+4 ; Copy of the tile address pointer
_BASE_ADDR equ tiletmp+6 ; Copy of BTableLow for this tile
_SPR_X_REG equ tiletmp+8 ; Cache address of sprite plane source for a tile
_JTBL_CACHE equ tiletmp+10 ; Cache the offset to the exception handler for a column
_OP_CACHE equ tiletmp+12 ; Cache of a relevant operand / oeprator
_TILE_ID equ tiletmp+14 ; Copy of the tile descriptor
; Low-level function to take a tile descriptor and return the address in the tiledata
; bank. This is not too useful in the fast-path because the fast-path does more
; incremental calculations, but it is handy for other utility functions
;
; A = tile descriptor
;
; The address is the TileID * 128 + (HFLIP * 64)
GetTileAddr ENT
jsr _GetTileAddr
rtl
_GetTileAddr
asl ; Multiply by 2
bit #2*TILE_HFLIP_BIT ; Check if the horizontal flip bit is set
beq :no_flip
inc ; Set the LSB
:no_flip asl ; x4
asl ; x8
asl ; x16
asl ; x32
asl ; x64
asl ; x128
rts
; Ignore the horizontal flip bit
_GetBaseTileAddr
asl ; Multiply by 2
asl ; x4
asl ; x8
asl ; x16
asl ; x32
asl ; x64
asl ; x128
rts
; On entry
;
; B is set to the correct BG1 data bank
; A is set to the the tile descriptor
; Y is set to the top-left address of the tile in the BG1 data bank
;
; tmp0/tmp1 is reserved
_RenderTileBG1
pha ; Save the tile descriptor
and #TILE_VFLIP_BIT+TILE_HFLIP_BIT ; Only horizontal and vertical flips are supported for BG1
xba
tax
ldal :actions,x
stal :tiledisp+1
pla
and #TILE_ID_MASK ; Mask out the ID and save just that
_Mul128 ; multiplied by 128
tax
:tiledisp jmp $0000
:actions dw _TBSolidBG1_00,_TBSolidBG1_0H,_TBSolidBG1_V0,_TBSolidBG1_VH
; Given an address to a Tile Store record, dispatch to the appropriate tile renderer. The Tile
; Store record contains all of the low-level information that's needed to call the renderer.
;
; Y = address of tile
; There are two execution paths that are handled here. First, if there is no sprite, then
; the tile data is read directly and written into the code field in a single pass. If there
; are sprites that overlap the tile, then the sprite data is combined with the tile data
; and written to a temporary direct page buffer. If
;
; This routine sets the direct page register to the second page since we use that space to
; build and cache tile and sprite data, when necessary
_RenderTile2
pea >TileStore ; Need that addressing flexibility here. Caller is responsible for restoring bank reg
plb
plb
txy ; We can be better than this....
lda TileStore+TS_SPRITE_FLAG,x ; This is a bitfield of all the sprites that intersect this tile, only care if non-zero or not
bne do_dirty_sprite
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
; Handle the non-sprite tile blit
CopyNoSprites
sep #$20
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later
; txa
; jsr BuildActiveSpriteArray ; Build the max 4 array of active sprites for this tile
; sta ActiveSpriteCount
; lda TileStore+TS_BASE_ADDR+1,x ; load the base address of the code field ($0000 or $8000)
; sta _BASE_ADDR+1 ; so we can get by just copying the high byte
rep #$20
lda TileStore+TS_VBUFF_ARRAY_ADDR,y ; Scratch space
sta _SPR_X_REG
phy
ldy spriteIdx
lda (_SPR_X_REG),y
sta _SPR_X_REG
ply
lda TileStore+TS_TILE_ID,y
ora #TILE_SPRITE_BIT
; ldx TileStore+TS_VBUFF_ARRAY_ADDR,y
; stx _SPR_X_REG
:nosprite
sta _TILE_ID ; Some tile blitters need to get the tile descriptor
and #TILE_CTRL_MASK
xba
tax
ldal TileProcs,x ; load and patch in the appropriate subroutine
lda TileStore+TS_BASE_TILE_DISP,x ; Get the address of the renderer for this tile
stal :tiledisp+1
ldx TileStore+TS_TILE_ADDR,y ; load the address of this tile's data (pre-calculated)
lda TileStore+TS_TILE_ID,x
sta _TILE_ID ; Some tile blitters need to get the tile descriptor
sep #$20 ; load the bank of the target code field line
lda TileStore+TS_CODE_ADDR_HIGH,y
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x ; load the address of this tile's data (pre-calculated)
pha
rep #$20
lda TileStore+TS_CODE_ADDR_LOW,y ; load the address of the code field
pha
lda TileStore+TS_BASE_ADDR,y ; load the base address of the code field
sta _BASE_ADDR
lda TileStore+TS_WORD_OFFSET,y
ply
plb ; set the bank
lda TileStore+TS_WORD_OFFSET,x
plx
plb ; set the bank to the code field that will be updated
; B is set to the correct code field bank
; A is set to the tile word offset (0 through 80 in steps of 4)
@ -171,6 +84,147 @@ _RenderTile2
:tiledisp jmp $0000 ; render the tile
; The sprite code is just responsible for quickly copying all of the sprite data
; into the direct page temp area.
do_dirty_sprite
pei TileStoreBankAndTileDataBank ; Special value that has the TileStore bank in LSB and TileData bank in MSB
plb
; Cache a couple of values into the direct page that are used across all copy routines
lda TileStore+TS_TILE_ADDR,y ; load the address of this tile's data (pre-calculated)
sta tileAddr
ldx TileStore+TS_VBUFF_ADDR_COUNT,y
jmp (dirty_sprite_dispatch,x)
dirty_sprite_dispatch
da CopyNoSprites
da CopyOneSprite
da CopyTwoSprites
da CopyThreeSprites
da CopyFourSprites ; MAX, don't bother with more than 4 sprites per tile
; We can optimize later, for now just copy the sprite data and mask into its own
; direct page buffer and combine with the tile data later
;
; We set up direct page pointers to the mask bank and use the bank register for the
; data.
CopyFourSprites
lda TileStore+TS_VBUFF_ADDR_0,y
sta spriteIdx
lda TileStore+TS_VBUFF_ADDR_1,y
sta spriteIdx+4
lda TileStore+TS_VBUFF_ADDR_2,y
sta spriteIdx+8
lda TileStore+TS_VBUFF_ADDR_3,y
sta spriteIdx+12
; Copy three sprites into a temporary direct page buffer
LDA_IL equ $A7 ; lda [dp]
LDA_ILY equ $B7 ; lda [dp],y
AND_IL equ $27 ; and [dp]
AND_ILY equ $37 ; and [dp],y
CopyThreeSprites
lda TileStore+TS_VBUFF_ADDR_0,y
sta spriteIdx
lda TileStore+TS_VBUFF_ADDR_1,y
sta spriteIdx+4
lda TileStore+TS_VBUFF_ADDR_2,y
sta spriteIdx+8
]line equ 0
lup 8
ldy #]line*SPRITE_PLANE_SPAN
lda (spriteIdx+8),y
db AND_ILY,spriteIdx+4 ; Can't use long indirect inside LUP because of ']'
ora (spriteIdx+4),y
db AND_ILY,spriteIdx+0
ora (spriteIdx+0),y
sta tmp_sprite_data+{]line*4}
db LDA_ILY,spriteIdx+8
db AND_ILY,spriteIdx+4
db AND_ILY,spriteIdx+0
sta tmp_sprite_mask+{]line*4}
ldy #]line*SPRITE_PLANE_SPAN+2
lda (spriteIdx+8),y
db AND_ILY,spriteIdx+4
ora (spriteIdx+4),y
db AND_ILY,spriteIdx+0
ora (spriteIdx+0),y
sta tmp_sprite_data+{]line*4}+2
db LDA_ILY,spriteIdx+8
db AND_ILY,spriteIdx+4
db AND_ILY,spriteIdx+0
sta tmp_sprite_mask+{]line*4}+2
]line equ ]line+1
--^
; jmp FinishTile
; Copy two sprites into a temporary direct page buffer
CopyTwoSprites
lda TileStore+TS_VBUFF_ADDR_0,y
sta spriteIdx
lda TileStore+TS_VBUFF_ADDR_1,y
sta spriteIdx+4
]line equ 0
lup 8
ldy #]line*SPRITE_PLANE_SPAN
lda (spriteIdx+4),y
db AND_ILY,spriteIdx+0
ora (spriteIdx+0),y
sta tmp_sprite_data+{]line*4}
db LDA_ILY,spriteIdx+4
db AND_ILY,spriteIdx+0
sta tmp_sprite_mask+{]line*4}
ldy #]line*SPRITE_PLANE_SPAN+2
lda (spriteIdx+4),y
db AND_ILY,spriteIdx+0
ora (spriteIdx+0),y
sta tmp_sprite_data+{]line*4}+2
db LDA_ILY,spriteIdx+4
db AND_ILY,spriteIdx+0
sta tmp_sprite_mask+{]line*4}+2
]line equ ]line+1
--^
; jmp FinishTile
CopyOneSprite
clc
lda TileStore+TS_VBUFF_ADDR_0,y
sta spriteIdx
adc #2
sta spriteIdx+4
]line equ 0
lup 8
; ldal tiledata,x
; and [spriteIdx]
; ora (spriteIdx)
; sta tmp_sprite_data+{]line*4}
ldal spritedata+{]line*SPRITE_PLANE_SPAN},x
sta tmp_sprite_data+{]line*4}
ldal spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
sta tmp_sprite_data+{]line*4}+2
ldal spritemask+{]line*SPRITE_PLANE_SPAN},x
sta tmp_sprite_mask+{]line*4}
ldal spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
sta tmp_sprite_mask+{]line*4}+2
]line equ ]line+1
--^
; jmp FinishTile
; Reference all of the tile rendering subroutines defined in the TileXXXXX files. Each file defines
; 8 entry points:
;
@ -297,123 +351,6 @@ ClearTile
rep #$20
rts
; Helper functions to copy tile data to the appropriate location in Bank 0
; X = tile ID
; Y = dynamic tile ID
CopyTileToDyn ENT
txa
jsr _GetTileAddr
tax
tya
and #$001F ; Maximum of 32 dynamic tiles
asl
asl ; 4 bytes per page
adc BlitterDP ; Add to the bank 00 base address
adc #$0100 ; Go to the next page
tay
jsr CopyTileDToDyn ; Copy the tile data
jsr CopyTileMToDyn ; Copy the tile mask
rtl
; X = address of tile
; Y = tile address in bank 0
CopyTileDToDyn
phb
pea $0000
plb
plb
ldal tiledata+0,x
sta: $0000,y
ldal tiledata+2,x
sta: $0002,y
ldal tiledata+4,x
sta $0100,y
ldal tiledata+6,x
sta $0102,y
ldal tiledata+8,x
sta $0200,y
ldal tiledata+10,x
sta $0202,y
ldal tiledata+12,x
sta $0300,y
ldal tiledata+14,x
sta $0302,y
ldal tiledata+16,x
sta $0400,y
ldal tiledata+18,x
sta $0402,y
ldal tiledata+20,x
sta $0500,y
ldal tiledata+22,x
sta $0502,y
ldal tiledata+24,x
sta $0600,y
ldal tiledata+26,x
sta $0602,y
ldal tiledata+28,x
sta $0700,y
ldal tiledata+30,x
sta $0702,y
plb
rts
; Helper function to copy tile mask to the appropriate location in Bank 0
;
; X = address of tile
; Y = tile address in bank 0
;
; Argument are the same as CopyTileDToDyn, the code takes care of adjust offsets.
; This make is possible to call the two functions back-to-back
;
; ldx tileAddr
; ldy dynTileAddr
; jsr CopyTileDToDyn
; jsr CopyTileMToDyn
CopyTileMToDyn
phb
pea $0000
plb
plb
ldal tiledata+32+0,x
sta: $0080,y
ldal tiledata+32+2,x
sta: $0082,y
ldal tiledata+32+4,x
sta $0180,y
ldal tiledata+32+6,x
sta $0182,y
ldal tiledata+32+8,x
sta $0280,y
ldal tiledata+32+10,x
sta $0282,y
ldal tiledata+32+12,x
sta $0380,y
ldal tiledata+32+14,x
sta $0382,y
ldal tiledata+32+16,x
sta $0480,y
ldal tiledata+32+18,x
sta $0482,y
ldal tiledata+32+20,x
sta $0580,y
ldal tiledata+32+22,x
sta $0582,y
ldal tiledata+32+24,x
sta $0680,y
ldal tiledata+32+26,x
sta $0682,y
ldal tiledata+32+28,x
sta $0780,y
ldal tiledata+32+30,x
sta $0782,y
plb
rts
; CopyBG0Tile
;
; A low-level function that copies 8x8 tiles directly into the code field space.
@ -421,14 +358,6 @@ CopyTileMToDyn
; A = Tile ID (0 - 511)
; X = Tile column (0 - 40)
; Y = Tile row (0 - 25)
CopyBG0Tile ENT
phb
phk
plb
jsr _CopyBG0Tile
plb
rtl
_CopyBG0Tile
phb ; save the current bank
phx ; save the original x-value
@ -474,14 +403,6 @@ _CopyBG0Tile
; A = Tile ID (0 - 511)
; X = Tile column (0 - 40)
; Y = Tile row (0 - 25)
CopyBG1Tile
phb
phk
plb
jsr _CopyBG1Tile
plb
rtl
_CopyBG1Tile
phb ; save the current bank
phx ; save the original x-value
@ -518,7 +439,7 @@ _CopyBG1Tile
; a tile.
;
; TileStore+TS_TILE_ID : Tile descriptor
; TileStore+TS_DIRTY : $FFFF is clean, otherwise stores a back-reference to the DirtyTiles array
; TileStore+TS_DIRTY : $0000 is clean, any other value indicated a dirty tile
; TileStore+TS_TILE_ADDR : Address of the tile in the tile data buffer
; TileStore+TS_CODE_ADDR_LOW : Low word of the address in the code field that receives the tile
; TileStore+TS_CODE_ADDR_HIGH : High word of the address in the code field that receives the tile
@ -543,262 +464,117 @@ _CopyBG1Tile
; TileStore+TS_SPRITE_ADDR_15
; TileStore+TS_SPRITE_ADDR_16
; TileStore+
;TileStore ENT
; ds TILE_STORE_SIZE*11
; A list of dirty tiles that need to be updated in a given frame
DirtyTileCount ds 2
DirtyTiles ds TILE_STORE_SIZE ; At most this many tiles can possibly be update at once
; Initialize the tile storage data structures. This takes care of populating the tile records with the
; appropriate constant values.
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
lda #0
:yloop
sta TileStoreYTable,y
; To make processing the tile faster, we do them in chunks of eight. This allows the loop to be
; unrolled, which means we don't have to keep track of the register value and makes it faster to
; clear the dirty tile flag after being processed.
; _ApplyTilesUnrolled
tdc ; Move to the dedicated direct page for tile rendering
clc
adc #41*2
iny
iny
cpy #26*2
bcc :yloop
adc #$100
tcd
; Next, initialize the Tile Store itself
phb ; Save the current bank
tsc
sta tmp0 ; Save it on the direct page
bra at_loop
ldx #TILE_STORE_SIZE-2
lda #25
sta :row
lda #40
sta :col
lda #$8000
sta :vbuff
; The DirtyTiles array and the TileStore information is in the Tile Store bank. Because we
; process up to 8 tiles as a time and the tile code sets the bank register to the target
; code field bank, we need to restore the bank register each time. So, we pre-push
; 8 copies of the TileStore bank onto the stack.
:loop
; The first set of values in the Tile Store are changed during each frame based on the actions
; that are happening
at_exit
tdc ; Move back to the original direct page
sec
sbc #$100
tcd
lda #0
stal TileStore+TS_TILE_ID,x ; clear the tile store with the special zero tile
stal TileStore+TS_TILE_ADDR,x
stal TileStore+TS_TILE_DISP,x
stal TileStore+TS_SPRITE_FLAG,x ; no sprites are set at the beginning
lda #$FFFF ; none of the tiles are dirty
stal TileStore+TS_DIRTY,x
lda :vbuff ; array of sprite vbuff addresses per tile
stal TileStore+TS_VBUFF_ARRAY_ADDR,x
clc
adc #32
sta :vbuff
; The next set of values are constants that are simply used as cached parameters to avoid needing to
; calculate any of these values during tile rendering
lda :row ; Set the long address of where this tile
asl ; exists in the code fields
tay
lda BRowTableHigh,y
stal TileStore+TS_CODE_ADDR_HIGH,x ; High word of the tile address (just the bank)
lda BRowTableLow,y
stal TileStore+TS_BASE_ADDR,x ; May not be needed later if we can figure out the right constant...
lda :col ; Set the offset values based on the column
asl ; of this tile
asl
stal TileStore+TS_WORD_OFFSET,x ; This is the offset from 0 to 82, used in LDA (dp),y instruction
tay
lda Col2CodeOffset+2,y
clc
adcl TileStore+TS_BASE_ADDR,x
stal TileStore+TS_CODE_ADDR_LOW,x ; Low word of the tile address in the code field
dec :col
bpl :hop
dec :row
lda #40
sta :col
:hop
dex
dex
bpl :loop
plb ; Restore the original data bank and return
rts
dt_base equ $FE ; top of second direct page space
_ClearDirtyTiles
bra :hop
:loop
jsr _PopDirtyTile
:hop
lda DirtyTileCount
bne :loop
rts
at_loop
lda tmp0
tcs
; Helper function to get the address offset into the tile cachce / tile backing store
; X = tile column [0, 40] (41 columns)
; Y = tile row [0, 25] (26 rows)
GetTileStoreOffset ENT
phb
phk
plb
jsr _GetTileStoreOffset
plb
rtl
lda DirtyTileCount ; This is pre-multiplied by 2
beq at_exit ; If there are no items, exit
ldx TileStoreBankDoubled
phx
phx
phx
_GetTileStoreOffset
phx ; preserve the registers
phy
cmp #16 ; If there are >= 8 elements, then
bcs at_chunk ; do a full chunk
jsr _GetTileStoreOffset0
ply
plx
rts
_GetTileStoreOffset0
tya
asl
tay
txa
asl
clc
adc TileStoreYTable,y
rts
; Set a tile value in the tile backing store. Mark dirty if the value changes
;
; A = tile id
; X = tile column [0, 40] (41 columns)
; Y = tile row [0, 25] (26 rows)
;
; Registers are not preserved
_SetTile
pha
jsr _GetTileStoreOffset0 ; Get the address of the X,Y tile position
stz DirtyTileCount ; Otherwise, this pass will handle them all
tax
pla
jmp (at_table,x)
at_table da at_exit,at_one,at_two,at_three
da at_four,at_five,at_six,at_seven
cmpl TileStore+TS_TILE_ID,x ; Only set to dirty if the value changed
beq :nochange
at_chunk sec
sbc #16
sta DirtyTileCount ; Fall through
stal TileStore+TS_TILE_ID,x ; Value is different, store it.
jsr _GetTileAddr
stal TileStore+TS_TILE_ADDR,x ; Committed to drawing this tile, so get the address of the tile in the tiledata bank for later
; Because all of the registers get used in the _RenderTile2 subroutine, we
; push the values from the DirtyTiles array onto the stack and then pop off
; the values as we go
ldal TileStore+TS_TILE_ID,x
and #TILE_VFLIP_BIT+TILE_HFLIP_BIT ; get the lookup value
xba
stal TileStore+TS_TILE_DISP,x
ldy dt_base ; Reload the base index
ldx DirtyTiles+14,y ; Load the TileStore offset
stz TileStore+TS_DIRTY,x ; Clear this tile's dirty flag
jsr _RenderTile2 ; Draw the tile
plb ; Reset the data bank to the TileStore bank
; txa ; Add this tile to the list of dirty tiles to refresh
jmp _PushDirtyTileX ; on the next call to _ApplyTiles
:nochange rts
; Append a new dirty tile record
;
; A = result of _GetTileStoreOffset for X, Y
;
; The main purpose of this function is to
;
; 1. Avoid marking the same tile dirty multiple times, and
; 2. Pre-calculating all of the information necessary to render the tile
PushDirtyTile ENT
phb
phk
plb
jsr _PushDirtyTile
plb
rtl
; alternate version that is very slightly slower, but preserves the y-register
_PushDirtyTile
tax
; alternate entry point if the x-register is already set
_PushDirtyTileX
ldal TileStore+TS_DIRTY,x
bpl :occupied2
txa ; any non-negative value will work, this saves work below
stal TileStore+TS_DIRTY,x ; and is 1 cycle faster than loading a constant value
ldx DirtyTileCount ; 4
sta DirtyTiles,x ; 6
inx ; 2
inx ; 2
stx DirtyTileCount ; 4 = 18
rts
:occupied2
txa ; Make sure TileStore offset is returned in the accumulator
rts
; Remove a dirty tile from the list and return it in state ready to be rendered. It is important
; that the core rendering functions *only* use _PopDirtyTile to get a list of tiles to update,
; because this routine merges the tile IDs stored in the Tile Store with the Sprite
; information to set the TILE_SPRITE_BIT. This is the *only* place in the entire code base that
; applies this bit to a tile descriptor.
PopDirtyTile ENT
phb
phk
plb
jsr _PopDirtyTile
plb
rtl
_PopDirtyTile
ldy DirtyTileCount
bne _PopDirtyTile2
rts
_PopDirtyTile2 ; alternate entry point
dey
dey
sty DirtyTileCount ; remove last item from the list
ldx DirtyTiles,y ; load the offset into the Tile Store
lda #$FFFF
stal TileStore+TS_DIRTY,x ; clear the occupied backlink
rts
; Run through the dirty tile list and render them into the code field
ApplyTiles ENT
phb
phk
plb
jsr _ApplyTiles
plb
rtl
_ApplyTiles
bra :begin
:loop
; Retrieve the offset of the next dirty Tile Store items in the X-register
jsr _PopDirtyTile2
; Call the generic dispatch with the Tile Store record pointer at by the X-register.
phb
jsr _RenderTile2
at_seven
ldy dt_base
ldx DirtyTiles+12,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile2
plb
; Loop again until the list of dirty tiles is empty
at_six
ldy dt_base
ldx DirtyTiles+10,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile2
plb
:begin ldy DirtyTileCount
bne :loop
rts
at_five
ldy dt_base
ldx DirtyTiles+8,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile2
plb
at_four
ldy dt_base
ldx DirtyTiles+6,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile2
plb
at_three
ldy dt_base
ldx DirtyTiles+4,y
jsr _RenderTile2
plb
at_two
ldy dt_base
ldx DirtyTiles+2,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile2
plb
at_one
ldy dt_base
ldx DirtyTiles+0,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile2
plb
jmp at_loop

View File

@ -36,6 +36,53 @@ _TBSolidTile_VH
;
; This does not increase the FPS by 37% because only a small number of tiles are drawn each frame, but it
; has an impact and can significantly help out when sprites trigger more dirty tile updates than normal.
; This is called via a JMP (abs,x) with an extra byte on the stack that holds the bank
; register value. This must be restored prior to returning
CopyTileAFast
tax
_CopyTileAFast
]line equ 0
lup 8
ldal tiledata+{]line*4},x
sta: $0004+{]line*$1000},y
ldal tiledata+{]line*4}+2,x
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
plb
rts
CopyTileASlow
tax
jsr FillPEAOpcode
jmp _CopyTileAFast
CopyTileVFast
tax
_CopyTileVFast
]src equ 7
]dest equ 0
lup 8
ldal tiledata+{]src*4},x
sta: $0004+{]dest*$1000},y
ldal tiledata+{]src*4}+2,x
sta: $0001+{]dest*$1000},y
]src equ ]src-1
]dest equ ]dest+1
--^
plb
rts
CopyTileVSlow
tax
jsr FillPEAOpcode
jmp _CopyTileVFast
; Old routines
_TBCopyData
]line equ 0
lup 8
@ -47,17 +94,6 @@ _TBCopyData
--^
rts
;_TBCopyDataH
;]line equ 0
; lup 8
; ldal tiledata+{]line*4}+64,x
; sta: $0004+{]line*$1000},y
; ldal tiledata+{]line*4}+66,x
; sta: $0001+{]line*$1000},y
;]line equ ]line+1
; --^
; rts
_TBCopyDataV
]src equ 7
]dest equ 0
@ -71,40 +107,3 @@ _TBCopyDataV
--^
rts
;_TBCopyDataVH
;]src equ 7
;]dest equ 0
; lup 8
; ldal tiledata+{]src*4}+64,x
; sta: $0004+{]dest*$1000},y
; ldal tiledata+{]src*4}+66,x
; sta: $0001+{]dest*$1000},y
;]src equ ]src-1
;]dest equ ]dest+1
; --^
; rts
; A simple helper function that fill in all of the opcodes of a tile with the PEA opcode. This is
; a common function since a tile must be explicitly flagged to use a mask, so this routine is used
; quite frequently in a well-designed tile map.
_TBFillPEAOpcode
sep #$20
lda #$F4
sta: $0000,y
sta: $0003,y
sta $1000,y
sta $1003,y
sta $2000,y
sta $2003,y
sta $3000,y
sta $3003,y
sta $4000,y
sta $4003,y
sta $5000,y
sta $5003,y
sta $6000,y
sta $6003,y
sta $7000,y
sta $7003,y
rep #$20
rts

View File

@ -12,6 +12,48 @@ _TBDynamicTile_00
jsr _TBDynamicData
jmp _TBFillLdaDpOpcode
_TBDynamic
ldal TileStore+TS_TILE_ID,x
and #$007F
ora #$4800
]line equ 0 ; render the first column
lup 8
sta: $0004+{]line*$1000},y
]line equ ]line+1
--^
inc ; advance to the next word
inc
]line equ 0 ; render the second column
lup 8
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
sep #$20
lda #$B5
sta: $0000,y
sta: $0003,y
sta $1000,y
sta $1003,y
sta $2000,y
sta $2003,y
sta $3000,y
sta $3003,y
sta $4000,y
sta $4003,y
sta $5000,y
sta $5003,y
sta $6000,y
sta $6003,y
sta $7000,y
sta $7003,y
rep #$20
plb
rts
; Primitive to render a dynamic tile
;
; LDA 00,x / PHA where the operand is fixed when the tile is rendered

View File

@ -30,7 +30,6 @@ _TBFastSpriteTile_VH
; Need to update the X-register before calling this
_TBApplySpriteData
ldx _SPR_X_REG ; set to the unaligned tile block address in the sprite plane
]line equ 0
lup 8
lda blttmp+{]line*4}
@ -46,119 +45,47 @@ _TBApplySpriteData
--^
rts
; Copy tile data into the direct page compositing buffer. The main reason to do this in full passes is
; because we can avoid needing to use both the X and Y registers during the compositing process and
; reserve Y to hold the code field address.
;
; Also, we can get away with not setting the bank register, this is a wash in terms of speed, but results
; in simpler, more composable subroutines
_TBCopyTileDataToCBuff
_TBApplySpriteDataOne
ldx spriteIdx
]line equ 0
lup 8
ldal tiledata+{]line*4},x
sta blttmp+{]line*4}
lda blttmp+{]line*4}
andl spritemask+{]line*SPRITE_PLANE_SPAN},x
oral spritedata+{]line*SPRITE_PLANE_SPAN},x
sta: $0004+{]line*$1000},y
ldal tiledata+{]line*4}+2,x
sta blttmp+{]line*4}+2
lda blttmp+{]line*4}+2
andl spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
oral spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
rts
;_TBCopyTileDataToCBuffH
;]line equ 0
; lup 8
; ldal tiledata+{]line*4}+64,x
; sta blttmp+{]line*4}
;
; ldal tiledata+{]line*4}+64+2,x
; sta blttmp+{]line*4}+2
;]line equ ]line+1
; --^
; rts
_TBCopyTileDataToCBuffV
]src equ 7
]dest equ 0
lup 8
ldal tiledata+{]src*4},x
sta blttmp+{]dest*4}
ldal tiledata+{]src*4}+2,x
sta blttmp+{]dest*4}+2
]src equ ]src-1
]dest equ ]dest+1
--^
rts
;_TBCopyTileDataToCBuffVH
;]src equ 7
;]dest equ 0
; lup 8
; ldal tiledata+{]src*4}+64,x
; sta blttmp+{]dest*4}
;
; ldal tiledata+{]src*4}+64+2,x
; sta blttmp+{]dest*4}+2
;]src equ ]src-1
;]dest equ ]dest+1
; --^
; rts
; Copy tile mask data into the direct page compositing buffer.
_TBCopyTileMaskToCBuff
_TBApplySpriteDataTwo
]line equ 0
lup 8
ldal tiledata+{]line*4}+32,x
sta blttmp+{]line*4}+32
lda blttmp+{]line*4}
ldx spriteIdx+2
andl spritemask+{]line*SPRITE_PLANE_SPAN},x
oral spritedata+{]line*SPRITE_PLANE_SPAN},x
ldx spriteIdx
andl spritemask+{]line*SPRITE_PLANE_SPAN},x
oral spritedata+{]line*SPRITE_PLANE_SPAN},x
sta: $0004+{]line*$1000},y
ldal tiledata+{]line*4}+32+2,x
sta blttmp+{]line*4}+32+2
lda blttmp+{]line*4}+2
ldx spriteIdx+2
andl spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
oral spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
ldx spriteIdx
andl spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
oral spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
rts
;_TBCopyTileMaskToCBuffH
;]line equ 0
; lup 8
; ldal tiledata+{]line*4}+32+64,x
; sta blttmp+{]line*4}+32
;
; ldal tiledata+{]line*4}+32+64+2,x
; sta blttmp+{]line*4}+32+2
;]line equ ]line+1
; --^
; rts
_TBCopyTileMaskToCBuffV
]src equ 7
]dest equ 0
lup 8
ldal tiledata+{]src*4}+32,x
sta blttmp+{]dest*4}+32
ldal tiledata+{]src*4}+32+2,x
sta blttmp+{]dest*4}+32+2
]src equ ]src-1
]dest equ ]dest+1
--^
rts
;_TBCopyTileMaskToCBuffVH
;]src equ 7
;]dest equ 0
; lup 8
; ldal tiledata+{]src*4}+32+64,x
; sta blttmp+{]dest*4}+32
;
; ldal tiledata+{]src*4}+32+64+2,x
; sta blttmp+{]dest*4}+32+2
;]src equ ]src-1
;]dest equ ]dest+1
; --^
; rts
; Copy just the data into the code field from the composite buffer
_TBSolidComposite
]line equ 0

View File

@ -3,6 +3,48 @@
; This tile type does not explicitly support horizontal or vertical flipping. An appropriate tile
; descriptor should be passed into CopyTileToDyn to put the horizontally or vertically flipped source
; data into the dynamic tile buffer
_TBDynamicSpriteTile
sta _X_REG
ldal TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
ldal TileStore+TS_TILE_ID,x ; Get the original tile descriptor
and #$007F ; clamp to < (32 * 4)
ora #$B500
xba
sta _OP_CACHE ; This is the 2-byte opcode for to load the data
CopyDynWord 0;$0003
CopyDynWord 4;$1003
CopyDynWord 8;$2003
CopyDynWord 12;$3003
CopyDynWord 16;$4003
CopyDynWord 20;$5003
CopyDynWord 24;$6003
CopyDynWord 28;$7003
clc
lda _JTBL_CACHE
adc #32 ; All the snippets are 32 bytes wide and, since we're
sta _JTBL_CACHE ; within one tile, the second column is consecutive
lda _OP_CACHE
adc #$0200 ; Advance to the next word
sta _OP_CACHE
CopyDynWord 2;$0000
CopyDynWord 6;$1000
CopyDynWord 10;$2000
CopyDynWord 14;$3000
CopyDynWord 18;$4000
CopyDynWord 22;$5000
CopyDynWord 26;$6000
CopyDynWord 30;$7000
plb
rts
_TBDynamicSpriteTile_00
sty _Y_REG ; This is restored in the macro
@ -53,6 +95,56 @@ _TBDynamicSpriteTile_00
rts
; Create a masked render based on data in the direct page temporary buffer
;
; ]1 : sprite buffer offset
; ]2 : code field offset
CopyDynWord mac
lda tmp_sprite_mask+{]1} ; 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
lda tmp_sprite_data+{]1} ; load the sprite data
sta: ]2+1,y ; PEA operand
bra next
mixed cmp #$FFFF ; All 1's in the mask is a fully transparent sprite word
beq transparent
lda #$004C ; JMP to handler
sta: {]2},y
lda _JTBL_CACHE ; Get the offset to the exception handler for this column
ora #{]2&$F000} ; adjust for the current row offset
sta: {]2}+1,y
tax ; This becomes the new address that we use to patch in
lda _OP_CACHE ; Get the LDA dp,x instruction for this column
sta: $0000,x
lda #$0029 ; AND #SPRITE_MASK
sta: $0002,x
lda tmp_sprite_mask+{]1}
sta: $0003,x
lda #$0009 ; ORA #SPRITE_DATA
sta: $0005,x
lda tmp_sprite_data+{]1}
sta: $0006,x
lda #$0D80 ; branch to the prologue (BRA *+15)
sta: $0008,x
bra next
; This is a transparent word, so just show the dynamic data
transparent
lda #$4800 ; Put the PHA in the third byte
sta: {]2}+1,y
lda _OP_CACHE ; Store the LDA dp,x instruction with operand
sta: {]2},y
next
<<<
; Masked renderer for a dynamic tile with sprite data overlaid.
;
@ -70,13 +162,13 @@ CopyDynSpriteWord MAC
;
; If MASK == 0, then we can do a PEA. If MASK == $FFFF, then fall back to the simple Dynamic Tile
; code.
ldal spritemask+]1,x ; load the mask value
bne mixed ; a non-zero value may be mixed
ldal spritemask+{]1},x ; 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 spritedata+]1,x ; load the sprite data
ldal spritedata+{]1},x ; load the sprite data
sta: ]2+1,y ; PEA operand
bra next
@ -95,12 +187,12 @@ mixed cmp #$FFFF ; All 1's in the mask is a fully transpare
lda #$0029 ; AND #SPRITE_MASK
sta: $0002,y
ldal spritemask+]1,x
ldal spritemask+{]1},x
sta: $0003,y
lda #$0009 ; ORA #SPRITE_DATA
sta: $0005,y
ldal spritedata+]1,x
ldal spritedata+{]1},x
sta: $0006,y
lda #$0D80 ; branch to the prologue (BRA *+15)

View File

@ -82,13 +82,13 @@ CopyDynMaskedSpriteWord MAC
; If MASK == 0, then we can do a PEA. If MASK == $FFFF, then fall back to the simple Dynamic Tile
; code and eliminate the constanct AND/ORA instructions.
ldal spritemask+]1,x ; load the mask value
ldal spritemask+{]1},x ; 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 spritedata+]1,x ; load the sprite data
ldal spritedata+{]1},x ; load the sprite data
sta: ]2+1,y ; PEA operand
bra next
@ -99,7 +99,7 @@ mixed
sta: ]2,y
lda _JTBL_CACHE ; Get the offset to the exception handler for this column
ora #{]2&$F000} ; adjust for the current row offset
sta: ]2+1,y
sta: {]2}+1,y
tay ; This becomes the new address that we use to patch in
lda _OP_CACHE
@ -111,14 +111,14 @@ mixed
lda #$0029 ; AND #SPRITE_MASK
sta: $0006,y
ldal spritemask+]1,x
ldal spritemask+{]1},x
cmp #$FFFF ; All 1's in the mask is a fully transparent sprite word
beq transparent ; so we can use the Tile00011 method
sta: $0007,y
lda #$0009 ; ORA #SPRITE_DATA
sta: $0009,y
ldal spritedata+]1,x
ldal spritedata+{]1},x
sta: $000A,y
lda #$0980 ; branch to the prologue (BRA *+11)

View File

@ -77,7 +77,7 @@ CopyDynPriSpriteWord MAC
lda #$00A9 ; LDA #DATA
sta: $0000,y
ldal spritedata+]1,x
ldal spritedata+{]1},x
sta: $0001,y
lda _OP_CACHE

View File

@ -90,14 +90,14 @@ CopyDynPrioMaskedSpriteWord MAC
lda #$0029 ; AND #SPRITE_MASK
sta: $0002,y
ldal spritemask+]1,x
ldal spritemask+{]1},x
cmp #$FFFF ; All 1's in the mask is a fully transparent sprite word
beq transparent ; so we can use the Tile00011 method
sta: $0003,y
lda #$0009 ; ORA #SPRITE_DATA
sta: $0005,y
ldal spritedata+]1,x
ldal spritedata+{]1},x
sta: $0006,y
lda _T_PTR

View File

@ -1,4 +1,26 @@
; On entry
;
; B is set to the correct BG1 data bank
; A is set to the the tile descriptor
; Y is set to the top-left address of the tile in the BG1 data bank
;
; tmp0/tmp1 is reserved
_RenderTileBG1
pha ; Save the tile descriptor
and #TILE_VFLIP_BIT+TILE_HFLIP_BIT ; Only horizontal and vertical flips are supported for BG1
xba
tax
pla
and #TILE_ID_MASK ; Mask out the ID and save just that
_Mul128 ; multiplied by 128
jmp (:actions,x)
:actions dw _TBSolidBG1_00,_TBSolidBG1_0H,_TBSolidBG1_V0,_TBSolidBG1_VH
_TBSolidBG1_00
tax
]line equ 0
lup 8
ldal tiledata+{]line*4},x
@ -10,6 +32,7 @@ _TBSolidBG1_00
rts
_TBSolidBG1_0H
tax
]line equ 0
lup 8
ldal tiledata+{]line*4}+64,x
@ -21,6 +44,7 @@ _TBSolidBG1_0H
rts
_TBSolidBG1_V0
tax
]src equ 7
]dest equ 0
lup 8
@ -34,6 +58,7 @@ _TBSolidBG1_V0
rts
_TBSolidBG1_VH
tax
]src equ 7
]dest equ 0
lup 8

View File

@ -2,28 +2,6 @@
; 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.
; SetBG0YPos
;
; Set the virtual position of the primary background layer.
SetBG0YPos ENT
jsr _SetBG0YPos
rtl
_SetBG0YPos
cmp StartY
beq :out ; Easy, if nothing changed, then nothing changes
ldx StartY ; Load the old value (but don't save it yet)
sta StartY ; Save the new position
lda #DIRTY_BIT_BG0_Y
tsb DirtyBits ; Check if the value is already dirty, if so exit
bne :out ; without overwriting the original value
stx OldStartY ; First change, so preserve the value
:out rts
; Based on the current value of StartY in the direct page. Set up the dispatch
; information so that the BltRange driver will render the correct code field
; lines in the correct order
@ -67,6 +45,7 @@ _ApplyBG0YPos
; and ~2,500 per secord. This is ~1% of our total CPU budget and is *just* enough cycles to be
; interesting.... Another 8 cycles could be removed by doing all calculatinos pre-multiplied by 2
; to avoid several 'asl' instructions
phb
:loop
lda :virt_line
asl
@ -112,9 +91,9 @@ _ApplyBG0YPos
sta :lines_left
jne :loop
phk
plb
:out
rts
; Unrolled copy routine to move RTable intries into STK_ADDR position.
@ -167,7 +146,7 @@ CopyRTableToStkAddr
:x14 ldal RTable+26,x
sta STK_ADDR+$D000,y
:x13 ldal RTable+24,x
sta: STK_ADDR+$C000,y
sta STK_ADDR+$C000,y
:x12 ldal RTable+22,x
sta STK_ADDR+$B000,y
:x11 ldal RTable+20,x
@ -175,7 +154,7 @@ CopyRTableToStkAddr
:x10 ldal RTable+18,x
sta STK_ADDR+$9000,y
:x09 ldal RTable+16,x
sta: STK_ADDR+$8000,y
sta STK_ADDR+$8000,y
:x08 ldal RTable+14,x
sta STK_ADDR+$7000,y
:x07 ldal RTable+12,x
@ -183,7 +162,7 @@ CopyRTableToStkAddr
:x06 ldal RTable+10,x
sta STK_ADDR+$5000,y
:x05 ldal RTable+08,x
sta: STK_ADDR+$4000,y
sta STK_ADDR+$4000,y
:x04 ldal RTable+06,x
sta STK_ADDR+$3000,y
:x03 ldal RTable+04,x

711
src/render/Dynamic.s Normal file
View File

@ -0,0 +1,711 @@
; Rendering functions for Dynamic tiles. There are no Fast/Slow variants here
CopyDynamicTile
ldal TileStore+TS_TILE_ID,x
and #$007F
ora #$4800
]line equ 0 ; render the first column
lup 8
sta: $0004+{]line*$1000},y
]line equ ]line+1
--^
inc ; advance to the next word
inc
]line equ 0 ; render the second column
lup 8
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
sep #$20
lda #$B5
sta: $0000,y
sta: $0003,y
sta $1000,y
sta $1003,y
sta $2000,y
sta $2003,y
sta $3000,y
sta $3003,y
sta $4000,y
sta $4003,y
sta $5000,y
sta $5003,y
sta $6000,y
sta $6003,y
sta $7000,y
sta $7003,y
rep #$20
plb
rts
; These routines handle the sprites. They rely on a fairly complicated macro that takes care of
; populating the code field and snippet space
DynamicOver
lda TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
lda TileStore+TS_TILE_ID,x ; Get the original tile descriptor
and #$007F ; clamp to < (32 * 4)
ora #$B500
xba
sta _OP_CACHE ; This is the 2-byte opcode for to load the data
lda TileStore+TS_CODE_ADDR_HIGH,x
pha
ldy TileStore+TS_CODE_ADDR_LOW,x
plb
CopyDynOver 0;$0003
CopyDynOver 4;$1003
CopyDynOver 8;$2003
CopyDynOver 12;$3003
CopyDynOver 16;$4003
CopyDynOver 20;$5003
CopyDynOver 24;$6003
CopyDynOver 28;$7003
sec
lda _JTBL_CACHE
sbc #SNIPPET_SIZE ; Advance to the next snippet (Reverse indexing)
sta _JTBL_CACHE
clc
lda _OP_CACHE
adc #$0200 ; Advance to the next word
sta _OP_CACHE
CopyDynOver 2;$0000
CopyDynOver 6;$1000
CopyDynOver 10;$2000
CopyDynOver 14;$3000
CopyDynOver 18;$4000
CopyDynOver 22;$5000
CopyDynOver 26;$6000
CopyDynOver 30;$7000
plb
rts
DynamicUnder
lda TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
lda TileStore+TS_TILE_ID,x ; Get the original tile descriptor
and #$007F ; clamp to < (32 * 4)
ora #$3580 ; Pre-calc the AND $80,x opcode + operand
xba
sta _OP_CACHE ; This is the 2-byte opcode for to load the data
lda TileStore+TS_CODE_ADDR_HIGH,x
pha
ldy TileStore+TS_CODE_ADDR_LOW,x
plb
CopyDynUnder 0;$0003
CopyDynUnder 4;$1003
CopyDynUnder 8;$2003
CopyDynUnder 12;$3003
CopyDynUnder 16;$4003
CopyDynUnder 20;$5003
CopyDynUnder 24;$6003
CopyDynUnder 28;$7003
sec
lda _JTBL_CACHE
sbc #SNIPPET_SIZE
sta _JTBL_CACHE
clc
lda _OP_CACHE
adc #$0200 ; Advance to the next word
sta _OP_CACHE
CopyDynUnder 2;$0000
CopyDynUnder 6;$1000
CopyDynUnder 10;$2000
CopyDynUnder 14;$3000
CopyDynUnder 18;$4000
CopyDynUnder 22;$5000
CopyDynUnder 26;$6000
CopyDynUnder 30;$7000
; Now fill in the JMP opcodes
_DynFillJmpOpcode
sep #$20
lda #$4C
sta: $0000,y
sta: $0003,y
sta $1000,y
sta $1003,y
sta $2000,y
sta $2003,y
sta $3000,y
sta $3003,y
sta $4000,y
sta $4003,y
sta $5000,y
sta $5003,y
sta $6000,y
sta $6003,y
sta $7000,y
sta $7003,y
rep #$20
plb
rts
; Bank is already set to code field
; Y register is the offset
; X register is the TileStore
; A is the tile address
CopyDynamicTileTwoLyr
ldal TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
ldal TileStore+TS_WORD_OFFSET,x
ora #$B100 ; Pre-calc the LDA (dp),y opcode + operand
xba
sta _OP_CACHE
; We need to do an AND dp|$80,x / ORA dp,x. The opcode values are $35 and $15, respectively.
; We pre-calculate the AND opcode with the high bit of the operand set and then, in the macro
; perform and EOR #$2080 to covert the opcode and operand in one instruction
ldal TileStore+TS_TILE_ID,x ; Get the original tile descriptor
and #$007F ; clamp to < (32 * 4)
ora #$3580 ; Pre-calc the AND $80,x opcode + operand
xba
sta _OP_CACHE2 ; This is an op to load the dynamic tile data
CopyMaskedDWord $0003
CopyMaskedDWord $1003
CopyMaskedDWord $2003
CopyMaskedDWord $3003
CopyMaskedDWord $4003
CopyMaskedDWord $5003
CopyMaskedDWord $6003
CopyMaskedDWord $7003
sec
lda _JTBL_CACHE
sbc #SNIPPET_SIZE ; Advance to the next snippet (Reverse indexing)
sta _JTBL_CACHE
clc
lda _OP_CACHE
adc #$0200 ; Advance to the next word
sta _OP_CACHE
lda _OP_CACHE2
adc #$0200
sta _OP_CACHE2
CopyMaskedDWord $0000
CopyMaskedDWord $1000
CopyMaskedDWord $2000
CopyMaskedDWord $3000
CopyMaskedDWord $4000
CopyMaskedDWord $5000
CopyMaskedDWord $6000
CopyMaskedDWord $7000
jmp _DynFillJmpOpcode
; Render a sprite on top of a dyamic tile with transparent areas shwing the second background
DynamicOverTwoLyr
lda TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
lda TileStore+TS_WORD_OFFSET,x
ora #$B100 ; Pre-calc the LDA (dp),y opcode + operand
xba
sta _OP_CACHE
; We need to do an AND dp|$80,x / ORA dp,x. The opcode values are $35 and $15, respectively.
; We pre-calculate the AND opcode with the high bit of the operand set and then, in the macro
; perform and EOR #$2080 to covert the opcode and operand in one instruction
lda TileStore+TS_TILE_ID,x ; Get the original tile descriptor
and #$007F ; clamp to < (32 * 4)
ora #$3580 ; Pre-calc the AND $80,x opcode + operand
xba
sta _OP_CACHE2 ; This is an op to load the dynamic tile data
lda TileStore+TS_CODE_ADDR_HIGH,x
pha
ldy TileStore+TS_CODE_ADDR_LOW,x
plb
CopyDynMaskedSpriteWord 0;$0003
CopyDynMaskedSpriteWord 4;$1003
CopyDynMaskedSpriteWord 8;$2003
CopyDynMaskedSpriteWord 12;$3003
CopyDynMaskedSpriteWord 16;$4003
CopyDynMaskedSpriteWord 20;$5003
CopyDynMaskedSpriteWord 24;$6003
CopyDynMaskedSpriteWord 28;$7003
sec
lda _JTBL_CACHE
sbc #SNIPPET_SIZE ; Advance to the next snippet (Reverse indexing)
sta _JTBL_CACHE
clc
lda _OP_CACHE
adc #$0200 ; Advance to the next word
sta _OP_CACHE
lda _OP_CACHE2
adc #$0200 ; Advance to the next word
sta _OP_CACHE2
CopyDynMaskedSpriteWord 2;$0000
CopyDynMaskedSpriteWord 6;$1000
CopyDynMaskedSpriteWord 10;$2000
CopyDynMaskedSpriteWord 14;$3000
CopyDynMaskedSpriteWord 18;$4000
CopyDynMaskedSpriteWord 22;$5000
CopyDynMaskedSpriteWord 26;$6000
CopyDynMaskedSpriteWord 30;$7000
plb
rts
; Render a sprite on top of a dyamic tile with transparent areas shwing the second background
DynamicUnderTwoLyr
lda TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
lda TileStore+TS_WORD_OFFSET,x
ora #$B100 ; Pre-calc the LDA (dp),y opcode + operand
xba
sta _OP_CACHE
; We need to do an AND dp|$80,x / ORA dp,x. The opcode values are $35 and $15, respectively.
; We pre-calculate the AND opcode with the high bit of the operand set and then, in the macro
; perform and EOR #$2080 to covert the opcode and operand in one instruction
lda TileStore+TS_TILE_ID,x ; Get the original tile descriptor
and #$007F ; clamp to < (32 * 4)
ora #$3580 ; Pre-calc the AND $80,x opcode + operand
xba
sta _OP_CACHE2 ; This is an op to load the dynamic tile data
lda TileStore+TS_CODE_ADDR_HIGH,x
pha
ldy TileStore+TS_CODE_ADDR_LOW,x
plb
CopyDynPrioMaskedSpriteWord 0;$0003
CopyDynPrioMaskedSpriteWord 4;$1003
CopyDynPrioMaskedSpriteWord 8;$2003
CopyDynPrioMaskedSpriteWord 12;$3003
CopyDynPrioMaskedSpriteWord 16;$4003
CopyDynPrioMaskedSpriteWord 20;$5003
CopyDynPrioMaskedSpriteWord 24;$6003
CopyDynPrioMaskedSpriteWord 28;$7003
sec
lda _JTBL_CACHE
sbc #SNIPPET_SIZE ; Advance to the next snippet (Reverse indexing)
sta _JTBL_CACHE
clc
lda _OP_CACHE
adc #$0200 ; Advance to the next word
sta _OP_CACHE
lda _OP_CACHE2
adc #$0200 ; Advance to the next word
sta _OP_CACHE2
CopyDynPrioMaskedSpriteWord 2;$0000
CopyDynPrioMaskedSpriteWord 6;$1000
CopyDynPrioMaskedSpriteWord 10;$2000
CopyDynPrioMaskedSpriteWord 14;$3000
CopyDynPrioMaskedSpriteWord 18;$4000
CopyDynPrioMaskedSpriteWord 22;$5000
CopyDynPrioMaskedSpriteWord 26;$6000
CopyDynPrioMaskedSpriteWord 30;$7000
plb
rts
; Create a masked render based on data in the direct page temporary buffer.
;
; If the MASK is $0000, then insert a PEA
; If the MASK is $FFFF, then insert a LDA DP,x / PHA
; If mixed, create a snippet of LDA DP,x / AND #MASK / ORA #DATA / PHA
;
; ]1 : sprite buffer offset
; ]2 : code field offset
CopyDynOver mac
lda tmp_sprite_mask+{]1} ; 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
lda tmp_sprite_data+{]1} ; load the sprite data
sta: ]2+1,y ; PEA operand
bra next
mixed cmp #$FFFF ; All 1's in the mask is a fully transparent sprite word
beq transparent
lda #$004C ; JMP to handler
sta: {]2},y
lda _JTBL_CACHE ; Get the offset to the exception handler for this column
ora #{]2&$7000} ; adjust for the current row offset
sta: {]2}+1,y
tax ; This becomes the new address that we use to patch in
lda _OP_CACHE ; Get the LDA dp,x instruction for this column
sta: $0000,x
lda #$0029 ; AND #SPRITE_MASK
sta: $0002,x
lda tmp_sprite_mask+{]1}
sta: $0003,x
lda #$0009 ; ORA #SPRITE_DATA
sta: $0005,x
lda tmp_sprite_data+{]1}
sta: $0006,x
lda #$0D80 ; branch to the prologue (BRA *+15)
sta: $0008,x
bra next
; This is a transparent word, so just show the dynamic data
transparent
lda #$4800 ; Put the PHA in the third byte
sta: {]2}+1,y
lda _OP_CACHE ; Store the LDA dp,x instruction with operand
sta: {]2},y
next
<<<
; Masked renderer for a dynamic tile on top of the sprite data. There are no transparent vs
; solid vs mixed considerations here. This only sets the JMP address, setting the JMP opcodes
; must happen elsewhere
;
; ]1 : sprite plane offset
; ]2 : code field offset
CopyDynUnder MAC
; Need to fill in the first 9 bytes of the JMP handler with the following code sequence where
; the data and mask from from the sprite plane
;
; lda #DATA
; and $80,x
; ora $00,x
; bra *+16
lda _JTBL_CACHE ; Get the offset to the exception handler for this column
ora #{]2&$7000} ; adjust for the current row offset
sta: {]2}+1,y
tax ; This becomes the new address that we use to patch in
lda #$00A9 ; LDA #DATA
sta: $0000,x
lda tmp_sprite_data+{]1}
sta: $0001,x
lda _OP_CACHE
sta: $0003,x ; AND $80,x
eor #$8020 ; Switch the opcode to an ORA and remove the high bit of the operand
sta: $0005,x ; ORA $00,x
lda #$0E80 ; branch to the prologue (BRA *+16)
sta: $0007,x
eom
; Masked renderer for a dynamic tile. 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 : code field offset
;
; This macro does not set the opcode since they will all be JMP instructions, they can be
; filled more efficiently in a separate routine.
CopyMaskedDWord MAC
; Need to fill in the first 6 bytes of the JMP handler with the following code sequence
;
; lda (00),y
; and $80,x
; ora $00,x
; bra *+17
lda _JTBL_CACHE
ora #{{]1}&$7000} ; adjust for the current row offset
sta: {]1}+1,y
tax ; This becomes the new address that we use to patch in
lda _OP_CACHE
sta: $0000,x ; LDA (00),y
lda _OP_CACHE2
sta: $0002,x ; AND $80,x
eor #$8020 ; Switch the opcode to an ORA and remove the high bit of the operand
sta: $0004,x ; ORA $00,x
lda #$0F80 ; branch to the prologue (BRA *+17)
sta: $0006,x
eom
; Masked renderer for a masked dynamic tile with sprite data overlaid.
;
; ]1 : sprite plane offset
; ]2 : code field offset
CopyDynMaskedSpriteWord MAC
; Need to fill in the first 14 bytes of the JMP handler with the following code sequence where
; the data and mask from from the sprite plane
;
; lda ($00),y
; and $80,x
; ora $00,x
; and #MASK
; ora #DATA
; bra *+15
;
; If MASK == 0, then we can do a PEA. If MASK == $FFFF, then fall back to the simple Dynamic Tile
; code and eliminate the constanct AND/ORA instructions.
lda tmp_sprite_mask+{]1} ; 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
lda tmp_sprite_data+{]1} ; load the sprite data
sta: {]2}+1,y ; PEA operand
bra next
; We will always do a JMP to the exception handler, so set that up, then check for sprite
; transparency
mixed
lda #$004C ; JMP to handler
sta: {]2},y
lda _JTBL_CACHE ; Get the offset to the exception handler for this column
ora #{]2&$7000} ; adjust for the current row offset
sta: {]2}+1,y
tax ; This becomes the new address that we use to patch in
lda _OP_CACHE
sta: $0000,x ; LDA (00),y
lda _OP_CACHE2
sta: $0002,x ; AND $80,x
eor #$8020 ; Switch the opcode to an ORA and remove the high bit of the operand
sta: $0004,x ; ORA $00,x
lda #$0029 ; AND #SPRITE_MASK
sta: $0006,x
lda tmp_sprite_mask+{]1}
cmp #$FFFF ; All 1's in the mask is a fully transparent sprite word
beq transparent
sta: $0007,x
lda #$0009 ; ORA #SPRITE_DATA
sta: $0009,x
lda tmp_sprite_data+{]1}
sta: $000A,x
lda #$0980 ; branch to the prologue (BRA *+11)
sta: $000C,x
bra next
; This is a transparent word, so just show the dynamic data
transparent
lda #$0F80 ; branch to the epilogue (BRA *+17)
sta: $0006,x
next
eom
; Masked renderer for a masked dynamic tile with sprite data underlaid.
;
; ]1 : sprite plane offset
; ]2 : code field offset
CopyDynPrioMaskedSpriteWord MAC
; Need to fill in the first 14 bytes of the JMP handler with the following code sequence where
; the data and mask from from the sprite plane
;
; lda ($00),y
; and #MASK
; ora #DATA
; and $80,x
; ora $00,x
; bra *+15
lda #$004C ; JMP to handler
sta: {]2},y
lda _JTBL_CACHE ; Get the offset to the exception handler for this column
ora #{]2&$7000} ; adjust for the current row offset
sta: {]2}+1,y
tax ; This becomes the new address that we use to patch in
lda _OP_CACHE
sta: $0000,x ; LDA (00),y
lda #$0029 ; AND #SPRITE_MASK
sta: $0002,x
lda tmp_sprite_mask+{]1}
cmp #$FFFF ; All 1's in the mask is a fully transparent sprite word
beq transparent ; so we can use the Tile00011 method
sta: $0003,x
lda #$0009 ; ORA #SPRITE_DATA
sta: $0005,x
lda tmp_sprite_data+{]1}
sta: $0006,x
lda _OP_CACHE2
sta: $0008,x ; AND $80,x
eor #$8020 ; Switch the opcode to an ORA and remove the high bit of the operand
sta: $000A,x ; ORA $00,x
lda #$0980 ; branch to the prologue (BRA *+11)
sta: $000C,x
bra next
; This is a transparent word, so just show the dynamic data
transparent
lda _OP_CACHE2
sta: $0002,x ; AND $80,x
eor #$8020 ; Switch the opcode to an ORA and remove the high bit of the operand
sta: $0004,x ; ORA $00,x
lda #$0F80 ; branch to the epilogue (BRA *+17)
sta: $0006,x
next
eom
; Helper functions to move tile data into the dynamic tile space
; Helper functions to copy tile data to the appropriate location in Bank 0
; X = tile ID
; Y = dynamic tile ID
CopyTileToDyn
txa
jsr _GetTileAddr
tax
tya
and #$001F ; Maximum of 32 dynamic tiles
asl
asl ; 4 bytes per page
adc BlitterDP ; Add to the bank 00 base address
adc #$0100 ; Go to the next page
tay
jsr CopyTileDToDyn ; Copy the tile data
jmp CopyTileMToDyn ; Copy the tile mask
; X = address of tile
; Y = tile address in bank 0
CopyTileDToDyn
phb
pea $0000
plb
plb
ldal tiledata+0,x
sta: $0000,y
ldal tiledata+2,x
sta: $0002,y
ldal tiledata+4,x
sta $0100,y
ldal tiledata+6,x
sta $0102,y
ldal tiledata+8,x
sta $0200,y
ldal tiledata+10,x
sta $0202,y
ldal tiledata+12,x
sta $0300,y
ldal tiledata+14,x
sta $0302,y
ldal tiledata+16,x
sta $0400,y
ldal tiledata+18,x
sta $0402,y
ldal tiledata+20,x
sta $0500,y
ldal tiledata+22,x
sta $0502,y
ldal tiledata+24,x
sta $0600,y
ldal tiledata+26,x
sta $0602,y
ldal tiledata+28,x
sta $0700,y
ldal tiledata+30,x
sta $0702,y
plb
rts
; Helper function to copy tile mask to the appropriate location in Bank 0
;
; X = address of tile
; Y = tile address in bank 0
;
; Argument are the same as CopyTileDToDyn, the code takes care of adjust offsets.
; This make is possible to call the two functions back-to-back
;
; ldx tileAddr
; ldy dynTileAddr
; jsr CopyTileDToDyn
; jsr CopyTileMToDyn
CopyTileMToDyn
phb
pea $0000
plb
plb
ldal tiledata+32+0,x
sta: $0080,y
ldal tiledata+32+2,x
sta: $0082,y
ldal tiledata+32+4,x
sta $0180,y
ldal tiledata+32+6,x
sta $0182,y
ldal tiledata+32+8,x
sta $0280,y
ldal tiledata+32+10,x
sta $0282,y
ldal tiledata+32+12,x
sta $0380,y
ldal tiledata+32+14,x
sta $0382,y
ldal tiledata+32+16,x
sta $0480,y
ldal tiledata+32+18,x
sta $0482,y
ldal tiledata+32+20,x
sta $0580,y
ldal tiledata+32+22,x
sta $0582,y
ldal tiledata+32+24,x
sta $0680,y
ldal tiledata+32+26,x
sta $0682,y
ldal tiledata+32+28,x
sta $0780,y
ldal tiledata+32+30,x
sta $0782,y
plb
rts

258
src/render/Fast.s Normal file
View File

@ -0,0 +1,258 @@
; Collection of render function used when the engine is in "FAST" mode. In this mode
; there are no dynamic tile or two layer tiles enabled, so all of the tiles are comprised
; of PEA opcodes. These functions take advantage of this as the fact that masks are
; not needed to improve rendering speed.
ConstTile0Fast
lda #0
sta: $0001,y
sta: $0004,y
sta $1001,y
sta $1004,y
sta $2001,y
sta $2004,y
sta $3001,y
sta $3004,y
sta $4001,y
sta $4004,y
sta $5001,y
sta $5004,y
sta $6001,y
sta $6004,y
sta $7001,y
sta $7004,y
plb
rts
SpriteOverAFast
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x
tax
plb
_SpriteOverAFast ; Alternate entry point for the "Slow" routines
]line equ 0
lup 8
ldal tiledata+{]line*4},x
and tmp_sprite_mask+{]line*4}
ora tmp_sprite_data+{]line*4}
sta: $0004+{]line*$1000},y
ldal tiledata+{]line*4}+2,x
and tmp_sprite_mask+{]line*4}+2
ora tmp_sprite_data+{]line*4}+2
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
plb
rts
SpriteOverVFast
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x
tax
plb
_SpriteOverVFast
]src equ 7
]dest equ 0
lup 8
ldal tiledata+{]src*4},x
and tmp_sprite_mask+{]dest*4}
ora tmp_sprite_data+{]dest*4}
sta: $0004+{]dest*$1000},y
ldal tiledata+{]src*4}+2,x
and tmp_sprite_mask+{]dest*4}+2
ora tmp_sprite_data+{]dest*4}+2
sta: $0001+{]dest*$1000},y
]src equ ]src-1
]dest equ ]dest+1
--^
plb
rts
SpriteOver0Fast
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
plb
_SpriteOver0Fast
]line equ 0
lup 8
lda tmp_sprite_data+{]line*4}
sta: $0004+{]line*$1000},y
lda tmp_sprite_data+{]line*4}+2
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
plb
rts
SpriteUnderAFast
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x
tax
plb
_SpriteUnderAFast
]line equ 0
lup 8
lda tmp_sprite_data+{]line*4}
andl tiledata+{]line*4}+32,x
oral tiledata+{]line*4},x
sta: $0004+{]line*$1000},y
lda tmp_sprite_data+{]line*4}+2
andl tiledata+{]line*4}+32+2,x
oral tiledata+{]line*4}+2,x
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
plb
rts
SpriteUnderVFast
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x
tax
plb
_SpriteUnderVFast
]src equ 7
]dest equ 0
lup 8
lda tmp_sprite_data+{]dest*4}
andl tiledata+{]src*4}+32,x
oral tiledata+{]src*4},x
sta: $0004+{]dest*$1000},y
lda tmp_sprite_data+{]dest*4}+2
andl tiledata+{]src*4}+32+2,x
oral tiledata+{]src*4}+2,x
sta: $0001+{]dest*$1000},y
]src equ ]src-1
]dest equ ]dest+1
--^
plb
rts
SpriteUnder0Fast
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
plb
_SpriteUnder0Fast
lda #0
]line equ 0
lup 8
sta: $0004+{]line*$1000},y
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
plb
rts
; Simple pair of routines that copies just the tile data to the direct page workspace. Data Bank
; must be set to the TileData bank in entry.
;
; Preserves the X-register
FastCopyTileDataA
ldy TileStore+TS_TILE_ADDR,x ; load the tile address
pei DP2_TILEDATA_AND_TILESTORE_BANKS
plb ; set to the tiledata bank
]line equ 0
lup 8
lda tiledata+{]line*4},y
sta tmp_tile_data+{]line*4}
lda tiledata+{]line*4}+2,y
sta tmp_tile_data+{]line*4}+2
]line equ ]line+1
--^
plb
rts
FastCopyTileDataV
ldy TileStore+TS_TILE_ADDR,x ; load the tile address
pei DP2_TILEDATA_AND_TILESTORE_BANKS
plb ; set to the tiledata bank
]src equ 7
]dest equ 0
lup 8
lda tiledata+{]src*4},y
sta tmp_tile_data+{]dest*4}
lda tiledata+{]src*4}+2,y
sta tmp_tile_data+{]dest*4}+2
]src equ ]src-1
]dest equ ]dest+1
--^
plb
rts
FastCopyTileDataAndMaskA
ldy TileStore+TS_TILE_ADDR,x ; load the tile address
pei DP2_TILEDATA_AND_TILESTORE_BANKS
plb ; set to the tiledata bank
]line equ 0
lup 8
lda tiledata+{]line*4},y
sta tmp_tile_data+{]line*4}
lda tiledata+{]line*4}+32,y
sta tmp_tile_mask+{]line*4}
lda tiledata+{]line*4}+2,y
sta tmp_tile_data+{]line*4}+2
lda tiledata+{]line*4}+32+2,y
sta tmp_tile_mask+{]line*4}+2
]line equ ]line+1
--^
plb
rts
FastCopyTileDataAndMaskV
ldy TileStore+TS_TILE_ADDR,x ; load the tile address
pei DP2_TILEDATA_AND_TILESTORE_BANKS
plb ; set to the tiledata bank
]src equ 7
]dest equ 0
lup 8
lda tiledata+{]src*4},y
sta tmp_tile_data+{]dest*4}
lda tiledata+{]src*4}+32,y
sta tmp_tile_mask+{]dest*4}
lda tiledata+{]src*4}+2,y
sta tmp_tile_data+{]dest*4}+2
lda tiledata+{]src*4}+32+2,y
sta tmp_tile_mask+{]dest*4}+2
]src equ ]src-1
]dest equ ]dest+1
--^
plb
rts

83
src/render/README.txt Normal file
View File

@ -0,0 +1,83 @@
This folder contains the rendering tuples for the different type of tile rendering modes
that are defined by both the engine mode and the specific tile attributes. There are
a *lot* or variants, so they are cataloged here.
The top-level TileRender function in the main entry point that defined the overal tile render
flow as well as the register parameters and calling conventions for each of the modular
components.
There are 5 pluggable functions that make up a rendering mode
1. K_TS_BASE_TILE_DISP
An address to a function that will render a tile into the code field. There are no
sprites to handle in this case.
Arguments:
A: TileData/TileMask address
B: code field bank
Y: address of the tile in the code bank
X: TileStore offset
Return:
None
If additional TileStore properties are needed for the renderer, they can be read using the X
register.
2. K_TS_SPRITE_TILE_DISP
Selects the top-level handler for rendering a tile with a sprite. Currently, this is used to
select between rendering a sprite above the tile, or under the tile based on the value of the
TILE_PRIORITY_BIT.
Arguments:
A: TileStore+TS_SPRITE_FLAG
X: TileStore offset
Return:
Y: TileStore offset
sprite_ptrX dirct page values set to the sprite VBuff addresses
The handler routine is responsible for examining the TS_SPRITE_FLAG value and dispatching
to an appropriate routine to handle the number of sprites intersecting the tile.
3. K_TS_ONE_SPRITE
A specialized routine when K_TS_SPRITE_TILE_DISP determines there is only one sprite to render
it MUST dispatch to this function. The K_TS_ONE_SPRITE routine MAY make use of the K_TS_COPY_TILE_DATA
and K_TS_APPLY_TILE_DATA functions, but is not required to do so.
4. K_TS_COPY_TILE_DATA & K_TS_APPLY_TILE_DATA
A pair of function that copye tile data (and possible mask information) into a temporary
direct page space and then render that workspace into the code field.
These functions are used as building blocks by the generic Over/Under multi-sprite
rendering code.
K_TS_COPY_TILE_DATA
Arguments:
B: Set to the TileData bank
Y: Set to the tile address
Return:
X: preserve the X register
K_TS_APPLY_TILE_DATA
Arguments:
B: code field bank
Y: address of the tile in the code bank
Return:
None
Generic Flow
1. Is there a sprite?
No -> Call K_TS_BASE_TILE_DISP to render a tile into the code field
Yes -> Call K_TS_SPRITE_TILE_DISP
Over : Copy tile data + mask to DP, Copy sprite data + mask to DP, render tile to code field
Under : Copy sprite data to DP,

86
src/render/Render.s Normal file
View File

@ -0,0 +1,86 @@
; 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
lda TileStore+TS_SPRITE_FLAG,x ; any sprites on this line?
bne :sprites
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x ; load the address of this tile's data (pre-calculated)
plb ; set the code field bank
jmp (K_TS_BASE_TILE_DISP,x) ; go to the tile copy routine
; Execute the sprite tree. If there is only one sprite, control will immediately be passed to
; the routine at K_TS_ONE_SPRITE. Otherwise, the control passed to the routines with a different
; number of sprites. These routines need to copy the flattened sprite data and mask into the
; direct page workspace to be used by the K_TS_SPRITE_TILE_DISP routine
:sprites txy
SpriteBitsToVBuffAddrs $0000;TwoSprites;ThreeSprites;FourSprites
; Dispatch vectors for the two, three and four sprite functions. These just
; flatten the sprite data into the direct page workspace and then pass control
; to the configurable routine which is set in SetTile and knows what to do
; based on the tile properties (over/under, engine mode, etc.)
;
; NOTE: Could pull the CopyXXXSprites function inline and same the 3 cycles for the JMP,
; - or - put the TYX into the macro and jump directly from there.
TwoSprites tyx
jmp CopyTwoSpritesDataAndMaskToDP
ThreeSprites tyx
jmp CopyThreeSpritesDataAndMaskToDP
FourSprites tyx
jmp CopyFourSpritesDataAndMaskToDP
; Now, implement the generic Two, Three and Four sprite routines for both Over and Under rendering. These
; are fairly involved, so we try to only have a single implementation of them for now without excessve
; specialization.
FourSpriteLine mac
; and [sprite_ptr3],y
db $37,sprite_ptr3
ora (sprite_ptr3),y
; and [sprite_ptr2],y
db $37,sprite_ptr2
ora (sprite_ptr2),y
; and [sprite_ptr1],y
db $37,sprite_ptr1
ora (sprite_ptr1),y
; and [sprite_ptr0],y
db $37,sprite_ptr0
ora (sprite_ptr0),y
<<<
FourSpritesFast
tyx ; save for after compositing the sprites
ldy TileStore+TS_TILE_ADDR,x
pei DP2_TILEDATA_AND_TILESTORE_BANKS
plb
jsr (K_TS_COPY_TILE_DATA,x)
plb
pei DP2_SPRITEDATA_AND_TILESTORE_BANKS
plb ; set the sprite data bank
]line equ 0
lup 8
ldy #{]line*SPRITE_PLANE_SPAN}
lda tmp_tile_data+{]line*4}
FourSpriteLine
sta tmp_tile_data+{]line*4}
ldy #{]line*SPRITE_PLANE_SPAN}+2
lda tmp_tile_data+{]line*4}+2
FourSpriteLine
sta tmp_tile_data+{]line*4}+2
]line equ ]line+1
--^
plb
jmp (K_TS_APPLY_TILE_DATA,x)

86
src/render/Slow.s Normal file
View File

@ -0,0 +1,86 @@
; Identical routines to those in Fast.s, but also set the opcode. Used to render solid
; tiles when the engine mode has other capabilities turned on
;
; The following functions are defined here
;
; GenericOverSlow : Places data from tmp_sprite_data on top of the TileStore's tile
; GenericUnderSlow : Places the TileStore's tile on top of tmp_sprite_data
ConstTile0Slow
jsr FillPEAOpcode
jmp ConstTile0Fast
SpriteOverASlow
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x
tax
plb
jsr FillPEAOpcode
jmp _SpriteOverAFast
SpriteOverVSlow
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x
tax
plb
jsr FillPEAOpcode
jmp _SpriteOverVFast
SpriteOver0Slow
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
plb
jsr FillPEAOpcode
jmp _SpriteOver0Fast
SpriteUnderASlow
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x
tax
plb
jsr FillPEAOpcode
jmp _SpriteUnderAFast
SpriteUnderVSlow
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
lda TileStore+TS_TILE_ADDR,x
tax
plb
jsr FillPEAOpcode
jmp _SpriteUnderVFast
SpriteUnder0Slow
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
plb
jsr FillPEAOpcode
jmp _SpriteUnder0Fast
; Helper function; no stack manipulation
FillPEAOpcode
sep #$20
lda #$F4
]line equ 0
lup 8
sta: $0000+{]line*$1000},y
sta: $0003+{]line*$1000},y
]line equ ]line+1
--^
rep #$20
rts
; This is a stub; will be removed eventually
_FillPEAOpcode
jsr FillPEAOpcode
plb ; Restore the TileStore bank
rts

265
src/render/Sprite1.s Normal file
View File

@ -0,0 +1,265 @@
; Specialized routines that can be assigned to K_TS_ONE_SPRITE for rendering a single sprite into
; a tile. There are more variants of this function because having a single sprite in a tile is a very
; common scenario, so we put additional effort into optimizing this case.
;------------------------------
; Section: Above Tile Renderers
; The simplest implementation. When drawing a sprite over Tile 0 in FAST mode, we can just copy the
; sprite data into the coe field directly.
OneSpriteFastOver0
ldy TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
phy ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
tax ; VBuff address from SpriteBitsToVBuffAddrs macro
plb ; set to the code field bank
_OneSpriteFastOver0
]line equ 0
lup 8
ldal spritedata+{]line*SPRITE_PLANE_SPAN},x
sta: $0004+{]line*$1000},y
ldal spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
plb ; Restore the TileStore bank
rts
; Next implementation; drawing a sprite onto a regular tile. The 1-sprite dispatch preserves the
; X-register, so it already points to the TileStore
OneSpriteFastOverV
jsr FastCopyTileDataV
bra _OneSpriteFastOver
OneSpriteFastOverA
jsr FastCopyTileDataA
_OneSpriteFastOver
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
ldx sprite_ptr0
plb
_OneSpriteFastOverA
_OneSpriteFastOverV
]line equ 0
lup 8
lda tmp_tile_data+{]line*4}
andl spritemask+{]line*SPRITE_PLANE_SPAN},x
oral spritedata+{]line*SPRITE_PLANE_SPAN},x
sta: $0004+{]line*$1000},y
lda tmp_tile_data+{]line*4}+2
andl spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
oral spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
plb
rts
; This is the "SLOW" variant that fills in the PEA opcode specialized for Tile 0.
OneSpriteSlowOver0
ldy TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
phy ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
tax ; VBuff address from SpriteBitsToVBuffAddrs macro
plb ; set to the code field bank
jsr FillPEAOpcode
jmp _OneSpriteFastOver0
; Slow variant for regular tile.
OneSpriteSlowOverV
jsr FastCopyTileDataV
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
ldx sprite_ptr0
plb
jsr FillPEAOpcode
jmp _OneSpriteFastOverV
OneSpriteSlowOverA
jsr FastCopyTileDataA
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
ldx sprite_ptr0
plb
jsr FillPEAOpcode
jmp _OneSpriteFastOverA
;------------------------------
; Section: Below Tile Renderers
; Drawing under the zero tile is the same as not drawing a sprite fo both the fast and slow cases
OneSpriteFastUnderA
jsr FastCopyTileDataAndMaskA
bra _OneSpriteFastUnder
OneSpriteFastUnderV
jsr FastCopyTileDataAndMaskV
_OneSpriteFastUnder
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
ldx sprite_ptr0
plb
_OneSpriteFastUnderA
_OneSpriteFastUnderV
]line equ 0
lup 8
ldal spritedata+{]line*SPRITE_PLANE_SPAN},x
and tmp_tile_mask+{]line*4}
ora tmp_tile_data+{]line*4}
sta: $0004+{]line*$1000},y
ldal spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
and tmp_tile_mask+{]line*4}+2
ora tmp_tile_data+{]line*4}+2
sta: $0001+{]line*$1000},y
]line equ ]line+1
--^
plb
rts
OneSpriteSlowUnderA
jsr FastCopyTileDataAndMaskA
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
ldx sprite_ptr0
plb
jsr FillPEAOpcode
jmp _OneSpriteFastUnderA
OneSpriteSlowUnderV
jsr FastCopyTileDataAndMaskV
lda TileStore+TS_CODE_ADDR_HIGH,x ; load the bank of the target code field line
pha ; and put on the stack for later. Has TileStore bank in high byte.
ldy TileStore+TS_CODE_ADDR_LOW,x ; load the address of the code field
ldx sprite_ptr0
plb
jsr FillPEAOpcode
jmp _OneSpriteFastUnderV
;-------------------------------
; Dynamic tiles with one sprite.
OneSpriteDynamicUnder
txy
tax
jsr _CopySpriteDataToDP
tyx
jmp DynamicUnder
OneSpriteDynamicOver
txy
tax
jsr _CopySpriteDataAndMaskToDP
tyx
jmp DynamicOver
;-------------------------------
; Two Layer tiles with one sprite. Just copy the data and go through the generic sprite path
OneSpriteOver0TwoLyr
txy
tax
jsr _CopySpriteDataAndMaskToDP
tyx
jmp SpriteOver0TwoLyr
OneSpriteTwoLyrOverA
txy
tax
jsr _CopySpriteDataAndMaskToDP
tyx
jmp SpriteOverATwoLyr
OneSpriteTwoLyrOverV
txy
tax
jsr _CopySpriteDataAndMaskToDP
tyx
jmp SpriteOverVTwoLyr
OneSpriteTwoLyrUnderA
txy
tax
jsr _CopySpriteDataAndMaskToDP
tyx
jmp SpriteUnderATwoLyr
OneSpriteTwoLyrUnderV
txy
tax
jsr _CopySpriteDataAndMaskToDP
tyx
jmp SpriteUnderVTwoLyr
;-----------------------------------------
; Dynamic two-layer tiles with one sprite.
OneSpriteDynamicOverTwoLyr
txy
tax
jsr _CopySpriteDataAndMaskToDP
tyx
jmp DynamicOverTwoLyr
OneSpriteDynamicUnderTwoLyr
txy
tax
jsr _CopySpriteDataAndMaskToDP
tyx
jmp DynamicUnderTwoLyr
;-------------------------------------
; Generic helpers
_CopySpriteDataToDP
]line equ 0
lup 8
ldal spritedata+{]line*SPRITE_PLANE_SPAN},x
sta tmp_sprite_data+{]line*4}
ldal spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
sta tmp_sprite_data+{]line*4}+2
]line equ ]line+1
--^
rts
_CopySpriteMaskToDP
]line equ 0
lup 8
ldal spritemask+{]line*SPRITE_PLANE_SPAN},x
sta tmp_sprite_mask+{]line*4}
ldal spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
sta tmp_sprite_mask+{]line*4}+2
]line equ ]line+1
--^
rts
_CopySpriteDataAndMaskToDP
]line equ 0
lup 8
ldal spritedata+{]line*SPRITE_PLANE_SPAN},x
sta tmp_sprite_data+{]line*4}
ldal spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
sta tmp_sprite_data+{]line*4}+2
ldal spritemask+{]line*SPRITE_PLANE_SPAN},x
sta tmp_sprite_mask+{]line*4}
ldal spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
sta tmp_sprite_mask+{]line*4}+2
]line equ ]line+1
--^
rts

37
src/render/Sprite2.s Normal file
View File

@ -0,0 +1,37 @@
; Specialize routines for handling two sprites.
TwoSpriteData mac
lda (sprite_ptr1),y
db $37,sprite_ptr0 ; and [sprite_ptr0],y
ora (sprite_ptr0),y
<<<
TwoSpriteMask mac
db $B7,sprite_ptr1 ; lda [sprite_ptr1],y
db $37,sprite_ptr0 ; and [sprite_ptr0],y
<<<
CopyFourSpritesDataAndMaskToDP
CopyThreeSpritesDataAndMaskToDP
CopyTwoSpritesDataAndMaskToDP
pei DP2_SPRITEDATA_AND_TILESTORE_BANKS
plb
]line equ 0
lup 8
ldy #{]line*SPRITE_PLANE_SPAN}
TwoSpriteData
sta tmp_sprite_data+{]line*4}
TwoSpriteMask
sta tmp_sprite_mask+{]line*4}
ldy #{]line*SPRITE_PLANE_SPAN}+2
TwoSpriteData
sta tmp_sprite_data+{]line*4}+2
TwoSpriteMask
sta tmp_sprite_mask+{]line*4}+2
]line equ ]line+1
--^
plb
jmp (K_TS_SPRITE_TILE_DISP,x)

0
src/render/Sprite3.s Normal file
View File

0
src/render/Sprite4.s Normal file
View File

376
src/render/TwoLayer.s Normal file
View File

@ -0,0 +1,376 @@
; Collection of render function used when the engine is in "Two Layer" mode. Other than the Tile 0
; routines, there's nothing in here that is particularly well optimized.
Tile0TwoLyr
ldal TileStore+TS_WORD_OFFSET,x
and #$00FF
ora #$4800
sta: $0004,y
sta $1004,y
sta $2004,y
sta $3004,y
sta $4004,y
sta $5004,y
sta $6004,y
sta $7004,y
inc
inc
sta: $0001,y
sta $1001,y
sta $2001,y
sta $3001,y
sta $4001,y
sta $5001,y
sta $6001,y
sta $7001,y
sep #$20
lda #$B1 ; This is a special case where we can set all the words to LDA (DP),y
sta: $0000,y
sta: $0003,y
sta $1000,y
sta $1003,y
sta $2000,y
sta $2003,y
sta $3000,y
sta $3003,y
sta $4000,y
sta $4003,y
sta $5000,y
sta $5003,y
sta $6000,y
sta $6003,y
sta $7000,y
sta $7003,y
rep #$20
plb
rts
; Draw from the sprite buffer into a fully transparent tile
SpriteOver0TwoLyr
lda TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
lda TileStore+TS_WORD_OFFSET,x ; Load the word offset of this tile (0 to 82 in steps of 2)
ora #$B100 ; Pre-calc the LDA (dp),y opcode + operand
xba
sta _OP_CACHE
lda TileStore+TS_CODE_ADDR_HIGH,x
pha
ldy TileStore+TS_CODE_ADDR_LOW,x
plb
CopyTwoLayerOver tmp_sprite_data+0;$0003
CopyTwoLayerOver tmp_sprite_data+4;$1003
CopyTwoLayerOver tmp_sprite_data+8;$2003
CopyTwoLayerOver tmp_sprite_data+12;$3003
CopyTwoLayerOver tmp_sprite_data+16;$4003
CopyTwoLayerOver tmp_sprite_data+20;$5003
CopyTwoLayerOver tmp_sprite_data+24;$6003
CopyTwoLayerOver tmp_sprite_data+28;$7003
sec
lda _JTBL_CACHE
sbc #SNIPPET_SIZE ; Advance to the next snippet (Reverse indexing)
sta _JTBL_CACHE
clc
lda _OP_CACHE
adc #$0200 ; Advance to the next word
sta _OP_CACHE
CopyTwoLayerOver tmp_sprite_data+2;$0000
CopyTwoLayerOver tmp_sprite_data+6;$1000
CopyTwoLayerOver tmp_sprite_data+10;$2000
CopyTwoLayerOver tmp_sprite_data+14;$3000
CopyTwoLayerOver tmp_sprite_data+18;$4000
CopyTwoLayerOver tmp_sprite_data+22;$5000
CopyTwoLayerOver tmp_sprite_data+26;$6000
CopyTwoLayerOver tmp_sprite_data+30;$7000
plb
rts
TmpTileDataToCodeField
lda TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
lda TileStore+TS_WORD_OFFSET,x ; Load the word offset of this tile (0 to 82 in steps of 2)
ora #$B100 ; Pre-calc the LDA (dp),y opcode + operand
xba
sta _OP_CACHE
lda TileStore+TS_CODE_ADDR_HIGH,x
pha
ldy TileStore+TS_CODE_ADDR_LOW,x
plb
_TmpTileDataToCodeField
CopyTwoLayerOver tmp_tile_data+0;$0003
CopyTwoLayerOver tmp_tile_data+4;$1003
CopyTwoLayerOver tmp_tile_data+8;$2003
CopyTwoLayerOver tmp_tile_data+12;$3003
CopyTwoLayerOver tmp_tile_data+16;$4003
CopyTwoLayerOver tmp_tile_data+20;$5003
CopyTwoLayerOver tmp_tile_data+24;$6003
CopyTwoLayerOver tmp_tile_data+28;$7003
sec
lda _JTBL_CACHE
sbc #SNIPPET_SIZE ; Advance to the next snippet (Reverse indexing)
sta _JTBL_CACHE
clc
lda _OP_CACHE
adc #$0200 ; Advance to the next word
sta _OP_CACHE
CopyTwoLayerOver tmp_tile_data+2;$0000
CopyTwoLayerOver tmp_tile_data+6;$1000
CopyTwoLayerOver tmp_tile_data+10;$2000
CopyTwoLayerOver tmp_tile_data+14;$3000
CopyTwoLayerOver tmp_tile_data+18;$4000
CopyTwoLayerOver tmp_tile_data+22;$5000
CopyTwoLayerOver tmp_tile_data+26;$6000
CopyTwoLayerOver tmp_tile_data+30;$7000
plb
rts
; Copy a tile into the tile data buffer and then render to the code field
CopyTileATwoLyr
ldal TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
ldal TileStore+TS_WORD_OFFSET,x ; Load the word offset of this tile (0 to 82 in steps of 2)
ora #$B100 ; Pre-calc the LDA (dp),y opcode + operand
xba
sta _OP_CACHE
ldal TileStore+TS_TILE_ADDR,x
tax
]line equ 0
lup 8
ldal tiledata+{]line*4},x
sta tmp_tile_data+{]line*4}
ldal tiledata+{]line*4}+32,x
sta tmp_tile_mask+{]line*4}
ldal tiledata+{]line*4}+2,x
sta tmp_tile_data+{]line*4}+2
ldal tiledata+{]line*4}+32+2,x
sta tmp_tile_mask+{]line*4}+2
]line equ ]line+1
--^
jmp _TmpTileDataToCodeField
CopyTileVTwoLyr
ldal TileStore+TS_JMP_ADDR,x ; Get the address of the exception handler
sta _JTBL_CACHE
ldal TileStore+TS_WORD_OFFSET,x ; Load the word offset of this tile (0 to 82 in steps of 2)
ora #$B100 ; Pre-calc the LDA (dp),y opcode + operand
xba
sta _OP_CACHE
ldal TileStore+TS_TILE_ADDR,x
tax
]src equ 7
]dest equ 0
lup 8
ldal tiledata+{]src*4},x
sta tmp_tile_data+{]dest*4}
ldal tiledata+{]src*4}+32,x
sta tmp_tile_mask+{]dest*4}
ldal tiledata+{]src*4}+2,x
sta tmp_tile_data+{]dest*4}+2
ldal tiledata+{]src*4}+32+2,x
sta tmp_tile_mask+{]dest*4}+2
]src equ ]src-1
]dest equ ]dest+1
--^
jmp _TmpTileDataToCodeField
; Handle sprites + tiles. Strategy is to merge the sprite and tile data and write it to the
; temporary space an defer the actual work to the _TmpTileDataToCodeField helper
SpriteOverATwoLyr
ldy TileStore+TS_TILE_ADDR,x
pei DP2_TILEDATA_AND_TILESTORE_BANKS
plb
]line equ 0
lup 8
lda tiledata+{]line*4},y
and tmp_sprite_mask+{]line*4}
ora tmp_sprite_data+{]line*4}
sta tmp_tile_data+{]line*4}
lda tiledata+{]line*4}+32,y
and tmp_sprite_mask+{]line*4}
sta tmp_tile_mask+{]line*4}
lda tiledata+{]line*4}+2,y
and tmp_sprite_mask+{]line*4}+2
ora tmp_sprite_data+{]line*4}+2
sta tmp_tile_data+{]line*4}+2
lda tiledata+{]line*4}+32+2,y
and tmp_sprite_mask+{]line*4}+2
sta tmp_tile_mask+{]line*4}+2
]line equ ]line+1
--^
plb
jmp TmpTileDataToCodeField
SpriteOverVTwoLyr
ldy TileStore+TS_TILE_ADDR,x
pei DP2_TILEDATA_AND_TILESTORE_BANKS
plb
]src equ 7
]dest equ 0
lup 8
lda tiledata+{]src*4},y
and tmp_sprite_mask+{]dest*4}
ora tmp_sprite_data+{]dest*4}
sta tmp_tile_data+{]dest*4}
lda tiledata+{]src*4}+32,y
and tmp_sprite_mask+{]dest*4}
sta tmp_tile_mask+{]dest*4}
lda tiledata+{]src*4}+2,y
and tmp_sprite_mask+{]dest*4}+2
ora tmp_sprite_data+{]dest*4}+2
sta tmp_tile_data+{]dest*4}+2
lda tiledata+{]src*4}+32+2,y
and tmp_sprite_mask+{]dest*4}+2
sta tmp_tile_mask+{]dest*4}+2
]src equ ]src-1
]dest equ ]dest+1
--^
plb
jmp TmpTileDataToCodeField
SpriteUnderATwoLyr
ldy TileStore+TS_TILE_ADDR,x
pei DP2_TILEDATA_AND_TILESTORE_BANKS
plb
]line equ 0
lup 8
lda tmp_sprite_data+{]line*4}
and tiledata+{]line*4}+32,y
ora tiledata+{]line*4},y
sta tmp_tile_data+{]line*4}
lda tiledata+{]line*4}+32,y
and tmp_sprite_mask+{]line*4}
sta tmp_tile_mask+{]line*4}
lda tmp_sprite_data+{]line*4}+2
and tiledata+{]line*4}+32+2,y
ora tiledata+{]line*4}+2,y
sta tmp_tile_data+{]line*4}+2
lda tiledata+{]line*4}+32+2,y
and tmp_sprite_mask+{]line*4}+2
sta tmp_tile_mask+{]line*4}+2
]line equ ]line+1
--^
plb
jmp TmpTileDataToCodeField
SpriteUnderVTwoLyr
ldy TileStore+TS_TILE_ADDR,x
pei DP2_TILEDATA_AND_TILESTORE_BANKS
plb
]src equ 7
]dest equ 0
lup 8
lda tmp_sprite_data+{]dest*4}
and tiledata+{]src*4}+32,y
ora tiledata+{]src*4},y
sta tmp_tile_data+{]dest*4}
lda tiledata+{]src*4}+32,y
and tmp_sprite_mask+{]dest*4}
sta tmp_tile_mask+{]dest*4}
lda tmp_sprite_data+{]dest*4}+2
and tiledata+{]src*4}+32+2,y
ora tiledata+{]src*4}+2,y
sta tmp_tile_data+{]dest*4}+2
lda tiledata+{]src*4}+32+2,y
and tmp_sprite_mask+{]dest*4}+2
sta tmp_tile_mask+{]dest*4}+2
]src equ ]src-1
]dest equ ]dest+1
--^
plb
jmp TmpTileDataToCodeField
; Macro to fill in the code field from a direct page temporary buffer
;
; ]1 : direct page address with data, the mask direct page address is data + 32
; ]2 : code field offset
;
; Y is the code field address
CopyTwoLayerOver 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
lda {]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
lda #$004C ; JMP instruction
sta: {]2},y
lda _JTBL_CACHE ; Get the offset to the exception handler for this column
ora #{]2&$7000} ; adjust for the current row offset
sta: {]2}+1,y
tax ; This becomes the new address that we use to patch in
lda #$29
sta: $0002,x ; AND #$0000 opcode
lda #$09
sta: $0005,x ; ORA #$0000 opcode
lda _OP_CACHE ; Get the LDA (dp),y instruction for this column
sta: $0000,x
lda {]1}+32 ; insert the tile mask and data into the exception
sta: $0003,x ; handler.
lda {]1}
sta: $0006,x
lda #$0D80 ; branch to the prologue (BRA *+15)
sta: $0008,x
bra next
; This is a transparent word, so just show the second background layer
transparent
lda #$4800 ; put a PHA after the offset
sta: {]2}+1,y
lda _OP_CACHE
sta: {]2},y
next
eom

View File

@ -0,0 +1,92 @@
; Functions to handle rendering sprites into 8x8 tile buffers for dirty tile rendering. Because we
; are rendering directly to the graphics screen instead of the code field, we can map the direct
; page into Bank 01 and use that to avoid writing the merge sprite and tile data to an intermediate
; buffer.
;DirtyTileSpriteProcs dw _TBDirtySpriteTile_00,_TBDirtySpriteTile_0H,_TBDirtySpriteTile_V0,_TBDirtySpriteTile_VH
; Optimization Note: The single-sprite blitter seems like it could be made faster by taking advantage of
; the fact that only a single set of sprite data needs to be read, but the extra overhead
; of using the direct page and setting up and restoring registers wipes out the 2 cycle
; per word advantage.
;
; A = screen address
; X = address of sprite data
; Y = address of tile data
; B = tile data bank
_OneDirtySprite_00
_OneDirtySprite_0H
phd
sei
clc
tcd
_R0W1
_ODS_Line 0,0,$0
_ODS_Line 1,1,$A0
tdc
adc #320
tcd
_ODS_Line 2,2,$0
_ODS_Line 3,3,$A0
tdc
adc #320
tcd
_ODS_Line 4,4,$0
_ODS_Line 5,5,$A0
tdc
adc #320
tcd
_ODS_Line 6,6,$0
_ODS_Line 7,7,$A0
_R0W0
cli
pld
rts
_OneDirtySprite_V0
_OneDirtySprite_VH
phd
sei
clc
tcd
_R0W1
_ODS_Line 0,7,$0
_ODS_Line 1,6,$A0
tdc
adc #320
tcd
_ODS_Line 2,5,$0
_ODS_Line 3,4,$A0
tdc
adc #320
tcd
_ODS_Line 4,3,$0
_ODS_Line 5,2,$A0
tdc
adc #320
tcd
_ODS_Line 6,1,$0
_ODS_Line 7,0,$A0
_R0W0
cli
pld
rts
; Build up from here
_FourDirtySprites
lda TileStore+TS_VBUFF_ADDR_0,y
sta spriteIdx
lda TileStore+TS_VBUFF_ADDR_1,y
sta spriteIdx+4
lda TileStore+TS_VBUFF_ADDR_2,y
sta spriteIdx+8
lda TileStore+TS_VBUFF_ADDR_3,y
sta spriteIdx+12

150
src/sprites/SpriteProcs.s Normal file
View File

@ -0,0 +1,150 @@
; Functions to handle rendering sprite information into buffers for updates to the
; code field. Due to lack of parallel structure, the sprites are combined with the
; tile data and then written to a single direct page buffer. The data is read from
; this buffer and then applied to the code field
; Merge a single block of sprite data with a tile
_OneSprite_00
_OneSprite_H0
ldx TileStore+TS_VBUFF_ADDR_0,y
lda TileStore+TS_TILE_ADDR,y
tay
]line equ 0
lup 8
lda tiledata+{]line*TILE_DATA_SPAN},y
andl spritemask+{]line*SPRITE_PLANE_SPAN},x
oral spritedata+{]line*SPRITE_PLANE_SPAN},x
sta tmp_sprite_data+{]line*4}
lda tiledata+{]line*TILE_DATA_SPAN}+2,y
andl spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
oral spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
sta tmp_sprite_data+{]line*4}+2
]line equ ]line+1
--^
_OneSprite_V0
_OneSprite_VH
ldx TileStore+TS_VBUFF_ADDR_0,y
lda TileStore+TS_TILE_ADDR,y
tay
]line equ 7
]dest equ 0
lup 8
lda tiledata+{]line*TILE_DATA_SPAN},y
andl spritemask+{]dest*SPRITE_PLANE_SPAN},x
oral spritedata+{]dest*SPRITE_PLANE_SPAN},x
sta tmp_sprite_data+{]dest*4}
lda tiledata+{]line*TILE_DATA_SPAN}+2,y
andl spritemask+{]dest*SPRITE_PLANE_SPAN}+2,x
oral spritedata+{]dest*SPRITE_PLANE_SPAN}+2,x
sta tmp_sprite_data+{]dest*4}+2
]line equ ]line-1
]dest equ ]dest+1
--^
rts
; Merge two blocks of sprite data. This is more involved because we need to use the
; direct page pointers to stack the sprite information
_TwoSprite_00
_TwoSprite_H0
lda TileStore+TS_VBUFF_ADDR_0,y
sta sprite_0
lda TileStore+TS_VBUFF_ADDR_1,y
sta sprite_1
ldx TileStore+TS_TILE_ADDR,y
; line 0
lda tiledata+{0*TILE_DATA_SPAN},x
and [sprite_1]
ora (sprite_1)
and [sprite_0]
ora (sprite_0)
sta tmp_sprite_data+{0*4}
ldy #{0*SPRITE_PLANE_SPAN}+2
lda tiledata+{0*TILE_DATA_SPAN}+2,x
and [sprite_1],y
ora (sprite_1),y
and [sprite_0],y
ora (sprite_0),y
sta tmp_sprite_data+{0*4}+2
; line 1
ldy #{1*SPRITE_PLANE_SPAN}
lda tiledata+{1*TILE_DATA_SPAN},x
and [sprite_1],y
ora (sprite_1),y
and [sprite_0],y
ora (sprite_0),y
sta tmp_sprite_data+{1*4}
ldy #{1*SPRITE_PLANE_SPAN}+2
lda tiledata+{1*TILE_DATA_SPAN}+2,x
and [sprite_1],y
ora (sprite_1),y
and [sprite_0],y
ora (sprite_0),y
sta tmp_sprite_data+{1*4}+2
rts
; Merge three blocks of sprite data. This is more involved because we need to use the
; direct page pointers to stack the sprite information
_ThreeSprite_00
_ThreeSprite_H0
lda TileStore+TS_VBUFF_ADDR_0,y
sta sprite_0
lda TileStore+TS_VBUFF_ADDR_1,y
sta sprite_1
lda TileStore+TS_VBUFF_ADDR_2,y
sta sprite_2
ldx TileStore+TS_TILE_ADDR,y
; line 0
lda tiledata+{0*TILE_DATA_SPAN},x
and [sprite_2]
ora (sprite_2)
and [sprite_1]
ora (sprite_1)
and [sprite_0]
ora (sprite_0)
sta tmp_sprite_data+{0*4}
ldy #{0*SPRITE_PLANE_SPAN}+2
lda tiledata+{0*TILE_DATA_SPAN}+2,x
and [sprite_2],y
ora (sprite_2),y
and [sprite_1],y
ora (sprite_1),y
and [sprite_0],y
ora (sprite_0),y
sta tmp_sprite_data+{0*4}+2
; line 1
ldy #{1*SPRITE_PLANE_SPAN}
lda tiledata+{1*TILE_DATA_SPAN},x
and [sprite_2],y
ora (sprite_2),y
and [sprite_1],y
ora (sprite_1),y
and [sprite_0],y
ora (sprite_0),y
sta tmp_sprite_data+{1*4}
ldy #{1*SPRITE_PLANE_SPAN}+2
lda tiledata+{1*TILE_DATA_SPAN}+2,x
and [sprite_2],y
ora (sprite_2),y
and [sprite_1],y
ora (sprite_1),y
and [sprite_0],y
ora (sprite_0),y
sta tmp_sprite_data+{1*4}+2
rts

5
src/static/SprData.s Normal file
View File

@ -0,0 +1,5 @@
; sprite stamp pixel data
spritedata ENT
; ds 65535
ds 65536

5
src/static/SprMask.s Normal file
View File

@ -0,0 +1,5 @@
; sprite stamp masks
spritemask ENT
; ds 65535
ds 65536

5
src/static/TileData.s Normal file
View File

@ -0,0 +1,5 @@
; Bank of memory that holds the 8x8 tile data
tiledata ENT
; ds 65535
ds 65536

View File

@ -1,5 +1,88 @@
; Collection of data tables
; Bank of memory that holds the core sprite and tile store data structures
put ../Defs.s
put TileStoreDefs.s
;-------------------------------------------------------------------------------------
;
put ../blitter/Template.s
;-------------------------------------------------------------------------------------
TileStore ENT
ds {TILE_STORE_SIZE*TILE_STORE_NUM}
;-------------------------------------------------------------------------------------
;
; A list of dirty tiles that need to be updated in a given frame
ds \,$00 ; pad to the next page boundary
DirtyTileCount ENT
ds 2
DirtyTiles ENT
ds TILE_STORE_SIZE ; At most this many tiles can possibly be updated at once
;-------------------------------------------------------------------------------------
;
ds \,$00 ; pad to the next page boundary
_Sprites ENT
ds SPRITE_REC_SIZE*MAX_SPRITES
;-------------------------------------------------------------------------------------
;
; A double-sized table of lookup values. It is double-width and double-height so that,
; if we know a tile's address position of (X + 41*Y), then any relative tile store address
; can be looked up by adding a constant value.
ds \,$00 ; pad to the next page boundary
TileStoreLookupYTable ENT
]line equ 0
lup TS_LOOKUP_HEIGHT
dw ]line
]line equ ]line+{2*TS_LOOKUP_SPAN}
--^
; Width of tile store is 41 elements
TileStoreData mac
dw ]1+0,]1+2,]1+4,]1+6,]1+8,]1+10,]1+12,]1+14
dw ]1+16,]1+18,]1+20,]1+22,]1+24,]1+26,]1+28,]1+30
dw ]1+32,]1+34,]1+36,]1+38,]1+40,]1+42,]1+44,]1+46
dw ]1+48,]1+50,]1+52,]1+54,]1+56,]1+58,]1+60,]1+62
dw ]1+64,]1+66,]1+68,]1+70,]1+72,]1+74,]1+76,]1+78
dw ]1+80
<<<
; Create a lookup table with two runs of offsets, plus an overlap area on the end (41+41+1 = 83 = TS_LOOKUP_SPAN)
TileStoreLookup ENT
; First copy
]row equ 0
lup TILE_STORE_HEIGHT
TileStoreData ]row*2*TILE_STORE_WIDTH
TileStoreData ]row*2*TILE_STORE_WIDTH
dw ]row*2*TILE_STORE_WIDTH,]row*2*TILE_STORE_WIDTH+2
]row equ ]row+1
--^
; Second copy
]row equ 0
lup TILE_STORE_HEIGHT
TileStoreData ]row*2*TILE_STORE_WIDTH
TileStoreData ]row*2*TILE_STORE_WIDTH
dw ]row*2*TILE_STORE_WIDTH,]row*2*TILE_STORE_WIDTH+2
]row equ ]row+1
--^
; Last two rows
TileStoreData 0*2*TILE_STORE_WIDTH
TileStoreData 0*2*TILE_STORE_WIDTH
dw 0*2*TILE_STORE_WIDTH,0*2*TILE_STORE_WIDTH+2
TileStoreData 1*2*TILE_STORE_WIDTH
TileStoreData 1*2*TILE_STORE_WIDTH
dw 1*2*TILE_STORE_WIDTH,1*2*TILE_STORE_WIDTH+2
;-------------------------------------------------------------------------------------
;
; Other data tables
; Col2CodeOffset
;
@ -19,21 +102,21 @@
;
; 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
]step equ 0
dw CODE_TOP ; There is a spot where we load Col2CodeOffet-2,x
Col2CodeOffset lup 82
Col2CodeOffset ENT
lup 82
dw CODE_TOP+{{81-]step}*PER_TILE_SIZE}
]step equ ]step+1
--^
dw CODE_TOP+{81*PER_TILE_SIZE}
; A parallel table to Col2CodeOffset that holds the offset to the exception handler address for each column
SNIPPET_SIZE equ 32
]step equ 0
dw SNIPPET_BASE
JTableOffset lup 82
JTableOffset ENT
lup 82
dw SNIPPET_BASE+{{81-]step}*SNIPPET_SIZE}
]step equ ]step+1
--^
@ -46,7 +129,7 @@ JTableOffset lup 82
;
; These tables are reversed to be parallel with the JTableOffset and Col2CodeOffset tables above. The
; physical word index that each instruction is intended to be placed at is in the comment.
CodeFieldEvenBRA
CodeFieldEvenBRA ENT
bra *+6 ; 81 -- need to skip over the JMP loop that passed control back
bra *+9 ; 80
bra *+12 ; 79
@ -130,7 +213,7 @@ CodeFieldEvenBRA
bra *-6 ; 1
bra *-3 ; 0
CodeFieldOddBRA
CodeFieldOddBRA ENT
bra *+9 ; 81 -- need to skip over two JMP instructions
bra *+12 ; 80
bra *+15 ; 79
@ -240,7 +323,7 @@ TileStoreYTable ENT
; 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
NextCol ENT
]step equ 0
lup 41
dw ]step
@ -252,16 +335,12 @@ NextCol
]step = ]step+2
--^
; A double-sized table of lookup values. This is basically the cross-product of TileStoreYTable and
; NextCol. If is double-width and double-height so that, if we know a tile's address position
; of (X + 41*Y), then any relative tile store address can be looked up by adding a constan value.
;TileStore2DLookup ds {26*41*2}*4
; This is a double-length table that holds the right-edge adresses of the playfield on the physical
; screen. At most, it needs to hold 200 addresses for a full height playfield. It is double-length
; so that code can pick any offset and copy values without needing to check for a wrap-around. If the
; playfield is less than 200 lines tall, then any values after 2 * PLAYFIELD_HEIGHT are undefined.
RTable ds 400
RTable ENT
ds 400
ds 400
; Array of addresses for the banks that hold the blitter.
@ -271,17 +350,22 @@ BlitBuff ENT
; The blitter table (BTable) is a double-length table that holds the full 4-byte address of each
; line of the blit fields. We decompose arrays of pointers into separate high and low words so
; that everything can use the same indexing offsets
BTableHigh ds 208*2*2
BTableLow ds 208*2*2
BTableHigh ENT
ds 208*2*2
BTableLow ENT
ds 208*2*2
; A shorter table that just holds the blitter row addresses
BRowTableHigh ds 26*2*2
BRowTableLow ds 26*2*2
BRowTableHigh ENT
ds 26*2*2
BRowTableLow ENT
ds 26*2*2
; A double-length table of addresses for the BG1 bank. The BG1 buffer is 208 rows of 256 bytes each and
; the first row starts $1800 bytes in to center the buffer in the bank
]step equ $1800
BG1YTable lup 208
BG1YTable ENT
lup 208
dw ]step
]step = ]step+256
--^
@ -292,11 +376,135 @@ BG1YTable lup 208
--^
; Repeat
BG1YOffsetTable lup 26
BG1YOffsetTable ENT
lup 26
dw 1,1,1,2,2,2,2,2,1,1,1,0,0,0,0,0
--^
; Other Toolset variables
OneSecondCounter ENT
dw 0
OldOneSecVec ENT
ds 4
Timers ENT
ds TIMER_REC_SIZE*MAX_TIMERS
Overlays ENT
dw 0 ; count
ds 8 ; only support one or now (start_line, end_line, function call)
; From the IIgs ref
DefaultPalette ENT
dw $0000,$0777,$0841,$072C
dw $000F,$0080,$0F70,$0D00
dw $0FA9,$0FF0,$00E0,$04DF
dw $0DAF,$078F,$0CCC,$0FFF
; 0. Full Screen : 40 x 25 320 x 200 (32,000 bytes (100.0%))
; 1. Sword of Sodan : 34 x 24 272 x 192 (26,112 bytes ( 81.6%))
; 2. ~NES : 32 x 25 256 x 200 (25,600 bytes ( 80.0%))
; 3. Task Force : 32 x 22 256 x 176 (22,528 bytes ( 70.4%))
; 4. Defender of the World : 35 x 20 280 x 160 (22,400 bytes ( 70.0%))
; 5. Rastan : 32 x 20 256 x 160 (20,480 bytes ( 64.0%))
; 6. Game Boy Advanced : 30 x 20 240 x 160 (19,200 bytes ( 60.0%))
; 7. Ancient Land of Y's : 36 x 16 288 x 128 (18,432 bytes ( 57.6%))
; 8. Game Boy Color : 20 x 18 160 x 144 (11,520 bytes ( 36.0%))
; 9. Agony (Amiga) : 36 x 24 288 x 192 (27,648 bytes ( 86.4%))
; 10. Atari Lynx : 20 x 13 160 x 102 (8,160 bytes ( 25.5%))
ScreenModeWidth ENT
dw 320,272,256,256,280,256,240,288,160,288,160,320
ScreenModeHeight ENT
dw 200,192,200,176,160,160,160,128,144,192,102,1
; VBuff arrays for each sprite. We need at least a 3x3 block for each sprite and the shape of the
; array must match the TileStore structure. The TileStore is 41 blocks wide.
;
; It is *critical* that this array be placed in a memory location that is greater than the largest
; TileStore offset because the engine maintaines a per-sprite pointer equal to the VBuff array
; address minut the TileStore offset for the top-left corner of that sprite. This allows all of
; the sprites to share the same table, but the result of the subtraction has to be positive.
;
; Each block of data contains fixed offsets for the relative position of vbuff addresses. There
; are multiple copies of the array to handle cases where a sprite needs to transition across the
; boundary.
;
; For example. If a sprite is drawn in the last column, but is two blocks wide, the TileIndex
; value for the first column is $52 and the second column is $00. Since the pointer to the
; VBuffArray is pre-adjusted by the first column's size, the first offset value will be read
; from (VBuffArray - $52)[$52] = VBuffArray[0], which is correct. However, the second column will be
; read from (VBuffArray - $52)[$00] which is one row off from the correct value's location.
;
; The wrapping also need to account for vertical wrapping. Consider a 16x16 sprite with its top-left
; conder inside the physical tile that is the bottom-right-most tile in the Tile Store. So, the
; lookup index for this tile is (26*41*2)-2 = 2130. When using the lookup table, each step to the
; right or down will cause wrap-around. So the lookup addresses look like this
;
; +------+------+ +------+------+
; | $852 | $800 | | $000 | $004 |
; +------+------+ --> +------+------+
; | $052 | $000 | | $030 | $034 |
; +------+------+ +------+------+
;
; We need to maintain 9 different lookup table variations, which is equal to the number of tile
; in the largest sprite (3x3 tiles = 9 different border cases)
;COL_BYTES equ 4 ; VBUFF_TILE_COL_BYTES
;ROW_BYTES equ 384 ; VBUFF_TILE_ROW_BYTES
; Define the offset values
;___NA_NA___ equ 0
;ROW_0_COL_0 equ {{0*COL_BYTES}+{0*ROW_BYTES}}
;ROW_0_COL_1 equ {{1*COL_BYTES}+{0*ROW_BYTES}}
;ROW_0_COL_2 equ {{2*COL_BYTES}+{0*ROW_BYTES}}
;ROW_1_COL_0 equ {{0*COL_BYTES}+{1*ROW_BYTES}}
;ROW_1_COL_1 equ {{1*COL_BYTES}+{1*ROW_BYTES}}
;ROW_1_COL_2 equ {{2*COL_BYTES}+{1*ROW_BYTES}}
;ROW_2_COL_0 equ {{0*COL_BYTES}+{2*ROW_BYTES}}
;ROW_2_COL_1 equ {{1*COL_BYTES}+{2*ROW_BYTES}}
;ROW_2_COL_2 equ {{2*COL_BYTES}+{2*ROW_BYTES}}
; Allocate an amount of space equal to a TileStore block because we could have vertical wrap around.
; The rest of the values are in just the first few rows following this block
;
; The first block of 4 values is the "normal" case, (X in [0, N-3], Y in [0, M-3]), so no wrap around is needed
; The second block is (X = N-1, Y in [0, M-3])
; The third block is (X = N-2, Y in [0, M-3])
; The fourth block is (X in [0, N-3], Y = M-1)
; The fifth block is (X = N-1, Y = M-1)
; The sixth block is (X = N-2, Y = M-1)
; The seventh block is (X in [0, N-3], Y = M-2)
; The eighth block is (X = N-1, Y = M-2)
; The ninth block is (X = N-2, Y = M-2)
VBuffVertTableSelect ENT ; 51 entries
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,48,24
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,48,24
VBuffHorzTableSelect ENT
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,16,8
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,16,8
VBuffStart ds TILE_STORE_SIZE
VBuffArray ENT
ds {TILE_STORE_WIDTH*2}*3
; Convert sprite index to a bit position
_SpriteBits ENT
dw $0001,$0002,$0004,$0008,$0010,$0020,$0040,$0080,$0100,$0200,$0400,$0800,$1000,$2000,$4000,$8000
_SpriteBitsNot ENT
dw $FFFE,$FFFD,$FFFB,$FFF7,$FFEF,$FFDF,$FFBF,$FF7F,$FEFF,$FDFF,$FBFF,$F7FF,$EFFF,$DFFF,$BFFF,$7FFF
; Steps to the different sprite stamps
_stamp_step ENT
dw 0,12,24,36
blt_return
stk_save

113
src/static/TileStoreDefs.s Normal file
View File

@ -0,0 +1,113 @@
; Tile storage parameters
TILE_DATA_SPAN equ 4
TILE_STORE_WIDTH equ 41
TILE_STORE_HEIGHT equ 26
MAX_TILES equ {26*41} ; Number of tiles in the code field (41 columns * 26 rows)
TILE_STORE_SIZE equ {MAX_TILES*2} ; The tile store contains a tile descriptor in each slot
TS_TILE_ID equ {TILE_STORE_SIZE*0} ; tile descriptor for this location
TS_DIRTY equ {TILE_STORE_SIZE*1} ; Flag. Used to prevent a tile from being queued multiple times per frame
TS_SPRITE_FLAG equ {TILE_STORE_SIZE*2} ; Bitfield of all sprites that intersect this tile. 0 if no sprites.
TS_TILE_ADDR equ {TILE_STORE_SIZE*3} ; cached value, the address of the tiledata for this tile
TS_CODE_ADDR_LOW equ {TILE_STORE_SIZE*4} ; const value, address of this tile in the code fields
TS_CODE_ADDR_HIGH equ {TILE_STORE_SIZE*5}
TS_WORD_OFFSET equ {TILE_STORE_SIZE*6} ; const value, word offset value for this tile if LDA (dp),y instructions re used
;TS_BASE_ADDR equ {TILE_STORE_SIZE*7} ; const value, because there are two rows of tiles per bank, this is set to $0000 or $8000.
TS_JMP_ADDR equ {TILE_STORE_SIZE*7} ; const value, address of the 32-byte snippet space for this tile
TS_SCREEN_ADDR equ {TILE_STORE_SIZE*8} ; cached value of on-screen location of tile. Used for DirtyRender.
; TODO: Move these arrays into the K bank to support direct dispatch via jmp (abs,x)
; TS_BASE_TILE_COPY equ {TILE_STORE_SIZE*9} ; derived from TS_TILE_ID to optimize tile copy to support sprite rendering
; TS_BASE_TILE_DISP equ {TILE_STORE_SIZE*10} ; derived from TS_TILE_ID to optimize base (non-sprite) tile dispatch in the Render function
; TS_DIRTY_TILE_DISP equ {TILE_STORE_SIZE*11} ; derived from TS_TILE_ID to optimize dirty tile dispatch in the Render function
TILE_STORE_NUM equ 12 ; Need this many parallel arrays
; Sprite data structures. We cache quite a few pieces of information about the sprite
; to make calculations faster, so this is hidden from the caller.
MAX_SPRITES equ 16
SPRITE_REC_SIZE equ 42
; Mark each sprite as ADDED, UPDATED, MOVED, REMOVED depending on the actions applied to it
; on this frame. Quick note, the same Sprite ID cannot be removed and added in the same frame.
; A REMOVED sprite if removed from the sprite list during the Render call, so it's ID is not
; available to the AddSprite function until the next frame.
SPRITE_STATUS_EMPTY equ $0000 ; If the status value is zero, this sprite slot is available
SPRITE_STATUS_OCCUPIED equ $8000 ; Set the MSB to flag it as occupied
SPRITE_STATUS_ADDED equ $0001 ; Sprite was just added (new sprite)
SPRITE_STATUS_MOVED equ $0002 ; Sprite's position was changed
SPRITE_STATUS_UPDATED equ $0004 ; Sprite's non-position attributes were changed
SPRITE_STATUS_REMOVED equ $0008 ; Sprite has been removed.
; These values are set by the user
SPRITE_STATUS equ {MAX_SPRITES*0}
SPRITE_ID equ {MAX_SPRITES*2}
SPRITE_X equ {MAX_SPRITES*4}
SPRITE_Y equ {MAX_SPRITES*6}
VBUFF_ADDR equ {MAX_SPRITES*8} ; Base address of the sprite's stamp in the data/mask banks
; These values are cached / calculated during the rendering process
TS_LOOKUP_INDEX equ {MAX_SPRITES*10} ; The index from the TileStoreLookup table that corresponds to the top-left corner of the sprite
TS_COVERAGE_SIZE equ {MAX_SPRITES*12} ; Representation of how many TileStore tiles (NxM) are covered by this sprite
SPRITE_DISP equ {MAX_SPRITES*14} ; Cached address of the specific stamp based on sprite flags
SPRITE_CLIP_LEFT equ {MAX_SPRITES*16}
SPRITE_CLIP_RIGHT equ {MAX_SPRITES*18}
SPRITE_CLIP_TOP equ {MAX_SPRITES*20}
SPRITE_CLIP_BOTTOM equ {MAX_SPRITES*22}
IS_OFF_SCREEN equ {MAX_SPRITES*24}
SPRITE_WIDTH equ {MAX_SPRITES*26}
SPRITE_HEIGHT equ {MAX_SPRITES*28}
SPRITE_CLIP_WIDTH equ {MAX_SPRITES*30}
SPRITE_CLIP_HEIGHT equ {MAX_SPRITES*32}
TS_VBUFF_BASE equ {MAX_SPRITES*34} ; Finalized VBUFF address based on the sprite position and tile offsets
VBUFF_ARRAY_ADDR equ {MAX_SPRITES*36} ; Fixed address where this sprite's VBUFF addresses are stores. The array is the same shape as TileStore, but much smaller
; 52 rows by 82 columns + 2 extra rows and columns for sprite sizes
;
; 53 rows = TILE_STORE_HEIGHT + TILE_STORE_HEIGHT + 1
; 83 cols = TILE_STORE_WIDTH + TILE_STORE_WIDTH + 1
;
; TILE_STORE_WIDTH equ 41
; TILE_STORE_HEIGHT equ 26
TS_LOOKUP_WIDTH equ 82
TS_LOOKUP_HEIGHT equ 52
TS_LOOKUP_BORDER equ 2
TS_LOOKUP_SPAN equ {TS_LOOKUP_WIDTH+TS_LOOKUP_BORDER}
TS_LOOKUP_ROWS equ {TS_LOOKUP_HEIGHT+TS_LOOKUP_BORDER}
; Blitter template constants
PER_TILE_SIZE equ 3
SNIPPET_SIZE equ 32
;----------------------------------------------------------------------
;
; Timer implementation
;
; The engire provides four timer slot that can be used by one-shot or
; recurring timers. Each timer is given an initial tick count, a
; reset tick count (0 = one-shot), and an action to perform.
;
; The timers handle overflow, so if a recurring timer has a tick count of 3
; and 7 VBL ticks have passed, then the timer will be fired twice and
; a tick count of 2 will be set.
;
; As such, the timers are appropriate to drive physical and other game
; behaviors at a frame-independent rate.
;
; A collection of 4 timers that are triggered when their countdown
; goes below zero. Each timer takes up 16 bytes
;
; A timer can fire multiple times during a singular evaluation. For example, if the
; timer delay is set to 1 and 3 VBL ticks happen, then the timer delta is -2, will fire,
; have the delay added and get -1, fire again, increment to zero, first again and then
; finally reset to 1.
;
; +0 counter decremented by the number of ticks since last run
; +2 reset copied into counter when triggered. 0 turns off the timer.
; +4 addr long address of timer routine
; +8 user 8 bytes of user data space for timer state
MAX_TIMERS equ 4
TIMER_REC_SIZE equ 16

View File

@ -0,0 +1,43 @@
; A collection of tile blitters used in the dirty renderer. These renderers copy data directly
; to the graphics screen. Also, because the dirty render assumes that the screen is not moving,
; there is no support for two layer tiles.
; Address table of the rendering functions
DirtyTileProcs dw _TBDirtyTile_00,_TBDirtyTile_0H,_TBDirtyTile_V0,_TBDirtyTile_VH
; Normal and horizontally flipped tiles. The horizontal variant is selected by choosing
; and appropriate value for the X register, so these can share the same code.
;
; B = Bank 01
; X = address of tile data
; Y = screen address
_TBDirtyTile_00
_TBDirtyTile_0H
]line equ 0
lup 8
ldal tiledata+{]line*4},x
sta: $0000+{]line*160},y
ldal tiledata+{]line*4}+2,x
sta: $0002+{]line*160},y
]line equ ]line+1
--^
rts
; Vertically flipped tile renderers
;
; B = Bank 01
; X = address of tile data
; Y = screen address
_TBDirtyTile_V0
_TBDirtyTile_VH
]line equ 7
]dest equ 0
lup 8
ldal tiledata+{]line*4},x
sta: $0000+{]dest*160},y
ldal tiledata+{]line*4}+2,x
sta: $0002+{]dest*160},y
]line equ ]line-1
]dest equ ]dest+1
--^
rts

181
src/tiles/DirtyTileQueue.s Normal file
View File

@ -0,0 +1,181 @@
_ClearDirtyTiles
bra :hop
:loop
jsr _PopDirtyTile
:hop
lda DirtyTileCount
bne :loop
rts
; Append a new dirty tile record
;
; A = result of _GetTileStoreOffset for X, Y
;
; The main purpose of this function is to
;
; 1. Avoid marking the same tile dirty multiple times, and
; 2. Pre-calculating all of the information necessary to render the tile
_PushDirtyTile
tax
; alternate entry point if the x-register is already set
_PushDirtyTileX
lda TileStore+TS_DIRTY,x
bne :occupied2
inc ; any non-zero value will work
sta TileStore+TS_DIRTY,x ; and is 1 cycle faster than loading a constant value
txa
ldx DirtyTileCount ; 4
sta DirtyTiles,x ; 6
inx ; 2
inx ; 2
stx DirtyTileCount ; 4 = 18
rts
:occupied2
txa ; Make sure TileStore offset is returned in the accumulator
rts
; alternate entry point if the Y-register is already set
_PushDirtyTileY
lda TileStore+TS_DIRTY,y
bne :occupied2
inc ; any non-zero value will work
sta TileStore+TS_DIRTY,y ; and is 1 cycle faster than loading a constant value
tya
ldy DirtyTileCount ; 4
sta DirtyTiles,y ; 6
iny ; 2
iny ; 2
sty DirtyTileCount ; 4 = 18
rts
:occupied2
tya ; Make sure TileStore offset is returned in the accumulator
rts
; Remove a dirty tile from the list and return it in state ready to be rendered. It is important
; that the core rendering functions *only* use _PopDirtyTile to get a list of tiles to update.
_PopDirtyTile
ldy DirtyTileCount
bne _PopDirtyTile2
rts
_PopDirtyTile2 ; alternate entry point
dey
dey
sty DirtyTileCount ; remove last item from the list
ldx DirtyTiles,y ; load the offset into the Tile Store
lda #$FFFF
stal TileStore+TS_DIRTY,x ; clear the occupied backlink
rts
; An optimized subroutine that runs through the dirty tile list and executes a callback function
; for each dirty tile. This is an unrolled loop, so we avoid the need to track a register and
; decrement on each iteration.
;
; Also, if we are handling less than 8 dirty tiles, we use a code path that does not
; need to use an index register
;
; Bank = Tile Store
; D = Page 2
_PopDirtyTilesFast
ldx DP2_DIRTY_TILE_COUNT ; This is pre-multiplied by 2
bne pdtf_not_empty ; If there are no items, exit
at_exit rts
pdtf_not_empty
cpx #16 ; If there are >= 8 elements, then
bcs full_chunk ; do a full chunk
jmp (at_table,x)
at_table da at_exit,at_one,at_two,at_three
da at_four,at_five,at_six,at_seven
full_chunk txa
sbc #16 ; carry set from branch
sta DP2_DIRTY_TILE_COUNT ; fall through
tay ; use the Y-register for the index
; Because all of the registers get used in the subroutine, we
; push the values from the DirtyTiles array onto the stack and then pop off
; the values as we go
ldx DirtyTiles+14,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile
ldy DP2_DIRTY_TILE_COUNT
ldx DirtyTiles+12,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile
ldy DP2_DIRTY_TILE_COUNT
ldx DirtyTiles+10,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile
ldy DP2_DIRTY_TILE_COUNT
ldx DirtyTiles+8,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile
ldy DP2_DIRTY_TILE_COUNT
ldx DirtyTiles+6,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile
ldy DP2_DIRTY_TILE_COUNT
ldx DirtyTiles+4,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile
ldy DP2_DIRTY_TILE_COUNT
ldx DirtyTiles+2,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile
ldy DP2_DIRTY_TILE_COUNT
ldx DirtyTiles+0,y
stz TileStore+TS_DIRTY,x
jsr _RenderTile
jmp _PopDirtyTilesFast
; These routines just handle between 1 and 7 dirty tiles
at_seven
ldx DirtyTiles+12
stz TileStore+TS_DIRTY,x
jsr _RenderTile
at_six
ldx DirtyTiles+10
stz TileStore+TS_DIRTY,x
jsr _RenderTile
at_five
ldx DirtyTiles+8
stz TileStore+TS_DIRTY,x
jsr _RenderTile
at_four
ldx DirtyTiles+6
stz TileStore+TS_DIRTY,x
jsr _RenderTile
at_three
ldx DirtyTiles+4
stz TileStore+TS_DIRTY,x
jsr _RenderTile
at_two
ldx DirtyTiles+2
stz TileStore+TS_DIRTY,x
jsr _RenderTile
at_one
ldx DirtyTiles+0
stz TileStore+TS_DIRTY,x
jmp _RenderTile