Skip to content

Instantly share code, notes, and snippets.

@sevonj
Last active December 9, 2022 10:48
Show Gist options
  • Save sevonj/9e10a2ff824b5348ae5d32048b7fb38d to your computer and use it in GitHub Desktop.
Save sevonj/9e10a2ff824b5348ae5d32048b7fb38d to your computer and use it in GitHub Desktop.
TitoTiles v0.0.3
; TitoTiles v0.0.3
; Tilemap renderer test for TitoKone
; https://www.cs.helsinki.fi/group/titokone/
; Can render tiles, haven't done the map part.
; You may want to enable turbomode:
; Options -> Running options -> Turbo mode
; The program outputs video.
; Open the graphics window from the toolbar
scrX EQU 160 ; H px count
scrY EQU 120 ; V px count
scrXY EQU 19200 ; Total Pixel count
scrTilesX EQU 20 ; H tile count
scrTilesY EQU 15 ; V tile count
scrTilesXY EQU 300 ; Total tile count
scrClearColor Equ 0 ; Black
tileCnt EQU 10 ;
memPalet EQU 3996 ; Where palette should be loaded.
memTileset EQU 4000 ; Address of tileset stored in memory.
memSP EQU 5000 ; Store SP here if need more registers
memFP EQU 5001 ; Store FP here if need more registers
memI EQU 5002 ; Iterator
memJ EQU 5003 ; Iterator 2
memAttemptNo EQU 6969 ; Address reserved program run counter. increments every time the program finishes.
memScreen EQU 8192 ; Screen address in memory.
paletAddr dc 0;
; Palette of four colours.
; I assume the color format used by the screen is 0x_rrggbbxx. Haven't bothered to find out.
Palet0 dc -1 ;
Palet01 dc 4004 ;
Palet02 dc 6006 ;
Palet03 dc 8008 ;
Palet1 dc -1 ;
Palet11 dc -1 ;
Palet12 dc 0 ;
Palet13 dc 0 ;
; Tile format: https://www.huderlem.com/demos/gameboy2bpp.html
; Quick description:
; - 8x8 px
; - 4 colors
; - 16B in size
; I used this to make the tiles: https://spkelly.net/tilegen/
; Because of titokone limitations, the tiles are divided into 4B slices.
; Because of titokone limitations, each 4B slice has to be typed in as a signed integer (BE).
; If you add a tile, remember to increment tileCnt
; BGTile0
Tile0 dc -16744705 ; rows 0 - 1
Tile01 dc -2055108221 ; rows 2 - 3
Tile02 dc -1819957877 ; rows 4 - 5
Tile03 dc -912818433 ; rows 6 - 7
; BGTile1
Tile1 dc -2130689279 ;
Tile12 dc 1057177347 ;
Tile11 dc 1057177347 ;
Tile13 dc 2134769534 ;
; char T
Tile2 dc 32382 ;
Tile21 dc 1515853848 ;
Tile22 dc 404232216 ;
Tile23 dc 404241468 ;
; char i
Tile3 dc 6168 ;
Tile31 dc 14392 ;
Tile32 dc 404232216 ;
Tile33 dc 404241468 ;
; char t
Tile4 dc 6168 ;
Tile41 dc 1010571288 ;
Tile42 dc 404232216 ;
Tile43 dc 404233244 ;
; char o
Tile5 dc 0 ;
Tile51 dc 15420 ;
Tile52 dc 1717986918 ;
Tile53 dc 1717976124 ;
; char n
Tile6 dc 0 ;
Tile61 dc 60652 ;
Tile62 dc 1987470950 ;
Tile63 dc 1718019814 ;
; char l
Tile7 dc 14392 ;
Tile71 dc 404232216 ;
Tile72 dc 404232216 ;
Tile73 dc 404241468 ;
; char e
Tile8 dc 0 ;
Tile81 dc 15420 ;
Tile82 dc 1717992572 ;
Tile83 dc 1616919612 ;
; char s
Tile9 dc 0 ;
Tile91 dc 15420 ;
Tile92 dc 1616936060 ;
Tile93 dc 202143864 ;
; ----- Init ---------------------------------------
call sp, ScrClear
; Palette init
load r1, =Palet0 ;
add r1, memAttemptNo; Shift palette read address by amount of times the program has run
; in order to produce different random colors each time.
;
; Directly after palettes there's tile data. This tends to give good colors and variation.
; Reading palettes from program instructions (start from =0) gives dark tones.
push sp, r1 ;
call sp, LoadPalette ;
; tile init - process tile images so they're easier to render later.
load r5, =0 ; iterator, tile_idx
tileInitLoop load r4, r5 ; tile_idx
mul r4, =4 ; tile_idx * 16B = tile_offset
push sp, r5 ; param -3: tile_idx ; Index of output tile
push sp, =Tile0(r4) ; param -2: unprocessed_tile_addr ;
call sp, StoreTile ; look at StoreTile for an explanation
add r5, =1 ;
comp r5, =tileCnt ;
jles tileInitLoop ;
; ------ Fill screen with tiles
;
load r4, =0; ; tile pos X
load r5, =0; ; tile pos Y
load r3, =1; ; bg tile id
;load r3, memAttemptNo ; Choose different BG image on odd/even runs.
;and r3, =1 ; Choose either tile index 0 or 1
fillLoop nop;
; quick skip for tile locations with text
comp r5, =6 ; Row <= 5, no text, draw tile
jles doTile ;
comp r5, =8 ; Row >= 8
jgre doTile ;
comp r4, =6 ; Col <= 5
jles doTile ;
comp r4, =10 ; Col >= 10
jgre doTile ;
comp r5, =7 ;
jgre fillLoopEnd;
comp r4, =9 ; Col >= 9
jgre doTile ;
comp r5, =7 ;
jles fillLoopEnd;
doTile push sp, =0 ; return -4: screen_pos
push sp, r4 ; param -3: tile pos X
push sp, r5 ; param -2: tile pos Y
call sp, TilePos2ScrPos ; TilePos2ScrPos(x, y)
pop sp, r1 ; return -4: screen_pos
push sp, r3 ; param -3: tile_index
push sp, r1 ; param -2: screen_pos
call sp, DrawTile ; DrawTile(tile_idx, screen_pos)
fillLoopEnd add r4, =1 ; X++
comp r4, =scrTilesX ; Next row check: if !(X == scrTilesX) continue;
jnequ fillLoop ;
load r4, =0 ; X = 0
add r5, =1 ; Y++
comp r5, =scrTilesY ; Exit check: if !(Y == scrTilesY) continue;
jnequ fillLoop ;
; ------ Draw Text tiles
push sp, = Palet1 ;
call sp, LoadPalette ;
push sp, =0 ; ret value
push sp, =6 ; x
push sp, =6 ; y
call sp, TilePos2ScrPos ;
pop sp, r1 ; ret value
push sp, =2 ; tile id
push sp, r1 ; tile pos
call sp, DrawTile ;
push sp, =0 ; ret value
push sp, =7 ; x
push sp, =6 ; y
call sp, TilePos2ScrPos ;
pop sp, r1 ; ret value
push sp, =3 ; tile id
push sp, r1 ; tile pos
call sp, DrawTile ;
push sp, =0 ; ret value
push sp, =8 ; x
push sp, =6 ; y
call sp, TilePos2ScrPos ;
pop sp, r1 ; ret value
push sp, =4 ; tile id
push sp, r1 ; tile pos
call sp, DrawTile ;
push sp, =0 ; ret value
push sp, =9 ; x
push sp, =6 ; y
call sp, TilePos2ScrPos ;
pop sp, r1 ; ret value
push sp, =5 ; tile id
push sp, r1 ; tile pos
call sp, DrawTile ;
push sp, =0 ; ret value
push sp, =6 ; x
push sp, =8 ; y
call sp, TilePos2ScrPos ;
pop sp, r1 ; ret value
push sp, =2 ; tile id
push sp, r1 ; tile pos
call sp, DrawTile ;
push sp, =0 ; ret value
push sp, =7 ; x
push sp, =8 ; y
call sp, TilePos2ScrPos ;
pop sp, r1 ; ret value
push sp, =3 ; tile id
push sp, r1 ; tile pos
call sp, DrawTile ;
push sp, =0 ; ret value
push sp, =8 ; x
push sp, =8 ; y
call sp, TilePos2ScrPos ;
pop sp, r1 ; ret value
push sp, =7 ; tile id
push sp, r1 ; tile pos
call sp, DrawTile ;
push sp, =0 ; ret value
push sp, =9 ; x
push sp, =8 ; y
call sp, TilePos2ScrPos ;
pop sp, r1 ; ret value
push sp, =8 ; tile id
push sp, r1 ; tile pos
call sp, DrawTile ;
push sp, =0 ; ret value
push sp, =10 ; x
push sp, =8 ; y
call sp, TilePos2ScrPos ;
pop sp, r1 ; ret value
push sp, =9 ; tile id
push sp, r1 ; tile pos
call sp, DrawTile ;
; ---------- the end ------------------ ;
load r1, memAttemptNo ;
add r1, =1 ;
store r1, memAttemptNo ;
svc sp, =halt ;
;---------------- got some of them aliohjelmas for you
;-----------------------------------------------------
;-------------------------------------------------------------------------------------
; ScrClear - Super optimized
; This approach nears one instruction per pixel!
;-------------------------------------------------------------------------------------
ScrClear pushr sp ;
store sp, memSP ; We're using sp for this so gotta store its old value
load r1, =memScreen ;
add r1, =scrXY ;
load sp, =memScreen ;
sub sp, =1 ;
ScrClearLoop push sp, r0 ; We push r0 to the screen. This combines store and increment instructions.
push sp, r0 ;
push sp, r0 ; Loop size:
push sp, r0 ; 1 1/32th instructions per px
push sp, r0 ; not bad. This *could* be unrolled futher that doesn't mean it should.
push sp, r0 ;
push sp, r0 ;
push sp, r0 ; 7
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ; 15
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ; 23
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ; 31
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ; 39
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ; 47
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ; 55
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ;
push sp, r0 ; 63
comp sp, r1 ; These two loop mgmt instructions are shared by 64 pixels,
jles ScrClearLoop ; making them 1/32th instructions per px.
load sp, memSP ;
popr sp ;
exit sp, =0 ;
;-------------------------------------------------------------------------------------
; StoreTile - Process a tile and store it to memTileset
; param -3: tile id (where to store result)
; param -2: pointer to the tile to process
;-------------------------------------------------------------------------------------
storeTile pushr sp ;
;store sp, memSP ;
;load sp, memTileset ;
load r1, =0 ;
load r4, -3(fp) ;
mul r4, =64 ;
add r4, =memTileset ; address of current pixel in tilemap
add r4, =16 ; We iterate from 15 to 0 in a slica
storeTileLoop push sp, r1 ;
load r2, -2(fp) ;
add r2, r1 ;
load r1, 0(r2) ; tile slice (not processed)
; r2, ; temp bit from byte1
; r3, ; temp bit from byte0
load r5, =16 ; iterator
; I decided to use nes/gameboy tile format. (in 8x8)
; For some reason the 2 bits for each pixel are not stored
; adjacent to each other, but in 2 different bytes.
; So this loop moves them next to each other before they're stored.
; Unlike before, this version does not cram all 16 px of a slice into one word.
; Instead, each px has it's own address. It multiplies the memory footprint
; of processed tileset by 16, but saves many instructions when rendering the tile.
;
loop load r2, r1 ; Get last bit of byte 1
and r2, =1 ;
load r3, r1 ; Get last bit of byte 0
shr r3, =8 ;
and r3, =1 ;
shl r2, =1 ; Merge the bits
add r2, r3 ;
sub r4, =1 ;
store r2, @r4 ;
shr r1, =1 ; shift to move next px to last
sub r5, =1 ;
comp r5, =8 ; row change check (triggered once since a slice has 2 rows)
jnequ loop2; ; skip below
shr r1, =8 ; get rid of the already used byte of 1st row
loop2 jpos r5, loop ;
add r4, =32 ; jump to the last px of next slice.
pop sp, r1 ; get slice no. (0 - 3)
load r2 -3(fp) ; tile no.
mul r2, =4 ; tile no. * tilesize = offset of this tile
add r2, r1 ; offset of this tile + slice no. = offset of this slice
add r1, =1 ;
comp r1, =4 ;
jles storeTileLoop ;
popr sp; ;
exit sp, =2 ;
;-------------------------------------------------------------------------------------
; DrawTile - Draws a tile on screen
; param -3: tile_idx
; param -2: screen_pos
;-------------------------------------------------------------------------------------
DrawTile pushr sp;
store sp, memSP ;
load sp, -2(fp) ; screen_pos
add sp, =memScreen ;
sub sp, =1 ; because push iterates before storing the value
load r1, =0 ; slice_idx as iterator
DrawTileSlice store r1, memI ;
load r2, -3(fp) ; Tile id
mul r2, =4 ; Number of slices from memTileset
add r2, r1 ; + current slice id 0 - 3
mul r2, =16 ; offset from memtileset
add r2, =memTileset ; Slice address
; First row of slice
load r4, @r2 ; Load color_idx
push sp, memPalet(r4) ; Push color_idx
add r2, =1 ; Increment px address
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ; px7
push sp, memPalet(r4) ;
add r2, =1 ;
add sp, =152 ; Move SP to next row
; Second row of slice
load r4, @r2 ; Load color_idx
push sp, memPalet(r4) ; Push color_idx
add r2, =1 ; Increment px address
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ;
push sp, memPalet(r4) ;
add r2, =1 ;
load r4, @r2 ; px15
push sp, memPalet(r4) ;
add sp, =152 ; Move SP to next row
load r1, memI ;
add r1, =1 ;
comp r1, =4 ;
jles DrawTileSlice ;
; ----- End of DrawTileLoop
load sp, memSP;
popr sp ;
exit sp, =2 ;
;-------------------------------------------------------------------------------------
; TilePos2ScrPos - Convert tile pos (x,y) to 1D screen memory offset
; return -4: screen mem off
; param -3: tile pos x
; param -2: tile pos y
;-------------------------------------------------------------------------------------
TilePos2ScrPos pushr sp ;
load r1, -3(fp) ;
mul r1, =8 ;
load r2, -2(fp) ;
mul r2, =1280 ;
add r1, r2 ;
store r1, -4(fp) ;
popr sp ;
exit sp, =2 ;
;-------------------------------------------------------------------------------------
; LoadPalette - Change current palette
; param -2: address of palette to load
;-------------------------------------------------------------------------------------
LoadPalette pushr sp ;
load r5, -2(fp) ;
load r2, =memPalet ;
load r1, 0(r5) ; Copy inputPalet(0)
store r1, 0(r2) ; to memPalet(0)
load r1, 1(r5) ;
store r1, 1(r2) ;
load r1, 2(r5) ;
store r1, 2(r2) ;
load r1, 3(r5) ;
store r1, 3(r2) ;
popr sp ;
exit sp, =1 ;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment